github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/help.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "html/template" 7 "io" 8 "log" 9 "os" 10 "sort" 11 "strings" 12 "text/tabwriter" 13 ) 14 15 var helpEnviron = &Command{ 16 Usage: "environ", 17 Category: "hk", 18 Short: "environment variables used by hk", 19 Long: ` 20 Several environment variables affect hk's behavior. 21 22 HEROKU_API_URL 23 24 The base URL hk will use to make api requests in the format: 25 https://[username][:password]@host[:port]/ 26 27 If username and password are present in the URL, they will 28 override .netrc. 29 30 Its default value is https://api.heroku.com/ 31 32 HEROKU_SSL_VERIFY 33 34 When set to disable, hk will insecurely skip SSL verification. 35 36 HKHEADER 37 38 A NL-separated list of fields to set in each API request header. 39 These override any fields set by hk if they have the same name. 40 41 HKPATH 42 43 A list of directories to search for plugins. This variable takes 44 the same form as the system PATH var. If unset, the value is 45 taken to be "/usr/local/lib/hk/plugin" on Unix. 46 47 See 'hk help plugins' for information about the plugin interface. 48 49 HKDEBUG 50 51 When this is set, hk prints the wire representation of each API 52 request to stderr just before sending the request, and prints the 53 response. This will most likely include your secret API key in 54 the Authorization header field, so be careful with the output. 55 `, 56 } 57 58 var cmdVersion = &Command{ 59 Run: runVersion, 60 Usage: "version", 61 Category: "hk", 62 Short: "show hk version", 63 Long: `Version shows the hk client version string.`, 64 } 65 66 func runVersion(cmd *Command, args []string) { 67 fmt.Println(Version) 68 } 69 70 var cmdHelp = &Command{ 71 Usage: "help [<topic>]", 72 Category: "hk", 73 Long: `Help shows usage for a command or other topic.`, 74 } 75 76 var helpMore = &Command{ 77 Usage: "more", 78 Category: "hk", 79 Short: "additional commands, less frequently used", 80 Long: "(not displayed; see special case in runHelp)", 81 } 82 83 var helpCommands = &Command{ 84 Usage: "commands", 85 Category: "hk", 86 Short: "list all commands with usage", 87 Long: "(not displayed; see special case in runHelp)", 88 } 89 90 var helpStyleGuide = &Command{ 91 Usage: "styleguide", 92 Category: "hk", 93 Short: "generate an html styleguide for all commands with usage", 94 Long: "(not displayed; see special case in runHelp)", 95 } 96 97 func init() { 98 cmdHelp.Run = runHelp // break init loop 99 } 100 101 func runHelp(cmd *Command, args []string) { 102 if len(args) == 0 { 103 printUsageTo(os.Stdout) 104 return // not os.Exit(2); success 105 } 106 if len(args) != 1 { 107 printFatal("too many arguments") 108 } 109 switch args[0] { 110 case helpMore.Name(): 111 printExtra() 112 return 113 case helpCommands.Name(): 114 printAllUsage() 115 return 116 case helpStyleGuide.Name(): 117 printStyleGuide() 118 return 119 } 120 121 for _, cmd := range commands { 122 if cmd.Name() == args[0] { 123 cmd.PrintLongUsage() 124 return 125 } 126 } 127 128 if lookupPlugin(args[0]) != "" { 129 _, _, long := pluginInfo(string(args[0])) 130 log.Println(long) 131 return 132 } 133 134 log.Printf("Unknown help topic: %q. Run 'hk help'.\n", args[0]) 135 os.Exit(2) 136 } 137 138 func maxStrLen(strs []string) (strlen int) { 139 for i := range strs { 140 if len(strs[i]) > strlen { 141 strlen = len(strs[i]) 142 } 143 } 144 return 145 } 146 147 var usageTemplate = template.Must(template.New("usage").Parse(` 148 Usage: hk <command> [-a <app or remote>] [options] [arguments] 149 150 151 Commands: 152 {{range .Commands}}{{if .Runnable}}{{if .List}} 153 {{.Name | printf (print "%-" $.MaxRunListName "s")}} {{.Short}}{{end}}{{end}}{{end}} 154 {{range .Plugins}} 155 {{.Name | printf (print "%-" $.MaxRunListName "s")}} {{.Short}} (plugin){{end}} 156 157 Run 'hk help [command]' for details. 158 159 160 Additional help topics: 161 {{range .Commands}}{{if not .Runnable}} 162 {{.Name | printf "%-8s"}} {{.Short}}{{end}}{{end}} 163 164 {{if .Dev}}This dev build of hk cannot auto-update itself. 165 {{end}}`[1:])) 166 167 var extraTemplate = template.Must(template.New("usage").Parse(` 168 Additional commands: 169 {{range .Commands}}{{if .Runnable}}{{if .ListAsExtra}} 170 {{.Name | printf (print "%-" $.MaxRunExtraName "s")}} {{.ShortExtra}}{{end}}{{end}}{{end}} 171 172 Run 'hk help [command]' for details. 173 174 `[1:])) 175 176 func printUsageTo(w io.Writer) { 177 var plugins []plugin 178 for _, path := range strings.Split(hkPath, ":") { 179 d, err := os.Open(path) 180 if err != nil { 181 if os.IsNotExist(err) { 182 continue 183 } 184 printFatal(err.Error()) 185 } 186 fi, err := d.Readdir(-1) 187 if err != nil { 188 printFatal(err.Error()) 189 } 190 for _, f := range fi { 191 if !f.IsDir() && f.Mode()&0111 != 0 { 192 plugins = append(plugins, plugin(f.Name())) 193 } 194 } 195 } 196 197 var runListNames []string 198 for i := range commands { 199 if commands[i].Runnable() && commands[i].List() { 200 runListNames = append(runListNames, commands[i].Name()) 201 } 202 } 203 for i := range plugins { 204 runListNames = append(runListNames, plugins[i].Name()) 205 } 206 207 usageTemplate.Execute(w, struct { 208 Commands []*Command 209 Plugins []plugin 210 Dev bool 211 MaxRunListName int 212 }{ 213 commands, 214 plugins, 215 Version == "dev", 216 maxStrLen(runListNames), 217 }) 218 } 219 220 func printExtra() { 221 var runExtraNames []string 222 for i := range commands { 223 if commands[i].Runnable() && commands[i].ListAsExtra() { 224 runExtraNames = append(runExtraNames, commands[i].Name()) 225 } 226 } 227 228 extraTemplate.Execute(os.Stdout, struct { 229 Commands []*Command 230 MaxRunExtraName int 231 }{ 232 commands, 233 maxStrLen(runExtraNames), 234 }) 235 } 236 237 func printAllUsage() { 238 w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0) 239 defer w.Flush() 240 cl := commandList(commands) 241 sort.Sort(cl) 242 for i := range cl { 243 if cl[i].Runnable() { 244 listRec(w, "hk "+cl[i].FullUsage(), "# "+cl[i].Short) 245 } 246 } 247 } 248 249 func printStyleGuide() { 250 cmap := make(map[string]commandList) 251 // group by category 252 for i := range commands { 253 if _, exists := cmap[commands[i].Category]; !exists { 254 cmap[commands[i].Category] = commandList{commands[i]} 255 } else { 256 cmap[commands[i].Category] = append(cmap[commands[i].Category], commands[i]) 257 } 258 } 259 // sort each category 260 for _, cl := range cmap { 261 sort.Sort(cl) 262 } 263 err := styleGuideTemplate.Execute(os.Stdout, struct { 264 CommandMap commandMap 265 }{ 266 cmap, 267 }) 268 if err != nil { 269 printFatal(err.Error()) 270 } 271 } 272 273 func (c *Command) UsageJSON() commandJSON { 274 return commandJSON{Root: c.Name(), Arguments: strings.TrimLeft(c.FullUsage(), c.Name()+" "), Comment: c.Short} 275 } 276 277 type commandJSON struct { 278 Root string `json:"root"` 279 Arguments string `json:"arguments"` 280 Comment string `json:"comment"` 281 } 282 283 type commandList []*Command 284 285 func (cl commandList) Len() int { return len(cl) } 286 func (cl commandList) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] } 287 func (cl commandList) Less(i, j int) bool { return cl[i].Name() < cl[j].Name() } 288 289 func (cl commandList) UsageJSON() []commandJSON { 290 a := make([]commandJSON, len(cl)) 291 for i := range cl { 292 a[i] = cl[i].UsageJSON() 293 } 294 return a 295 } 296 297 type commandMap map[string]commandList 298 299 func (cm commandMap) UsageJSON(prefix string) template.JS { 300 all := make([]map[string]interface{}, 0) 301 categories := make([]string, len(cm)) 302 iall := 0 303 for k := range cm { 304 categories[iall] = k 305 iall += 1 306 } 307 sort.Strings(categories) 308 for _, k := range categories { 309 m := map[string]interface{}{"title": k, "commands": cm[k].UsageJSON()} 310 all = append(all, m) 311 } 312 buf, err := json.MarshalIndent(all, prefix, " ") 313 if err != nil { 314 return template.JS(fmt.Sprintf("{\"error\": %q}", err.Error())) 315 } 316 resp := strings.Replace(string(buf), "\\u003c", "<", -1) 317 resp = strings.Replace(resp, "\\u003e", ">", -1) 318 return template.JS(resp) 319 } 320 321 var styleGuideTemplate = template.Must(template.New("styleguide").Delims("{{{", "}}}").Parse(` 322 <!DOCTYPE html> 323 <html> 324 <head> 325 <title>hk style guide</title> 326 327 <style> 328 body { 329 background: #282A36; 330 color: white; 331 font-family: Helvetica; 332 } 333 334 #viewing-options { 335 padding: 0; 336 } 337 338 #viewing-options li { 339 display: inline-block; 340 margin-right: 20px; 341 } 342 343 td { 344 font-family: monospace; 345 padding-right: 10px; 346 } 347 348 td:first-child { 349 width: 540px; 350 } 351 352 h2 { 353 color: #5A5D6E; 354 } 355 356 .prompt, 357 .comment { 358 color: #6272A4; 359 } 360 361 .command { 362 color: white; 363 } 364 365 .root { 366 color: #FF79C6; 367 font-weight: bold; 368 } 369 370 .arguments { 371 color: #66D9D0; 372 } 373 </style> 374 </head> 375 376 <body> 377 <script id="command-structure" type="text/x-handlebars-template"> 378 {{#groups}} 379 <h2>{{title}}</h2> 380 381 <table> 382 {{#commands}} 383 <tr> 384 <td> 385 <span class='prompt'>$</span> 386 <span class='command'>hk</span> 387 <span class='root'>{{root}}</span> 388 <span class='arguments'>{{arguments}}</span> 389 </td> 390 <td class='comment'># {{comment}}</td> 391 </tr> 392 {{/commands}} 393 </table> 394 {{/groups}} 395 </script> 396 397 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> 398 <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.1.2/handlebars.min.js"></script> 399 400 <script> 401 var source = $('#command-structure').html(); 402 var template = Handlebars.compile(source); 403 404 var data = {{{.CommandMap.UsageJSON " "}}} 405 406 $('body').append(template({groups: data})); 407 </script> 408 </body> 409 </html> 410 `[1:]))