diff --git a/docs.go b/docs.go index 4c0e1f6..021cc2b 100644 --- a/docs.go +++ b/docs.go @@ -68,15 +68,16 @@ func prepareCommands(commands []*Command, level int) []string { if command.Hidden { continue } - usage := "" - if command.Usage != "" { - usage = command.Usage - } - prepared := fmt.Sprintf("%s %s\n\n%s\n", + usageText := prepareUsageText(command) + + usage := prepareUsage(command, usageText) + + prepared := fmt.Sprintf("%s %s\n\n%s%s", strings.Repeat("#", level+2), strings.Join(command.Names(), ", "), usage, + usageText, ) flags := prepareArgsWithValues(command.Flags) @@ -155,3 +156,40 @@ func flagDetails(flag DocGenerationFlag) string { } return ": " + description } + +func prepareUsageText(command *Command) string { + if command.UsageText == "" { + return "" + } + + // Remove leading and trailing newlines + preparedUsageText := strings.Trim(command.UsageText, "\n") + + var usageText string + if strings.Contains(preparedUsageText, "\n") { + // Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such + // that it will not break the continuous code block. + for _, ln := range strings.Split(preparedUsageText, "\n") { + usageText += fmt.Sprintf(" %s\n", ln) + } + } else { + // Style a single line as a note + usageText = fmt.Sprintf(">%s\n", preparedUsageText) + } + + return usageText +} + +func prepareUsage(command *Command, usageText string) string { + if command.Usage == "" { + return "" + } + + usage := command.Usage + "\n" + // Add a newline to the Usage IFF there is a UsageText + if usageText != "" { + usage += "\n" + } + + return usage +} diff --git a/docs_test.go b/docs_test.go index fac106f..962c366 100644 --- a/docs_test.go +++ b/docs_test.go @@ -67,6 +67,47 @@ func testApp() *App { }, { Name: "hidden-command", Hidden: true, + }, { + Aliases: []string{"u"}, + Flags: []Flag{ + &StringFlag{ + Name: "flag", + Aliases: []string{"fl", "f"}, + TakesFile: true, + }, + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", + }, + }, + Name: "usage", + Usage: "standard usage text", + UsageText: ` +Usage for the usage text +- formatted: Based on the specified ConfigMap and summon secrets.yml +- list: Inspect the environment for a specific process running on a Pod +- for_effect: Compare 'namespace' environment with 'local' + +` + "```" + ` +func() { ... } +` + "```" + ` + +Should be a part of the same code block +`, + Subcommands: []*Command{{ + Aliases: []string{"su"}, + Flags: []Flag{ + &BoolFlag{ + Name: "sub-command-flag", + Aliases: []string{"s"}, + Usage: "some usage text", + }, + }, + Name: "sub-usage", + Usage: "standard usage text", + UsageText: "Single line of UsageText", + }}, }} app.UsageText = "app [first_arg] [second_arg]" app.Usage = "Some app" @@ -175,3 +216,110 @@ func TestToManWithSection(t *testing.T) { expect(t, err, nil) expectFileContent(t, "testdata/expected-doc-full.man", res) } + +func Test_prepareUsageText(t *testing.T) { + t.Run("no UsageText provided", func(t *testing.T) { + // Given + cmd := Command{} + + // When + res := prepareUsageText(&cmd) + + // Then + expect(t, res, "") + }) + + t.Run("single line UsageText", func(t *testing.T) { + // Given + cmd := Command{UsageText: "Single line usage text"} + + // When + res := prepareUsageText(&cmd) + + // Then + expect(t, res, ">Single line usage text\n") + }) + + t.Run("multiline UsageText", func(t *testing.T) { + // Given + cmd := Command{ + UsageText: ` +Usage for the usage text +- Should be a part of the same code block +`, + } + + // When + res := prepareUsageText(&cmd) + + // Then + test := ` Usage for the usage text + - Should be a part of the same code block +` + expect(t, res, test) + }) + + t.Run("multiline UsageText has formatted embedded markdown", func(t *testing.T) { + // Given + cmd := Command{ + UsageText: ` +Usage for the usage text + +` + "```" + ` +func() { ... } +` + "```" + ` + +Should be a part of the same code block +`, + } + + // When + res := prepareUsageText(&cmd) + + // Then + test := ` Usage for the usage text + + ` + "```" + ` + func() { ... } + ` + "```" + ` + + Should be a part of the same code block +` + expect(t, res, test) + }) +} + +func Test_prepareUsage(t *testing.T) { + t.Run("no Usage provided", func(t *testing.T) { + // Given + cmd := Command{} + + // When + res := prepareUsage(&cmd, "") + + // Then + expect(t, res, "") + }) + + t.Run("simple Usage", func(t *testing.T) { + // Given + cmd := Command{Usage: "simple usage text"} + + // When + res := prepareUsage(&cmd, "") + + // Then + expect(t, res, cmd.Usage+"\n") + }) + + t.Run("simple Usage with UsageText", func(t *testing.T) { + // Given + cmd := Command{Usage: "simple usage text"} + + // When + res := prepareUsage(&cmd, "a non-empty string") + + // Then + expect(t, res, cmd.Usage+"\n\n") + }) +} diff --git a/testdata/expected-doc-full.man b/testdata/expected-doc-full.man index 2bea507..3ac3553 100644 --- a/testdata/expected-doc-full.man +++ b/testdata/expected-doc-full.man @@ -75,4 +75,46 @@ another usage test .PP retrieve generic information -.SH some\-command \ No newline at end of file +.SH some\-command +.SH usage, u +.PP +standard usage text + +.PP +.RS + +.nf +Usage for the usage text +\- formatted: Based on the specified ConfigMap and summon secrets.yml +\- list: Inspect the environment for a specific process running on a Pod +\- for\_effect: Compare 'namespace' environment with 'local' + +``` +func() { ... } +``` + +Should be a part of the same code block + +.fi +.RE + +.PP +\fB\-\-another\-flag, \-b\fP: another usage text + +.PP +\fB\-\-flag, \-\-fl, \-f\fP="": + +.SS sub\-usage, su +.PP +standard usage text + +.PP +.RS + +.PP +Single line of UsageText + +.RE + +.PP +\fB\-\-sub\-command\-flag, \-s\fP: some usage text diff --git a/testdata/expected-doc-full.md b/testdata/expected-doc-full.md index f272274..7b8c0d7 100644 --- a/testdata/expected-doc-full.md +++ b/testdata/expected-doc-full.md @@ -58,3 +58,29 @@ retrieve generic information ## some-command +## usage, u + +standard usage text + + Usage for the usage text + - formatted: Based on the specified ConfigMap and summon secrets.yml + - list: Inspect the environment for a specific process running on a Pod + - for_effect: Compare 'namespace' environment with 'local' + + ``` + func() { ... } + ``` + + Should be a part of the same code block + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-usage, su + +standard usage text + +>Single line of UsageText + +**--sub-command-flag, -s**: some usage text diff --git a/testdata/expected-doc-no-authors.md b/testdata/expected-doc-no-authors.md index f272274..7b8c0d7 100644 --- a/testdata/expected-doc-no-authors.md +++ b/testdata/expected-doc-no-authors.md @@ -58,3 +58,29 @@ retrieve generic information ## some-command +## usage, u + +standard usage text + + Usage for the usage text + - formatted: Based on the specified ConfigMap and summon secrets.yml + - list: Inspect the environment for a specific process running on a Pod + - for_effect: Compare 'namespace' environment with 'local' + + ``` + func() { ... } + ``` + + Should be a part of the same code block + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-usage, su + +standard usage text + +>Single line of UsageText + +**--sub-command-flag, -s**: some usage text diff --git a/testdata/expected-doc-no-flags.md b/testdata/expected-doc-no-flags.md index 2994593..ebd0416 100644 --- a/testdata/expected-doc-no-flags.md +++ b/testdata/expected-doc-no-flags.md @@ -43,3 +43,29 @@ retrieve generic information ## some-command +## usage, u + +standard usage text + + Usage for the usage text + - formatted: Based on the specified ConfigMap and summon secrets.yml + - list: Inspect the environment for a specific process running on a Pod + - for_effect: Compare 'namespace' environment with 'local' + + ``` + func() { ... } + ``` + + Should be a part of the same code block + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-usage, su + +standard usage text + +>Single line of UsageText + +**--sub-command-flag, -s**: some usage text diff --git a/testdata/expected-fish-full.fish b/testdata/expected-fish-full.fish index dc41e5a..cc449c5 100644 --- a/testdata/expected-fish-full.fish +++ b/testdata/expected-fish-full.fish @@ -2,7 +2,7 @@ function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet' for i in (commandline -opc) - if contains -- $i config c sub-config s ss info i in some-command + if contains -- $i config c sub-config s ss info i in some-command usage u sub-usage su return 1 end end @@ -27,3 +27,10 @@ complete -c greet -n '__fish_seen_subcommand_from info i in' -f -l help -s h -d complete -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information' complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help' complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command' +complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_greet_no_subcommand' -a 'usage u' -d 'standard usage text' +complete -c greet -n '__fish_seen_subcommand_from usage u' -l flag -s fl -s f -r +complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l another-flag -s b -d 'another usage text' +complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_seen_subcommand_from usage u' -a 'sub-usage su' -d 'standard usage text' +complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l sub-command-flag -s s -d 'some usage text'