X Tutup
Skip to content

Commit dae77fd

Browse files
authored
Merge pull request cli#4675 from cli/docs-web-formatting
Overhaul manual pages for the web
2 parents db6e1d2 + a7254ac commit dae77fd

File tree

5 files changed

+184
-71
lines changed

5 files changed

+184
-71
lines changed

internal/docs/markdown.go

Lines changed: 148 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,181 @@
11
package docs
22

33
import (
4-
"bytes"
54
"fmt"
5+
"html/template"
66
"io"
77
"os"
88
"path/filepath"
99
"strings"
1010

1111
"github.com/spf13/cobra"
12+
"github.com/spf13/pflag"
1213
)
1314

14-
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error {
15+
func printOptions(w io.Writer, cmd *cobra.Command) error {
1516
flags := cmd.NonInheritedFlags()
16-
flags.SetOutput(buf)
17+
flags.SetOutput(w)
1718
if flags.HasAvailableFlags() {
18-
buf.WriteString("### Options\n\n```\n")
19-
flags.PrintDefaults()
20-
buf.WriteString("```\n\n")
19+
fmt.Fprint(w, "### Options\n\n")
20+
if err := printFlagsHTML(w, flags); err != nil {
21+
return err
22+
}
23+
fmt.Fprint(w, "\n\n")
2124
}
2225

2326
parentFlags := cmd.InheritedFlags()
24-
parentFlags.SetOutput(buf)
25-
if parentFlags.HasAvailableFlags() {
26-
buf.WriteString("### Options inherited from parent commands\n\n```\n")
27-
parentFlags.PrintDefaults()
28-
buf.WriteString("```\n\n")
27+
parentFlags.SetOutput(w)
28+
if hasNonHelpFlags(parentFlags) {
29+
fmt.Fprint(w, "### Options inherited from parent commands\n\n")
30+
if err := printFlagsHTML(w, parentFlags); err != nil {
31+
return err
32+
}
33+
fmt.Fprint(w, "\n\n")
2934
}
3035
return nil
3136
}
3237

38+
func hasNonHelpFlags(fs *pflag.FlagSet) (found bool) {
39+
fs.VisitAll(func(f *pflag.Flag) {
40+
if !f.Hidden && f.Name != "help" {
41+
found = true
42+
}
43+
})
44+
return
45+
}
46+
47+
type flagView struct {
48+
Name string
49+
Varname string
50+
Shorthand string
51+
Usage string
52+
}
53+
54+
var flagsTemplate = `
55+
<dl class="flags">{{ range . }}
56+
<dt>{{ if .Shorthand }}<code>-{{.Shorthand}}</code>, {{ end -}}
57+
<code>--{{.Name}}{{ if .Varname }} &lt;{{.Varname}}&gt;{{ end }}</code></dt>
58+
<dd>{{.Usage}}</dd>
59+
{{ end }}</dl>
60+
`
61+
62+
var tpl = template.Must(template.New("flags").Parse(flagsTemplate))
63+
64+
func printFlagsHTML(w io.Writer, fs *pflag.FlagSet) error {
65+
var flags []flagView
66+
fs.VisitAll(func(f *pflag.Flag) {
67+
if f.Hidden || f.Name == "help" {
68+
return
69+
}
70+
varname, usage := pflag.UnquoteUsage(f)
71+
flags = append(flags, flagView{
72+
Name: f.Name,
73+
Varname: varname,
74+
Shorthand: f.Shorthand,
75+
Usage: usage,
76+
})
77+
})
78+
return tpl.Execute(w, flags)
79+
}
80+
3381
// GenMarkdown creates markdown output.
3482
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
3583
return GenMarkdownCustom(cmd, w, func(s string) string { return s })
3684
}
3785

3886
// GenMarkdownCustom creates custom markdown output.
3987
func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
40-
cmd.InitDefaultHelpCmd()
41-
cmd.InitDefaultHelpFlag()
88+
fmt.Fprintf(w, "## %s\n\n", cmd.CommandPath())
4289

43-
buf := new(bytes.Buffer)
44-
name := cmd.CommandPath()
90+
hasLong := cmd.Long != ""
91+
if !hasLong {
92+
fmt.Fprintf(w, "%s\n\n", cmd.Short)
93+
}
94+
if cmd.Runnable() {
95+
fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine())
96+
}
97+
if hasLong {
98+
fmt.Fprintf(w, "%s\n\n", cmd.Long)
99+
}
45100

