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" {