github.com/clusterize-io/tusk@v0.6.3-0.20211001020217-cfe8a8cd0d4a/appcli/help.go (about) 1 package appcli 2 3 import ( 4 "bytes" 5 "fmt" 6 "html/template" 7 "io" 8 "strings" 9 10 "github.com/clusterize-io/tusk/runner" 11 "github.com/clusterize-io/tusk/ui" 12 "github.com/urfave/cli" 13 ) 14 15 // init sets the help templates for urfave/cli. 16 // nolint: lll, gochecknoinits 17 func init() { 18 // These are both used, so both must be overridden 19 cli.HelpPrinterCustom = wrapPrinter(cli.HelpPrinterCustom) 20 cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { 21 cli.HelpPrinterCustom(w, templ, data, nil) 22 } 23 24 cli.FlagNamePrefixer = flagPrefixer 25 26 cli.AppHelpTemplate = `{{.Name}}{{if .Usage}} - {{.Usage}}{{end}} 27 28 Usage: 29 {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} <task> [task options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} 30 31 Version: 32 {{.Version}}{{end}}{{end}}{{if .Description}} 33 34 Description: 35 {{indent 3 .Description}}{{end}}{{if .VisibleCommands}} 36 37 Tasks:{{range .VisibleCategories}}{{$categoryName := .Name}}{{if $categoryName}} 38 {{$categoryName}}:{{end}}{{range .VisibleCommands}} 39 {{if $categoryName}} {{end}}{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} 40 {{end}} 41 ` 42 } 43 44 // ShowAppHelp shows the help for a given app. 45 func ShowAppHelp(logger *ui.Logger, app *cli.App) { 46 app.Setup() 47 cli.HelpPrinter(logger.Stdout, cli.AppHelpTemplate, app) 48 } 49 50 type helpPrinterCustom = func(io.Writer, string, interface{}, map[string]interface{}) 51 52 // helpPrinter includes the custom indent template function. 53 func wrapPrinter(p helpPrinterCustom) helpPrinterCustom { 54 return func(w io.Writer, tpl string, data interface{}, funcs map[string]interface{}) { 55 customFuncs := map[string]interface{}{ 56 "indent": func(spaces int, text string) string { 57 padding := strings.Repeat(" ", spaces) 58 return padding + strings.ReplaceAll(text, "\n", "\n"+padding) 59 }, 60 } 61 62 for k, v := range funcs { 63 customFuncs[k] = v 64 } 65 66 p(w, tpl, data, customFuncs) 67 } 68 } 69 70 // flagPrefixer formats the command-line flag usage. 71 func flagPrefixer(fullName, placeholder string) string { 72 var output string 73 74 parts := strings.Split(fullName, ",") 75 for _, flagName := range parts { 76 flagName = strings.Trim(flagName, " ") 77 output = joinFlagString(output, flagName) 78 } 79 80 if strings.HasPrefix(output, "--") { 81 output = " " + output 82 } 83 84 if placeholder != "" { 85 output = output + " <" + placeholder + ">" 86 } 87 88 return output 89 } 90 91 func joinFlagString(existing, flagName string) string { 92 if existing == "" { 93 return prependHyphens(flagName) 94 } 95 96 if len(flagName) == 1 { 97 return prependHyphens(flagName) + ", " + existing 98 } 99 100 return existing + ", " + prependHyphens(flagName) 101 } 102 103 func prependHyphens(flagName string) string { 104 if len(flagName) == 1 { 105 return "-" + flagName 106 } 107 108 return "--" + flagName 109 } 110 111 func createCommandHelp(t *runner.Task) string { 112 // nolint: lll 113 return fmt.Sprintf(`{{.HelpName}}{{if .Usage}} - {{.Usage}}{{end}} 114 115 Usage: 116 {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{end}}{{end}}{{if .Category}} 117 118 Category: 119 {{.Category}}{{end}}{{if .Description}} 120 121 Description: 122 {{indent 3 .Description}}{{end}}%s{{if .VisibleFlags}} 123 124 Options: 125 {{range $index, $option := .VisibleFlags}}{{if $index}} 126 {{end}}{{$option}}{{end}}{{end}} 127 `, createArgsSection(t)) 128 } 129 130 func createArgsSection(t *runner.Task) string { 131 argsTpl := `{{if .}} 132 133 Arguments: 134 {{range $index, $arg := .}}{{if $index}} 135 {{end}}{{$arg}}{{end}}{{end}}` 136 137 tpl := template.New(fmt.Sprintf("%s help", t.Name)) 138 tpl = template.Must(tpl.Parse(argsTpl)) 139 140 padArg := getArgPadder(t) 141 142 args := make([]string, 0, len(t.Args)) 143 for _, arg := range t.Args { 144 text := fmt.Sprintf("%s%s", padArg(arg.Name), arg.Usage) 145 args = append(args, strings.Trim(text, " ")) 146 } 147 148 var argsSection bytes.Buffer 149 if err := tpl.Execute(&argsSection, args); err != nil { 150 panic(err) 151 } 152 153 return argsSection.String() 154 } 155 156 func getArgPadder(t *runner.Task) func(string) string { 157 maxLength := 0 158 for _, arg := range t.Args { 159 if len(arg.Name) > maxLength { 160 maxLength = len(arg.Name) 161 } 162 } 163 s := fmt.Sprintf("%%-%ds", maxLength+2) 164 return func(text string) string { 165 return fmt.Sprintf(s, text) 166 } 167 }