github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/router/router.go (about)

     1  package router
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"log"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/otto/ui"
    13  )
    14  
    15  // ErrHelp can be returned by Execute functions to force the action to
    16  // show help for a given command.
    17  var ErrHelp = errors.New("help")
    18  
    19  // Router is a helper to route subcommands to specific callbacks.
    20  //
    21  // Actions are available on a lot of commands such as dev, deploy, etc. and
    22  // this can be used to add custom actions.
    23  type Router struct {
    24  	Actions map[string]Action
    25  }
    26  
    27  // Action defines an action that is available for the router.
    28  type Action interface {
    29  	// Execute is the callback that'll be called to execute this action.
    30  	Execute(ctx Context) error
    31  
    32  	// Help is the help text for this action.
    33  	Help() string
    34  
    35  	// Synopsis is the text that will be shown as a short sentence
    36  	// about what this action does.
    37  	Synopsis() string
    38  }
    39  
    40  // Context is passed to the router and used to select which action is executed.
    41  // This same value will also be passed down into the selected Action's Execute
    42  // function. This is so that actions typecast the context to access
    43  // implementation-specific data.
    44  type Context interface {
    45  	RouteName() string
    46  	RouteArgs() []string
    47  	UI() ui.Ui
    48  }
    49  
    50  // Route will route the given Context to the proper Action.
    51  func (r *Router) Route(ctx Context) error {
    52  	if _, ok := r.Actions["help"]; !ok {
    53  		r.Actions["help"] = &SimpleAction{
    54  			ExecuteFunc:  r.help,
    55  			SynopsisText: "This help",
    56  		}
    57  	}
    58  
    59  	action, ok := r.Actions[ctx.RouteName()]
    60  	if !ok {
    61  		log.Printf("[DEBUG] No action found: %q; executing help.", ctx.RouteName())
    62  		return r.help(ctx)
    63  	}
    64  
    65  	err := action.Execute(ctx)
    66  	if err == flag.ErrHelp {
    67  		// We special case and allow flag.ErrHelp to mean our help error
    68  		// so that we show help properly.
    69  		err = ErrHelp
    70  	}
    71  	if err != nil && err == ErrHelp {
    72  		return r.Route(&simpleContext{
    73  			Name:  "help",
    74  			Args:  []string{ctx.RouteName()},
    75  			UIVal: ctx.UI(),
    76  		})
    77  	}
    78  
    79  	return err
    80  }
    81  
    82  func (r *Router) help(ctx Context) error {
    83  	badAction := false
    84  	var message bytes.Buffer
    85  
    86  	// If this is the help command we've been given a specific subcommand
    87  	// to look up, then do that.
    88  	if ctx.RouteName() == "help" && len(ctx.RouteArgs()) > 0 {
    89  		if a, ok := r.Actions[ctx.RouteArgs()[0]]; ok {
    90  			ctx.UI().Raw(a.Help() + "\n")
    91  			return nil
    92  		}
    93  		message.WriteString(fmt.Sprintf(
    94  			"Unsupported action: %s\n\n", ctx.RouteArgs()[0]))
    95  		badAction = true
    96  	}
    97  
    98  	// Normal help output...
    99  	if ctx.RouteName() != "" && ctx.RouteName() != "help" {
   100  		message.WriteString(fmt.Sprintf(
   101  			"Unsupported action: %s\n\n", ctx.RouteName()))
   102  		badAction = true
   103  	}
   104  
   105  	message.WriteString(fmt.Sprintf(
   106  		"The available subcommands are shown below along with a\n" +
   107  			"brief description of what that command does. For more complete\n" +
   108  			"help, call the `help` subcommand with the name of the specific\n" +
   109  			"subcommand you want help for, such as `help foo`.\n\n" +
   110  			"The subcommand '(default)' is the blank subcommand. For this\n" +
   111  			"you don't specify any additional text.\n\n"))
   112  
   113  	longestName := len("(default)")
   114  	actionLines := make([]string, 0, len(r.Actions))
   115  
   116  	for n, _ := range r.Actions {
   117  		if len(n) > longestName {
   118  			longestName = len(n)
   119  		}
   120  	}
   121  	fmtStr := fmt.Sprintf("    %%%ds\t%%s\n", longestName)
   122  
   123  	for n, a := range r.Actions {
   124  		if n == "" {
   125  			n = "(default)"
   126  		}
   127  
   128  		actionLines = append(actionLines, fmt.Sprintf(fmtStr, n, a.Synopsis()))
   129  	}
   130  
   131  	sort.Strings(actionLines)
   132  	message.WriteString(strings.Join(actionLines, ""))
   133  
   134  	if !badAction {
   135  		ctx.UI().Raw(message.String())
   136  		return nil
   137  	}
   138  
   139  	return fmt.Errorf(message.String())
   140  }
   141  
   142  type SimpleAction struct {
   143  	ExecuteFunc  func(Context) error
   144  	HelpText     string
   145  	SynopsisText string
   146  }
   147  
   148  func (sa *SimpleAction) Execute(ctx Context) error {
   149  	return sa.ExecuteFunc(ctx)
   150  }
   151  
   152  func (sa *SimpleAction) Help() string {
   153  	return sa.HelpText
   154  }
   155  
   156  func (sa *SimpleAction) Synopsis() string {
   157  	return sa.SynopsisText
   158  }