Skip to content

Commit 4ae5ade

Browse files
orobardethoshsadiq
authored andcommitted
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. Merge spf13/cobra#939 Fixes spf13/cobra#680
1 parent 83db09f commit 4ae5ade

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

doc/cmd_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package doc
22

33
import (
4+
"regexp"
45
"strings"
56
"testing"
67

@@ -89,3 +90,15 @@ func checkStringOmits(t *testing.T, got, expected string) {
8990
t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got)
9091
}
9192
}
93+
94+
func checkStringMatch(t *testing.T, got, pattern string) {
95+
if ok, _ := regexp.MatchString(pattern, got); !ok {
96+
t.Errorf("Expected to match: \n%v\nGot:\n %v\n", pattern, got)
97+
}
98+
}
99+
100+
func checkStringDontMatch(t *testing.T, got, pattern string) {
101+
if ok, _ := regexp.MatchString(pattern, got); ok {
102+
t.Errorf("Expected not to match: \n%v\nGot:\n %v\n", pattern, got)
103+
}
104+
}

doc/man_docs.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,31 @@ func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command,
155155
cobra.WriteStringAndCheck(buf, description+"\n\n")
156156
}
157157

158+
func manPrintCommands(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command) {
159+
// Find sub-commands that need to be documented
160+
subCommands := []*cobra.Command{}
161+
for _, c := range cmd.Commands() {
162+
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
163+
continue
164+
}
165+
subCommands = append(subCommands, c)
166+
}
167+
168+
// No need to go further if there is no sub-commands to document
169+
if len(subCommands) <= 0 {
170+
return
171+
}
172+
173+
// Add a 'COMMANDS' section in the generated documentation
174+
cobra.WriteStringAndCheck(buf, "# COMMANDS\n")
175+
// For each sub-commands, and an entry with the command name and it's Short description and reference to dedicated
176+
// man page
177+
for _, c := range subCommands {
178+
dashedPath := strings.Replace(c.CommandPath(), " ", "-", -1)
179+
cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n\t%s \n\tSee **%s(%s)**.\n\n", c.Name(), c.Short, dashedPath, header.Section))
180+
}
181+
}
182+
158183
func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) {
159184
flags.VisitAll(func(flag *pflag.Flag) {
160185
if len(flag.Deprecated) > 0 || flag.Hidden {
@@ -208,6 +233,7 @@ func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
208233
buf := new(bytes.Buffer)
209234

210235
manPreamble(buf, header, cmd, dashCommandName)
236+
manPrintCommands(buf, header, cmd)
211237
manPrintOptions(buf, cmd)
212238
if len(cmd.Example) > 0 {
213239
buf.WriteString("# EXAMPLE\n")

doc/man_docs_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,45 @@ func TestManPrintFlagsHidesShortDeperecated(t *testing.T) {
189189
}
190190
}
191191

192+
func TestGenManCommands(t *testing.T) {
193+
header := &GenManHeader{
194+
Title: "Project",
195+
Section: "2",
196+
}
197+
198+
// Root command
199+
buf := new(bytes.Buffer)
200+
if err := GenMan(rootCmd, header, buf); err != nil {
201+
t.Fatal(err)
202+
}
203+
output := buf.String()
204+
205+
checkStringContains(t, output, ".SH COMMANDS")
206+
checkStringMatch(t, output, "\\\\fBecho\\\\fP\n[ \t]+Echo anything to the screen\n[ \t]+See \\\\fBroot\\\\-echo\\(2\\)\\\\fP\\\\&\\.")
207+
checkStringOmits(t, output, ".PP\n\\fBprint\\fP\n")
208+
209+
// Echo command
210+
buf = new(bytes.Buffer)
211+
if err := GenMan(echoCmd, header, buf); err != nil {
212+
t.Fatal(err)
213+
}
214+
output = buf.String()
215+
216+
checkStringContains(t, output, ".SH COMMANDS")
217+
checkStringMatch(t, output, "\\\\fBtimes\\\\fP\n[ \t]+Echo anything to the screen more times\n[ \t]+See \\\\fBroot\\\\-echo\\\\-times\\(2\\)\\\\fP\\\\&\\.")
218+
checkStringMatch(t, output, "\\\\fBechosub\\\\fP\n[ \t]+second sub command for echo\n[ \t]+See \\\\fBroot\\\\-echo\\\\-echosub\\(2\\)\\\\fP\\\\&\\.")
219+
checkStringOmits(t, output, ".PP\n\\fBdeprecated\\fP\n")
220+
221+
// Time command as echo's subcommand
222+
buf = new(bytes.Buffer)
223+
if err := GenMan(timesCmd, header, buf); err != nil {
224+
t.Fatal(err)
225+
}
226+
output = buf.String()
227+
228+
checkStringOmits(t, output, ".SH COMMANDS")
229+
}
230+
192231
func TestGenManTree(t *testing.T) {
193232
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
194233
header := &GenManHeader{Section: "2"}

0 commit comments

Comments
 (0)