forked from toolshed/abra
		
	
		
			
				
	
	
		
			247 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2013-2023 The Cobra Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //      http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package doc
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/cpuguy83/go-md2man/v2/md2man"
 | |
| 	"github.com/spf13/cobra"
 | |
| 	"github.com/spf13/pflag"
 | |
| )
 | |
| 
 | |
| // GenManTree will generate a man page for this command and all descendants
 | |
| // in the directory given. The header may be nil. This function may not work
 | |
| // correctly if your command names have `-` in them. If you have `cmd` with two
 | |
| // subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third`
 | |
| // it is undefined which help output will be in the file `cmd-sub-third.1`.
 | |
| func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
 | |
| 	return GenManTreeFromOpts(cmd, GenManTreeOptions{
 | |
| 		Header:           header,
 | |
| 		Path:             dir,
 | |
| 		CommandSeparator: "-",
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // GenManTreeFromOpts generates a man page for the command and all descendants.
 | |
| // The pages are written to the opts.Path directory.
 | |
| func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
 | |
| 	header := opts.Header
 | |
| 	if header == nil {
 | |
| 		header = &GenManHeader{}
 | |
| 	}
 | |
| 	for _, c := range cmd.Commands() {
 | |
| 		if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
 | |
| 			continue
 | |
| 		}
 | |
| 		if err := GenManTreeFromOpts(c, opts); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	section := "1"
 | |
| 	if header.Section != "" {
 | |
| 		section = header.Section
 | |
| 	}
 | |
| 
 | |
| 	separator := "_"
 | |
| 	if opts.CommandSeparator != "" {
 | |
| 		separator = opts.CommandSeparator
 | |
| 	}
 | |
| 	basename := strings.ReplaceAll(cmd.CommandPath(), " ", separator)
 | |
| 	filename := filepath.Join(opts.Path, basename+"."+section)
 | |
| 	f, err := os.Create(filename)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	headerCopy := *header
 | |
| 	return GenMan(cmd, &headerCopy, f)
 | |
| }
 | |
| 
 | |
| // GenManTreeOptions is the options for generating the man pages.
 | |
| // Used only in GenManTreeFromOpts.
 | |
| type GenManTreeOptions struct {
 | |
| 	Header           *GenManHeader
 | |
| 	Path             string
 | |
| 	CommandSeparator string
 | |
| }
 | |
| 
 | |
| // GenManHeader is a lot like the .TH header at the start of man pages. These
 | |
| // include the title, section, date, source, and manual. We will use the
 | |
| // current time if Date is unset and will use "Auto generated by spf13/cobra"
 | |
| // if the Source is unset.
 | |
| type GenManHeader struct {
 | |
| 	Title   string
 | |
| 	Section string
 | |
| 	Date    *time.Time
 | |
| 	date    string
 | |
| 	Source  string
 | |
| 	Manual  string
 | |
| }
 | |
| 
 | |
| // GenMan will generate a man page for the given command and write it to
 | |
| // w. The header argument may be nil, however obviously w may not.
 | |
| func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
 | |
| 	if header == nil {
 | |
| 		header = &GenManHeader{}
 | |
| 	}
 | |
| 	if err := fillHeader(header, cmd.CommandPath(), cmd.DisableAutoGenTag); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	b := genMan(cmd, header)
 | |
| 	_, err := w.Write(md2man.Render(b))
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error {
 | |
| 	if header.Title == "" {
 | |
| 		header.Title = strings.ToUpper(strings.ReplaceAll(name, " ", "\\-"))
 | |
| 	}
 | |
| 	if header.Section == "" {
 | |
| 		header.Section = "1"
 | |
| 	}
 | |
| 	if header.Date == nil {
 | |
| 		now := time.Now()
 | |
| 		if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
 | |
| 			unixEpoch, err := strconv.ParseInt(epoch, 10, 64)
 | |
| 			if err != nil {
 | |
| 				return fmt.Errorf("invalid SOURCE_DATE_EPOCH: %v", err)
 | |
| 			}
 | |
| 			now = time.Unix(unixEpoch, 0)
 | |
| 		}
 | |
| 		header.Date = &now
 | |
| 	}
 | |
| 	header.date = header.Date.Format("Jan 2006")
 | |
| 	if header.Source == "" && !disableAutoGen {
 | |
| 		header.Source = "Auto generated by spf13/cobra"
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, dashedName string) {
 | |
| 	description := cmd.Long
 | |
| 	if len(description) == 0 {
 | |
| 		description = cmd.Short
 | |
| 	}
 | |
| 
 | |
| 	cobra.WriteStringAndCheck(buf, fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s"
 | |
| # NAME
 | |
| `, header.Title, header.Section, header.date, header.Source, header.Manual))
 | |
| 	cobra.WriteStringAndCheck(buf, fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short))
 | |
| 	cobra.WriteStringAndCheck(buf, "# SYNOPSIS\n")
 | |
| 	cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n\n", cmd.UseLine()))
 | |
| 	cobra.WriteStringAndCheck(buf, "# DESCRIPTION\n")
 | |
| 	cobra.WriteStringAndCheck(buf, description+"\n\n")
 | |
| }
 | |
| 
 | |
| func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) {
 | |
| 	flags.VisitAll(func(flag *pflag.Flag) {
 | |
| 		if len(flag.Deprecated) > 0 || flag.Hidden {
 | |
| 			return
 | |
| 		}
 | |
| 		format := ""
 | |
| 		if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
 | |
| 			format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
 | |
| 		} else {
 | |
| 			format = fmt.Sprintf("**--%s**", flag.Name)
 | |
| 		}
 | |
| 		if len(flag.NoOptDefVal) > 0 {
 | |
| 			format += "["
 | |
| 		}
 | |
| 		if flag.Value.Type() == "string" {
 | |
| 			// put quotes on the value
 | |
| 			format += "=%q"
 | |
| 		} else {
 | |
| 			format += "=%s"
 | |
| 		}
 | |
| 		if len(flag.NoOptDefVal) > 0 {
 | |
| 			format += "]"
 | |
| 		}
 | |
| 		format += "\n\t%s\n\n"
 | |
| 		cobra.WriteStringAndCheck(buf, fmt.Sprintf(format, flag.DefValue, flag.Usage))
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func manPrintOptions(buf io.StringWriter, command *cobra.Command) {
 | |
| 	flags := command.NonInheritedFlags()
 | |
| 	if flags.HasAvailableFlags() {
 | |
| 		cobra.WriteStringAndCheck(buf, "# OPTIONS\n")
 | |
| 		manPrintFlags(buf, flags)
 | |
| 		cobra.WriteStringAndCheck(buf, "\n")
 | |
| 	}
 | |
| 	flags = command.InheritedFlags()
 | |
| 	if flags.HasAvailableFlags() {
 | |
| 		cobra.WriteStringAndCheck(buf, "# OPTIONS INHERITED FROM PARENT COMMANDS\n")
 | |
| 		manPrintFlags(buf, flags)
 | |
| 		cobra.WriteStringAndCheck(buf, "\n")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
 | |
| 	cmd.InitDefaultHelpCmd()
 | |
| 	cmd.InitDefaultHelpFlag()
 | |
| 
 | |
| 	// something like `rootcmd-subcmd1-subcmd2`
 | |
| 	dashCommandName := strings.ReplaceAll(cmd.CommandPath(), " ", "-")
 | |
| 
 | |
| 	buf := new(bytes.Buffer)
 | |
| 
 | |
| 	manPreamble(buf, header, cmd, dashCommandName)
 | |
| 	manPrintOptions(buf, cmd)
 | |
| 	if len(cmd.Example) > 0 {
 | |
| 		buf.WriteString("# EXAMPLE\n")
 | |
| 		buf.WriteString(fmt.Sprintf("```\n%s\n```\n", cmd.Example))
 | |
| 	}
 | |
| 	if hasSeeAlso(cmd) {
 | |
| 		buf.WriteString("# SEE ALSO\n")
 | |
| 		seealsos := make([]string, 0)
 | |
| 		if cmd.HasParent() {
 | |
| 			parentPath := cmd.Parent().CommandPath()
 | |
| 			dashParentPath := strings.ReplaceAll(parentPath, " ", "-")
 | |
| 			seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
 | |
| 			seealsos = append(seealsos, seealso)
 | |
| 			cmd.VisitParents(func(c *cobra.Command) {
 | |
| 				if c.DisableAutoGenTag {
 | |
| 					cmd.DisableAutoGenTag = c.DisableAutoGenTag
 | |
| 				}
 | |
| 			})
 | |
| 		}
 | |
| 		children := cmd.Commands()
 | |
| 		sort.Sort(byName(children))
 | |
| 		for _, c := range children {
 | |
| 			if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
 | |
| 				continue
 | |
| 			}
 | |
| 			seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
 | |
| 			seealsos = append(seealsos, seealso)
 | |
| 		}
 | |
| 		buf.WriteString(strings.Join(seealsos, ", ") + "\n")
 | |
| 	}
 | |
| 	if !cmd.DisableAutoGenTag {
 | |
| 		buf.WriteString(fmt.Sprintf("# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")))
 | |
| 	}
 | |
| 	return buf.Bytes()
 | |
| }
 |