1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package doc
16
17 import (
18 "bytes"
19 "fmt"
20 "io"
21 "os"
22 "path/filepath"
23 "sort"
24 "strconv"
25 "strings"
26 "time"
27
28 "github.com/cpuguy83/go-md2man/v2/md2man"
29 "github.com/spf13/cobra"
30 "github.com/spf13/pflag"
31 )
32
33
34
35
36
37
38 func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
39 return GenManTreeFromOpts(cmd, GenManTreeOptions{
40 Header: header,
41 Path: dir,
42 CommandSeparator: "-",
43 })
44 }
45
46
47
48 func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
49 header := opts.Header
50 if header == nil {
51 header = &GenManHeader{}
52 }
53 for _, c := range cmd.Commands() {
54 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
55 continue
56 }
57 if err := GenManTreeFromOpts(c, opts); err != nil {
58 return err
59 }
60 }
61 section := "1"
62 if header.Section != "" {
63 section = header.Section
64 }
65
66 separator := "_"
67 if opts.CommandSeparator != "" {
68 separator = opts.CommandSeparator
69 }
70 basename := strings.ReplaceAll(cmd.CommandPath(), " ", separator)
71 filename := filepath.Join(opts.Path, basename+"."+section)
72 f, err := os.Create(filename)
73 if err != nil {
74 return err
75 }
76 defer f.Close()
77
78 headerCopy := *header
79 return GenMan(cmd, &headerCopy, f)
80 }
81
82
83
84 type GenManTreeOptions struct {
85 Header *GenManHeader
86 Path string
87 CommandSeparator string
88 }
89
90
91
92
93
94 type GenManHeader struct {
95 Title string
96 Section string
97 Date *time.Time
98 date string
99 Source string
100 Manual string
101 }
102
103
104
105 func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
106 if header == nil {
107 header = &GenManHeader{}
108 }
109 if err := fillHeader(header, cmd.CommandPath(), cmd.DisableAutoGenTag); err != nil {
110 return err
111 }
112
113 b := genMan(cmd, header)
114 _, err := w.Write(md2man.Render(b))
115 return err
116 }
117
118 func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error {
119 if header.Title == "" {
120 header.Title = strings.ToUpper(strings.ReplaceAll(name, " ", "\\-"))
121 }
122 if header.Section == "" {
123 header.Section = "1"
124 }
125 if header.Date == nil {
126 now := time.Now()
127 if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
128 unixEpoch, err := strconv.ParseInt(epoch, 10, 64)
129 if err != nil {
130 return fmt.Errorf("invalid SOURCE_DATE_EPOCH: %v", err)
131 }
132 now = time.Unix(unixEpoch, 0)
133 }
134 header.Date = &now
135 }
136 header.date = (*header.Date).Format("Jan 2006")
137 if header.Source == "" && !disableAutoGen {
138 header.Source = "Auto generated by spf13/cobra"
139 }
140 return nil
141 }
142
143 func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, dashedName string) {
144 description := cmd.Long
145 if len(description) == 0 {
146 description = cmd.Short
147 }
148
149 cobra.WriteStringAndCheck(buf, fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s"
150 # NAME
151 `, header.Title, header.Section, header.date, header.Source, header.Manual))
152 cobra.WriteStringAndCheck(buf, fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short))
153 cobra.WriteStringAndCheck(buf, "# SYNOPSIS\n")
154 cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n\n", cmd.UseLine()))
155 cobra.WriteStringAndCheck(buf, "# DESCRIPTION\n")
156 cobra.WriteStringAndCheck(buf, description+"\n\n")
157 }
158
159 func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) {
160 flags.VisitAll(func(flag *pflag.Flag) {
161 if len(flag.Deprecated) > 0 || flag.Hidden {
162 return
163 }
164 format := ""
165 if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
166 format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
167 } else {
168 format = fmt.Sprintf("**--%s**", flag.Name)
169 }
170 if len(flag.NoOptDefVal) > 0 {
171 format += "["
172 }
173 if flag.Value.Type() == "string" {
174
175 format += "=%q"
176 } else {
177 format += "=%s"
178 }
179 if len(flag.NoOptDefVal) > 0 {
180 format += "]"
181 }
182 format += "\n\t%s\n\n"
183 cobra.WriteStringAndCheck(buf, fmt.Sprintf(format, flag.DefValue, flag.Usage))
184 })
185 }
186
187 func manPrintOptions(buf io.StringWriter, command *cobra.Command) {
188 flags := command.NonInheritedFlags()
189 if flags.HasAvailableFlags() {
190 cobra.WriteStringAndCheck(buf, "# OPTIONS\n")
191 manPrintFlags(buf, flags)
192 cobra.WriteStringAndCheck(buf, "\n")
193 }
194 flags = command.InheritedFlags()
195 if flags.HasAvailableFlags() {
196 cobra.WriteStringAndCheck(buf, "# OPTIONS INHERITED FROM PARENT COMMANDS\n")
197 manPrintFlags(buf, flags)
198 cobra.WriteStringAndCheck(buf, "\n")
199 }
200 }
201
202 func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
203 cmd.InitDefaultHelpCmd()
204 cmd.InitDefaultHelpFlag()
205
206
207 dashCommandName := strings.ReplaceAll(cmd.CommandPath(), " ", "-")
208
209 buf := new(bytes.Buffer)
210
211 manPreamble(buf, header, cmd, dashCommandName)
212 manPrintOptions(buf, cmd)
213 if len(cmd.Example) > 0 {
214 buf.WriteString("# EXAMPLE\n")
215 buf.WriteString(fmt.Sprintf("```\n%s\n```\n", cmd.Example))
216 }
217 if hasSeeAlso(cmd) {
218 buf.WriteString("# SEE ALSO\n")
219 seealsos := make([]string, 0)
220 if cmd.HasParent() {
221 parentPath := cmd.Parent().CommandPath()
222 dashParentPath := strings.ReplaceAll(parentPath, " ", "-")
223 seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
224 seealsos = append(seealsos, seealso)
225 cmd.VisitParents(func(c *cobra.Command) {
226 if c.DisableAutoGenTag {
227 cmd.DisableAutoGenTag = c.DisableAutoGenTag
228 }
229 })
230 }
231 children := cmd.Commands()
232 sort.Sort(byName(children))
233 for _, c := range children {
234 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
235 continue
236 }
237 seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
238 seealsos = append(seealsos, seealso)
239 }
240 buf.WriteString(strings.Join(seealsos, ", ") + "\n")
241 }
242 if !cmd.DisableAutoGenTag {
243 buf.WriteString(fmt.Sprintf("# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")))
244 }
245 return buf.Bytes()
246 }
247
View as plain text