1 // Copyright 2015 Red Hat Inc. All rights reserved.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
26 "github.com/cpuguy83/go-md2man/md2man"
27 "github.com/spf13/cobra"
28 "github.com/spf13/pflag"
31 // GenManTree will generate a man page for this command and all descendants
32 // in the directory given. The header may be nil. This function may not work
33 // correctly if your command names have `-` in them. If you have `cmd` with two
34 // subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third`
35 // it is undefined which help output will be in the file `cmd-sub-third.1`.
36 func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
37 return GenManTreeFromOpts(cmd, GenManTreeOptions{
40 CommandSeparator: "-",
44 // GenManTreeFromOpts generates a man page for the command and all descendants.
45 // The pages are written to the opts.Path directory.
46 func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
49 header = &GenManHeader{}
51 for _, c := range cmd.Commands() {
52 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
55 if err := GenManTreeFromOpts(c, opts); err != nil {
60 if header.Section != "" {
61 section = header.Section
65 if opts.CommandSeparator != "" {
66 separator = opts.CommandSeparator
68 basename := strings.Replace(cmd.CommandPath(), " ", separator, -1)
69 filename := filepath.Join(opts.Path, basename+"."+section)
70 f, err := os.Create(filename)
77 return GenMan(cmd, &headerCopy, f)
80 // GenManTreeOptions is the options for generating the man pages.
81 // Used only in GenManTreeFromOpts.
82 type GenManTreeOptions struct {
85 CommandSeparator string
88 // GenManHeader is a lot like the .TH header at the start of man pages. These
89 // include the title, section, date, source, and manual. We will use the
90 // current time if Date if unset and will use "Auto generated by spf13/cobra"
91 // if the Source is unset.
92 type GenManHeader struct {
101 // GenMan will generate a man page for the given command and write it to
102 // w. The header argument may be nil, however obviously w may not.
103 func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
105 header = &GenManHeader{}
107 fillHeader(header, cmd.CommandPath())
109 b := genMan(cmd, header)
110 _, err := w.Write(md2man.Render(b))
114 func fillHeader(header *GenManHeader, name string) {
115 if header.Title == "" {
116 header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
118 if header.Section == "" {
121 if header.Date == nil {
125 header.date = (*header.Date).Format("Jan 2006")
126 if header.Source == "" {
127 header.Source = "Auto generated by spf13/cobra"
131 func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, dashedName string) {
132 description := cmd.Long
133 if len(description) == 0 {
134 description = cmd.Short
137 buf.WriteString(fmt.Sprintf(`%% %s(%s)%s
141 `, header.Title, header.Section, header.date, header.Source, header.Manual))
142 buf.WriteString(fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short))
143 buf.WriteString("# SYNOPSIS\n")
144 buf.WriteString(fmt.Sprintf("**%s**\n\n", cmd.UseLine()))
145 buf.WriteString("# DESCRIPTION\n")
146 buf.WriteString(description + "\n\n")
149 func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) {
150 flags.VisitAll(func(flag *pflag.Flag) {
151 if len(flag.Deprecated) > 0 || flag.Hidden {
155 if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
156 format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
158 format = fmt.Sprintf("**--%s**", flag.Name)
160 if len(flag.NoOptDefVal) > 0 {
163 if flag.Value.Type() == "string" {
164 // put quotes on the value
169 if len(flag.NoOptDefVal) > 0 {
172 format += "\n\t%s\n\n"
173 buf.WriteString(fmt.Sprintf(format, flag.DefValue, flag.Usage))
177 func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) {
178 flags := command.NonInheritedFlags()
179 if flags.HasFlags() {
180 buf.WriteString("# OPTIONS\n")
181 manPrintFlags(buf, flags)
182 buf.WriteString("\n")
184 flags = command.InheritedFlags()
185 if flags.HasFlags() {
186 buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n")
187 manPrintFlags(buf, flags)
188 buf.WriteString("\n")
192 func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
193 cmd.InitDefaultHelpCmd()
194 cmd.InitDefaultHelpFlag()
196 // something like `rootcmd-subcmd1-subcmd2`
197 dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1)
199 buf := new(bytes.Buffer)
201 manPreamble(buf, header, cmd, dashCommandName)
202 manPrintOptions(buf, cmd)
203 if len(cmd.Example) > 0 {
204 buf.WriteString("# EXAMPLE\n")
205 buf.WriteString(fmt.Sprintf("```\n%s\n```\n", cmd.Example))
208 buf.WriteString("# SEE ALSO\n")
209 seealsos := make([]string, 0)
211 parentPath := cmd.Parent().CommandPath()
212 dashParentPath := strings.Replace(parentPath, " ", "-", -1)
213 seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
214 seealsos = append(seealsos, seealso)
215 cmd.VisitParents(func(c *cobra.Command) {
216 if c.DisableAutoGenTag {
217 cmd.DisableAutoGenTag = c.DisableAutoGenTag
221 children := cmd.Commands()
222 sort.Sort(byName(children))
223 for _, c := range children {
224 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
227 seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
228 seealsos = append(seealsos, seealso)
230 buf.WriteString(strings.Join(seealsos, ", ") + "\n")
232 if !cmd.DisableAutoGenTag {
233 buf.WriteString(fmt.Sprintf("# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")))