Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions cli/cmd/yapi/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
"yapi.run/cli/internal/docs"
)

// TODO: Consolidate cobra-generated command docs (--help) with manual topic docs
// in internal/docs/topics/ so there's a single source of truth for all documentation.
func docsE(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return printTopicIndex()
}
return printTopic(args[0])
}

func printTopicIndex() error {
fmt.Println("Available documentation topics:")
fmt.Println()
for _, t := range docs.List() {
fmt.Printf(" yapi docs %-15s %s\n", t.Name, t.Summary)
}
fmt.Println()
fmt.Println("Run 'yapi docs <topic>' to read a topic.")
return nil
}

func printTopic(name string) error {
content, err := docs.Get(name)
if err != nil {
// Try fuzzy suggestion
if suggestion := docs.Suggest(name); suggestion != "" {
fmt.Fprintf(os.Stderr, "Unknown topic %q. Did you mean %q?\n\n", name, suggestion)
} else {
fmt.Fprintf(os.Stderr, "Unknown topic %q.\n\n", name)
}
fmt.Fprintf(os.Stderr, "Available topics: %s\n", strings.Join(docs.TopicNames(), ", "))
return fmt.Errorf("unknown topic %q", name)
}
rendered, err := docs.Render(content)
if err != nil {
return fmt.Errorf("rendering docs: %w", err)
}
fmt.Print(rendered)
return nil
}
3 changes: 2 additions & 1 deletion cli/cmd/yapi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func main() {
About: aboutE,
Import: importE,
Send: app.sendE,
Docs: docsE,
}