46-
buf.WriteString("## " + name + "\n\n")
47-
buf.WriteString(cmd.Short + "\n\n")
48-
if len(cmd.Long) > 0 {
49-
buf.WriteString("### Synopsis\n\n")
50-
buf.WriteString(cmd.Long + "\n\n")
101+
for _, g := range subcommandGroups(cmd) {
102+
if len(g.Commands) == 0 {
103+
continue
104+
}
105+
fmt.Fprintf(w, "### %s\n\n", g.Name)
106+
for _, subcmd := range g.Commands {
107+
fmt.Fprintf(w, "* [%s](%s)\n", subcmd.CommandPath(), linkHandler(cmdManualPath(subcmd)))
108+
}
109+
fmt.Fprint(w, "\n\n")
51110
}
52111

53-
if cmd.Runnable() {
54-
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
112+
if err := printOptions(w, cmd); err != nil {
113+
return err
55114
}
56115

57116
if len(cmd.Example) > 0 {
58-
buf.WriteString("### Examples\n\n")
59-
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
117+
fmt.Fprint(w, "### Examples\n\n{% highlight bash %}{% raw %}\n")
118+
fmt.Fprint(w, cmd.Example)
119+
fmt.Fprint(w, "{% endraw %}{% endhighlight %}\n\n")
60120
}
61121

62-
if err := printOptions(buf, cmd, name); err != nil {
63-
return err
122+
if cmd.HasParent() {
123+
p := cmd.Parent()
124+
fmt.Fprint(w, "### See also\n\n")
125+
fmt.Fprintf(w, "* [%s](%s)\n", p.CommandPath(), linkHandler(cmdManualPath(p)))
126+
}
127+
128+
return nil
129+
}
130+
131+
type commandGroup struct {
132+
Name string
133+
Commands []*cobra.Command
134+
}
135+
136+
// subcommandGroups lists child commands of a Cobra command split into groups.
137+
// TODO: have rootHelpFunc use this instead of repeating the same logic.
138+
func subcommandGroups(c *cobra.Command) []commandGroup {
139+
var rest []*cobra.Command
140+
var core []*cobra.Command
141+
var actions []*cobra.Command
142+
143+
for _, subcmd := range c.Commands() {
144+
if !subcmd.IsAvailableCommand() {
145+
continue
146+
}
147+
if _, ok := subcmd.Annotations["IsCore"]; ok {
148+
core = append(core, subcmd)
149+
} else if _, ok := subcmd.Annotations["IsActions"]; ok {
150+
actions = append(actions, subcmd)
151+
} else {
152+
rest = append(rest, subcmd)
153+
}
154+
}
155+
156+
if len(core) > 0 {
157+
return []commandGroup{
158+
{
159+
Name: "Core commands",
160+
Commands: core,
161+
},
162+
{
163+
Name: "Actions commands",
164+
Commands: actions,
165+
},
166+
{
167+
Name: "Additional commands",
168+
Commands: rest,
169+
},
170+
}
171+
}
172+
173+
return []commandGroup{
174+
{
175+
Name: "Commands",
176+
Commands: rest,
177+
},
64178
}
65-
_, err := buf.WriteTo(w)
66-
return err
67179
}
68180

69181
// GenMarkdownTree will generate a markdown page for this command and all
@@ -92,12 +204,7 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa
92204
}
93205
}
94206

95-
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md"
96-
if basenameOverride, found := cmd.Annotations["markdown:basename"]; found {
97-
basename = basenameOverride + ".md"
98-
}
99-
100-
filename := filepath.Join(dir, basename)
207+
filename := filepath.Join(dir, cmdManualPath(cmd))
101208
f, err := os.Create(filename)
102209
if err != nil {
103210
return err
@@ -112,3 +219,10 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa
112219
}
113220
return nil
114221
}
222+
223+
func cmdManualPath(c *cobra.Command) string {
224+
if basenameOverride, found := c.Annotations["markdown:basename"]; found {
225+
return basenameOverride + ".md"
226+
}
227+
return strings.ReplaceAll(c.CommandPath(), " ", "_") + ".md"
228+
}

pkg/cmd/actions/actions.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package actions
22

