From 5ad2b5dbb46f68f6e17bbd5a5d5c9f6ae0b35afd Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Sat, 3 Aug 2019 12:41:50 +0200 Subject: [PATCH] Add markdown and man page docs generation methods This adds two new methods to the `App` struct: - `ToMarkdown`: creates a markdown documentation string - `ToMan`: creates a man page string Signed-off-by: Sascha Grunert --- app_test.go | 36 +++++--- docs.go | 153 ++++++++++++++++++++++++++++++++ docs_test.go | 82 +++++++++++++++++ flag.go | 10 +++ flag_generated.go | 221 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 4 + help.go | 79 +---------------- template.go | 108 ++++++++++++++++++++++ 9 files changed, 606 insertions(+), 88 deletions(-) create mode 100644 docs.go create mode 100644 docs_test.go create mode 100644 template.go diff --git a/app_test.go b/app_test.go index 69d1418d4c..48b40fc117 100644 --- a/app_test.go +++ b/app_test.go @@ -895,7 +895,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "error_case_empty_input_with_required_flag_on_command", appRunInput: []string{"myCLI", "myCommand"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, }}, @@ -904,9 +904,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "error_case_empty_input_with_required_flag_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", - Subcommands: []Command{Command{ + Subcommands: []Command{{ Name: "mySubCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, }}, @@ -922,7 +922,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "valid_case_help_input_with_required_flag_on_command", appRunInput: []string{"myCLI", "myCommand", "--help"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, }}, @@ -930,9 +930,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "valid_case_help_input_with_required_flag_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", - Subcommands: []Command{Command{ + Subcommands: []Command{{ Name: "mySubCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, }}, @@ -948,7 +948,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "error_case_optional_input_with_required_flag_on_command", appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, }}, @@ -957,9 +957,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "error_case_optional_input_with_required_flag_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", - Subcommands: []Command{Command{ + Subcommands: []Command{{ Name: "mySubCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, }}, @@ -975,7 +975,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "valid_case_required_flag_input_on_command", appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, }}, @@ -983,9 +983,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "valid_case_required_flag_input_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"}, - appCommands: []Command{Command{ + appCommands: []Command{{ Name: "myCommand", - Subcommands: []Command{Command{ + Subcommands: []Command{{ Name: "mySubCommand", Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, }}, @@ -1742,6 +1742,18 @@ func (c *customBoolFlag) GetName() string { return c.Nombre } +func (c *customBoolFlag) TakesValue() bool { + return false +} + +func (c *customBoolFlag) GetValue() string { + return "value" +} + +func (c *customBoolFlag) GetUsage() string { + return "usage" +} + func (c *customBoolFlag) Apply(set *flag.FlagSet) { set.String(c.Nombre, c.Nombre, "") } diff --git a/docs.go b/docs.go new file mode 100644 index 0000000000..38cc0342e9 --- /dev/null +++ b/docs.go @@ -0,0 +1,153 @@ +package cli + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + "text/template" + "time" + + "github.com/cpuguy83/go-md2man/md2man" +) + +// ToMarkdown creates a markdown string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToMarkdown() (string, error) { + var w bytes.Buffer + if err := a.writeDocTemplate(&w); err != nil { + return "", err + } + return w.String(), nil +} + +// ToMan creates a man page string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToMan() (string, error) { + var w bytes.Buffer + if err := a.writeDocTemplate(&w); err != nil { + return "", err + } + man := md2man.Render(w.Bytes()) + return string(man), nil +} + +type CliTemplate struct { + App *App + Date string + Commands []string + GlobalArgs []string + SynopsisArgs []string +} + +func (a *App) writeDocTemplate(w io.Writer) error { + now := time.Now() + const name = "cli" + t, err := template.New(name).Parse(markdownDocTemplate) + if err != nil { + return err + } + return t.ExecuteTemplate(w, name, &CliTemplate{ + App: a, + Date: fmt.Sprintf("%s %d", now.Month(), now.Year()), + Commands: prepareCommands(a.Commands, 0), + GlobalArgs: prepareArgsWithValues(a.Flags), + SynopsisArgs: prepareArgsSynopsis(a.Flags), + }) +} + +const nl = "\n" +const noDescription = "_no description available_" + +func prepareCommands(commands []Command, level int) []string { + coms := []string{} + for i := range commands { + command := &commands[i] + prepared := strings.Repeat("#", level+2) + " " + + strings.Join(command.Names(), ", ") + nl + + usage := noDescription + if command.Usage != "" { + usage = command.Usage + } + prepared += nl + usage + nl + + flags := prepareArgsWithValues(command.Flags) + if len(flags) > 0 { + prepared += nl + } + prepared += strings.Join(flags, nl) + if len(flags) > 0 { + prepared += nl + } + + coms = append(coms, prepared) + + // recursevly iterate subcommands + if len(command.Subcommands) > 0 { + coms = append( + coms, + prepareCommands(command.Subcommands, level+1)..., + ) + } + } + + return coms +} + +func prepareArgsWithValues(flags []Flag) []string { + return prepareFlags(flags, ", ", "**", "**", `""`, true) +} + +func prepareArgsSynopsis(flags []Flag) []string { + return prepareFlags(flags, "|", "[", "]", "[value]", false) +} + +func prepareFlags( + flags []Flag, + sep, opener, closer, value string, + addDetails bool, +) []string { + args := []string{} + for _, flag := range flags { + modifiedArg := opener + for _, s := range strings.Split(flag.GetName(), ",") { + trimmed := strings.TrimSpace(s) + if len(modifiedArg) > len(opener) { + modifiedArg += sep + } + if len(trimmed) > 1 { + modifiedArg += "--" + trimmed + } else { + modifiedArg += "-" + trimmed + } + } + modifiedArg += closer + if flag.TakesValue() { + modifiedArg += "=" + value + } + + if addDetails { + modifiedArg += flagDetails(flag) + } + + args = append(args, modifiedArg+nl) + + } + sort.Strings(args) + return args +} + +// flagDetails returns a string containing the flags metadata +func flagDetails(flag Flag) string { + description := flag.GetUsage() + if flag.GetUsage() == "" { + description = noDescription + } + value := flag.GetValue() + if value != "" { + description += " (default: " + value + ")" + } + return ": " + description +} diff --git a/docs_test.go b/docs_test.go new file mode 100644 index 0000000000..173555ddaf --- /dev/null +++ b/docs_test.go @@ -0,0 +1,82 @@ +package cli + +import ( + "testing" +) + +func testApp() *App { + app := NewApp() + app.Name = "greet" + app.Flags = []Flag{ + StringFlag{ + Name: "socket, s", + Usage: "some usage text", + Value: "value", + }, + StringFlag{Name: "flag, fl, f"}, + BoolFlag{ + Name: "another-flag, b", + Usage: "another usage text", + }, + } + app.Commands = []Command{{ + Aliases: []string{"c"}, + Flags: []Flag{ + StringFlag{Name: "flag, fl, f"}, + BoolFlag{ + Name: "another-flag, b", + Usage: "another usage text", + }, + }, + Name: "config", + Usage: "another usage test", + Subcommands: []Command{{ + Aliases: []string{"s", "ss"}, + Flags: []Flag{ + StringFlag{Name: "sub-flag, sub-fl, s"}, + BoolFlag{ + Name: "sub-command-flag, s", + Usage: "some usage text", + }, + }, + Name: "sub-config", + Usage: "another usage test", + }}, + }, { + Aliases: []string{"i", "in"}, + Name: "info", + Usage: "retrieve generic information", + }, { + Name: "some-command", + }} + app.UsageText = "app [first_arg] [second_arg]" + app.Usage = "Some app" + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" + app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + return app +} + +func TestToMarkdown(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.ToMarkdown() + + // Then + // TODO: extend test case + expect(t, err, nil) +} + +func TestToMan(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.ToMan() + + // Then + // TODO: extend test case + expect(t, err, nil) +} diff --git a/flag.go b/flag.go index d98c808b47..39b78d55c3 100644 --- a/flag.go +++ b/flag.go @@ -73,6 +73,16 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) GetName() string + + // TakesValue returns true of the flag takes a value, otherwise false + TakesValue() bool + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string } // RequiredFlag is an interface that allows us to mark flags as required diff --git a/flag_generated.go b/flag_generated.go index a3e4d6e9d5..045b3b9451 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -2,6 +2,7 @@ package cli import ( "flag" + "fmt" "strconv" "time" ) @@ -30,6 +31,22 @@ func (f BoolFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f BoolFlag) TakesValue() bool { + return false +} + +// GetUsage returns the usage string for the flag +func (f BoolFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f BoolFlag) GetValue() string { + return "" +} + // IsRequired returns the whether or not the flag is required func (f BoolFlag) IsRequired() bool { return f.Required @@ -84,6 +101,22 @@ func (f BoolTFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f BoolTFlag) TakesValue() bool { + return false +} + +// GetUsage returns the usage string for the flag +func (f BoolTFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f BoolTFlag) GetValue() string { + return "" +} + // IsRequired returns the whether or not the flag is required func (f BoolTFlag) IsRequired() bool { return f.Required @@ -139,6 +172,22 @@ func (f DurationFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f DurationFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f DurationFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f DurationFlag) GetValue() string { + return f.Value.String() +} + // IsRequired returns the whether or not the flag is required func (f DurationFlag) IsRequired() bool { return f.Required @@ -194,6 +243,22 @@ func (f Float64Flag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f Float64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f Float64Flag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f Float64Flag) GetValue() string { + return fmt.Sprintf("%f", f.Value) +} + // IsRequired returns the whether or not the flag is required func (f Float64Flag) IsRequired() bool { return f.Required @@ -248,6 +313,25 @@ func (f GenericFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f GenericFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f GenericFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f GenericFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + // IsRequired returns the whether or not the flag is required func (f GenericFlag) IsRequired() bool { return f.Required @@ -303,6 +387,22 @@ func (f Int64Flag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f Int64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f Int64Flag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f Int64Flag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + // IsRequired returns the whether or not the flag is required func (f Int64Flag) IsRequired() bool { return f.Required @@ -358,6 +458,22 @@ func (f IntFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f IntFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f IntFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f IntFlag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + // IsRequired returns the whether or not the flag is required func (f IntFlag) IsRequired() bool { return f.Required @@ -412,6 +528,25 @@ func (f IntSliceFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f IntSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f IntSliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f IntSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + // IsRequired returns the whether or not the flag is required func (f IntSliceFlag) IsRequired() bool { return f.Required @@ -466,6 +601,25 @@ func (f Int64SliceFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f Int64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f Int64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f Int64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + // IsRequired returns the whether or not the flag is required func (f Int64SliceFlag) IsRequired() bool { return f.Required @@ -521,6 +675,22 @@ func (f StringFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f StringFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f StringFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f StringFlag) GetValue() string { + return f.Value +} + // IsRequired returns the whether or not the flag is required func (f StringFlag) IsRequired() bool { return f.Required @@ -575,6 +745,25 @@ func (f StringSliceFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f StringSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f StringSliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f StringSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + // IsRequired returns the whether or not the flag is required func (f StringSliceFlag) IsRequired() bool { return f.Required @@ -630,6 +819,22 @@ func (f Uint64Flag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f Uint64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f Uint64Flag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f Uint64Flag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + // IsRequired returns the whether or not the flag is required func (f Uint64Flag) IsRequired() bool { return f.Required @@ -685,6 +890,22 @@ func (f UintFlag) GetName() string { return f.Name } +// TakesValue returns true of the flag takes a value, otherwise false +func (f UintFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f UintFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f UintFlag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + // IsRequired returns the whether or not the flag is required func (f UintFlag) IsRequired() bool { return f.Required diff --git a/go.mod b/go.mod index d5b55c460c..968f53fa57 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.12 require ( github.com/BurntSushi/toml v0.3.1 + github.com/cpuguy83/go-md2man v1.0.10 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index a2ea70251e..a8e436ad6d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/help.go b/help.go index d611971465..c79e3441de 100644 --- a/help.go +++ b/help.go @@ -9,79 +9,6 @@ import ( "text/template" ) -// AppHelpTemplate is the text template for the Default help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} - -VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} - -DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} - -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} - -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} - -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} - -COPYRIGHT: - {{.Copyright}}{{end}} -` - -// CommandHelpTemplate is the text template for the command help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var CommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} - -CATEGORY: - {{.Category}}{{end}}{{if .Description}} - -DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} - -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} -` - -// SubcommandHelpTemplate is the text template for the subcommand help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} - -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} - -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} -` - var helpCommand = Command{ Name: "help", Aliases: []string{"h"}, @@ -140,7 +67,7 @@ func ShowAppHelpAndExit(c *Context, exitCode int) { // ShowAppHelp is an action that displays the help. func ShowAppHelp(c *Context) (err error) { if c.App.CustomAppHelpTemplate == "" { - HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + HelpPrinter(c.App.Writer, appHelpTemplate, c.App) return } customAppData := func() map[string]interface{} { @@ -183,7 +110,7 @@ func ShowCommandHelpAndExit(c *Context, command string, code int) { func ShowCommandHelp(ctx *Context, command string) error { // show the subcommand help for a command with subcommands if command == "" { - HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) + HelpPrinter(ctx.App.Writer, subcommandHelpTemplate, ctx.App) return nil } @@ -192,7 +119,7 @@ func ShowCommandHelp(ctx *Context, command string) error { if c.CustomHelpTemplate != "" { HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil) } else { - HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + HelpPrinter(ctx.App.Writer, commandHelpTemplate, c) } return nil } diff --git a/template.go b/template.go new file mode 100644 index 0000000000..39baa8e68e --- /dev/null +++ b/template.go @@ -0,0 +1,108 @@ +package cli + +// appHelpTemplate is the text template for the Default help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +const appHelpTemplate = `NAME: + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if len .Authors}} + +AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +GLOBAL OPTIONS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{.Copyright}}{{end}} +` + +// commandHelpTemplate is the text template for the command help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +const commandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +// subcommandHelpTemplate is the text template for the subcommand help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var subcommandHelpTemplate = `NAME: + {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +const markdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }} +% {{ .App.Author }} +% {{ .Date }} + +# NAME + +{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} + +# SYNOPSIS + +{{ .App.Name }} +{{ if .SynopsisArgs }} +` + "```" + `{{ range $v := .SynopsisArgs }} +{{ $v }}{{ end }} +` + "```" + ` +{{ end }}{{ if .App.UsageText }} +# DESCRIPTION + +{{ .App.UsageText }} +{{ end }} +**Usage**: + +` + "```" + ` +{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +` + "```" + ` +{{ if .GlobalArgs }} +# GLOBAL OPTIONS +{{ range $v := .GlobalArgs }} +{{ $v }}{{ end }} +{{ end }}{{ if .Commands }} +# COMMANDS +{{ range $v := .Commands }} +{{ $v }}{{ end }}{{ end }}`