...

Source file src/github.com/spf13/cobra/doc/man_docs.go

Documentation: github.com/spf13/cobra/doc

     1  // Copyright 2013-2023 The Cobra Authors
     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  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  // GenManTree will generate a man page for this command and all descendants
    34  // in the directory given. The header may be nil. This function may not work
    35  // correctly if your command names have `-` in them. If you have `cmd` with two
    36  // subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third`
    37  // it is undefined which help output will be in the file `cmd-sub-third.1`.
    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  // GenManTreeFromOpts generates a man page for the command and all descendants.
    47  // The pages are written to the opts.Path directory.
    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  // GenManTreeOptions is the options for generating the man pages.
    83  // Used only in GenManTreeFromOpts.
    84  type GenManTreeOptions struct {
    85  	Header           *GenManHeader
    86  	Path             string
    87  	CommandSeparator string
    88  }
    89  
    90  // GenManHeader is a lot like the .TH header at the start of man pages. These
    91  // include the title, section, date, source, and manual. We will use the
    92  // current time if Date is unset and will use "Auto generated by spf13/cobra"
    93  // if the Source is unset.
    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  // GenMan will generate a man page for the given command and write it to
   104  // w. The header argument may be nil, however obviously w may not.
   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  			// put quotes on the value
   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  	// something like `rootcmd-subcmd1-subcmd2`
   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