Add "reference" help topic (#2223)

* Add "reference" help topic

* Only print reference as a help topic

* fix for color fns, slightly generalize

* WIP for switching to markdown

* escape gt/lt

* minor

* higher wrap point

* detect terminal theme

* futz with angle brackets once more

* minor cleanup

* prepend parent commands

* rename help topic fns and add test

* Simplify reference help generation

- the `<...>` characters from command usage line are now preserved by enclosing the entire usage synopsis in a code span
- hard breaks in flag usage lines are preserved by enclosing flag usage in a code block
- TTY detection and Markdown rendering are now delayed until the user explicitly requests `gh help reference`
- `gh help reference` output is now pager-enabled

Co-authored-by: vilmibm <vilmibm@github.com>
Co-authored-by: vilmibm <vilmibm@neongrid.space>
Co-authored-by: Mislav Marohnić <mislav@github.com>
This commit is contained in:
Nikola Ristić 2020-11-18 19:31:36 +01:00 committed by GitHub
parent ee4827483d
commit e87b5bcaff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 4 deletions

View file

@ -0,0 +1,69 @@
package root
import (
"bytes"
"fmt"
"io"
"strings"
"github.com/cli/cli/pkg/iostreams"
"github.com/cli/cli/pkg/markdown"
"github.com/spf13/cobra"
)
func referenceHelpFn(io *iostreams.IOStreams) func(*cobra.Command, []string) {
return func(cmd *cobra.Command, args []string) {
wrapWidth := 0
style := "notty"
if io.IsStdoutTTY() {
wrapWidth = io.TerminalWidth()
style = markdown.GetStyle(io.DetectTerminalTheme())
}
md, err := markdown.RenderWrap(cmd.Long, style, wrapWidth)
if err != nil {
fmt.Fprintln(io.ErrOut, err)
return
}
if !io.IsStdoutTTY() {
fmt.Fprint(io.Out, dedent(md))
return
}
_ = io.StartPager()
defer io.StopPager()
fmt.Fprint(io.Out, md)
}
}
func referenceLong(cmd *cobra.Command) string {
buf := bytes.NewBufferString("# gh reference\n\n")
for _, c := range cmd.Commands() {
if c.Hidden {
continue
}
cmdRef(buf, c, 2)
}
return buf.String()
}
func cmdRef(w io.Writer, cmd *cobra.Command, depth int) {
// Name + Description
fmt.Fprintf(w, "%s `%s`\n\n", strings.Repeat("#", depth), cmd.UseLine())
fmt.Fprintf(w, "%s\n\n", cmd.Short)
// Flags
// TODO: fold in InheritedFlags/PersistentFlags, but omit `--help` due to repetitiveness
if flagUsages := cmd.Flags().FlagUsages(); flagUsages != "" {
fmt.Fprintf(w, "```\n%s````\n\n", dedent(flagUsages))
}
// Subcommands
for _, c := range cmd.Commands() {
if c.Hidden {
continue
}
cmdRef(w, c, depth+1)
}
}

View file

@ -47,6 +47,9 @@ var HelpTopics = map[string]map[string]string{
error if a newer version was found.
`),
},
"reference": {
"short": "A comprehensive reference of all gh commands",
},
}
func NewHelpTopic(topic string) *cobra.Command {
@ -55,8 +58,6 @@ func NewHelpTopic(topic string) *cobra.Command {
Short: HelpTopics[topic]["short"],
Long: HelpTopics[topic]["long"],
Hidden: true,
Args: cobra.NoArgs,
Run: helpTopicHelpFunc,
Annotations: map[string]string{
"markdown:generate": "true",
"markdown:basename": "gh_help_" + topic,

View file

@ -34,7 +34,7 @@ func TestNewHelpTopic(t *testing.T) {
topic: "environment",
args: []string{"invalid"},
flags: []string{},
wantsErr: true,
wantsErr: false,
},
{
name: "more than zero flags",
@ -48,7 +48,7 @@ func TestNewHelpTopic(t *testing.T) {
topic: "environment",
args: []string{"help"},
flags: []string{},
wantsErr: true,
wantsErr: false,
},
{
name: "help flag",

View file

@ -92,9 +92,14 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
// Help topics
cmd.AddCommand(NewHelpTopic("environment"))
referenceCmd := NewHelpTopic("reference")
referenceCmd.SetHelpFunc(referenceHelpFn(f.IOStreams))
cmd.AddCommand(referenceCmd)
cmdutil.DisableAuthCheck(cmd)
// this needs to appear last:
referenceCmd.Long = referenceLong(cmd)
return cmd
}

View file

@ -24,6 +24,23 @@ func Render(text, style string, baseURL string) (string, error) {
return tr.Render(text)
}
func RenderWrap(text, style string, wrap int) (string, error) {
// Glamour rendering preserves carriage return characters in code blocks, but
// we need to ensure that no such characters are present in the output.
text = strings.ReplaceAll(text, "\r\n", "\n")
tr, err := glamour.NewTermRenderer(
glamour.WithStylePath(style),
// glamour.WithBaseURL(""), // TODO: make configurable
glamour.WithWordWrap(wrap),
)
if err != nil {
return "", err
}
return tr.Render(text)
}
func GetStyle(defaultStyle string) string {
style := fromEnv()
if style != "" && style != "auto" {