From c67582c76b17259a9bb270b9ca95c6bfc1582fc8 Mon Sep 17 00:00:00 2001 From: Virgil Date: Tue, 31 Mar 2026 19:31:23 +0000 Subject: [PATCH] fix(help): make help command AX-friendly Co-Authored-By: Virgil --- cmd/core/go.sum | 4 ++ cmd/core/help/cmd.go | 76 ++++++++++++++++++++++---------------- cmd/core/help/cmd_test.go | 78 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 32 deletions(-) create mode 100644 cmd/core/help/cmd_test.go diff --git a/cmd/core/go.sum b/cmd/core/go.sum index 141f923..0695a06 100644 --- a/cmd/core/go.sum +++ b/cmd/core/go.sum @@ -160,9 +160,13 @@ github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3 github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/oasdiff/kin-openapi v0.136.1 h1:x1G9doDyPcagCNXDcMK5dt5yAmIgsSCiK7F5gPUiQdM= +github.com/oasdiff/kin-openapi v0.136.1/go.mod h1:BMeaLn+GmFJKtHJ31JrgXFt91eZi/q+Og4tr7sq0BzI= github.com/oasdiff/oasdiff v1.12.3 h1:eUzJ/AiyyCY1KwUZPv7fosgDyETacIZbFesJrRz+QdY= +github.com/oasdiff/oasdiff v1.12.3/go.mod h1:ApEJGlkuRdrcBgTE4ioicwIM7nzkxPqLPPvcB5AytQ0= github.com/oasdiff/yaml v0.0.1 h1:dPrn0F2PJ7HdzHPndJkArvB2Fw0cwgFdVUKCEkoFuds= +github.com/oasdiff/yaml v0.0.1/go.mod h1:r8bgVgpWT5iIN/AgP0GljFvB6CicK+yL1nIAbm+8/QQ= github.com/oasdiff/yaml3 v0.0.1 h1:kReOSraQLTxuuGNX9aNeJ7tcsvUB2MS+iupdUrWe4Z0= +github.com/oasdiff/yaml3 v0.0.1/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= diff --git a/cmd/core/help/cmd.go b/cmd/core/help/cmd.go index 67f2704..50385bf 100644 --- a/cmd/core/help/cmd.go +++ b/cmd/core/help/cmd.go @@ -1,62 +1,74 @@ package help import ( - "fmt" + "strings" "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-help" + gohelp "forge.lthn.ai/core/go-help" + "github.com/spf13/cobra" ) func AddHelpCommands(root *cli.Command) { - var searchFlag string + var searchQuery string helpCmd := &cli.Command{ Use: "help [topic]", Short: "Display help documentation", - Run: func(cmd *cli.Command, args []string) { - catalog := help.DefaultCatalog() + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cli.Command, args []string) error { + catalog := gohelp.DefaultCatalog() - if searchFlag != "" { - results := catalog.Search(searchFlag) - if len(results) == 0 { - fmt.Println("No topics found.") - return - } - fmt.Println("Search Results:") - for _, res := range results { - fmt.Printf(" %s - %s\n", res.Topic.ID, res.Topic.Title) - } - return + if searchQuery != "" { + return renderSearchResults(catalog.Search(searchQuery), searchQuery) } if len(args) == 0 { - topics := catalog.List() - fmt.Println("Available Help Topics:") - for _, t := range topics { - fmt.Printf(" %s - %s\n", t.ID, t.Title) - } - return + return renderTopicList(catalog.List()) } topic, err := catalog.Get(args[0]) if err != nil { - fmt.Printf("Error: %v\n", err) - return + return cli.Err("help topic %q not found", args[0]) } renderTopic(topic) + return nil }, } - helpCmd.Flags().StringVarP(&searchFlag, "search", "s", "", "Search help topics") + helpCmd.Flags().StringVarP(&searchQuery, "search", "s", "", "Search help topics") root.AddCommand(helpCmd) } -func renderTopic(t *help.Topic) { - // Simple ANSI rendering for now - // Use explicit ANSI codes or just print - fmt.Printf("\n\033[1;34m%s\033[0m\n", t.Title) // Blue bold title - fmt.Println("----------------------------------------") - fmt.Println(t.Content) - fmt.Println() +func renderSearchResults(results []*gohelp.SearchResult, query string) error { + if len(results) == 0 { + return cli.Err("no help topics matched %q", query) + } + + cli.Section("Search Results") + for _, res := range results { + cli.Println(" %s - %s", res.Topic.ID, res.Topic.Title) + } + return nil +} + +func renderTopicList(topics []*gohelp.Topic) error { + if len(topics) == 0 { + return cli.Err("no help topics available") + } + + cli.Section("Available Help Topics") + for _, topic := range topics { + cli.Println(" %s - %s", topic.ID, topic.Title) + } + return nil +} + +func renderTopic(t *gohelp.Topic) { + cli.Blank() + cli.Println("%s", cli.TitleStyle.Render(t.Title)) + cli.Println("%s", strings.Repeat("-", len(t.Title))) + cli.Blank() + cli.Println("%s", t.Content) + cli.Blank() } diff --git a/cmd/core/help/cmd_test.go b/cmd/core/help/cmd_test.go new file mode 100644 index 0000000..a1859f1 --- /dev/null +++ b/cmd/core/help/cmd_test.go @@ -0,0 +1,78 @@ +package help + +import ( + "bytes" + "io" + "os" + "testing" + + "forge.lthn.ai/core/cli/pkg/cli" + gohelp "forge.lthn.ai/core/go-help" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func captureOutput(t *testing.T, fn func()) string { + t.Helper() + + oldOut := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + defer func() { + os.Stdout = oldOut + }() + + fn() + + require.NoError(t, w.Close()) + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + require.NoError(t, err) + return buf.String() +} + +func newHelpCommand(t *testing.T) *cli.Command { + t.Helper() + + root := &cli.Command{Use: "core"} + AddHelpCommands(root) + + cmd, _, err := root.Find([]string{"help"}) + require.NoError(t, err) + return cmd +} + +func TestAddHelpCommands_Good(t *testing.T) { + cmd := newHelpCommand(t) + + topics := gohelp.DefaultCatalog().List() + require.NotEmpty(t, topics) + + out := captureOutput(t, func() { + err := cmd.RunE(cmd, nil) + require.NoError(t, err) + }) + assert.Contains(t, out, "AVAILABLE HELP TOPICS") + assert.Contains(t, out, topics[0].ID) +} + +func TestAddHelpCommands_Bad(t *testing.T) { + t.Run("missing search results", func(t *testing.T) { + cmd := newHelpCommand(t) + require.NoError(t, cmd.Flags().Set("search", "zzzyyyxxx")) + + err := cmd.RunE(cmd, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "no help topics matched") + }) + + t.Run("missing topic", func(t *testing.T) { + cmd := newHelpCommand(t) + err := cmd.RunE(cmd, []string{"definitely-not-a-real-topic"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "help topic") + }) +} -- 2.45.3