Skip to content

Commit

Permalink
Add a COMMANDS section to generated man pages
Browse files Browse the repository at this point in the history
When a command has available sub-commands, the COMMANDS
man section is generated with the list of sub-commands, with
their name, short description, and the name of the dedicated
man page.
  • Loading branch information
orobardet authored and orobardet-orange committed Apr 20, 2022
1 parent 68b6b24 commit 9a6f197
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 0 deletions.
13 changes: 13 additions & 0 deletions doc/cmd_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package doc

import (
"regexp"
"strings"
"testing"

Expand Down Expand Up @@ -89,3 +90,15 @@ func checkStringOmits(t *testing.T, got, expected string) {
t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got)
}
}

func checkStringMatch(t *testing.T, got, pattern string) {
if ok, _ := regexp.MatchString(pattern, got); !ok {
t.Errorf("Expected to match: \n%v\nGot:\n %v\n", pattern, got)
}
}

func checkStringDontMatch(t *testing.T, got, pattern string) {
if ok, _ := regexp.MatchString(pattern, got); ok {
t.Errorf("Expected not to match: \n%v\nGot:\n %v\n", pattern, got)
}
}
26 changes: 26 additions & 0 deletions doc/man_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,31 @@ func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command,
cobra.WriteStringAndCheck(buf, description+"\n\n")
}

func manPrintCommands(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command) {
// Find sub-commands that need to be documented
subCommands := []*cobra.Command{}
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
subCommands = append(subCommands, c)
}

// No need to go further if there is no sub-commands to document
if len(subCommands) <= 0 {
return
}

// Add a 'COMMANDS' section in the generated documentation
cobra.WriteStringAndCheck(buf, "# COMMANDS\n")
// For each sub-commands, and an entry with the command name and it's Short description and reference to dedicated
// man page
for _, c := range subCommands {
dashedPath := strings.Replace(c.CommandPath(), " ", "-", -1)
cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n %s \n See **%s(%s)**.\n\n", c.Name(), c.Short, dashedPath, header.Section))
}
}

func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden {
Expand Down Expand Up @@ -208,6 +233,7 @@ func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
buf := new(bytes.Buffer)

manPreamble(buf, header, cmd, dashCommandName)
manPrintCommands(buf, header, cmd)
manPrintOptions(buf, cmd)
if len(cmd.Example) > 0 {
buf.WriteString("# EXAMPLE\n")
Expand Down
39 changes: 39 additions & 0 deletions doc/man_docs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,45 @@ func TestManPrintFlagsHidesShortDeperecated(t *testing.T) {
}
}

func TestGenManCommands(t *testing.T) {
header := &GenManHeader{
Title: "Project",
Section: "2",
}

// Root command
buf := new(bytes.Buffer)
if err := GenMan(rootCmd, header, buf); err != nil {
t.Fatal(err)
}
output := buf.String()

checkStringContains(t, output, ".SH COMMANDS")
checkStringMatch(t, output, "\\\\fBecho\\\\fP\n[ \t]+Echo anything to the screen\n[ \t]+See \\\\fBroot-echo\\(2\\)\\\\fP\\\\&\\.")
checkStringOmits(t, output, ".PP\n\\fBprint\\fP\n")

// Echo command
buf = new(bytes.Buffer)
if err := GenMan(echoCmd, header, buf); err != nil {
t.Fatal(err)
}
output = buf.String()

checkStringContains(t, output, ".SH COMMANDS")
checkStringMatch(t, output, "\\\\fBtimes\\\\fP\n[ \t]+Echo anything to the screen more times\n[ \t]+See \\\\fBroot-echo-times\\(2\\)\\\\fP\\\\&\\.")
checkStringMatch(t, output, "\\\\fBechosub\\\\fP\n[ \t]+second sub command for echo\n[ \t]+See \\\\fBroot-echo-echosub\\(2\\)\\\\fP\\\\&\\.")
checkStringOmits(t, output, ".PP\n\\fBdeprecated\\fP\n")

// Time command as echo's subcommand
buf = new(bytes.Buffer)
if err := GenMan(timesCmd, header, buf); err != nil {
t.Fatal(err)
}
output = buf.String()

checkStringOmits(t, output, ".SH COMMANDS")
}

func TestGenManTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
header := &GenManHeader{Section: "2"}
Expand Down

0 comments on commit 9a6f197

Please sign in to comment.