rootCmd := commands.BuildRoot(cfg, handlers)
Expand All @@ -111,7 +112,7 @@ func main() {
rootCmd.PersistentPostRun = func(cmd *cobra.Command, args []string) {
// Log command to history (skip meta commands)
switch cmd.Name() {
case "history", "version", "lsp", "help", "yapi", "about":
case "history", "version", "lsp", "help", "yapi", "about", "docs":
return
}
logHistoryCmd(reconstructCommand(cmd, args))
Expand Down
12 changes: 10 additions & 2 deletions cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/alecthomas/chroma/v2 v2.20.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/fullstorydev/grpcurl v1.9.3
github.com/go-git/go-git/v5 v5.16.4
github.com/golang/protobuf v1.5.4
Expand All @@ -32,10 +32,13 @@ require (
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/glamour v0.10.0 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect
Expand All @@ -50,6 +53,7 @@ require (
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand All @@ -59,8 +63,10 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
Expand All @@ -77,6 +83,8 @@ require (
github.com/tliron/go-kutil v0.4.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.38.0 // indirect
Expand Down
23 changes: 23 additions & 0 deletions cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
Expand All @@ -29,12 +31,20 @@ github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlv
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
Expand Down Expand Up @@ -87,6 +97,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
Expand Down Expand Up @@ -125,12 +137,17 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
Expand All @@ -146,6 +163,7 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
Expand Down Expand Up @@ -188,6 +206,11 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
Expand Down
14 changes: 13 additions & 1 deletion cli/internal/cli/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Handlers struct {
About func(cmd *cobra.Command, args []string) error
Import func(cmd *cobra.Command, args []string) error
Send func(cmd *cobra.Command, args []string) error
Docs func(cmd *cobra.Command, args []string) error
}

// BuildRoot builds the root command tree with optional handlers.
Expand All @@ -42,6 +43,7 @@ func BuildRoot(cfg *Config, handlers *Handlers) *cobra.Command {
rootCmd := &cobra.Command{
Use: "yapi",
Short: "yapi is a unified API client for HTTP, gRPC, and TCP",
Long: "yapi is a unified API client for HTTP, gRPC, GraphQL, and TCP.\n\nRun 'yapi docs' to browse topic-based documentation.",
SilenceUsage: true,
SilenceErrors: true,
Run: func(cmd *cobra.Command, args []string) {},
Expand Down Expand Up @@ -70,6 +72,7 @@ var cmdManifest = []CommandSpec{
{
Use: "run [file]",
Short: "Run a request defined in a yapi config file (reads from stdin if no file specified)",
Long: "Run a request defined in a yapi config file (reads from stdin if no file specified).\n\nRelated: yapi docs assert, yapi docs chain, yapi docs variables",
Args: cobra.MaximumNArgs(1),
Flags: []FlagSpec{
{Name: "env", Shorthand: "e", Type: "string", Default: "", Usage: "Target environment from yapi.config.yml"},
Expand Down Expand Up @@ -125,6 +128,7 @@ var cmdManifest = []CommandSpec{
{
Use: "test [directory]",
Short: "Run all *.test.yapi, *.test.yapi.yml, *.test.yapi.yaml files in the current directory or specified directory",
Long: "Run all *.test.yapi, *.test.yapi.yml, *.test.yapi.yaml files in the current directory or specified directory.\n\nRelated: yapi docs testing, yapi docs assert",
Args: cobra.MaximumNArgs(1),
Flags: []FlagSpec{
{Name: "all", Shorthand: "a", Type: "bool", Default: false, Usage: "Run all *.yapi, *.yapi.yml, *.yapi.yaml files (not just test files)"},
Expand Down Expand Up @@ -168,7 +172,7 @@ var cmdManifest = []CommandSpec{
{
Use: "send <url> [body]",
Short: "Send a quick request without a config file",
Long: "Send a one-off HTTP or TCP request directly from the command line.\nThe transport is auto-detected from the URL scheme (tcp://, grpc://, or HTTP by default).\n\nExamples:\n yapi send https://httpbin.org/get\n yapi send -X POST https://httpbin.org/post '{\"hello\":\"world\"}'\n yapi send tcp://localhost:9877 '{\"type\":\"health\",\"params\":{}}'",
Long: "Send a one-off HTTP or TCP request directly from the command line.\nThe transport is auto-detected from the URL scheme (tcp://, grpc://, or HTTP by default).\n\nExamples:\n yapi send https://httpbin.org/get\n yapi send -X POST https://httpbin.org/post '{\"hello\":\"world\"}'\n yapi send tcp://localhost:9877 '{\"type\":\"health\",\"params\":{}}'\n\nRelated: yapi docs send, yapi docs protocols",
Args: cobra.RangeArgs(1, 2),
Flags: []FlagSpec{
{Name: "method", Shorthand: "X", Type: "string", Default: "", Usage: "HTTP method (default: GET, or POST if body is provided)"},
Expand All @@ -178,6 +182,12 @@ var cmdManifest = []CommandSpec{
{Name: "jq", Type: "string", Default: "", Usage: "JQ filter to apply to the response"},
},
},
{
Use: "docs [topic]",
Short: "Browse topic-based documentation",
Long: "Browse topic-based documentation for yapi features.\nRun 'yapi docs' to see all topics, or 'yapi docs <topic>' to read one.",
Args: cobra.MaximumNArgs(1),
},
{
Use: "import [file]",
Short: "Import an external collection (Postman) to yapi format",
Expand Down Expand Up @@ -231,6 +241,8 @@ func getHandler(h *Handlers, use string) func(*cobra.Command, []string) error {
return h.About
case "send":
return h.Send
case "docs":
return h.Docs
case "import":
return h.Import
default:
Expand Down
100 changes: 100 additions & 0 deletions cli/internal/docs/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Package docs provides embedded topic-based documentation for yapi.
package docs

import (
"embed"
"fmt"
"sort"
"strings"

"github.com/charmbracelet/glamour"
)

//go:embed topics
var topicsFS embed.FS

// Topic represents a documentation topic.
type Topic struct {
Name string
Summary string
}

// topics defines all available documentation topics.
var topics = []Topic{
{Name: "assert", Summary: "Assertions on status, body, and headers"},
{Name: "chain", Summary: "Multi-step request chaining"},
{Name: "config", Summary: "YAML config field reference"},
{Name: "environments", Summary: "Multi-environment configuration"},
{Name: "jq", Summary: "JQ filtering and expressions"},
{Name: "polling", Summary: "Polling with wait_for"},
{Name: "protocols", Summary: "HTTP, gRPC, GraphQL, TCP"},
{Name: "send", Summary: "Quick one-off requests"},
{Name: "testing", Summary: "Test runner and CI/CD"},
{Name: "variables", Summary: "Variable interpolation and resolution"},
}

// Get returns the content of a topic by name.
func Get(name string) (string, error) {
name = strings.ToLower(strings.TrimSpace(name))
data, err := topicsFS.ReadFile("topics/" + name + ".md")
if err != nil {
return "", fmt.Errorf("unknown topic %q. Run 'yapi docs' to see available topics", name)
}
return string(data), nil
}

// List returns all available topics.
func List() []Topic {
return topics
}

// Suggest returns the closest topic name if one is similar enough, or empty string.
func Suggest(input string) string {
input = strings.ToLower(strings.TrimSpace(input))
// Check prefix matches
var matches []string
for _, t := range topics {
if strings.HasPrefix(t.Name, input) {
matches = append(matches, t.Name)
}
}
if len(matches) == 1 {
return matches[0]
}
// Check substring matches
matches = matches[:0]
for _, t := range topics {
if strings.Contains(t.Name, input) || strings.Contains(input, t.Name) {
matches = append(matches, t.Name)
}
}
if len(matches) == 1 {
return matches[0]
}
// Check summary substring
matches = matches[:0]
for _, t := range topics {
if strings.Contains(strings.ToLower(t.Summary), input) {
matches = append(matches, t.Name)
}
}
if len(matches) == 1 {
return matches[0]
}
return ""
}

// TopicNames returns a sorted list of topic names.
func TopicNames() []string {
names := make([]string, len(topics))
for i, t := range topics {
names[i] = t.Name
}
sort.Strings(names)
return names
}

// Render renders markdown content for terminal display using glamour.
func Render(markdown string) (string, error) {
return glamour.Render(markdown, "auto")
}
Loading
Loading