2023-03-06 02:28:31 +00:00
// Copyright 2013-2023 The Cobra Authors
2015-08-18 15:33:41 -07:00
//
// 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
2022-09-16 13:55:56 +02:00
//
// http://www.apache.org/licenses/LICENSE-2.0
2015-08-18 15:33:41 -07:00
//
// 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.
2015-11-24 00:19:16 +01:00
package doc
2015-08-18 15:33:41 -07:00
import (
"bytes"
"fmt"
2015-11-24 00:19:16 +01:00
"io"
2015-08-18 15:33:41 -07:00
"os"
2016-01-17 11:24:54 +11:00
"path/filepath"
2015-08-18 15:33:41 -07:00
"sort"
2018-10-22 01:11:14 +11:00
"strconv"
2015-08-18 15:33:41 -07:00
"strings"
"time"
2019-10-20 09:17:41 +11:00
"github.com/cpuguy83/go-md2man/v2/md2man"
2015-11-24 00:19:16 +01:00
"github.com/spf13/cobra"
2015-08-18 15:33:41 -07:00
"github.com/spf13/pflag"
)
2025-04-26 20:40:10 +00:00
// GenManTree generates a man page for the provided command and all its descendants in the specified directory.
//
// It takes a cobra.Command pointer representing the root command, an optional GenManHeader pointer to customize the header information, and a string specifying the output directory.
//
// The function assumes that command names do not contain hyphens (`-`). If they do, unexpected behavior may occur.
//
// Note: If a command named `cmd` has subcommands `sub` and `sub-third`, and `sub` itself has a subcommand called `third`, it is undefined which help output will be written to the file `cmd-sub-third.1`.
//
// Returns an error if the generation process encounters any issues, such as invalid input or permission errors when writing files to the specified directory.
2016-01-06 12:21:23 +01:00
func GenManTree ( cmd * cobra . Command , header * GenManHeader , dir string ) error {
2016-06-21 12:34:20 -04:00
return GenManTreeFromOpts ( cmd , GenManTreeOptions {
Header : header ,
Path : dir ,
2016-09-04 11:42:13 +00:00
CommandSeparator : "-" ,
2016-06-21 12:34:20 -04:00
} )
}
// GenManTreeFromOpts generates a man page for the command and all descendants.
2025-04-26 20:40:10 +00:00
// The pages are written to the opts.Path directory. It recursively processes each command in the tree,
// skipping non-available or additional help topic commands. Each man page is saved with a filename based on the command path and section.
// Parameters:
// - cmd: A pointer to the root cobra.Command for which man pages need to be generated.
// - opts: A GenManTreeOptions struct containing options for generating the man pages, such as header details, output directory, and separators.
// Returns:
// - error: If any error occurs during the generation of the man pages, it is returned.
2016-06-21 12:34:20 -04:00
func GenManTreeFromOpts ( cmd * cobra . Command , opts GenManTreeOptions ) error {
header := opts . Header
2015-09-08 16:02:02 -04:00
if header == nil {
header = & GenManHeader { }
}
2015-08-18 15:33:41 -07:00
for _ , c := range cmd . Commands ( ) {
2017-03-09 20:37:15 +05:00
if ! c . IsAvailableCommand ( ) || c . IsAdditionalHelpTopicCommand ( ) {
2015-08-18 15:33:41 -07:00
continue
}
2016-06-21 12:34:20 -04:00
if err := GenManTreeFromOpts ( c , opts ) ; err != nil {
2016-01-06 12:21:23 +01:00
return err
}
2015-08-18 15:33:41 -07:00
}
2016-06-21 12:25:26 -04:00
section := "1"
if header . Section != "" {
section = header . Section
}
2015-11-21 06:18:37 -07:00
2016-06-21 12:34:20 -04:00
separator := "_"
if opts . CommandSeparator != "" {
separator = opts . CommandSeparator
}
2022-05-14 22:10:36 +02:00
basename := strings . ReplaceAll ( cmd . CommandPath ( ) , " " , separator )
2016-08-20 00:09:46 -07:00
filename := filepath . Join ( opts . Path , basename + "." + section )
2016-01-06 12:21:23 +01:00
f , err := os . Create ( filename )
2015-08-18 15:33:41 -07:00
if err != nil {
2016-01-06 12:21:23 +01:00
return err
2015-08-18 15:33:41 -07:00
}
2016-01-06 12:21:23 +01:00
defer f . Close ( )
2016-06-21 12:25:26 -04:00
headerCopy := * header
return GenMan ( cmd , & headerCopy , f )
2015-08-18 15:33:41 -07:00
}
2017-05-09 11:14:48 +02:00
// GenManTreeOptions is the options for generating the man pages.
// Used only in GenManTreeFromOpts.
2016-06-21 12:34:20 -04:00
type GenManTreeOptions struct {
Header * GenManHeader
Path string
CommandSeparator string
}
2015-09-08 16:02:02 -04:00
// 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
2018-08-20 19:45:24 +02:00
// current time if Date is unset and will use "Auto generated by spf13/cobra"
2015-09-08 16:02:02 -04:00
// if the Source is unset.
type GenManHeader struct {
Title string
Section string
Date * time . Time
date string
Source string
Manual string
2015-08-18 15:33:41 -07:00
}
2025-04-26 20:40:10 +00:00
// GenMan generates a man page for the given command and writes it to the specified writer.
//
// Parameters:
// cmd - The cobra.Command for which to generate the man page.
// header - A pointer to a GenManHeader that contains additional information for the man page. If nil, a default header will be used.
// w - The io.Writer to which the generated man page will be written.
//
// Returns:
// error - If an error occurs during the generation of the man page, it will be returned here.
2016-01-06 12:21:23 +01:00
func GenMan ( cmd * cobra . Command , header * GenManHeader , w io . Writer ) error {
2015-09-08 16:02:02 -04:00
if header == nil {
header = & GenManHeader { }
}
2020-04-29 11:15:55 -06:00
if err := fillHeader ( header , cmd . CommandPath ( ) , cmd . DisableAutoGenTag ) ; err != nil {
2018-10-22 01:11:14 +11:00
return err
}
2016-06-21 12:25:26 -04:00
2016-01-06 12:21:23 +01:00
b := genMan ( cmd , header )
2017-04-24 20:40:15 +02:00
_ , err := w . Write ( md2man . Render ( b ) )
2016-01-06 12:21:23 +01:00
return err
2015-08-18 15:33:41 -07:00
}
2025-04-26 20:40:10 +00:00
// fillHeader populates the GenManHeader with default values if they are not already set.
//
// It sets the title to the uppercase version of `name`, replacing spaces with hyphens.
// If the section is empty, it defaults to "1".
// If the date is nil, it sets the date to the current time or a time specified by the SOURCE_DATE_EPOCH environment variable.
// The formatted date is stored in header.date.
// If source is empty and auto-generation is not disabled, it sets the source to "Auto generated by spf13/cobra".
//
// It returns an error if there is an issue parsing the SOURCE_DATE_EPOCH environment variable.
2020-04-29 11:15:55 -06:00
func fillHeader ( header * GenManHeader , name string , disableAutoGen bool ) error {
2015-09-08 16:02:02 -04:00
if header . Title == "" {
2022-05-14 22:10:36 +02:00
header . Title = strings . ToUpper ( strings . ReplaceAll ( name , " " , "\\-" ) )
2015-09-08 16:02:02 -04:00
}
if header . Section == "" {
header . Section = "1"
}
if header . Date == nil {
now := time . Now ( )
2018-10-22 01:11:14 +11:00
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 )
}
2015-09-08 16:02:02 -04:00
header . Date = & now
}
2024-04-01 12:42:08 +00:00
header . date = header . Date . Format ( "Jan 2006" )
2020-04-29 11:15:55 -06:00
if header . Source == "" && ! disableAutoGen {
2015-09-08 16:02:02 -04:00
header . Source = "Auto generated by spf13/cobra"
}
2018-10-22 01:11:14 +11:00
return nil
2015-09-08 16:02:02 -04:00
}
2025-04-26 20:40:10 +00:00
// manPreamble writes the preamble for a manual page to the given buffer.
//
// Parameters:
// buf - the io.StringWriter to write the preamble to.
// header - a pointer to a GenManHeader containing metadata for the manual page.
// cmd - a pointer to a cobra.Command representing the command being documented.
// dashedName - the dash-separated name of the command.
//
// This function constructs the preamble section of a man page, including
// the title, section, date, source, and manual. It also includes the command's
// short description and synopsis.
2021-02-08 00:08:50 +00:00
func manPreamble ( buf io . StringWriter , header * GenManHeader , cmd * cobra . Command , dashedName string ) {
2016-06-20 17:27:41 -04:00
description := cmd . Long
if len ( description ) == 0 {
description = cmd . Short
}
2021-02-08 00:08:50 +00:00
cobra . WriteStringAndCheck ( buf , fmt . Sprintf ( ` % % "%s" "%s" "%s" "%s" "%s"
2015-08-18 15:33:41 -07:00
# NAME
2017-04-24 20:40:15 +02:00
` , header . Title , header . Section , header . date , header . Source , header . Manual ) )
2021-02-08 00:08:50 +00:00
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" )
2015-08-18 15:33:41 -07:00
}
2025-04-26 20:40:10 +00:00
// manPrintFlags prints the flags in a format suitable for a man page.
//
// It takes an io.StringWriter to write to and a pflag.FlagSet containing the flags to print.
// For each flag, it checks if it is deprecated or hidden. If not, it formats the flag name,
// shorthand, and usage information according to its type (string or other) and whether it has
// a default value. The formatted string is then written to the provided writer.
2021-02-08 00:08:50 +00:00
func manPrintFlags ( buf io . StringWriter , flags * pflag . FlagSet ) {
2015-08-18 15:33:41 -07:00
flags . VisitAll ( func ( flag * pflag . Flag ) {
2015-09-08 22:43:49 -04:00
if len ( flag . Deprecated ) > 0 || flag . Hidden {
2015-08-18 15:33:41 -07:00
return
}
format := ""
2016-06-20 17:29:54 -04:00
if len ( flag . Shorthand ) > 0 && len ( flag . ShorthandDeprecated ) == 0 {
format = fmt . Sprintf ( "**-%s**, **--%s**" , flag . Shorthand , flag . Name )
2015-08-18 15:33:41 -07:00
} else {
2016-06-20 17:29:54 -04:00
format = fmt . Sprintf ( "**--%s**" , flag . Name )
2015-08-18 15:33:41 -07:00
}
if len ( flag . NoOptDefVal ) > 0 {
2017-04-24 20:40:15 +02:00
format += "["
2015-08-18 15:33:41 -07:00
}
if flag . Value . Type ( ) == "string" {
// put quotes on the value
2017-04-24 20:40:15 +02:00
format += "=%q"
2015-08-18 15:33:41 -07:00
} else {
2017-04-24 20:40:15 +02:00
format += "=%s"
2015-08-18 15:33:41 -07:00
}
if len ( flag . NoOptDefVal ) > 0 {
2017-04-24 20:40:15 +02:00
format += "]"
2015-08-18 15:33:41 -07:00
}
2017-04-24 20:40:15 +02:00
format += "\n\t%s\n\n"
2021-02-08 00:08:50 +00:00
cobra . WriteStringAndCheck ( buf , fmt . Sprintf ( format , flag . DefValue , flag . Usage ) )
2015-08-18 15:33:41 -07:00
} )
}
2025-04-26 20:40:10 +00:00
// manPrintOptions writes the options for a command to a StringWriter.
// It includes both non-inherited and inherited flags, if available.
2021-02-08 00:08:50 +00:00
func manPrintOptions ( buf io . StringWriter , command * cobra . Command ) {
2015-08-18 15:33:41 -07:00
flags := command . NonInheritedFlags ( )
2018-04-24 12:15:12 -04:00
if flags . HasAvailableFlags ( ) {
2021-02-08 00:08:50 +00:00
cobra . WriteStringAndCheck ( buf , "# OPTIONS\n" )
2017-04-24 20:40:15 +02:00
manPrintFlags ( buf , flags )
2021-02-08 00:08:50 +00:00
cobra . WriteStringAndCheck ( buf , "\n" )
2015-08-18 15:33:41 -07:00
}
flags = command . InheritedFlags ( )
2018-04-24 12:15:12 -04:00
if flags . HasAvailableFlags ( ) {
2021-02-08 00:08:50 +00:00
cobra . WriteStringAndCheck ( buf , "# OPTIONS INHERITED FROM PARENT COMMANDS\n" )
2017-04-24 20:40:15 +02:00
manPrintFlags ( buf , flags )
2021-02-08 00:08:50 +00:00
cobra . WriteStringAndCheck ( buf , "\n" )
2015-08-18 15:33:41 -07:00
}
}
2025-04-26 20:40:10 +00:00
// genMan generates a man page for the given Cobra command and header.
// It initializes default help commands and flags, processes the command path,
// and constructs the man page content including preamble, options, examples,
// see also sections, and history.
//
// Parameters:
// - cmd: The Cobra command for which to generate the man page.
// - header: Header information for the man page, such as section and date.
//
// Returns:
// - A byte slice containing the generated man page content.
2015-11-24 00:19:16 +01:00
func genMan ( cmd * cobra . Command , header * GenManHeader ) [ ] byte {
2017-05-20 19:28:06 +02:00
cmd . InitDefaultHelpCmd ( )
cmd . InitDefaultHelpFlag ( )
2015-08-18 15:33:41 -07:00
// something like `rootcmd-subcmd1-subcmd2`
2022-05-14 22:10:36 +02:00
dashCommandName := strings . ReplaceAll ( cmd . CommandPath ( ) , " " , "-" )
2015-08-18 15:33:41 -07:00
buf := new ( bytes . Buffer )
2016-06-20 17:27:41 -04:00
manPreamble ( buf , header , cmd , dashCommandName )
2015-08-18 15:33:41 -07:00
manPrintOptions ( buf , cmd )
if len ( cmd . Example ) > 0 {
2017-04-24 20:40:15 +02:00
buf . WriteString ( "# EXAMPLE\n" )
buf . WriteString ( fmt . Sprintf ( "```\n%s\n```\n" , cmd . Example ) )
2015-08-18 15:33:41 -07:00
}
2015-11-24 00:19:16 +01:00
if hasSeeAlso ( cmd ) {
2017-04-24 20:40:15 +02:00
buf . WriteString ( "# SEE ALSO\n" )
2016-01-17 15:18:47 +11:00
seealsos := make ( [ ] string , 0 )
2015-08-18 15:33:41 -07:00
if cmd . HasParent ( ) {
2015-09-08 12:32:15 -04:00
parentPath := cmd . Parent ( ) . CommandPath ( )
2022-05-14 22:10:36 +02:00
dashParentPath := strings . ReplaceAll ( parentPath , " " , "-" )
2016-01-17 15:18:47 +11:00
seealso := fmt . Sprintf ( "**%s(%s)**" , dashParentPath , header . Section )
seealsos = append ( seealsos , seealso )
2015-11-24 00:19:16 +01:00
cmd . VisitParents ( func ( c * cobra . Command ) {
2015-11-07 18:21:25 -07:00
if c . DisableAutoGenTag {
cmd . DisableAutoGenTag = c . DisableAutoGenTag
}
} )
2015-08-18 15:33:41 -07:00
}
children := cmd . Commands ( )
sort . Sort ( byName ( children ) )
2016-01-17 15:18:47 +11:00
for _ , c := range children {
2017-03-09 20:37:15 +05:00
if ! c . IsAvailableCommand ( ) || c . IsAdditionalHelpTopicCommand ( ) {
2015-08-18 15:33:41 -07:00
continue
}
2016-01-17 15:18:47 +11:00
seealso := fmt . Sprintf ( "**%s-%s(%s)**" , dashCommandName , c . Name ( ) , header . Section )
seealsos = append ( seealsos , seealso )
2015-08-18 15:33:41 -07:00
}
2017-04-24 20:40:15 +02:00
buf . WriteString ( strings . Join ( seealsos , ", " ) + "\n" )
2015-08-18 15:33:41 -07:00
}
2015-11-07 18:21:25 -07:00
if ! cmd . DisableAutoGenTag {
2017-04-24 20:40:15 +02:00
buf . WriteString ( fmt . Sprintf ( "# HISTORY\n%s Auto generated by spf13/cobra\n" , header . Date . Format ( "2-Jan-2006" ) ) )
2015-11-07 18:21:25 -07:00
}
2015-08-18 15:33:41 -07:00
return buf . Bytes ( )
}