chore: vendor docgen from sg/sg by burmudar · Pull Request #1303 · sourcegraph/src-cli · GitHub
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
19 changes: 19 additions & 0 deletions lib/docgen/default.go
193 changes: 193 additions & 0 deletions lib/docgen/markdown.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package docgen

import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"text/template"

"github.com/urfave/cli/v3"
)

// Markdown renders a Markdown reference for the app.
//
// It is adapted from https://sourcegraph.com/github.com/urfave/cli-docs/-/blob/docs.go?L16
func Markdown(root *cli.Command) (string, error) {
var w bytes.Buffer
if err := writeDocTemplate(root, &w); err != nil {
return "", err
}
return w.String(), nil
}

type cliTemplate struct {
App *cli.Command
Commands []string
GlobalArgs []string
}

func writeDocTemplate(root *cli.Command, w io.Writer) error {
const name = "cli"
t, err := template.New(name).Parse(markdownDocTemplate)
if err != nil {
return err
}
return t.ExecuteTemplate(w, name, &cliTemplate{
App: root,
Commands: prepareCommands(root.Name, root.Commands, 0),
GlobalArgs: prepareArgsWithValues(root.VisibleFlags()),
})
}

func prepareCommands(lineage string, commands []*cli.Command, level int) []string {
var coms []string
for _, command := range commands {
if command.Hidden {
continue
}

var commandDoc strings.Builder
commandDoc.WriteString(strings.Repeat("#", level+2))
commandDoc.WriteString(" ")
commandDoc.WriteString(fmt.Sprintf("%s %s", lineage, command.Name))
commandDoc.WriteString("\n\n")
commandDoc.WriteString(prepareUsage(command))
commandDoc.WriteString("\n\n")

if len(command.Description) > 0 {
commandDoc.WriteString(fmt.Sprintf("%s\n\n", command.Description))
}

commandDoc.WriteString(prepareUsageText(command))

flags := prepareArgsWithValues(command.Flags)
if len(flags) > 0 {
commandDoc.WriteString("\nFlags:\n\n")
for _, f := range flags {
commandDoc.WriteString("* " + f)
}
}

coms = append(coms, commandDoc.String())

// recursevly iterate subcommands
if len(command.Commands) > 0 {
coms = append(
coms,
prepareCommands(lineage+" "+command.Name, command.Commands, level+1)...,
)
}
}

return coms
}

func prepareArgsWithValues(flags []cli.Flag) []string {
return prepareFlags(flags, ", ", "`", "`", `"<value>"`, true)
}

func prepareFlags(
flags []cli.Flag,
sep, opener, closer, value string,
addDetails bool,
) []string {
args := []string{}
for _, f := range flags {
flag, ok := f.(cli.DocGenerationFlag)
if !ok {
continue
}
modifiedArg := opener

for _, s := range f.Names() {
trimmed := strings.TrimSpace(s)
if len(modifiedArg) > len(opener) {
modifiedArg += sep
}
if len(trimmed) > 1 {
modifiedArg += fmt.Sprintf("--%s", trimmed)
} else {
modifiedArg += fmt.Sprintf("-%s", trimmed)
}
}

if flag.TakesValue() {
modifiedArg += fmt.Sprintf("=%s", value)
}

modifiedArg += closer

if addDetails {
modifiedArg += flagDetails(flag)
}

args = append(args, modifiedArg+"\n")

}
sort.Strings(args)
return args
}

// flagDetails returns a string containing the flags metadata
func flagDetails(flag cli.DocGenerationFlag) string {
description := flag.GetUsage()
value := flag.GetValue()
if value != "" {
description += " (default: " + value + ")"
}
return ": " + description
}

func prepareUsageText(command *cli.Command) string {
if command.UsageText == "" {
if strings.TrimSpace(command.ArgsUsage) != "" {
return fmt.Sprintf("Arguments: `%s`\n", command.ArgsUsage)
}
return ""
}

// Write all usage examples as a big shell code block
var usageText strings.Builder
usageText.WriteString("```sh")
for line := range strings.SplitSeq(strings.TrimSpace(command.UsageText), "\n") {
usageText.WriteByte('\n')

line = strings.TrimSpace(line)
if strings.HasPrefix(line, "# ") {
usageText.WriteString(line)
} else if len(line) > 0 {
usageText.WriteString(fmt.Sprintf("$ %s", line))
}
}
usageText.WriteString("\n```\n")

return usageText.String()
}

func prepareUsage(command *cli.Command) string {
if command.Usage == "" {
return ""
}

return command.Usage + "."
}

var markdownDocTemplate = `# {{ .App.Name }} reference

{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
{{ if .App.Description }}
{{ .App.Description }}
{{ end }}
` + "```sh" + `{{ if .App.UsageText }}
{{ .App.UsageText }}
{{ else }}
{{ .App.Name }} [GLOBAL FLAGS] command [COMMAND FLAGS] [ARGUMENTS...]
{{ end }}` + "```" + `
{{ if .GlobalArgs }}
Global flags:

{{ range $v := .GlobalArgs }}* {{ $v }}{{ end }}{{ end }}{{ if .Commands }}
{{ range $v := .Commands }}
{{ $v }}{{ end }}{{ end }}`
4 changes: 4 additions & 0 deletions lib/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/sourcegraph/conc v0.3.0
github.com/sourcegraph/go-diff v0.7.0
github.com/sourcegraph/log v0.0.0-20250923023806-517b6960b55b
github.com/urfave/cli/v3 v3.8.0
github.com/xeipuuv/gojsonschema v1.2.0
github.com/xlab/treeprint v1.2.0
go.opentelemetry.io/otel v1.38.0
Expand Down Expand Up @@ -78,3 +79,6 @@ require (
golang.org/x/tools v0.37.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

// See: https://github.com/ghodss/yaml/pull/65
replace github.com/ghodss/yaml => github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152
7 changes: 5 additions & 2 deletions lib/go.sum
Loading