33
import (
4-
"fmt"
5-
64
"github.com/MakeNowJust/heredoc"
75
"github.com/cli/cli/v2/pkg/cmdutil"
86
"github.com/cli/cli/v2/pkg/iostreams"
@@ -14,11 +12,8 @@ func NewCmdActions(f *cmdutil.Factory) *cobra.Command {
1412

1513
cmd := &cobra.Command{
1614
Use: "actions",
17-
Short: "Learn about working with GitHub actions",
15+
Short: "Learn about working with GitHub Actions",
1816
Long: actionsExplainer(cs),
19-
Run: func(cmd *cobra.Command, args []string) {
20-
fmt.Fprintln(f.IOStreams.Out, actionsExplainer(cs))
21-
},
2217
Annotations: map[string]string{
2318
"IsActions": "true",
2419
},

pkg/cmd/codespace/ssh.go

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"path/filepath"
1313
"strings"
1414

15+
"github.com/MakeNowJust/heredoc"
1516
"github.com/cli/cli/v2/internal/codespaces"
1617
"github.com/cli/cli/v2/pkg/cmdutil"
1718
"github.com/cli/cli/v2/pkg/liveshare"
@@ -156,36 +157,36 @@ func newCpCmd(app *App) *cobra.Command {
156157
var opts cpOptions
157158

158159
cpCmd := &cobra.Command{
159-
Use: "cp [-e] [-r] srcs... dest",
160+
Use: "cp [-e] [-r] <sources>... <dest>",
160161
Short: "Copy files between local and remote file systems",
161-
Long: `
162-
The cp command copies files between the local and remote file systems.
163-
164-
As with the UNIX cp command, the first argument specifies the source and the last
165-
specifies the destination; additional sources may be specified after the first,
166-
if the destination is a directory.
167-
168-
The -r (recursive) flag is required if any source is a directory.
169-
170-
A 'remote:' prefix on any file name argument indicates that it refers to
171-
the file system of the remote (Codespace) machine. It is resolved relative
172-
to the home directory of the remote user.
173-
174-
By default, remote file names are interpreted literally. With the -e flag,
175-
each such argument is treated in the manner of scp, as a Bash expression to
176-
be evaluated on the remote machine, subject to expansion of tildes, braces,
177-
globs, environment variables, and backticks, as in these examples:
178-
179-
$ gh codespace cp -e README.md 'remote:/workspace/$RepositoryName/'
180-
$ gh codespace cp -e 'remote:~/*.go' ./gofiles/
181-
$ gh codespace cp -e 'remote:/workspace/myproj/go.{mod,sum}' ./gofiles/
182-
183-
For security, do not use the -e flag with arguments provided by untrusted
184-
users; see https://lwn.net/Articles/835962/ for discussion.
185-
`,
162+
Long: heredoc.Docf(`
163+
The cp command copies files between the local and remote file systems.
164+
165+
As with the UNIX %[1]scp%[1]s command, the first argument specifies the source and the last
166+
specifies the destination; additional sources may be specified after the first,
167+
if the destination is a directory.
168+
169+
The %[1]s--recursive%[1]s flag is required if any source is a directory.
170+
171+
A "remote:" prefix on any file name argument indicates that it refers to
172+
the file system of the remote (Codespace) machine. It is resolved relative
173+
to the home directory of the remote user.
174+
175+
By default, remote file names are interpreted literally. With the %[1]s--expand%[1]s flag,
176+
each such argument is treated in the manner of %[1]sscp%[1]s, as a Bash expression to
177+
be evaluated on the remote machine, subject to expansion of tildes, braces, globs,
178+
environment variables, and backticks. For security, do not use this flag with arguments
179+
provided by untrusted users; see <https://lwn.net/Articles/835962/> for discussion.
180+
`, "`"),
181+
Example: heredoc.Doc(`
182+
$ gh codespace cp -e README.md 'remote:/workspace/$RepositoryName/'
183+
$ gh codespace cp -e 'remote:~/*.go' ./gofiles/
184+
$ gh codespace cp -e 'remote:/workspace/myproj/go.{mod,sum}' ./gofiles/
185+
`),
186186
RunE: func(cmd *cobra.Command, args []string) error {
187187
return app.Copy(cmd.Context(), args, opts)
188188
},
189+
DisableFlagsInUseLine: true,
189190
}
190191

191192
// We don't expose all sshOptions.

pkg/cmd/root/help_topic.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var HelpTopics = map[string]map[string]string{
5454
to, e.g. "less".
5555
5656
GLAMOUR_STYLE: the style to use for rendering Markdown. See
57-
https://github.com/charmbracelet/glamour#styles
57+
<https://github.com/charmbracelet/glamour#styles>
5858
5959
NO_COLOR: set to any value to avoid printing ANSI escape sequences for color output.
6060
@@ -90,14 +90,14 @@ var HelpTopics = map[string]map[string]string{
9090
The %[1]s--jq%[1]s option accepts a query in jq syntax and will print only the resulting
9191
values that match the query. This is equivalent to piping the output to %[1]sjq -r%[1]s,
9292
but does not require the jq utility to be installed on the system. To learn more
93-
about the query syntax, see: https://stedolan.github.io/jq/manual/v1.6/
93+
about the query syntax, see: <https://stedolan.github.io/jq/manual/v1.6/>
9494
9595
With %[1]s--template%[1]s, the provided Go template is rendered using the JSON data as input.
96-
For the syntax of Go templates, see: https://golang.org/pkg/text/template/
96+
For the syntax of Go templates, see: <https://golang.org/pkg/text/template/>
9797
9898
The following functions are available in templates:
9999
- %[1]sautocolor%[1]s: like %[1]scolor%[1]s, but only emits color to terminals
100-
- %[1]scolor <style> <input>%[1]s: colorize input using https://github.com/mgutz/ansi
100+
- %[1]scolor <style> <input>%[1]s: colorize input using <https://github.com/mgutz/ansi>
101101
- %[1]sjoin <sep> <list>%[1]s: joins values in the list using a separator
102102
- %[1]spluck <field> <list>%[1]s: collects values of a field from all items in the input
103103
- %[1]stablerow <fields>...%[1]s: aligns fields in output vertically as a table

pkg/cmd/secret/secret.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ func NewCmdSecret(f *cmdutil.Factory) *cobra.Command {
1616
Long: heredoc.Doc(`
1717
Secrets can be set at the repository, environment, or organization level for use in
1818
GitHub Actions. Run "gh help secret set" to learn how to get started.
19-
`),
19+
`),
20+
Annotations: map[string]string{
21+
"IsActions": "true",
22+
},
2023
}
2124

2225
cmdutil.EnableRepoOverride(cmd, f)

0 commit comments

Comments
 (0)
X Tutup