github.com/leowmjw/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 }