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 mangen "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 80type GenManTreeOptions struct { 81 Header *GenManHeader 82 Path string 83 CommandSeparator string 84} 85 86// GenManHeader is a lot like the .TH header at the start of man pages. These 87// include the title, section, date, source, and manual. We will use the 88// current time if Date if unset and will use "Auto generated by spf13/cobra" 89// if the Source is unset. 90type GenManHeader struct { 91 Title string 92 Section string 93 Date *time.Time 94 date string 95 Source string 96 Manual string 97} 98 99// GenMan will generate a man page for the given command and write it to 100// w. The header argument may be nil, however obviously w may not. 101func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error { 102 if header == nil { 103 header = &GenManHeader{} 104 } 105 fillHeader(header, cmd.CommandPath()) 106 107 b := genMan(cmd, header) 108 _, err := w.Write(mangen.Render(b)) 109 return err 110} 111 112func fillHeader(header *GenManHeader, name string) { 113 if header.Title == "" { 114 header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1)) 115 } 116 if header.Section == "" { 117 header.Section = "1" 118 } 119 if header.Date == nil { 120 now := time.Now() 121 header.Date = &now 122 } 123 header.date = (*header.Date).Format("Jan 2006") 124 if header.Source == "" { 125 header.Source = "Auto generated by spf13/cobra" 126 } 127} 128 129func manPreamble(out io.Writer, header *GenManHeader, cmd *cobra.Command, dashedName string) { 130 description := cmd.Long 131 if len(description) == 0 { 132 description = cmd.Short 133 } 134 135 fmt.Fprintf(out, `%% %s(%s)%s 136%% %s 137%% %s 138# NAME 139`, header.Title, header.Section, header.date, header.Source, header.Manual) 140 fmt.Fprintf(out, "%s \\- %s\n\n", dashedName, cmd.Short) 141 fmt.Fprintf(out, "# SYNOPSIS\n") 142 fmt.Fprintf(out, "**%s**\n\n", cmd.UseLine()) 143 fmt.Fprintf(out, "# DESCRIPTION\n") 144 fmt.Fprintf(out, "%s\n\n", description) 145} 146 147func manPrintFlags(out io.Writer, flags *pflag.FlagSet) { 148 flags.VisitAll(func(flag *pflag.Flag) { 149 if len(flag.Deprecated) > 0 || flag.Hidden { 150 return 151 } 152 format := "" 153 if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 { 154 format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name) 155 } else { 156 format = fmt.Sprintf("**--%s**", flag.Name) 157 } 158 if len(flag.NoOptDefVal) > 0 { 159 format = format + "[" 160 } 161 if flag.Value.Type() == "string" { 162 // put quotes on the value 163 format = format + "=%q" 164 } else { 165 format = format + "=%s" 166 } 167 if len(flag.NoOptDefVal) > 0 { 168 format = format + "]" 169 } 170 format = format + "\n\t%s\n\n" 171 fmt.Fprintf(out, format, flag.DefValue, flag.Usage) 172 }) 173} 174 175func manPrintOptions(out io.Writer, command *cobra.Command) { 176 flags := command.NonInheritedFlags() 177 if flags.HasFlags() { 178 fmt.Fprintf(out, "# OPTIONS\n") 179 manPrintFlags(out, flags) 180 fmt.Fprintf(out, "\n") 181 } 182 flags = command.InheritedFlags() 183 if flags.HasFlags() { 184 fmt.Fprintf(out, "# OPTIONS INHERITED FROM PARENT COMMANDS\n") 185 manPrintFlags(out, flags) 186 fmt.Fprintf(out, "\n") 187 } 188} 189 190func genMan(cmd *cobra.Command, header *GenManHeader) []byte { 191 // something like `rootcmd-subcmd1-subcmd2` 192 dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1) 193 194 buf := new(bytes.Buffer) 195 196 manPreamble(buf, header, cmd, dashCommandName) 197 manPrintOptions(buf, cmd) 198 if len(cmd.Example) > 0 { 199 fmt.Fprintf(buf, "# EXAMPLE\n") 200 fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example) 201 } 202 if hasSeeAlso(cmd) { 203 fmt.Fprintf(buf, "# SEE ALSO\n") 204 seealsos := make([]string, 0) 205 if cmd.HasParent() { 206 parentPath := cmd.Parent().CommandPath() 207 dashParentPath := strings.Replace(parentPath, " ", "-", -1) 208 seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section) 209 seealsos = append(seealsos, seealso) 210 cmd.VisitParents(func(c *cobra.Command) { 211 if c.DisableAutoGenTag { 212 cmd.DisableAutoGenTag = c.DisableAutoGenTag 213 } 214 }) 215 } 216 children := cmd.Commands() 217 sort.Sort(byName(children)) 218 for _, c := range children { 219 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { 220 continue 221 } 222 seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section) 223 seealsos = append(seealsos, seealso) 224 } 225 fmt.Fprintf(buf, "%s\n", strings.Join(seealsos, ", ")) 226 } 227 if !cmd.DisableAutoGenTag { 228 fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")) 229 } 230 return buf.Bytes() 231} 232