From 5602a78bd45608ec400e56634c86a264b94362c2 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Mon, 17 Jul 2023 12:57:37 +0800 Subject: [PATCH 1/3] [refactor] add Subcommands field to Info struct - add tests for subcommands --- cmd.go | 27 ++++++++++++++++ cmd_test.go | 33 ++++++++++++++++++++ help.go | 27 +++++++++++++++- help_test.go | 5 +++ supercommand.go | 38 +++++------------------ supercommand_test.go | 73 +++++++++++++++++++++++++------------------- 6 files changed, 139 insertions(+), 64 deletions(-) diff --git a/cmd.go b/cmd.go index 6bba560e..c9ad4479 100644 --- a/cmd.go +++ b/cmd.go @@ -13,6 +13,7 @@ import ( "os" "os/signal" "path/filepath" + "sort" "strings" "github.com/juju/ansiterm" @@ -284,6 +285,9 @@ type Info struct { // Doc is the long documentation for the Command. Doc string + // Subcommands stores the name and description of each subcommand. + Subcommands map[string]string + // Examples is a collection of running examples. Examples string @@ -374,6 +378,9 @@ func (i *Info) HelpWithSuperFlags(superF *gnuflag.FlagSet, f *gnuflag.FlagSet) [ if len(i.Examples) > 0 { fmt.Fprintf(buf, "\nExamples:\n%s", i.Examples) } + if len(i.Subcommands) > 0 { + fmt.Fprintf(buf, "\n%s", i.describeCommands()) + } if len(i.SeeAlso) > 0 { fmt.Fprintf(buf, "\nSee also:\n") for _, entry := range i.SeeAlso { @@ -384,6 +391,26 @@ func (i *Info) HelpWithSuperFlags(superF *gnuflag.FlagSet, f *gnuflag.FlagSet) [ return buf.Bytes() } +func (i *Info) describeCommands() string { + // Sort command names, and work out length of the longest one + cmdNames := make([]string, 0, len(i.Subcommands)) + longest := 0 + for name := range i.Subcommands { + if len(name) > longest { + longest = len(name) + } + cmdNames = append(cmdNames, name) + } + sort.Strings(cmdNames) + + descr := "Subcommands:\n" + for _, name := range cmdNames { + purpose := i.Subcommands[name] + descr += fmt.Sprintf(" %-*s - %s\n", longest, name, purpose) + } + return descr +} + // Errors from commands can be ErrSilent (don't print an error message), // ErrHelp (show the help) or some other error related to needed flags // missing, or needed positional args missing, in which case we should diff --git a/cmd_test.go b/cmd_test.go index b4281f77..0663a22b 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -386,6 +386,39 @@ command details `[1:]) } +func (s *CmdHelpSuite) TestSuperShowsSubcommands(c *gc.C) { + s.info.Subcommands = map[string]string{ + "application": "Wait for an application to reach a specified state.", + "machine": "Wait for a machine to reach a specified state.", + "model": "Wait for a model to reach a specified state.", + "unit": "Wait for a unit to reach a specified state.", + } + + s.assertHelp(c, ` +Usage: verb [flags] + +Summary: +command purpose + +Flags: +--five (= "") + option-doc +--one (= "") + option-doc +--three (= "") + option-doc + +Details: +command details + +Subcommands: + application - Wait for an application to reach a specified state. + machine - Wait for a machine to reach a specified state. + model - Wait for a model to reach a specified state. + unit - Wait for a unit to reach a specified state. +`[1:]) +} + type CmdDocumentationSuite struct { testing.LoggingCleanupSuite diff --git a/help.go b/help.go index cc0e5aee..16bee033 100644 --- a/help.go +++ b/help.go @@ -31,7 +31,7 @@ func (c *helpCommand) init() { c.topics = map[string]topic{ "commands": { short: "Basic help for all commands", - long: func() string { return c.super.describeCommands(true) }, + long: func() string { return c.describeCommands() }, }, flagKey: { short: fmt.Sprintf("%vs common to all commands", strings.Title(c.super.FlagKnownAs)), @@ -61,6 +61,31 @@ func (c *helpCommand) addTopic(name, short string, long func() string, aliases . } } +func (c *helpCommand) describeCommands() string { + commands := c.super.describeCommands() + + // Sort command names, and work out length of the longest one + cmdNames := make([]string, 0, len(commands)) + longest := 0 + for name := range commands { + if len(name) > longest { + longest = len(name) + } + cmdNames = append(cmdNames, name) + } + sort.Strings(cmdNames) + + var descr string + for _, name := range cmdNames { + if len(descr) > 0 { + descr += "\n" + } + purpose := commands[name] + descr += fmt.Sprintf("%-*s %s", longest, name, purpose) + } + return descr +} + func (c *helpCommand) globalOptions() string { buf := &bytes.Buffer{} fmt.Fprintf(buf, `Global %vs diff --git a/help_test.go b/help_test.go index cbfb8473..21d9ec6c 100644 --- a/help_test.go +++ b/help_test.go @@ -70,6 +70,11 @@ func (s *HelpCommandSuite) TestHelpOutput(c *gc.C) { message: "too many args", args: []string{"help", "blah", "blah"}, errMatch: `extra arguments to command help: \["blah"\]`, + }, { + args: []string{"help", "commands"}, + helpMatch: "blah\\s+blah the juju" + + "documentation\\s+Generate the documentation for all commands" + + "help\\s+Show help on a command or other topic.", }, } { supername := "jujutest" diff --git a/supercommand.go b/supercommand.go index 303852f3..503af5c1 100644 --- a/supercommand.go +++ b/supercommand.go @@ -330,27 +330,9 @@ func (c *SuperCommand) insert(value commandReference) { } // describeCommands returns a short description of each registered subcommand. -func (c *SuperCommand) describeCommands(simple bool) string { - var lineFormat = " %-*s - %s" - var outputFormat = "commands:\n%s" - if simple { - lineFormat = "%-*s %s" - outputFormat = "%s" - } - cmds := make([]string, len(c.subcmds)) - i := 0 - longest := 0 - for name := range c.subcmds { - if len(name) > longest { - longest = len(name) - } - cmds[i] = name - i++ - } - sort.Strings(cmds) - var result []string - for _, name := range cmds { - action := c.subcmds[name] +func (c *SuperCommand) describeCommands() map[string]string { + result := make(map[string]string, len(c.subcmds)) + for name, action := range c.subcmds { if deprecated, _ := action.Deprecated(); deprecated { continue } @@ -359,9 +341,9 @@ func (c *SuperCommand) describeCommands(simple bool) string { if action.alias != "" { purpose = "Alias for '" + action.alias + "'." } - result = append(result, fmt.Sprintf(lineFormat, longest, name, purpose)) + result[name] = purpose } - return fmt.Sprintf(outputFormat, strings.Join(result, "\n")) + return result } // Info returns a description of the currently selected subcommand, or of the @@ -373,18 +355,12 @@ func (c *SuperCommand) Info() *Info { info.FlagKnownAs = c.FlagKnownAs return &info } - docParts := []string{} - if doc := strings.TrimSpace(c.Doc); doc != "" { - docParts = append(docParts, doc) - } - if cmds := c.describeCommands(false); cmds != "" { - docParts = append(docParts, cmds) - } return &Info{ Name: c.Name, Args: " ...", Purpose: c.Purpose, - Doc: strings.Join(docParts, "\n\n"), + Doc: strings.TrimSpace(c.Doc), + Subcommands: c.describeCommands(), Aliases: c.Aliases, FlagKnownAs: c.FlagKnownAs, } diff --git a/supercommand_test.go b/supercommand_test.go index 4f75eb41..65dff8f1 100644 --- a/supercommand_test.go +++ b/supercommand_test.go @@ -49,9 +49,16 @@ type SuperCommandSuite struct { var _ = gc.Suite(&SuperCommandSuite{}) -const docText = "\n documentation\\s+- Generate the documentation for all commands" -const helpText = "\n help\\s+- Show help on a command or other topic." -const helpCommandsText = "commands:" + docText + helpText +func baseSubcommandsPlus(newCommands map[string]string) map[string]string { + subcommands := map[string]string{ + "documentation": "Generate the documentation for all commands", + "help": "Show help on a command or other topic.", + } + for name, purpose := range newCommands { + subcommands[name] = purpose + } + return subcommands +} func (s *SuperCommandSuite) SetUpTest(c *gc.C) { s.IsolationSuite.SetUpTest(c) @@ -64,14 +71,18 @@ func (s *SuperCommandSuite) TestDispatch(c *gc.C) { info := jc.Info() c.Assert(info.Name, gc.Equals, "jujutest") c.Assert(info.Args, gc.Equals, " ...") - c.Assert(info.Doc, gc.Matches, helpCommandsText) + c.Assert(info.Doc, gc.Equals, "") + c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(nil)) jc, _, err := initDefenestrate([]string{"discombobulate"}) c.Assert(err, gc.ErrorMatches, "unrecognized command: jujutest discombobulate") info = jc.Info() c.Assert(info.Name, gc.Equals, "jujutest") c.Assert(info.Args, gc.Equals, " ...") - c.Assert(info.Doc, gc.Matches, "commands:\n defenestrate - defenestrate the juju"+docText+helpText) + c.Assert(info.Doc, gc.Equals, "") + c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{ + "defenestrate": "defenestrate the juju", + })) jc, tc, err := initDefenestrate([]string{"defenestrate"}) c.Assert(err, gc.IsNil) @@ -140,20 +151,14 @@ func (s *SuperCommandSuite) TestAliasesRegistered(c *gc.C) { jc.Register(&TestCommand{Name: "flip", Aliases: []string{"flap", "flop"}}) info := jc.Info() - c.Assert(info.Doc, gc.Equals, `commands: - documentation - Generate the documentation for all commands - flap - Alias for 'flip'. - flip - flip the juju - flop - Alias for 'flip'. - help - Show help on a command or other topic.`) + c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{ + "flap": "Alias for 'flip'.", + "flip": "flip the juju", + "flop": "Alias for 'flip'.", + })) } func (s *SuperCommandSuite) TestInfo(c *gc.C) { - commandsDoc := `commands: - documentation - Generate the documentation for all commands - flapbabble - flapbabble the juju - flip - flip the juju` - jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ Name: "jujutest", Purpose: "to be purposeful", @@ -162,18 +167,23 @@ func (s *SuperCommandSuite) TestInfo(c *gc.C) { info := jc.Info() c.Assert(info.Name, gc.Equals, "jujutest") c.Assert(info.Purpose, gc.Equals, "to be purposeful") - // info doc starts with the jc.Doc and ends with the help command - c.Assert(info.Doc, gc.Matches, jc.Doc+"(.|\n)*") - c.Assert(info.Doc, gc.Matches, "(.|\n)*"+helpCommandsText) + c.Assert(info.Doc, gc.Matches, jc.Doc) + c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(nil)) + subcommands := baseSubcommandsPlus(map[string]string{ + "flapbabble": "flapbabble the juju", + "flip": "flip the juju", + }) jc.Register(&TestCommand{Name: "flip"}) jc.Register(&TestCommand{Name: "flapbabble"}) info = jc.Info() - c.Assert(info.Doc, gc.Matches, jc.Doc+"\n\n"+commandsDoc+helpText) + c.Assert(info.Doc, gc.Matches, jc.Doc) + c.Assert(info.Subcommands, gc.DeepEquals, subcommands) jc.Doc = "" info = jc.Info() - c.Assert(info.Doc, gc.Matches, commandsDoc+helpText) + c.Assert(info.Doc, gc.Equals, "") + c.Assert(info.Subcommands, gc.DeepEquals, subcommands) } type testVersionFlagCommand struct { @@ -457,11 +467,11 @@ func (s *SuperCommandSuite) TestRegisterAlias(c *gc.C) { info := jc.Info() // NOTE: deprecated `bar` not shown in commands. - c.Assert(info.Doc, gc.Equals, `commands: - documentation - Generate the documentation for all commands - foo - Alias for 'test'. - help - Show help on a command or other topic. - test - to be simple`) + c.Assert(info.Doc, gc.Equals, "") + c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{ + "foo": "Alias for 'test'.", + "test": "to be simple", + })) for _, test := range []struct { name string @@ -523,12 +533,11 @@ func (s *SuperCommandSuite) TestRegisterSuperAlias(c *gc.C) { info := jc.Info() // NOTE: deprecated `bar` not shown in commands. - c.Assert(info.Doc, gc.Equals, `commands: - bar - bar functions - bar-foo - Alias for 'bar foo'. - documentation - Generate the documentation for all commands - help - Show help on a command or other topic. - test - to be simple`) + c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{ + "bar": "bar functions", + "bar-foo": "Alias for 'bar foo'.", + "test": "to be simple", + })) for _, test := range []struct { args []string From d24b33f68867ebfc89bc33dacbaa0e9b75f257e3 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Mon, 17 Jul 2023 15:07:51 +0800 Subject: [PATCH 2/3] Handle subcommands in documentation command - recursively generate docs for subcommands - add "Subcommands" section to generated Markdown - don't print default commands (help etc) --- cmd.go | 12 ++++ documentation.go | 144 +++++++++++++++++++++++++++++++----------- documentation_test.go | 1 + export_test.go | 4 +- 4 files changed, 121 insertions(+), 40 deletions(-) diff --git a/cmd.go b/cmd.go index c9ad4479..968c15e2 100644 --- a/cmd.go +++ b/cmd.go @@ -391,11 +391,23 @@ func (i *Info) HelpWithSuperFlags(superF *gnuflag.FlagSet, f *gnuflag.FlagSet) [ return buf.Bytes() } +// Default commands should be hidden from the help output. +func isDefaultCommand(cmd string) bool { + switch cmd { + case "documentation", "help", "version": + return true + } + return false +} + func (i *Info) describeCommands() string { // Sort command names, and work out length of the longest one cmdNames := make([]string, 0, len(i.Subcommands)) longest := 0 for name := range i.Subcommands { + if isDefaultCommand(name) { + continue + } if len(name) > longest { longest = len(name) } diff --git a/documentation.go b/documentation.go index 391506b7..a1af18b1 100644 --- a/documentation.go +++ b/documentation.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "sort" "strings" @@ -151,6 +152,11 @@ func (c *documentationCommand) computeReverseAliases() { // dumpSeveralFiles is invoked when every command is dumped into // a separated entity func (c *documentationCommand) dumpSeveralFiles() error { + if len(c.super.subcmds) == 0 { + fmt.Printf("No commands found for %s", c.super.Name) + return nil + } + // Attempt to create output directory. This will fail if: // - we don't have permission to create the dir // - a file already exists at the given path @@ -159,16 +165,6 @@ func (c *documentationCommand) dumpSeveralFiles() error { return err } - if len(c.super.subcmds) == 0 { - fmt.Printf("No commands found for %s", c.super.Name) - return nil - } - - sorted := c.getSortedListCommands() - c.computeReverseAliases() - - // if the ids were provided, we must have the same - // number of commands and ids if c.idsPath != "" { // get the list of ids c.ids, err = c.readFileIds(c.idsPath) @@ -186,31 +182,52 @@ func (c *documentationCommand) dumpSeveralFiles() error { } writer := bufio.NewWriter(f) - _, err = writer.WriteString(c.commandsIndex(sorted)) + _, err = writer.WriteString(c.commandsIndex()) if err != nil { return err } f.Close() } - folder := c.out + "/%s.md" - for _, command := range sorted { - target := fmt.Sprintf(folder, command) + return c.writeDocs(c.out, []string{c.super.Name}, true) +} + +// writeDocs (recursively) writes docs for all commands in the given folder. +func (c *documentationCommand) writeDocs(folder string, superCommands []string, printDefaultCommands bool) error { + c.computeReverseAliases() + + for name, ref := range c.super.subcmds { + if !printDefaultCommands && isDefaultCommand(name) { + continue + } + commandSeq := append(superCommands, name) + target := fmt.Sprintf("%s.md", strings.Join(commandSeq[1:], "_")) + target = strings.ReplaceAll(target, " ", "_") + target = filepath.Join(folder, target) + f, err := os.Create(target) if err != nil { return err } writer := bufio.NewWriter(f) - formatted := c.formatCommand(c.super.subcmds[command], false) + formatted := c.formatCommand(ref, false, commandSeq) _, err = writer.WriteString(formatted) if err != nil { return err } writer.Flush() f.Close() + + // Handle subcommands + if sc, ok := ref.command.(*SuperCommand); ok { + err = sc.documentation.writeDocs(folder, commandSeq, false) + if err != nil { + return err + } + } } - return err + return nil } func (c *documentationCommand) readFileIds(path string) (map[string]string, error) { @@ -234,33 +251,65 @@ func (c *documentationCommand) readFileIds(path string) (map[string]string, erro return ids, nil } +// TODO: handle subcommands here func (c *documentationCommand) dumpEntries(writer *bufio.Writer) error { if len(c.super.subcmds) == 0 { fmt.Printf("No commands found for %s", c.super.Name) return nil } - sorted := c.getSortedListCommands() - if !c.noIndex { - _, err := writer.WriteString(c.commandsIndex(sorted)) + _, err := writer.WriteString(c.commandsIndex()) if err != nil { return err } } - var err error - for _, nameCmd := range sorted { - _, err = writer.WriteString(c.formatCommand(c.super.subcmds[nameCmd], true)) + return c.writeSections(writer, []string{c.super.Name}, true) +} + +// writeSections (recursively) writes sections for all commands to the given file. +func (c *documentationCommand) writeSections(writer *bufio.Writer, superCommands []string, printDefaultCommands bool) error { + sorted := c.getSortedListCommands() + for _, name := range sorted { + if !printDefaultCommands && isDefaultCommand(name) { + continue + } + ref := c.super.subcmds[name] + commandSeq := append(superCommands, name) + _, err := writer.WriteString(c.formatCommand(ref, true, commandSeq)) if err != nil { return err } + + // Handle subcommands + if sc, ok := ref.command.(*SuperCommand); ok { + err = sc.documentation.writeSections(writer, commandSeq, false) + if err != nil { + return err + } + } } return nil } -func (c *documentationCommand) commandsIndex(listCommands []string) string { +func (c *documentationCommand) commandsIndex() string { index := "# Index\n" + + listCommands := c.getSortedListCommands() + for id, name := range listCommands { + if isDefaultCommand(name) { + continue + } + index += fmt.Sprintf("%d. [%s](%s)\n", id, name, c.linkForCommand(name)) + // TODO: handle subcommands ?? + } + index += "---\n\n" + return index +} + +// Return the URL/location for the given command +func (c *documentationCommand) linkForCommand(cmd string) string { prefix := "#" if c.ids != nil { prefix = "/t/" @@ -269,28 +318,22 @@ func (c *documentationCommand) commandsIndex(listCommands []string) string { prefix = c.url + "/" } - for id, name := range listCommands { - if name == "documentation" || name == "help" { - continue - } - target, err := c.getTargetCmd(name) - if err != nil { - fmt.Printf("[ERROR] command [%s] has no id, please add it to the list\n", name) - } - index += fmt.Sprintf("%d. [%s](%s%s)\n", id, name, prefix, target) + target, err := c.getTargetCmd(cmd) + if err != nil { + fmt.Printf("[ERROR] command [%s] has no id, please add it to the list\n", cmd) + return "" } - index += "---\n\n" - return index + return prefix + target } // formatCommand returns a string representation of the information contained // by a command in Markdown format. The title param can be used to set // whether the command name should be a title or not. This is particularly // handy when splitting the commands in different files. -func (c *documentationCommand) formatCommand(ref commandReference, title bool) string { +func (c *documentationCommand) formatCommand(ref commandReference, title bool, commandSeq []string) string { formatted := "" if title { - formatted = "# " + strings.ToUpper(ref.name) + "\n" + formatted = "# " + strings.ToUpper(strings.Join(commandSeq[1:], " ")) + "\n" } var info *Info @@ -338,9 +381,9 @@ func (c *documentationCommand) formatCommand(ref commandReference, title bool) s // Usage if strings.TrimSpace(info.Args) != "" { formatted += fmt.Sprintf(`## Usage -`+"```"+`%s %s [options] %s`+"```"+` +`+"```"+`%s [options] %s`+"```"+` -`, c.super.Name, info.Name, info.Args) +`, strings.Join(commandSeq, " "), info.Args) } // Options @@ -361,6 +404,7 @@ func (c *documentationCommand) formatCommand(ref commandReference, title bool) s formatted += "## Details\n" + doc + "\n\n" } + formatted += c.formatSubcommands(info.Subcommands, commandSeq) formatted += "---\n\n" return formatted @@ -524,3 +568,27 @@ func EscapeMarkdown(raw string) string { return escaped.String() } + +func (c *documentationCommand) formatSubcommands(subcommands map[string]string, commandSeq []string) string { + var output string + + sorted := []string{} + for name := range subcommands { + if isDefaultCommand(name) { + continue + } + sorted = append(sorted, name) + } + sort.Strings(sorted) + + if len(sorted) > 0 { + output += "## Subcommands\n" + for _, name := range sorted { + output += fmt.Sprintf("- [%s](%s)\n", name, + c.linkForCommand(strings.Join(append(commandSeq[1:], name), "_"))) + } + output += "\n" + } + + return output +} diff --git a/documentation_test.go b/documentation_test.go index 2bc2e042..feb4e606 100644 --- a/documentation_test.go +++ b/documentation_test.go @@ -95,6 +95,7 @@ insert details here... t.command, &cmd.SuperCommand{Name: "juju"}, t.title, + []string{"juju", t.command.Info().Name}, ) c.Check(output, gc.Equals, t.expected) } diff --git a/export_test.go b/export_test.go index 40f9a615..cd633198 100644 --- a/export_test.go +++ b/export_test.go @@ -7,8 +7,8 @@ func NewVersionCommand(version string, versionDetail interface{}) Command { return newVersionCommand(version, versionDetail) } -func FormatCommand(command Command, super *SuperCommand, title bool) string { +func FormatCommand(command Command, super *SuperCommand, title bool, commandSeq []string) string { docCmd := &documentationCommand{super: super} ref := commandReference{command: command} - return docCmd.formatCommand(ref, title) + return docCmd.formatCommand(ref, title, commandSeq) } From 9137ebd911f31e97f451fa3cbb83f64ed13512e0 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Tue, 18 Jul 2023 16:08:25 +0800 Subject: [PATCH 3/3] Add Examples field to SuperCommand --- documentation.go | 1 - supercommand.go | 20 ++++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/documentation.go b/documentation.go index a1af18b1..caab9cb7 100644 --- a/documentation.go +++ b/documentation.go @@ -251,7 +251,6 @@ func (c *documentationCommand) readFileIds(path string) (map[string]string, erro return ids, nil } -// TODO: handle subcommands here func (c *documentationCommand) dumpEntries(writer *bufio.Writer) error { if len(c.super.subcmds) == 0 { fmt.Printf("No commands found for %s", c.super.Name) diff --git a/supercommand.go b/supercommand.go index 503af5c1..7aff2f53 100644 --- a/supercommand.go +++ b/supercommand.go @@ -72,9 +72,10 @@ type SuperCommandParams struct { // in the help output. NotifyHelp func([]string) - Name string - Purpose string - Doc string + Name string + Purpose string + Doc string + Examples string // Log holds the Log value associated with the supercommand. If it's nil, // no logging flags will be configured. Log *Log @@ -113,11 +114,12 @@ type FlagAdder interface { // the fully initialized structure. func NewSuperCommand(params SuperCommandParams) *SuperCommand { command := &SuperCommand{ - Name: params.Name, - Purpose: params.Purpose, - Doc: params.Doc, - Log: params.Log, - Aliases: params.Aliases, + Name: params.Name, + Purpose: params.Purpose, + Doc: params.Doc, + Examples: params.Examples, + Log: params.Log, + Aliases: params.Aliases, globalFlags: params.GlobalFlags, usagePrefix: params.UsagePrefix, @@ -162,6 +164,7 @@ type SuperCommand struct { Name string Purpose string Doc string + Examples string Log *Log Aliases []string globalFlags FlagAdder @@ -361,6 +364,7 @@ func (c *SuperCommand) Info() *Info { Purpose: c.Purpose, Doc: strings.TrimSpace(c.Doc), Subcommands: c.describeCommands(), + Examples: c.Examples, Aliases: c.Aliases, FlagKnownAs: c.FlagKnownAs, }