X Tutup
package docs import ( "bytes" "fmt" "io" "os" "path/filepath" "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, dir string) error { return GenManTreeFromOpts(cmd, GenManTreeOptions{ 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 { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { continue } if err := GenManTreeFromOpts(c, opts); err != nil { return err } } section := "1" separator := "_" if opts.CommandSeparator != "" { separator = opts.CommandSeparator } basename := strings.Replace(cmd.CommandPath(), " ", separator, -1) filename := filepath.Join(opts.Path, basename+"."+section) f, err := os.Create(filename) if err != nil { return err } defer f.Close() var versionString string if v := os.Getenv("GH_VERSION"); v != "" { versionString = "GitHub CLI " + v } return GenMan(cmd, &GenManHeader{ Section: section, Source: versionString, Manual: "GitHub CLI manual", }, f) } // GenManTreeOptions is the options for generating the man pages. // Used only in GenManTreeFromOpts. type GenManTreeOptions struct { 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. type GenManHeader struct { Title string Section string Date *time.Time 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 err := fillHeader(header, cmd.CommandPath()); err != nil { return err } b := genMan(cmd, header) _, err := w.Write(md2man.Render(b)) return err } func fillHeader(header *GenManHeader, name string) error { if header.Title == "" { header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1)) } 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 } return nil } func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, dashedName string) { buf.WriteString(fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s" # NAME `, header.Title, header.Section, header.Date.Format("Jan 2006"), header.Source, header.Manual)) buf.WriteString(fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short)) buf.WriteString("# SYNOPSIS\n") buf.WriteString(fmt.Sprintf("`%s`\n\n", cmd.UseLine())) if cmd.Long != "" && cmd.Long != cmd.Short { buf.WriteString("# DESCRIPTION\n") buf.WriteString(cmd.Long + "\n\n") } } func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) { flags.VisitAll(func(flag *pflag.Flag) { if len(flag.Deprecated) > 0 || flag.Hidden || flag.Name == "help" { return } varname, usage := pflag.UnquoteUsage(flag) if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 { buf.WriteString(fmt.Sprintf("`-%s`, `--%s`", flag.Shorthand, flag.Name)) } else { buf.WriteString(fmt.Sprintf("`--%s`", flag.Name)) } if varname == "" { buf.WriteString("\n") } else { buf.WriteString(fmt.Sprintf(" `<%s>`\n", varname)) } buf.WriteString(fmt.Sprintf(": %s\n\n", usage)) }) } func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) { flags := command.NonInheritedFlags() if flags.HasAvailableFlags() { buf.WriteString("# OPTIONS\n") manPrintFlags(buf, flags) buf.WriteString("\n") } flags = command.InheritedFlags() if hasNonHelpFlags(flags) { buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n") manPrintFlags(buf, flags) buf.WriteString("\n") } } func genMan(cmd *cobra.Command, header *GenManHeader) []byte { cmd.InitDefaultHelpCmd() cmd.InitDefaultHelpFlag() // something like `rootcmd-subcmd1-subcmd2` dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1) buf := new(bytes.Buffer) manPreamble(buf, header, cmd, dashCommandName) for _, g := range subcommandGroups(cmd) { if len(g.Commands) == 0 { continue } fmt.Fprintf(buf, "# %s\n", strings.ToUpper(g.Name)) for _, subcmd := range g.Commands { fmt.Fprintf(buf, "`%s`\n: %s\n\n", manLink(subcmd), subcmd.Short) } } manPrintOptions(buf, cmd) if len(cmd.Example) > 0 { buf.WriteString("# EXAMPLE\n") buf.WriteString(fmt.Sprintf("```\n%s\n```\n", cmd.Example)) } if cmd.HasParent() { buf.WriteString("# SEE ALSO\n") buf.WriteString(fmt.Sprintf("`%s`\n", manLink(cmd.Parent()))) } return buf.Bytes() } func manLink(cmd *cobra.Command) string { p := cmd.CommandPath() return fmt.Sprintf("%s(%d)", strings.Replace(p, " ", "-", -1), 1) }
X Tutup