1// Copyright 2015 Red Hat Inc. All rights reserved.
2//
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
7//
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.
13
14package doc
15
16import (
17 "bytes"
18 "fmt"
19 "io"
20 "os"
21 "path/filepath"
22 "sort"
23 "strings"
24 "time"
25
26 "github.com/cpuguy83/go-md2man/md2man"
27 "github.com/spf13/cobra"
28 "github.com/spf13/pflag"
29)
30
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`.
36func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
37 return GenManTreeFromOpts(cmd, GenManTreeOptions{
38 Header: header,
39 Path: dir,
40 CommandSeparator: "-",
41 })
42}
43
44// GenManTreeFromOpts generates a man page for the command and all descendants.
45// The pages are written to the opts.Path directory.
46func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
47 header := opts.Header
48 if header == nil {
49 header = &GenManHeader{}
50 }
51 for _, c := range cmd.Commands() {
52 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
53 continue
54 }
55 if err := GenManTreeFromOpts(c, opts); err != nil {
56 return err
57 }
58 }
59 section := "1"
60 if header.Section != "" {
61 section = header.Section
62 }
63
64 separator := "_"
65 if opts.CommandSeparator != "" {
66 separator = opts.CommandSeparator
67 }
68 basename := strings.Replace(cmd.CommandPath(), " ", separator, -1)
69 filename := filepath.Join(opts.Path, basename+"."+section)
70 f, err := os.Create(filename)
71 if err != nil {
72 return err
73 }
74 defer f.Close()
75
76 headerCopy := *header
77 return GenMan(cmd, &headerCopy, f)
78}
79
80// GenManTreeOptions is the options for generating the man pages.
81// Used only in GenManTreeFromOpts.
82type GenManTreeOptions struct {
83 Header *GenManHeader
84 Path string
85 CommandSeparator string
86}
87
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.
92type GenManHeader struct {
93 Title string
94 Section string
95 Date *time.Time
96 date string
97 Source string
98 Manual string
99}
100
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.
103func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
104 if header == nil {
105 header = &GenManHeader{}
106 }
107 fillHeader(header, cmd.CommandPath())
108
109 b := genMan(cmd, header)
110 _, err := w.Write(md2man.Render(b))
111 return err
112}
113
114func fillHeader(header *GenManHeader, name string) {
115 if header.Title == "" {
116 header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
117 }
118 if header.Section == "" {
119 header.Section = "1"
120 }
121 if header.Date == nil {
122 now := time.Now()
123 header.Date = &now
124 }
125 header.date = (*header.Date).Format("Jan 2006")
126 if header.Source == "" {
127 header.Source = "Auto generated by spf13/cobra"
128 }
129}
130
131func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, dashedName string) {
132 description := cmd.Long
133 if len(description) == 0 {
134 description = cmd.Short
135 }
136
137 buf.WriteString(fmt.Sprintf(`%% %s(%s)%s
138%% %s
139%% %s
140# NAME
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")
147}
148
149func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) {
150 flags.VisitAll(func(flag *pflag.Flag) {
151 if len(flag.Deprecated) > 0 || flag.Hidden {
152 return
153 }
154 format := ""
155 if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
156 format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
157 } else {
158 format = fmt.Sprintf("**--%s**", flag.Name)
159 }
160 if len(flag.NoOptDefVal) > 0 {
161 format += "["
162 }
163 if flag.Value.Type() == "string" {
164 // put quotes on the value
165 format += "=%q"
166 } else {
167 format += "=%s"
168 }
169 if len(flag.NoOptDefVal) > 0 {
170 format += "]"
171 }
172 format += "\n\t%s\n\n"
173 buf.WriteString(fmt.Sprintf(format, flag.DefValue, flag.Usage))
174 })
175}
176
177func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) {
178 flags := command.NonInheritedFlags()
179 if flags.HasAvailableFlags() {
180 buf.WriteString("# OPTIONS\n")
181 manPrintFlags(buf, flags)
182 buf.WriteString("\n")
183 }
184 flags = command.InheritedFlags()
185 if flags.HasAvailableFlags() {
186 buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n")
187 manPrintFlags(buf, flags)
188 buf.WriteString("\n")
189 }
190}
191
192func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
193 cmd.InitDefaultHelpCmd()
194 cmd.InitDefaultHelpFlag()
195
196 // something like `rootcmd-subcmd1-subcmd2`
197 dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1)
198
199 buf := new(bytes.Buffer)
200
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))
206 }
207 if hasSeeAlso(cmd) {
208 buf.WriteString("# SEE ALSO\n")
209 seealsos := make([]string, 0)
210 if cmd.HasParent() {
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
218 }
219 })
220 }
221 children := cmd.Commands()
222 sort.Sort(byName(children))
223 for _, c := range children {
224 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
225 continue
226 }
227 seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
228 seealsos = append(seealsos, seealso)
229 }
230 buf.WriteString(strings.Join(seealsos, ", ") + "\n")
231 }
232 if !cmd.DisableAutoGenTag {
233 buf.WriteString(fmt.Sprintf("# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")))
234 }
235 return buf.Bytes()
236}