Merge pull request #4675 from cli/docs-web-formatting

Overhaul manual pages for the web
This commit is contained in:
Mislav Marohnić 2021-11-09 21:00:51 +01:00 committed by GitHub
commit dae77fd398
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 181 additions and 68 deletions

View file

@ -1,35 +1,83 @@
package docs
import (
"bytes"
"fmt"
"html/template"
"io"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error {
func printOptions(w io.Writer, cmd *cobra.Command) error {
flags := cmd.NonInheritedFlags()
flags.SetOutput(buf)
flags.SetOutput(w)
if flags.HasAvailableFlags() {
buf.WriteString("### Options\n\n```\n")
flags.PrintDefaults()
buf.WriteString("```\n\n")
fmt.Fprint(w, "### Options\n\n")
if err := printFlagsHTML(w, flags); err != nil {
return err
}
fmt.Fprint(w, "\n\n")
}
parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(buf)
if parentFlags.HasAvailableFlags() {
buf.WriteString("### Options inherited from parent commands\n\n```\n")
parentFlags.PrintDefaults()
buf.WriteString("```\n\n")
parentFlags.SetOutput(w)
if hasNonHelpFlags(parentFlags) {
fmt.Fprint(w, "### Options inherited from parent commands\n\n")
if err := printFlagsHTML(w, parentFlags); err != nil {
return err
}
fmt.Fprint(w, "\n\n")
}
return nil
}
func hasNonHelpFlags(fs *pflag.FlagSet) (found bool) {
fs.VisitAll(func(f *pflag.Flag) {
if !f.Hidden && f.Name != "help" {
found = true
}
})
return
}
type flagView struct {
Name string
Varname string
Shorthand string
Usage string
}
var flagsTemplate = `
<dl class="flags">{{ range . }}
<dt>{{ if .Shorthand }}<code>-{{.Shorthand}}</code>, {{ end -}}
<code>--{{.Name}}{{ if .Varname }} &lt;{{.Varname}}&gt;{{ end }}</code></dt>
<dd>{{.Usage}}</dd>
{{ end }}</dl>
`
var tpl = template.Must(template.New("flags").Parse(flagsTemplate))
func printFlagsHTML(w io.Writer, fs *pflag.FlagSet) error {
var flags []flagView
fs.VisitAll(func(f *pflag.Flag) {
if f.Hidden || f.Name == "help" {
return
}
varname, usage := pflag.UnquoteUsage(f)
flags = append(flags, flagView{
Name: f.Name,
Varname: varname,
Shorthand: f.Shorthand,
Usage: usage,
})
})
return tpl.Execute(w, flags)
}
// GenMarkdown creates markdown output.
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
return GenMarkdownCustom(cmd, w, func(s string) string { return s })
@ -37,33 +85,97 @@ func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
// GenMarkdownCustom creates custom markdown output.
func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()
fmt.Fprintf(w, "## %s\n\n", cmd.CommandPath())
buf := new(bytes.Buffer)
name := cmd.CommandPath()
buf.WriteString("## " + name + "\n\n")
buf.WriteString(cmd.Short + "\n\n")
if len(cmd.Long) > 0 {
buf.WriteString("### Synopsis\n\n")
buf.WriteString(cmd.Long + "\n\n")
hasLong := cmd.Long != ""
if !hasLong {
fmt.Fprintf(w, "%s\n\n", cmd.Short)
}
if cmd.Runnable() {
fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine())
}
if hasLong {
fmt.Fprintf(w, "%s\n\n", cmd.Long)
}
if cmd.Runnable() {
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
for _, g := range subcommandGroups(cmd) {
if len(g.Commands) == 0 {
continue
}
fmt.Fprintf(w, "### %s\n\n", g.Name)
for _, subcmd := range g.Commands {
fmt.Fprintf(w, "* [%s](%s)\n", subcmd.CommandPath(), linkHandler(cmdManualPath(subcmd)))
}
fmt.Fprint(w, "\n\n")
}
if err := printOptions(w, cmd); err != nil {
return err
}
if len(cmd.Example) > 0 {
buf.WriteString("### Examples\n\n")
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
fmt.Fprint(w, "### Examples\n\n{% highlight bash %}{% raw %}\n")
fmt.Fprint(w, cmd.Example)
fmt.Fprint(w, "{% endraw %}{% endhighlight %}\n\n")
}
if err := printOptions(buf, cmd, name); err != nil {
return err
if cmd.HasParent() {
p := cmd.Parent()
fmt.Fprint(w, "### See also\n\n")
fmt.Fprintf(w, "* [%s](%s)\n", p.CommandPath(), linkHandler(cmdManualPath(p)))
}
return nil
}
type commandGroup struct {
Name string
Commands []*cobra.Command
}
// subcommandGroups lists child commands of a Cobra command split into groups.
// TODO: have rootHelpFunc use this instead of repeating the same logic.
func subcommandGroups(c *cobra.Command) []commandGroup {
var rest []*cobra.Command
var core []*cobra.Command
var actions []*cobra.Command
for _, subcmd := range c.Commands() {
if !subcmd.IsAvailableCommand() {
continue
}
if _, ok := subcmd.Annotations["IsCore"]; ok {
core = append(core, subcmd)
} else if _, ok := subcmd.Annotations["IsActions"]; ok {
actions = append(actions, subcmd)
} else {
rest = append(rest, subcmd)
}
}
if len(core) > 0 {
return []commandGroup{
{
Name: "Core commands",
Commands: core,
},
{
Name: "Actions commands",
Commands: actions,
},
{
Name: "Additional commands",
Commands: rest,
},
}
}
return []commandGroup{
{
Name: "Commands",
Commands: rest,
},
}
_, err := buf.WriteTo(w)
return err
}
// GenMarkdownTree will generate a markdown page for this command and all
@ -92,12 +204,7 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa
}
}
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md"
if basenameOverride, found := cmd.Annotations["markdown:basename"]; found {
basename = basenameOverride + ".md"
}
filename := filepath.Join(dir, basename)
filename := filepath.Join(dir, cmdManualPath(cmd))
f, err := os.Create(filename)
if err != nil {
return err
@ -112,3 +219,10 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa
}
return nil
}
func cmdManualPath(c *cobra.Command) string {
if basenameOverride, found := c.Annotations["markdown:basename"]; found {
return basenameOverride + ".md"
}
return strings.ReplaceAll(c.CommandPath(), " ", "_") + ".md"
}

View file

@ -1,8 +1,6 @@
package actions
import (
"fmt"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
@ -14,11 +12,8 @@ func NewCmdActions(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "actions",
Short: "Learn about working with GitHub actions",
Short: "Learn about working with GitHub Actions",
Long: actionsExplainer(cs),
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintln(f.IOStreams.Out, actionsExplainer(cs))
},
Annotations: map[string]string{
"IsActions": "true",
},

View file

@ -12,6 +12,7 @@ import (
"path/filepath"
"strings"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/internal/codespaces"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/liveshare"
@ -156,36 +157,36 @@ func newCpCmd(app *App) *cobra.Command {
var opts cpOptions
cpCmd := &cobra.Command{
Use: "cp [-e] [-r] srcs... dest",
Use: "cp [-e] [-r] <sources>... <dest>",
Short: "Copy files between local and remote file systems",
Long: `
The cp command copies files between the local and remote file systems.
Long: heredoc.Docf(`
The cp command copies files between the local and remote file systems.
As with the UNIX cp command, the first argument specifies the source and the last
specifies the destination; additional sources may be specified after the first,
if the destination is a directory.
As with the UNIX %[1]scp%[1]s command, the first argument specifies the source and the last
specifies the destination; additional sources may be specified after the first,
if the destination is a directory.
The -r (recursive) flag is required if any source is a directory.
The %[1]s--recursive%[1]s flag is required if any source is a directory.
A 'remote:' prefix on any file name argument indicates that it refers to
the file system of the remote (Codespace) machine. It is resolved relative
to the home directory of the remote user.
A "remote:" prefix on any file name argument indicates that it refers to
the file system of the remote (Codespace) machine. It is resolved relative
to the home directory of the remote user.
By default, remote file names are interpreted literally. With the -e flag,
each such argument is treated in the manner of scp, as a Bash expression to
be evaluated on the remote machine, subject to expansion of tildes, braces,
globs, environment variables, and backticks, as in these examples:
$ gh codespace cp -e README.md 'remote:/workspace/$RepositoryName/'
$ gh codespace cp -e 'remote:~/*.go' ./gofiles/
$ gh codespace cp -e 'remote:/workspace/myproj/go.{mod,sum}' ./gofiles/
For security, do not use the -e flag with arguments provided by untrusted
users; see https://lwn.net/Articles/835962/ for discussion.
`,
By default, remote file names are interpreted literally. With the %[1]s--expand%[1]s flag,
each such argument is treated in the manner of %[1]sscp%[1]s, as a Bash expression to
be evaluated on the remote machine, subject to expansion of tildes, braces, globs,
environment variables, and backticks. For security, do not use this flag with arguments
provided by untrusted users; see <https://lwn.net/Articles/835962/> for discussion.
`, "`"),
Example: heredoc.Doc(`
$ gh codespace cp -e README.md 'remote:/workspace/$RepositoryName/'
$ gh codespace cp -e 'remote:~/*.go' ./gofiles/
$ gh codespace cp -e 'remote:/workspace/myproj/go.{mod,sum}' ./gofiles/
`),
RunE: func(cmd *cobra.Command, args []string) error {
return app.Copy(cmd.Context(), args, opts)
},
DisableFlagsInUseLine: true,
}
// We don't expose all sshOptions.

View file

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

View file

@ -16,7 +16,10 @@ func NewCmdSecret(f *cmdutil.Factory) *cobra.Command {
Long: heredoc.Doc(`
Secrets can be set at the repository, environment, or organization level for use in
GitHub Actions. Run "gh help secret set" to learn how to get started.
`),
`),
Annotations: map[string]string{
"IsActions": "true",
},
}
cmdutil.EnableRepoOverride(cmd, f)