...

Source file src/github.com/urfave/cli/v2/docs.go

Documentation: github.com/urfave/cli/v2

     1  //go:build !urfave_cli_no_docs
     2  // +build !urfave_cli_no_docs
     3  
     4  package cli
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"sort"
    11  	"strings"
    12  	"text/template"
    13  
    14  	"github.com/cpuguy83/go-md2man/v2/md2man"
    15  )
    16  
    17  // ToMarkdown creates a markdown string for the `*App`
    18  // The function errors if either parsing or writing of the string fails.
    19  func (a *App) ToMarkdown() (string, error) {
    20  	var w bytes.Buffer
    21  	if err := a.writeDocTemplate(&w, 0); err != nil {
    22  		return "", err
    23  	}
    24  	return w.String(), nil
    25  }
    26  
    27  // ToMan creates a man page string with section number for the `*App`
    28  // The function errors if either parsing or writing of the string fails.
    29  func (a *App) ToManWithSection(sectionNumber int) (string, error) {
    30  	var w bytes.Buffer
    31  	if err := a.writeDocTemplate(&w, sectionNumber); err != nil {
    32  		return "", err
    33  	}
    34  	man := md2man.Render(w.Bytes())
    35  	return string(man), nil
    36  }
    37  
    38  // ToMan creates a man page string for the `*App`
    39  // The function errors if either parsing or writing of the string fails.
    40  func (a *App) ToMan() (string, error) {
    41  	man, err := a.ToManWithSection(8)
    42  	return man, err
    43  }
    44  
    45  type cliTemplate struct {
    46  	App          *App
    47  	SectionNum   int
    48  	Commands     []string
    49  	GlobalArgs   []string
    50  	SynopsisArgs []string
    51  }
    52  
    53  func (a *App) writeDocTemplate(w io.Writer, sectionNum int) error {
    54  	const name = "cli"
    55  	t, err := template.New(name).Parse(MarkdownDocTemplate)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	return t.ExecuteTemplate(w, name, &cliTemplate{
    60  		App:          a,
    61  		SectionNum:   sectionNum,
    62  		Commands:     prepareCommands(a.Commands, 0),
    63  		GlobalArgs:   prepareArgsWithValues(a.VisibleFlags()),
    64  		SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
    65  	})
    66  }
    67  
    68  func prepareCommands(commands []*Command, level int) []string {
    69  	var coms []string
    70  	for _, command := range commands {
    71  		if command.Hidden {
    72  			continue
    73  		}
    74  
    75  		usageText := prepareUsageText(command)
    76  
    77  		usage := prepareUsage(command, usageText)
    78  
    79  		prepared := fmt.Sprintf("%s %s\n\n%s%s",
    80  			strings.Repeat("#", level+2),
    81  			strings.Join(command.Names(), ", "),
    82  			usage,
    83  			usageText,
    84  		)
    85  
    86  		flags := prepareArgsWithValues(command.VisibleFlags())
    87  		if len(flags) > 0 {
    88  			prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
    89  		}
    90  
    91  		coms = append(coms, prepared)
    92  
    93  		// recursively iterate subcommands
    94  		if len(command.Subcommands) > 0 {
    95  			coms = append(
    96  				coms,
    97  				prepareCommands(command.Subcommands, level+1)...,
    98  			)
    99  		}
   100  	}
   101  
   102  	return coms
   103  }
   104  
   105  func prepareArgsWithValues(flags []Flag) []string {
   106  	return prepareFlags(flags, ", ", "**", "**", `""`, true)
   107  }
   108  
   109  func prepareArgsSynopsis(flags []Flag) []string {
   110  	return prepareFlags(flags, "|", "[", "]", "[value]", false)
   111  }
   112  
   113  func prepareFlags(
   114  	flags []Flag,
   115  	sep, opener, closer, value string,
   116  	addDetails bool,
   117  ) []string {
   118  	args := []string{}
   119  	for _, f := range flags {
   120  		flag, ok := f.(DocGenerationFlag)
   121  		if !ok {
   122  			continue
   123  		}
   124  		modifiedArg := opener
   125  
   126  		for _, s := range flag.Names() {
   127  			trimmed := strings.TrimSpace(s)
   128  			if len(modifiedArg) > len(opener) {
   129  				modifiedArg += sep
   130  			}
   131  			if len(trimmed) > 1 {
   132  				modifiedArg += fmt.Sprintf("--%s", trimmed)
   133  			} else {
   134  				modifiedArg += fmt.Sprintf("-%s", trimmed)
   135  			}
   136  		}
   137  		modifiedArg += closer
   138  		if flag.TakesValue() {
   139  			modifiedArg += fmt.Sprintf("=%s", value)
   140  		}
   141  
   142  		if addDetails {
   143  			modifiedArg += flagDetails(flag)
   144  		}
   145  
   146  		args = append(args, modifiedArg+"\n")
   147  
   148  	}
   149  	sort.Strings(args)
   150  	return args
   151  }
   152  
   153  // flagDetails returns a string containing the flags metadata
   154  func flagDetails(flag DocGenerationFlag) string {
   155  	description := flag.GetUsage()
   156  	if flag.TakesValue() {
   157  		defaultText := flag.GetDefaultText()
   158  		if defaultText == "" {
   159  			defaultText = flag.GetValue()
   160  		}
   161  		if defaultText != "" {
   162  			description += " (default: " + defaultText + ")"
   163  		}
   164  	}
   165  	return ": " + description
   166  }
   167  
   168  func prepareUsageText(command *Command) string {
   169  	if command.UsageText == "" {
   170  		return ""
   171  	}
   172  
   173  	// Remove leading and trailing newlines
   174  	preparedUsageText := strings.Trim(command.UsageText, "\n")
   175  
   176  	var usageText string
   177  	if strings.Contains(preparedUsageText, "\n") {
   178  		// Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such
   179  		// that it will not break the continuous code block.
   180  		for _, ln := range strings.Split(preparedUsageText, "\n") {
   181  			usageText += fmt.Sprintf("    %s\n", ln)
   182  		}
   183  	} else {
   184  		// Style a single line as a note
   185  		usageText = fmt.Sprintf(">%s\n", preparedUsageText)
   186  	}
   187  
   188  	return usageText
   189  }
   190  
   191  func prepareUsage(command *Command, usageText string) string {
   192  	if command.Usage == "" {
   193  		return ""
   194  	}
   195  
   196  	usage := command.Usage + "\n"
   197  	// Add a newline to the Usage IFF there is a UsageText
   198  	if usageText != "" {
   199  		usage += "\n"
   200  	}
   201  
   202  	return usage
   203  }
   204  

View as plain text