Add a COMMANDS section to generated man pages

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.
This commit is contained in:
Olivier Robardet 2019-08-30 10:09:12 +02:00
parent 68b6b24f0c
commit 98e8c716ca
No known key found for this signature in database
GPG key ID: A61949FC3776DB27
3 changed files with 72 additions and 0 deletions

View file

@ -1,6 +1,7 @@
package doc package doc
import ( import (
"regexp"
"strings" "strings"
"testing" "testing"
@ -89,3 +90,9 @@ func checkStringOmits(t *testing.T, got, expected string) {
t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got) 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)
}
}

View file

@ -155,6 +155,31 @@ func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command,
cobra.WriteStringAndCheck(buf, description+"\n\n") 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) { func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) { flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden { if len(flag.Deprecated) > 0 || flag.Hidden {
@ -208,6 +233,7 @@ func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
manPreamble(buf, header, cmd, dashCommandName) manPreamble(buf, header, cmd, dashCommandName)
manPrintCommands(buf, header, cmd)
manPrintOptions(buf, cmd) manPrintOptions(buf, cmd)
if len(cmd.Example) > 0 { if len(cmd.Example) > 0 {
buf.WriteString("# EXAMPLE\n") buf.WriteString("# EXAMPLE\n")

View file

@ -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) { func TestGenManTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
header := &GenManHeader{Section: "2"} header := &GenManHeader{Section: "2"}