feat(docs): rework CLI docs (#6312)

This commit is contained in:
Ammar Bandukwala
2023-02-23 01:53:21 +00:00
committed by GitHub
parent 43e8ba0811
commit f6a8c360e5
71 changed files with 2342 additions and 2087 deletions
+49
View File
@@ -0,0 +1,49 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# {{ .Name }}
{{ if .Cmd.Long }}
{{ .Cmd.Long }}
{{ else }}
{{ .Cmd.Short }}
{{ end }}
{{- if .Cmd.Runnable}}
## Usage
```console
{{.Cmd.UseLine}}
```
{{end}}
{{- if .Cmd.HasExample}}
## Examples
```console
{{.Cmd.Example}}
```
{{end}}
{{- range $index, $cmd := .VisibleSubcommands }}
{{- if eq $index 0 }}
## Subcommands
| Name | Purpose |
| ---- | ----- |
{{- end }}
| [{{ $cmd.Name | wrapCode }}](./{{if $.AtRoot}}cli/{{end}}{{commandURI $cmd}}) | {{ $cmd.Short }} |
{{- end}}
{{ "" }}
{{- range $index, $flag := .Flags }}
{{- if eq $index 0 }}
## Flags
{{- end }}
### --{{ $flag.Name }}{{ if $flag.Shorthand}}, -{{ $flag.Shorthand }}{{end}}
{{ $flag.Usage | stripEnv | newLinesToBr }}
<br/>
| | |
| --- | --- |
{{- with $flag.Usage | parseEnv }}
| Consumes | {{ . | wrapCode }} |
{{- end }}
{{- with $flag.DefValue }}
| Default | {{" "}} {{- . | wrapCode }} |
{{ "" }}
{{ end }}
{{ "" }}
{{- end}}
+173
View File
@@ -0,0 +1,173 @@
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/acarl005/stripansi"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
_ "embed"
"github.com/coder/coder/buildinfo"
"github.com/coder/flog"
)
//go:embed command.tpl
var commandTemplateRaw string
var commandTemplate *template.Template
var envRegex = regexp.MustCompile(`Consumes (\$\w+).?$`)
func parseEnv(flagUsage string) string {
flagUsage = stripansi.Strip(flagUsage)
ss := envRegex.FindStringSubmatch(flagUsage)
if len(ss) == 0 {
return ""
}
return ss[len(ss)-1]
}
func stripEnv(flagUsage string) string {
flagUsage = stripansi.Strip(flagUsage)
ss := envRegex.FindStringSubmatch(flagUsage)
if len(ss) == 0 {
return flagUsage
}
return strings.TrimSpace(strings.ReplaceAll(flagUsage, ss[0], ""))
}
func init() {
commandTemplate = template.Must(
template.New("command.tpl").Funcs(template.FuncMap{
"newLinesToBr": func(s string) string {
return strings.ReplaceAll(s, "\n", "<br/>")
},
"wrapCode": func(s string) string {
return fmt.Sprintf("<code>%s</code>", s)
},
"parseEnv": parseEnv,
"stripEnv": stripEnv,
"commandURI": func(cmd *cobra.Command) string {
return strings.TrimSuffix(
fmtDocFilename(cmd),
".md",
)
},
},
).Parse(strings.TrimSpace(commandTemplateRaw)),
)
}
func writeCommand(w io.Writer, cmd *cobra.Command) error {
var (
flags []*pflag.Flag
inheritedFlags []*pflag.Flag
)
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Hidden {
return
}
flags = append(flags, f)
})
cmd.InheritedFlags().VisitAll(func(f *pflag.Flag) {
if f.Hidden {
return
}
inheritedFlags = append(inheritedFlags, f)
})
var b strings.Builder
err := commandTemplate.Execute(&b, map[string]any{
"Name": fullCommandName(cmd),
"Cmd": cmd,
"Flags": flags,
"InheritedFlags": inheritedFlags,
"AtRoot": cmd.Parent() == nil,
"VisibleSubcommands": func() []*cobra.Command {
var scs []*cobra.Command
for _, sub := range cmd.Commands() {
if sub.Hidden {
continue
}
scs = append(scs, sub)
}
return scs
}(),
})
if err != nil {
return err
}
content := stripansi.Strip(b.String())
// Remove the version and its right space, since during this script running
// there is no build info available
content = strings.ReplaceAll(content, buildinfo.Version()+" ", "")
// Remove references to the current working directory
cwd, err := os.Getwd()
if err != nil {
return err
}
content = strings.ReplaceAll(content, cwd, ".")
_, err = w.Write([]byte(content))
return err
}
func fullCommandName(cmd *cobra.Command) string {
name := cmd.Name()
if cmd.Parent() != nil {
return fullCommandName(cmd.Parent()) + " " + name
}
return name
}
func fmtDocFilename(cmd *cobra.Command) string {
fullName := fullCommandName(cmd)
if fullName == "coder" {
// Special case for index.
return "../cli.md"
}
name := strings.ReplaceAll(fullName, " ", "_")
return fmt.Sprintf("%s.md", name)
}
func generateDocsTree(rootCmd *cobra.Command, basePath string) error {
if rootCmd.Hidden {
return nil
}
// Write out root.
fi, err := os.OpenFile(
filepath.Join(basePath, fmtDocFilename(rootCmd)),
os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644,
)
if err != nil {
return err
}
defer fi.Close()
err = writeCommand(fi, rootCmd)
if err != nil {
return err
}
flog.Info("Generated docs for %q at %v", fullCommandName(rootCmd), fi.Name())
// Recursively generate docs.
for _, subcommand := range rootCmd.Commands() {
err = generateDocsTree(subcommand, basePath)
if err != nil {
return err
}
}
return nil
}
+40
View File
@@ -0,0 +1,40 @@
package main
import (
_ "embed"
"testing"
)
func Test_parseEnv(t *testing.T) {
t.Parallel()
type args struct {
flagUsage string
}
tests := []struct {
name string
args args
want string
}{
{
"no env",
args{"Perform a trial run with no changes made, showing a diff at the end."},
"",
},
{
"env",
args{`Specifies the path to an SSH config.
Consumes $CODER_SSH_CONFIG_FILE`},
"$CODER_SSH_CONFIG_FILE",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := parseEnv(tt.args.flagUsage); got != tt.want {
t.Errorf("parseEnv() = %v, want %v", got, tt.want)
}
})
}
}
+1 -54
View File
@@ -4,15 +4,9 @@ import (
"encoding/json"
"log"
"os"
"os/user"
"path"
"regexp"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
"github.com/coder/coder/buildinfo"
"github.com/coder/coder/cli"
)
@@ -48,11 +42,6 @@ func main() {
}
}
u, err := user.Current()
if err != nil {
log.Fatal("Error on getting the current user: ", err)
}
// Get the cmd CLI
cmd := cli.Root(cli.AGPL())
@@ -64,18 +53,8 @@ func main() {
markdownDocsDir := path.Join(basePath, "docs/cli")
manifestFilepath := path.Join(basePath, "docs/manifest.json")
// Disables the "Auto generated by spf13/cobra" tag
var disableAutoGenTag func(cmd *cobra.Command)
disableAutoGenTag = func(cmd *cobra.Command) {
for _, c := range cmd.Commands() {
disableAutoGenTag(c)
}
cmd.DisableAutoGenTag = true
}
disableAutoGenTag(cmd)
// Generate markdown
err = doc.GenMarkdownTree(cmd, markdownDocsDir)
err := generateDocsTree(cmd, markdownDocsDir)
if err != nil {
log.Fatal("Error on generating CLI markdown docs: ", err)
}
@@ -96,38 +75,6 @@ func main() {
Title: title,
Path: "./cli/" + file.Name(),
})
filepath := path.Join(markdownDocsDir, file.Name())
openFile, err := os.ReadFile(filepath)
if err != nil {
log.Fatal("Error on open file at ", filepath, ": ", err)
}
content := string(openFile)
// Remove non printable strings from generated markdown
// https://github.com/spf13/cobra/issues/1878
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
ansiRegex := regexp.MustCompile(ansi)
content = ansiRegex.ReplaceAllString(content, "")
// Remove the version and its right space, since during this script running
// there is no build info available
content = strings.ReplaceAll(content, buildinfo.Version()+" ", "")
// Remove references to the current working directory
dir, err := os.Getwd()
if err != nil {
log.Fatal("Error on getting the current directory:", err)
}
content = strings.ReplaceAll(content, dir, "<current-directory>")
// Remove all absolute home paths.
content = strings.ReplaceAll(content, u.HomeDir, "~")
err = os.WriteFile(filepath, []byte(content), 0o644) // #nosec
if err != nil {
log.Fatal("Error on save file at ", filepath, ": ", err)
}
}
// Read manifest