Refresh man pages
- Fix name of man pages for all but the toplevel command - Set title of all man pages to "GitHub CLI manual" - Include gh version information in man pages - Clean up rendering of flags section - List subcommands for every command
This commit is contained in:
parent
17291d1d5f
commit
31c7181297
6 changed files with 55 additions and 134 deletions
|
|
@ -8,7 +8,7 @@ release:
|
|||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- make manpages
|
||||
- make manpages GH_VERSION={{.Version}}
|
||||
|
||||
builds:
|
||||
- <<: &build_defaults
|
||||
|
|
|
|||
|
|
@ -58,13 +58,7 @@ func run(args []string) error {
|
|||
}
|
||||
|
||||
if *manPage {
|
||||
header := &docs.GenManHeader{
|
||||
Title: "gh",
|
||||
Section: "1",
|
||||
Source: "",
|
||||
Manual: "",
|
||||
}
|
||||
if err := docs.GenManTree(rootCmd, header, *dir); err != nil {
|
||||
if err := docs.GenManTree(rootCmd, *dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func Test_run(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("error reading `gh-issue-create.1`: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(manPage), `\fBgh issue create`) {
|
||||
if !strings.Contains(string(manPage), `\fB\fCgh issue create`) {
|
||||
t.Fatal("man page corrupted")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,12 +79,14 @@ var dummyCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func checkStringContains(t *testing.T, got, expected string) {
|
||||
t.Helper()
|
||||
if !strings.Contains(got, expected) {
|
||||
t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func checkStringOmits(t *testing.T, got, expected string) {
|
||||
t.Helper()
|
||||
if strings.Contains(got, expected) {
|
||||
t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -21,9 +20,8 @@ import (
|
|||
// 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 {
|
||||
func GenManTree(cmd *cobra.Command, dir string) error {
|
||||
return GenManTreeFromOpts(cmd, GenManTreeOptions{
|
||||
Header: header,
|
||||
Path: dir,
|
||||
CommandSeparator: "-",
|
||||
})
|
||||
|
|
@ -32,10 +30,6 @@ func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
|
|||
// 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
|
||||
|
|
@ -44,11 +38,8 @@ func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
section := "1"
|
||||
if header.Section != "" {
|
||||
section = header.Section
|
||||
}
|
||||
|
||||
section := "1"
|
||||
separator := "_"
|
||||
if opts.CommandSeparator != "" {
|
||||
separator = opts.CommandSeparator
|
||||
|
|
@ -61,14 +52,21 @@ func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
headerCopy := *header
|
||||
return GenMan(cmd, &headerCopy, f)
|
||||
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 {
|
||||
Header *GenManHeader
|
||||
Path string
|
||||
CommandSeparator string
|
||||
}
|
||||
|
|
@ -80,7 +78,6 @@ type GenManHeader struct {
|
|||
Title string
|
||||
Section string
|
||||
Date *time.Time
|
||||
date string
|
||||
Source string
|
||||
Manual string
|
||||
}
|
||||
|
|
@ -88,9 +85,6 @@ type GenManHeader struct {
|
|||
// 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()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -119,57 +113,40 @@ func fillHeader(header *GenManHeader, name string) error {
|
|||
}
|
||||
header.Date = &now
|
||||
}
|
||||
header.date = (*header.Date).Format("Jan 2006")
|
||||
return nil
|
||||
}
|
||||
|
||||
func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, dashedName string) {
|
||||
description := cmd.Long
|
||||
if len(description) == 0 {
|
||||
description = cmd.Short
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s"
|
||||
# NAME
|
||||
`, header.Title, header.Section, header.date, header.Source, header.Manual))
|
||||
`, 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()))
|
||||
|
||||
// "<>" is rendered as HTML in md
|
||||
synopsis := cmd.UseLine()
|
||||
escAngle := strings.Replace(synopsis, "<", "\\<", -1)
|
||||
escAngle = strings.Replace(escAngle, ">", "\\>", -1)
|
||||
buf.WriteString(fmt.Sprintf("**%s**\n\n", escAngle))
|
||||
|
||||
buf.WriteString("# DESCRIPTION\n")
|
||||
buf.WriteString(description + "\n\n")
|
||||
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 {
|
||||
if len(flag.Deprecated) > 0 || flag.Hidden || flag.Name == "help" {
|
||||
return
|
||||
}
|
||||
format := ""
|
||||
varname, usage := pflag.UnquoteUsage(flag)
|
||||
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
|
||||
format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
|
||||
buf.WriteString(fmt.Sprintf("`-%s`, `--%s`", flag.Shorthand, flag.Name))
|
||||
} else {
|
||||
format = fmt.Sprintf("**--%s**", flag.Name)
|
||||
buf.WriteString(fmt.Sprintf("`--%s`", flag.Name))
|
||||
}
|
||||
if len(flag.NoOptDefVal) > 0 {
|
||||
format += "["
|
||||
}
|
||||
if flag.Value.Type() == "string" {
|
||||
// put quotes on the value
|
||||
format += "=%q"
|
||||
if varname == "" {
|
||||
buf.WriteString("\n")
|
||||
} else {
|
||||
format += "=%s"
|
||||
buf.WriteString(fmt.Sprintf(" `<%s>`\n", varname))
|
||||
}
|
||||
if len(flag.NoOptDefVal) > 0 {
|
||||
format += "]"
|
||||
}
|
||||
format += "\n\t%s\n\n"
|
||||
buf.WriteString(fmt.Sprintf(format, flag.DefValue, flag.Usage))
|
||||
buf.WriteString(fmt.Sprintf(": %s\n\n", usage))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +158,7 @@ func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) {
|
|||
buf.WriteString("\n")
|
||||
}
|
||||
flags = command.InheritedFlags()
|
||||
if flags.HasAvailableFlags() {
|
||||
if hasNonHelpFlags(flags) {
|
||||
buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n")
|
||||
manPrintFlags(buf, flags)
|
||||
buf.WriteString("\n")
|
||||
|
|
@ -198,52 +175,28 @@ func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
|
|||
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 hasSeeAlso(cmd) {
|
||||
if cmd.HasParent() {
|
||||
buf.WriteString("# SEE ALSO\n")
|
||||
seealsos := make([]string, 0)
|
||||
if cmd.HasParent() {
|
||||
parentPath := cmd.Parent().CommandPath()
|
||||
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
|
||||
seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
|
||||
seealsos = append(seealsos, seealso)
|
||||
}
|
||||
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")
|
||||
buf.WriteString(fmt.Sprintf("`%s`\n", manLink(cmd.Parent())))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Test to see if we have a reason to print See Also information in docs
|
||||
// Basically this is a test for a parent command or a subcommand which is
|
||||
// both not deprecated and not the autogenerated help command.
|
||||
func hasSeeAlso(cmd *cobra.Command) bool {
|
||||
if cmd.HasParent() {
|
||||
return true
|
||||
}
|
||||
for _, c := range cmd.Commands() {
|
||||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
func manLink(cmd *cobra.Command) string {
|
||||
p := cmd.CommandPath()
|
||||
return fmt.Sprintf("%s(%d)", strings.Replace(p, " ", "-", -1), 1)
|
||||
}
|
||||
|
||||
type byName []*cobra.Command
|
||||
|
||||
func (s byName) Len() int { return len(s) }
|
||||
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func translate(in string) string {
|
|||
func TestGenManDoc(t *testing.T) {
|
||||
header := &GenManHeader{
|
||||
Title: "Project",
|
||||
Section: "2",
|
||||
Section: "1",
|
||||
}
|
||||
|
||||
// We generate on a subcommand so we have both subcommands and parents
|
||||
|
|
@ -49,7 +49,7 @@ func TestGenManDoc(t *testing.T) {
|
|||
func TestGenManNoHiddenParents(t *testing.T) {
|
||||
header := &GenManHeader{
|
||||
Title: "Project",
|
||||
Section: "2",
|
||||
Section: "1",
|
||||
}
|
||||
|
||||
// We generate on a subcommand so we have both subcommands and parents
|
||||
|
|
@ -94,15 +94,8 @@ func TestGenManSeeAlso(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
scanner := bufio.NewScanner(buf)
|
||||
|
||||
if err := assertLineFound(scanner, ".SH SEE ALSO"); err != nil {
|
||||
t.Fatalf("Couldn't find SEE ALSO section header: %v", err)
|
||||
}
|
||||
if err := assertNextLineEquals(scanner, ".PP"); err != nil {
|
||||
t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err)
|
||||
}
|
||||
if err := assertNextLineEquals(scanner, `\fBroot-bbb(1)\fP, \fBroot-ccc(1)\fP`); err != nil {
|
||||
t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err)
|
||||
if err := assertLineFound(scanner, ".SH SEE ALSO"); err == nil {
|
||||
t.Fatalf("Did not expect SEE ALSO section header")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,31 +108,26 @@ func TestManPrintFlagsHidesShortDeprecated(t *testing.T) {
|
|||
manPrintFlags(buf, c.Flags())
|
||||
|
||||
got := buf.String()
|
||||
expected := "**--foo**=\"default\"\n\tFoo flag\n\n"
|
||||
expected := "`--foo` `<string>`\n: Foo flag\n\n"
|
||||
if got != expected {
|
||||
t.Errorf("Expected %v, got %v", expected, got)
|
||||
t.Errorf("Expected %q, got %q", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenManTree(t *testing.T) {
|
||||
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
|
||||
header := &GenManHeader{Section: "2"}
|
||||
tmpdir, err := ioutil.TempDir("", "test-gen-man-tree")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create tmpdir: %s", err.Error())
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
if err := GenManTree(c, header, tmpdir); err != nil {
|
||||
if err := GenManTree(c, tmpdir); err != nil {
|
||||
t.Fatalf("GenManTree failed: %s", err.Error())
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(tmpdir, "do.2")); err != nil {
|
||||
t.Fatalf("Expected file 'do.2' to exist")
|
||||
}
|
||||
|
||||
if header.Title != "" {
|
||||
t.Fatalf("Expected header.Title to be unmodified")
|
||||
if _, err := os.Stat(filepath.Join(tmpdir, "do.1")); err != nil {
|
||||
t.Fatalf("Expected file 'do.1' to exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,22 +146,6 @@ func assertLineFound(scanner *bufio.Scanner, expectedLine string) error {
|
|||
return fmt.Errorf("hit EOF before finding %v", expectedLine)
|
||||
}
|
||||
|
||||
func assertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error {
|
||||
if scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == expectedLine {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("got %v, not %v", line, expectedLine)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("scan failed: %v", err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("hit EOF before finding %v", expectedLine)
|
||||
}
|
||||
|
||||
func BenchmarkGenManToFile(b *testing.B) {
|
||||
file, err := ioutil.TempFile(b.TempDir(), "")
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue