man_docs.go

  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}