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  }