From e87b5bcaff227aad0118532d74decc90e8528723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Risti=C4=87?= Date: Wed, 18 Nov 2020 19:31:36 +0100 Subject: [PATCH] Add "reference" help topic (#2223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: vilmibm Co-authored-by: Mislav Marohnić --- pkg/cmd/root/help_reference.go | 69 +++++++++++++++++++++++++++++++++ pkg/cmd/root/help_topic.go | 5 ++- pkg/cmd/root/help_topic_test.go | 4 +- pkg/cmd/root/root.go | 5 +++ pkg/markdown/markdown.go | 17 ++++++++ 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 pkg/cmd/root/help_reference.go diff --git a/pkg/cmd/root/help_reference.go b/pkg/cmd/root/help_reference.go new file mode 100644 index 000000000..ecc7ec856 --- /dev/null +++ b/pkg/cmd/root/help_reference.go @@ -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) + } +} diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go index c44bfca00..99055034b 100644 --- a/pkg/cmd/root/help_topic.go +++ b/pkg/cmd/root/help_topic.go @@ -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, diff --git a/pkg/cmd/root/help_topic_test.go b/pkg/cmd/root/help_topic_test.go index f194541ac..3aba4bdf3 100644 --- a/pkg/cmd/root/help_topic_test.go +++ b/pkg/cmd/root/help_topic_test.go @@ -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", diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index 906223d15..93cb4431d 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -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 } diff --git a/pkg/markdown/markdown.go b/pkg/markdown/markdown.go index 844e06811..505c7d401 100644 --- a/pkg/markdown/markdown.go +++ b/pkg/markdown/markdown.go @@ -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" {