diff --git a/app_test.go b/app_test.go index 3fc27b5..f7f53b7 100644 --- a/app_test.go +++ b/app_test.go @@ -978,7 +978,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}}, }}, @@ -987,9 +987,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}}, }}, @@ -1005,7 +1005,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}}, }}, @@ -1013,9 +1013,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}}, }}, @@ -1031,7 +1031,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"}}, }}, @@ -1040,9 +1040,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"}}, }}, @@ -1058,7 +1058,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}}, }}, @@ -1066,9 +1066,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}}, }}, @@ -1825,6 +1825,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 0000000..ef5699a --- /dev/null +++ b/docs.go @@ -0,0 +1,146 @@ +package cli + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + "text/template" + + "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 { + const name = "cli" + t, err := template.New(name).Parse(MarkdownDocTemplate) + if err != nil { + return err + } + return t.ExecuteTemplate(w, name, &cliTemplate{ + App: a, + Commands: prepareCommands(a.Commands, 0), + GlobalArgs: prepareArgsWithValues(a.Flags), + SynopsisArgs: prepareArgsSynopsis(a.Flags), + }) +} + +func prepareCommands(commands []Command, level int) []string { + coms := []string{} + for i := range commands { + command := &commands[i] + usage := "" + if command.Usage != "" { + usage = command.Usage + } + + prepared := fmt.Sprintf("%s %s\n\n%s\n", + strings.Repeat("#", level+2), + strings.Join(command.Names(), ", "), + usage, + ) + + flags := prepareArgsWithValues(command.Flags) + if len(flags) > 0 { + prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) + } + + 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 _, f := range flags { + flag, ok := f.(DocGenerationFlag) + if !ok { + continue + } + 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 += fmt.Sprintf("--%s", trimmed) + } else { + modifiedArg += fmt.Sprintf("-%s", trimmed) + } + } + modifiedArg += closer + if flag.TakesValue() { + modifiedArg += fmt.Sprintf("=%s", value) + } + + if addDetails { + modifiedArg += flagDetails(flag) + } + + args = append(args, modifiedArg+"\n") + + } + sort.Strings(args) + return args +} + +// flagDetails returns a string containing the flags metadata +func flagDetails(flag DocGenerationFlag) string { + description := flag.GetUsage() + 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 0000000..177eb19 --- /dev/null +++ b/docs_test.go @@ -0,0 +1,115 @@ +package cli + +import ( + "io/ioutil" + "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 expectFileContent(t *testing.T, file, expected string) { + data, err := ioutil.ReadFile(file) + expect(t, err, nil) + expect(t, string(data), expected) +} + +func TestToMarkdownFull(t *testing.T) { + // Given + app := testApp() + + // When + res, err := app.ToMarkdown() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-full.md", res) +} + +func TestToMarkdownNoFlags(t *testing.T) { + // Given + app := testApp() + app.Flags = nil + + // When + res, err := app.ToMarkdown() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-no-flags.md", res) +} + +func TestToMarkdownNoCommands(t *testing.T) { + // Given + app := testApp() + app.Commands = nil + + // When + res, err := app.ToMarkdown() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-no-commands.md", res) +} + +func TestToMan(t *testing.T) { + // Given + app := testApp() + + // When + res, err := app.ToMan() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-full.man", res) +} diff --git a/flag-gen/assets_vfsdata.go b/flag-gen/assets_vfsdata.go index 024039a..2e31627 100644 --- a/flag-gen/assets_vfsdata.go +++ b/flag-gen/assets_vfsdata.go @@ -28,9 +28,9 @@ var assets = func() http.FileSystem { "/source/flag-types.json": &vfsgen۰CompressedFileInfo{ name: "flag-types.json", modTime: time.Time{}, - uncompressedSize: 2559, + uncompressedSize: 3364, - compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x94\xc1\x6e\x9c\x30\x10\x86\xef\x3c\xc5\xc8\xbd\x40\xb5\x82\x1c\x56\x7b\xd8\x63\x55\xb5\xda\x5b\xa5\x34\xbd\x24\x51\xe4\x80\x21\x56\x1d\x1b\xd9\x43\xd4\x28\xca\xbb\x57\xf6\x2e\xbb\x60\x0c\x62\x5b\x2d\xb9\xfe\x23\x98\xef\x9b\xb1\xe6\x36\x02\x78\x8b\x00\x00\x88\xa4\xcf\x8c\x6c\x81\x7c\x51\x4a\x90\xd5\x3e\xc3\xd7\xda\x65\x8f\x9d\xec\x85\x8a\xc6\x86\x25\x15\x86\x1d\xb2\x82\x19\x24\x5b\x40\xdd\xb4\x49\xae\x24\xb2\x3f\xf8\x50\xb0\x92\x36\xc2\x16\x89\xfb\xa0\xfd\x4b\x4d\xb5\x61\xda\xc6\x06\x75\xae\xe4\x4b\xfa\xc3\x26\xb6\x79\x5c\xa6\xbf\x6c\x8f\xf4\x1a\x35\x97\x55\x9c\x24\x24\x02\x78\x5f\x85\x51\x7f\xfe\x3f\x6b\xa1\x72\xa4\x5c\xd8\x8f\x01\x9f\x28\x02\x37\xae\x0c\x8f\xaf\xd0\xf2\x2f\xa9\xf5\xb5\xd1\x14\xb9\x92\xbe\x19\xf2\x67\x96\xfa\xc5\x56\xb1\xeb\x33\x69\x18\x1b\xc6\xe0\x09\xb1\x36\xdb\x2c\xab\x94\xa0\xb2\x4a\x95\xae\xb2\xfa\x77\x95\xd9\x0e\xd9\x27\x87\xdc\xf6\x49\x26\xd4\xaf\x86\xda\x8e\xb1\xf7\x83\xf9\xde\xdf\x84\xa2\xb8\x59\xfb\xda\x65\x3f\x9e\x25\x3c\x0f\xb6\xb7\x23\xd7\x7d\x00\xbb\x82\xcd\x7a\x14\xf8\x3b\x93\x4c\xf3\xdc\x07\xf6\xe2\x71\xe0\xee\xb3\x0c\x10\x4b\x2e\xfc\xe1\xb7\x2d\xb8\x44\xa6\x4b\x9a\xb3\xb7\xf7\x31\xb8\x9d\x0c\xcc\x92\xcb\x05\x26\xb9\x93\xa1\x39\x5e\x4d\x8e\x72\x27\x31\xc0\xfa\xa1\xa4\xbd\x4f\x1f\x72\xea\xfa\x59\xaa\xd8\x45\xc5\x94\xcb\xb5\xe0\x39\xf3\x85\x3e\xfb\x85\x4b\xbc\x8c\xdb\xfb\xce\xdc\x4e\xda\x47\xcd\xf8\x48\x91\x24\xfb\xc8\x2a\x33\xad\x95\x8e\x25\x17\x53\x52\x9b\xf5\xa8\x96\x57\xba\x98\xd8\xe9\xf1\x8e\xa9\x1d\x48\xce\x93\xdb\xef\xde\x17\x33\xbd\xf4\x5f\xdf\xe0\x1d\xb9\x23\x43\xe8\xe1\xab\x9b\x4b\x19\xde\x41\xa0\x76\x99\x25\xf4\x87\x12\xdc\x42\x87\xe5\xbc\x35\xdc\xf0\xd0\xcd\x6a\x16\x39\x5a\xb6\xf7\xd9\x57\xeb\x86\x0f\xcf\x56\xb3\xc0\xdd\x9a\x86\x0d\x1f\xae\xc6\xbf\x5c\xd1\x7d\xf4\x37\x00\x00\xff\xff\x66\x52\x85\x44\xff\x09\x00\x00"), + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x95\x41\x6f\x9b\x40\x10\x85\xef\xfc\x8a\xe9\x56\x95\xa0\xb2\x20\x87\x28\x07\x4b\xbd\x54\x55\x2b\xdf\x2a\xb9\xe9\x25\x44\xd1\x06\x16\xb2\xea\x7a\x16\x2d\x43\xd4\xc8\xf2\x7f\xaf\x16\x9b\x04\x16\x70\x48\x5b\x2c\x5f\x7c\x78\xe3\xe5\xed\xfb\x66\x47\x73\xe3\x01\x6c\x3d\x00\x00\x86\x7c\x23\xd8\x12\xd8\x67\xad\x15\x5b\xec\x35\x7a\x2a\x6a\xed\xbe\xa5\x3d\x72\x55\x59\x31\xe3\xaa\x14\x07\x2d\x15\x25\xb1\x25\x90\xa9\x1a\x25\xd1\x48\xe2\x37\xdd\xa5\x22\xe3\x95\xb2\x45\x56\x1f\x68\xbe\x52\x70\x53\x0a\x63\xe5\x92\x4c\xa2\xf1\x31\xfc\x6e\x15\x6b\xee\x67\xe1\x4f\xeb\x11\xae\xc9\x48\xcc\xfd\x20\xe8\x58\xef\x55\x7b\xd2\x08\xaa\x0c\x42\xcc\x62\xc6\x3c\x80\xdd\x62\x38\xcc\x8f\x7f\x4f\x93\xea\x84\xb8\x54\xf6\x30\xd0\x03\x27\x90\x65\x5d\x86\xfb\x27\x68\x12\x9e\x57\xf0\x2f\x95\xe1\x24\x35\xba\xd9\x49\x6e\x44\xe8\x16\x1b\x08\xed\xc4\x47\x19\xf8\xa5\x10\xf0\x40\x54\x94\xcb\x28\xca\xb5\xe2\x98\x87\xda\xe4\x51\xf1\x2b\x8f\xac\x43\xf4\xbe\x0e\xd5\xf8\x04\x47\xe0\x5c\xf4\xc1\xd4\x77\xec\x7c\xe0\xad\x64\xdc\xbf\x8f\x51\xfa\xaa\x34\xa7\xab\x4b\x17\x52\xd6\x95\x27\xe1\x99\x16\xad\xd3\xf3\xda\xbd\x17\x6d\x01\x57\x97\xaf\xc5\xdb\x50\xb8\x2e\x8c\x44\xca\xfc\x98\x7d\xc8\x62\xb6\x68\x22\x8f\x46\xfd\x26\x50\x18\x99\xb8\x51\x1d\x79\x3c\x6a\x7b\x40\x06\xb2\xa2\x54\x6e\x93\x1b\x0b\x89\x24\x4c\xc6\x13\xb1\xdd\x8d\xc4\x92\x59\x73\x7d\x78\xf7\x09\x50\x2a\xd8\xc6\x18\x53\x4c\x23\xed\xb4\xc5\x9d\xfd\x99\x30\x08\x2b\x1c\x68\xb0\xc4\x13\xb4\x77\x85\x43\xcd\xbd\x78\x7b\x7f\xd3\x29\xfd\x5d\x21\x0d\xa4\x3c\x87\x8c\xfb\xa3\x77\x09\xaf\xfd\xec\xad\xfc\x5a\x4a\xe7\xa1\xb0\x56\x32\x11\x2e\x8a\x8f\x6e\x61\x8e\x87\x7e\x73\xdb\x22\xfe\x02\xec\x19\x90\xff\x7c\x8b\x20\xd8\x4b\x16\x96\x30\x46\x1b\x1f\xa5\x1a\xc3\x31\xfb\x74\x8c\x12\x73\x4a\xb3\x31\x7b\x99\xc5\x31\x6a\x87\x9b\x9c\x0d\xb7\x83\x8b\xc3\xac\xec\xa8\x7f\x3b\x73\xb5\x6f\x8f\x47\x7f\xca\x5e\x05\xd0\xcd\x79\x3c\xca\xf0\x1b\x18\xa8\xcd\xf3\x08\xba\xe4\x06\x5f\x41\xeb\x2e\x67\xf3\x0c\xae\xe5\xd0\x76\xa9\x4e\xb2\x5e\xac\xf7\x09\xf7\xcb\xb5\xec\x2f\x98\xea\x04\x1b\x66\x52\x4c\x67\xc5\x54\xff\x6f\xc7\x78\xb7\xde\x9f\x00\x00\x00\xff\xff\x61\x79\x63\x47\x24\x0d\x00\x00"), }, "/templates": &vfsgen۰DirInfo{ name: "templates", @@ -46,9 +46,9 @@ var assets = func() http.FileSystem { "/templates/cli_flags_generated.gotpl": &vfsgen۰CompressedFileInfo{ name: "cli_flags_generated.gotpl", modTime: time.Time{}, - uncompressedSize: 2102, + uncompressedSize: 2575, - compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x54\xc1\x52\xdb\x30\x10\x3d\xdb\x5f\xb1\xcd\x70\xb0\x99\x34\xb9\x97\xe1\x44\xa0\x65\xa6\x03\x4c\xa1\xdc\x15\x7b\xe5\x68\x10\x52\x90\xd6\x50\x26\x93\x7f\xef\xac\x24\x3b\x4e\x48\x21\x17\x7a\xd3\x6a\x9f\xde\x7b\x7a\x5a\x7b\x3a\x85\x33\x5b\x23\x34\x68\xd0\x09\xc2\x1a\xe6\xaf\x20\x9b\x13\x98\x5d\xc3\xd5\xf5\x1d\x9c\xcf\x2e\xef\x26\x79\xbe\x14\xd5\x83\x68\x10\x56\x2b\x98\xdc\xc4\xf5\x95\x78\x44\x58\xaf\xf3\x5c\x3d\x2e\xad\x23\x28\xf2\x6c\x24\xb5\x68\x46\x79\x36\xf2\xe4\x2a\x6b\x9e\x79\x49\xea\x11\x47\x79\x99\xaf\x56\xe0\x84\x69\x10\x8e\xd4\x18\x8e\x18\x08\xdf\x4e\x61\x72\xa1\x45\xe3\x99\x66\x3a\x65\xf2\xd0\x98\x24\x6a\xee\x81\xf2\x20\x20\xc0\x5f\x14\x2d\x80\x5e\x97\xb8\x01\xde\x71\xb5\x5e\xf7\xf5\xcc\x56\x24\x94\x66\xbe\x6d\xe0\x90\xd1\x93\x6b\x2b\x82\x55\x9e\xf1\x6e\x96\x79\x72\xca\x34\x79\xf6\xdb\x8b\x66\x50\x9e\x9b\xe7\x7b\xe1\x36\xf5\x85\xd2\x78\x23\x68\xd1\x6f\xfc\xc2\xa7\x56\x39\xac\xb3\xb9\xb5\x3a\xcf\x7e\xa8\xba\x46\x93\xa5\x6a\xb5\xfa\x0a\x4a\x02\x3e\x25\x03\xf7\x42\xb7\x08\xe4\xda\x90\x59\x16\xca\x6c\xf7\x1e\xf1\x18\x9a\xba\x5f\x0f\x29\x66\xe8\x49\x19\x41\xca\x9a\x0d\xd1\x60\x33\x3b\x7e\x97\x6f\x9d\x73\xc6\xb7\xc1\x3c\x38\xa4\xd6\x19\x8e\xd6\xa1\xa8\xc5\x5c\x23\x38\x5c\x3a\xf4\x68\x28\x2a\x58\x09\xb4\x50\x1e\x9e\xd9\x29\x9f\x2c\xa4\x75\xd0\x72\x46\x50\xa3\x14\xad\x26\x5f\xe6\xb2\x35\x15\x14\x72\x6f\xd0\x65\x12\x2b\x4a\x88\x91\x71\xe4\x51\x18\xb8\x1f\xbb\xe8\x0a\x59\x26\x73\xdf\x91\xc2\xf9\xce\x1d\x2d\x10\x0c\x6f\x04\x33\x18\xc6\xe0\x03\xc9\x44\xb1\x4f\x53\x06\x64\x92\xba\xf4\xdd\xf3\xf5\x6a\x2f\x0b\xa4\x05\x3a\xb0\x0e\x8c\xa5\x5e\x90\x27\xd0\x25\xec\x07\xe2\x1b\xd2\xa2\x04\x9e\x83\x2d\xf5\xae\x97\x1c\xec\x52\x80\xb6\xf6\xc1\x43\xbb\x0c\xca\x21\x76\xbe\xb7\x00\x6d\x2b\xa1\xf7\x2a\x8e\x3b\xef\x5b\x7c\x67\xd6\x10\xfe\xa1\x59\x7c\x24\x66\x56\x32\xdc\x48\xda\xd6\x74\x57\xa8\xe0\x38\xe1\xca\x37\xd4\x45\xc8\x3c\xe6\x17\xba\x7c\x1e\xa1\x23\x0e\xc3\x35\x1a\x0d\xbf\xbb\x61\x27\x6c\xa3\xf6\x5b\x1f\x66\xdf\xe9\xc6\x71\x90\x0c\xdf\xbb\x5d\xee\x35\x31\x86\x6a\xc2\x9b\xb7\x48\xfd\x8c\x68\x3b\x17\xfa\xe0\xf0\x9a\x00\xff\xcc\xf4\xf6\x1b\xfa\x5f\x19\x2a\x09\xd2\xf3\x9f\x34\xa6\x18\xcd\x5c\xc4\xc8\xba\x08\xcb\x13\xc6\x7c\x39\x05\xa3\xc2\x44\x1e\x14\xbc\xf4\x65\x9e\xad\xfb\x47\xfa\x77\x3e\xfc\x2e\x21\x95\x77\xe8\x52\x10\x63\xf0\x48\x70\x1c\xda\xc9\xe3\x67\x66\x23\x39\x17\x8f\x34\xf9\x19\x9c\x05\x27\x65\x8c\x6c\x98\xc6\xc6\xc0\x8d\x70\x1e\x5d\xd4\x5e\xf2\xba\x1e\x03\x3a\xc7\x34\xbd\x52\xc2\x0c\x5d\xec\x40\x65\xfc\xd9\x87\xda\xba\xc2\x28\x5d\x32\x36\xfd\xd5\x59\x9e\x81\x03\x03\x07\x64\x9c\xf1\x53\xbc\x71\x7a\x26\x3c\x45\xb7\xbb\x0c\x83\xfe\xd0\x69\x82\x45\xc3\x03\x53\x07\xbe\x73\x7f\xe2\x6f\x00\x00\x00\xff\xff\x9b\xde\x9c\x04\x36\x08\x00\x00"), + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x55\x4d\x8f\xdb\x36\x10\x3d\x4b\xbf\x62\x6a\xe4\x20\x2d\x5c\xfb\xde\x20\xa7\x75\x36\x0d\x50\x24\x8b\xc6\xcd\x9d\x96\x86\x32\xb1\x34\xa9\x90\xa3\xdd\x2e\x0c\xff\xf7\x62\x48\xea\xc3\x1f\x58\xfb\xb2\x3d\x89\xe4\x0c\xdf\x7b\x7c\x33\xa4\x96\x4b\xb8\xb7\x35\x42\x83\x06\x9d\x20\xac\x61\xf3\x0a\xb2\xf9\x08\xab\xef\xf0\xed\xfb\x1a\x3e\xaf\xbe\xae\x17\x79\xde\x8a\xea\x49\x34\x08\xfb\x3d\x2c\x1e\xe3\xf8\x9b\xd8\x21\x1c\x0e\x79\xae\x76\xad\x75\x04\x45\x9e\xcd\xa4\x16\xcd\x8c\xbf\x3b\xe2\x8f\x27\x57\x59\xf3\xcc\x43\x52\x3b\x9c\xe5\x65\xbe\xdf\x83\x13\xa6\x41\xf8\xa0\xe6\xf0\x81\xf3\xe1\x8f\x4f\xb0\x78\xd0\xa2\xf1\x8c\xb6\x5c\x32\x47\x08\x2c\x12\x03\xc7\x40\x79\x10\x10\xd2\x5f\x14\x6d\x81\x5e\x5b\x1c\x13\xd7\x3c\x3b\x1c\x86\xf9\xca\x56\x24\x94\x66\xbc\xe3\xc4\x29\xa2\x27\xd7\x55\x04\xfb\x3c\xe3\xd5\x2c\xf3\xe4\x94\x69\xf2\xec\x1f\x2f\x9a\xc9\xf4\xb3\x79\xfe\x29\xdc\x38\x7f\x50\x1a\x1f\x05\x6d\x87\x85\xbf\xf1\x57\xa7\x1c\xd6\xd9\xc6\x5a\x9d\x67\x7f\xaa\xba\x46\x93\xa5\xd9\x7e\xff\x3b\x28\x09\xf8\x2b\x09\xf8\x29\x74\x87\x40\xae\x0b\xd6\x65\x61\x9a\x9d\x9e\x23\x6e\x43\x53\x0f\xe3\x29\xc4\x0a\x3d\x29\x23\x48\x59\x33\x02\x4d\x16\xb3\xbb\x37\xf1\x0e\x39\x7b\xfc\x23\x88\x07\x87\xd4\x39\xc3\xd6\x3a\x14\xb5\xd8\x68\x04\x87\xad\x43\x8f\x86\x22\x83\x95\x40\x5b\xe5\xe1\x99\x95\xf2\xce\x42\x5a\x07\x1d\x7b\x04\x35\x4a\xd1\x69\xf2\x65\x2e\x3b\x53\x41\x21\x2f\x1a\x5d\x26\xb2\xa2\x84\x68\x19\x5b\x1e\x89\x81\xe3\x31\x8a\xae\x90\x65\x12\xf7\x05\x29\xec\xef\xd5\xd1\x16\xc1\xf0\x42\x10\x83\xa1\x0d\xae\x50\x26\x88\x4b\x9c\x32\x64\x26\xaa\xaf\xbe\x2f\xdf\xc0\xf6\xb2\x45\xda\xa2\x03\xeb\xc0\x58\x1a\x08\xb9\x03\x5d\xca\xbd\x42\x3e\x82\x16\x25\x70\x1f\x1c\xb1\xf7\xb1\xa4\x60\x2d\x9e\xd0\xc7\xb6\x18\xce\xcb\x55\x9d\x9c\x15\x88\x73\x40\xc4\x22\xcc\xc1\xb2\xbe\x17\xe5\x11\xa4\xd0\x1e\xaf\xa8\x19\x09\xce\xd5\x0c\x3b\xa2\x80\xa1\x3d\xbe\x20\x85\x6b\x70\x54\x82\x58\xf4\xe4\x27\x77\xc1\xed\xb5\x08\x60\x97\x8b\x11\x42\x23\xed\x89\x13\x89\x21\xf5\x1f\x08\xdf\x43\x9c\xb4\xa9\x30\x35\x08\x03\xb8\x6b\xe9\x95\x91\x52\x96\x3a\x33\xd1\xd8\x1e\x8a\x40\x68\xbd\xb8\xae\xbd\xb7\x6e\xd4\x7e\xec\x5a\xba\x4a\x83\x77\xa7\x40\xa0\xad\x7d\xf2\xd0\xb5\x41\x4a\x24\xb7\x12\x04\x68\x5b\x09\x7d\x91\x77\xde\x1b\x70\x84\x77\x6f\x0d\xe1\xbf\xb4\x8a\xb7\x8e\x91\x95\x0c\x2d\x2a\x6d\x67\xfa\x9e\xac\xe0\x2e\xe5\x95\x67\xd0\x45\xb8\x44\xf1\x1c\x21\xca\xfb\x11\x7a\xe0\xf0\x5a\xcc\x66\xd3\x87\x74\x1a\x09\xcb\xa8\xfd\xd1\x4b\x3b\x44\xfa\xf7\x65\x52\x5b\x3e\x77\xd7\x5e\x14\x31\x87\x6a\xc1\x8b\x3f\x90\x86\x4b\xaf\xed\x46\xe8\x9b\xcd\x6b\x42\xfa\x7b\xba\x77\x59\xd0\xff\xe5\xa1\x92\x20\x3d\xff\x1a\xa3\x8b\x51\xcc\x43\xb4\xac\xb7\xb0\xfc\xc8\x39\xbf\x7d\x02\xa3\xc2\xa5\xbe\xc9\x78\xe9\xcb\x3c\x3b\x9c\xbf\x00\x67\xfe\x70\x5d\x82\x2b\x6f\xc0\x25\x23\xe6\xe0\x91\xe0\x2e\x84\x93\xc6\xf7\xf4\x46\xb2\x2f\x1e\x69\xf1\x57\x50\x16\x94\x94\xd1\xb2\xa9\x1b\xa3\x80\x47\xe1\x3c\xba\xc8\xdd\xf2\xb8\x9e\x03\x3a\xc7\x30\x03\x53\xca\x99\xaa\x38\x49\x95\xf1\xbe\x87\xb9\x75\x85\x51\xba\xe4\xdc\xf4\x9b\x66\x7a\x4e\x9c\x08\xb8\xc1\xe3\x8c\x4b\x71\xa6\xf4\x5e\x78\x8a\x6a\x4f\x11\x26\xf1\xa9\xd2\x94\x16\x05\x4f\x44\xdd\x58\xe7\x71\xc7\x7f\x01\x00\x00\xff\xff\xa4\x4d\x6e\xce\x0f\x0a\x00\x00"), }, } fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{ diff --git a/flag-gen/main.go b/flag-gen/main.go index 9caa94e..579a7dd 100644 --- a/flag-gen/main.go +++ b/flag-gen/main.go @@ -27,6 +27,7 @@ type FlagType struct { ContextType string `json:"context_type"` Parser string `json:"parser"` ParserCast string `json:"parser_cast"` + ValueString string `json:"valueString"` } func main() { diff --git a/flag-gen/source/flag-types.json b/flag-gen/source/flag-types.json index 3306025..7ec5ddd 100644 --- a/flag-gen/source/flag-types.json +++ b/flag-gen/source/flag-types.json @@ -5,7 +5,8 @@ "value": false, "dest": true, "context_default": "false", - "parser": "strconv.ParseBool(f.Value.String())" + "parser": "strconv.ParseBool(f.Value.String())", + "valueString": "return \"\"" }, { "name": "BoolT", @@ -14,7 +15,8 @@ "dest": true, "doctail": " that is true by default", "context_default": "false", - "parser": "strconv.ParseBool(f.Value.String())" + "parser": "strconv.ParseBool(f.Value.String())", + "valueString": "return \"\"" }, { "name": "Duration", @@ -23,7 +25,8 @@ "dest": true, "doctail": " (see https://golang.org/pkg/time/#ParseDuration)", "context_default": "0", - "parser": "time.ParseDuration(f.Value.String())" + "parser": "time.ParseDuration(f.Value.String())", + "valueString": "return f.Value.String()" }, { "name": "Float64", @@ -31,7 +34,8 @@ "value": true, "dest": true, "context_default": "0", - "parser": "strconv.ParseFloat(f.Value.String(), 64)" + "parser": "strconv.ParseFloat(f.Value.String(), 64)", + "valueString": "return fmt.Sprintf(\"%f\", f.Value)" }, { "name": "Generic", @@ -39,7 +43,8 @@ "value": true, "dest": false, "context_default": "nil", - "context_type": "interface{}" + "context_type": "interface{}", + "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\"" }, { "name": "Int64", @@ -47,7 +52,8 @@ "value": true, "dest": true, "context_default": "0", - "parser": "strconv.ParseInt(f.Value.String(), 0, 64)" + "parser": "strconv.ParseInt(f.Value.String(), 0, 64)", + "valueString": "return fmt.Sprintf(\"%d\", f.Value)" }, { "name": "Int", @@ -56,7 +62,8 @@ "dest": true, "context_default": "0", "parser": "strconv.ParseInt(f.Value.String(), 0, 64)", - "parser_cast": "int(parsed)" + "parser_cast": "int(parsed)", + "valueString": "return fmt.Sprintf(\"%d\", f.Value)" }, { "name": "IntSlice", @@ -65,7 +72,8 @@ "dest": false, "context_default": "nil", "context_type": "[]int", - "parser": "(f.Value.(*IntSlice)).Value(), error(nil)" + "parser": "(f.Value.(*IntSlice)).Value(), error(nil)", + "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\"" }, { "name": "Int64Slice", @@ -74,7 +82,8 @@ "dest": false, "context_default": "nil", "context_type": "[]int64", - "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)" + "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)", + "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\"" }, { "name": "String", @@ -82,7 +91,8 @@ "value": true, "dest": true, "context_default": "\"\"", - "parser": "f.Value.String(), error(nil)" + "parser": "f.Value.String(), error(nil)", + "valueString": "return f.Value" }, { "name": "StringSlice", @@ -91,7 +101,8 @@ "dest": false, "context_default": "nil", "context_type": "[]string", - "parser": "(f.Value.(*StringSlice)).Value(), error(nil)" + "parser": "(f.Value.(*StringSlice)).Value(), error(nil)", + "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\"" }, { "name": "Uint64", @@ -99,7 +110,8 @@ "value": true, "dest": true, "context_default": "0", - "parser": "strconv.ParseUint(f.Value.String(), 0, 64)" + "parser": "strconv.ParseUint(f.Value.String(), 0, 64)", + "valueString": "return fmt.Sprintf(\"%d\", f.Value)" }, { "name": "Uint", @@ -108,6 +120,7 @@ "dest": true, "context_default": "0", "parser": "strconv.ParseUint(f.Value.String(), 0, 64)", - "parser_cast": "uint(parsed)" + "parser_cast": "uint(parsed)", + "valueString": "return fmt.Sprintf(\"%d\", f.Value)" } ] diff --git a/flag-gen/templates/cli_flags_generated.gotpl b/flag-gen/templates/cli_flags_generated.gotpl index 97bd49f..ffd18ae 100644 --- a/flag-gen/templates/cli_flags_generated.gotpl +++ b/flag-gen/templates/cli_flags_generated.gotpl @@ -4,6 +4,7 @@ package {{ .PackageName }} import ( "flag" + "fmt" "strconv" "time" ) @@ -40,6 +41,22 @@ func (f {{ $flag.Name }}Flag) IsRequired() bool { return f.Required } +// TakesValue returns true of the flag takes a value, otherwise false +func (f {{ $flag.Name }}Flag) TakesValue() bool { + return {{ $flag.Value }} +} + +// GetUsage returns the usage string for the flag +func (f {{ $flag.Name }}Flag) 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 {{ $flag.Name }}Flag) GetValue() string { + {{ $flag.ValueString }} +} + // {{ $flag.Name }} looks up the value of a local {{ $flag.Name }}Flag, returns // {{ $flag.ContextDefault }} if not found func (c *Context) {{ $flag.Name }}(name string) {{ if ne .ContextType "" }}{{ $flag.ContextType }}{{ else }}{{ $flag.Type }}{{- end }} { @@ -66,4 +83,4 @@ func lookup{{ $flag.Name }}(name string, set *flag.FlagSet) {{ if ne .ContextTyp } return {{ $flag.ContextDefault }} } -{{ end }} \ No newline at end of file +{{ end }} diff --git a/flag.go b/flag.go index d98c808..5b6db0c 100644 --- a/flag.go +++ b/flag.go @@ -83,6 +83,21 @@ type RequiredFlag interface { IsRequired() bool } +// DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationFlag interface { + Flag + + // 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 +} + // errorableFlag is an interface that allows us to return errors during apply // it allows flags defined in this library to return errors in a fashion backwards compatible // TODO remove in v2 and modify the existing Flag interface to return errors diff --git a/flag_generated.go b/flag_generated.go index 83e10d7..af68f95 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -4,6 +4,7 @@ package cli import ( "flag" + "fmt" "strconv" "time" ) @@ -35,6 +36,22 @@ func (f BoolFlag) IsRequired() bool { return f.Required } +// 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 "" +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { @@ -89,6 +106,22 @@ func (f BoolTFlag) IsRequired() bool { return f.Required } +// 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 "" +} + // BoolT looks up the value of a local BoolTFlag, returns // false if not found func (c *Context) BoolT(name string) bool { @@ -144,6 +177,22 @@ func (f DurationFlag) IsRequired() bool { return f.Required } +// 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() +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { @@ -199,6 +248,22 @@ func (f Float64Flag) IsRequired() bool { return f.Required } +// 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) +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { @@ -253,6 +318,25 @@ func (f GenericFlag) IsRequired() bool { return f.Required } +// 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 "" +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { @@ -308,6 +392,22 @@ func (f Int64Flag) IsRequired() bool { return f.Required } +// 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) +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { @@ -363,6 +463,22 @@ func (f IntFlag) IsRequired() bool { return f.Required } +// 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) +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { @@ -417,6 +533,25 @@ func (f IntSliceFlag) IsRequired() bool { return f.Required } +// 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 "" +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { @@ -471,6 +606,25 @@ func (f Int64SliceFlag) IsRequired() bool { return f.Required } +// 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 "" +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { @@ -526,6 +680,22 @@ func (f StringFlag) IsRequired() bool { return f.Required } +// 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 +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { @@ -580,6 +750,25 @@ func (f StringSliceFlag) IsRequired() bool { return f.Required } +// 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 "" +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { @@ -635,6 +824,22 @@ func (f Uint64Flag) IsRequired() bool { return f.Required } +// 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) +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { @@ -690,6 +895,22 @@ func (f UintFlag) IsRequired() bool { return f.Required } +// 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) +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { @@ -716,3 +937,4 @@ func lookupUint(name string, set *flag.FlagSet) uint { } return 0 } + diff --git a/go.mod b/go.mod index de7b6e5..91106bc 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.10 require ( github.com/BurntSushi/toml v0.3.1 + github.com/cpuguy83/go-md2man v1.0.10 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index f3cb3d0..2b8b863 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= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= diff --git a/help.go b/help.go index e504fc2..33985d3 100644 --- a/help.go +++ b/help.go @@ -10,79 +10,6 @@ import ( "unicode/utf8" ) -// 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"}, diff --git a/template.go b/template.go new file mode 100644 index 0000000..24c44c8 --- /dev/null +++ b/template.go @@ -0,0 +1,107 @@ +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. +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 MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }} + +% {{ .App.Author }} + +# 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 }}` diff --git a/testdata/expected-doc-full.man b/testdata/expected-doc-full.man new file mode 100644 index 0000000..a2d0e6d --- /dev/null +++ b/testdata/expected-doc-full.man @@ -0,0 +1,80 @@ +.nh +.TH greet(8) + +.SH Harrison + +.SH NAME +.PP +greet \- Some app + + +.SH SYNOPSIS +.PP +greet + +.PP +.RS + +.nf +[\-\-another\-flag|\-b] +[\-\-flag|\-\-fl|\-f]=[value] +[\-\-socket|\-s]=[value] + +.fi +.RE + + +.SH DESCRIPTION +.PP +app [first\_arg] [second\_arg] + +.PP +\fBUsage\fP: + +.PP +.RS + +.nf +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] + +.fi +.RE + + +.SH GLOBAL OPTIONS +.PP +\fB\-\-another\-flag, \-b\fP: another usage text + +.PP +\fB\-\-flag, \-\-fl, \-f\fP="": + +.PP +\fB\-\-socket, \-s\fP="": some usage text (default: value) + + +.SH COMMANDS +.SH config, c +.PP +another usage test + +.PP +\fB\-\-another\-flag, \-b\fP: another usage text + +.PP +\fB\-\-flag, \-\-fl, \-f\fP="": + +.SS sub\-config, s, ss +.PP +another usage test + +.PP +\fB\-\-sub\-command\-flag, \-s\fP: some usage text + +.PP +\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="": + +.SH info, i, in +.PP +retrieve generic information + +.SH some\-command \ No newline at end of file diff --git a/testdata/expected-doc-full.md b/testdata/expected-doc-full.md new file mode 100644 index 0000000..8fb9f58 --- /dev/null +++ b/testdata/expected-doc-full.md @@ -0,0 +1,62 @@ +% greet(8) + +% Harrison + +# NAME + +greet - Some app + +# SYNOPSIS + +greet + +``` +[--another-flag|-b] +[--flag|--fl|-f]=[value] +[--socket|-s]=[value] +``` + +# DESCRIPTION + +app [first_arg] [second_arg] + +**Usage**: + +``` +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# GLOBAL OPTIONS + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +**--socket, -s**="": some usage text (default: value) + + +# COMMANDS + +## config, c + +another usage test + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-config, s, ss + +another usage test + +**--sub-command-flag, -s**: some usage text + +**--sub-flag, --sub-fl, -s**="": + +## info, i, in + +retrieve generic information + +## some-command + + diff --git a/testdata/expected-doc-no-commands.md b/testdata/expected-doc-no-commands.md new file mode 100644 index 0000000..dba7bb0 --- /dev/null +++ b/testdata/expected-doc-no-commands.md @@ -0,0 +1,36 @@ +% greet(8) + +% Harrison + +# NAME + +greet - Some app + +# SYNOPSIS + +greet + +``` +[--another-flag|-b] +[--flag|--fl|-f]=[value] +[--socket|-s]=[value] +``` + +# DESCRIPTION + +app [first_arg] [second_arg] + +**Usage**: + +``` +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# GLOBAL OPTIONS + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +**--socket, -s**="": some usage text (default: value) + diff --git a/testdata/expected-doc-no-flags.md b/testdata/expected-doc-no-flags.md new file mode 100644 index 0000000..8ce60fd --- /dev/null +++ b/testdata/expected-doc-no-flags.md @@ -0,0 +1,47 @@ +% greet(8) + +% Harrison + +# NAME + +greet - Some app + +# SYNOPSIS + +greet + +# DESCRIPTION + +app [first_arg] [second_arg] + +**Usage**: + +``` +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# COMMANDS + +## config, c + +another usage test + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-config, s, ss + +another usage test + +**--sub-command-flag, -s**: some usage text + +**--sub-flag, --sub-fl, -s**="": + +## info, i, in + +retrieve generic information + +## some-command + +