Skip to content

Commit 100b8fb

Browse files
authored
Improved docs gen (#691)
## What was changed - Improved `Command` model for simpler docs rendering - Rendered inline options under each command, without cluttering up the right side nav. - Acorn rendering fix for JSON examples in `.mdx` - Add rendering of experimental options. ## Why? Nexus docs need to be created and want to use docs gen. ## Checklist 1. How was this tested: - `go run ./temporalcli/internal/cmd/gen-docs ` - verified via http://localhost:3000/ 2. Any docs updates needed? - will feed into overall docs cleanup --------- Signed-off-by: Phil Prasek <prasek@gmail.com>
1 parent 051ef29 commit 100b8fb

3 files changed

Lines changed: 134 additions & 40 deletions

File tree

temporalcli/commandsgen/code.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,6 @@ func (c *codeWriter) importIsatty() string { return c.importPkg("github.com/matt
104104

105105
func (c *Command) structName() string { return namify(c.FullName, true) + "Command" }
106106

107-
func (c *Command) isSubCommand(maybeParent *Command) bool {
108-
return len(c.NamePath) == len(maybeParent.NamePath)+1 && strings.HasPrefix(c.FullName, maybeParent.FullName+" ")
109-
}
110-
111107
func (o *OptionSets) writeCode(w *codeWriter) error {
112108
if o.Name == "" {
113109
return fmt.Errorf("missing option set name")

temporalcli/commandsgen/docs.go

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@ package commandsgen
33
import (
44
"bytes"
55
"fmt"
6+
"regexp"
67
"sort"
78
"strings"
89
)
910

10-
type DocsFile struct {
11-
FileName string
12-
}
13-
1411
func GenerateDocsFiles(commands Commands) (map[string][]byte, error) {
12+
1513
optionSetMap := make(map[string]OptionSets)
1614
for i, optionSet := range commands.OptionSets {
1715
optionSetMap[optionSet.Name] = commands.OptionSets[i]
1816
}
1917

20-
w := &docWriter{fileMap: make(map[string]*bytes.Buffer), optionSetMap: optionSetMap}
18+
w := &docWriter{
19+
fileMap: make(map[string]*bytes.Buffer),
20+
optionSetMap: optionSetMap,
21+
allCommands: commands.CommandList,
22+
}
2123

22-
// sort by parent command (activity, batch, etc)
24+
// sorted ascending by full name of command (activity complete, batch list, etc)
2325
for _, cmd := range commands.CommandList {
2426
if err := cmd.writeDoc(w); err != nil {
2527
return nil, fmt.Errorf("failed writing docs for command %s: %w", cmd.FullName, err)
@@ -35,33 +37,35 @@ func GenerateDocsFiles(commands Commands) (map[string][]byte, error) {
3537
}
3638

3739
type docWriter struct {
40+
allCommands []Command
3841
fileMap map[string]*bytes.Buffer
3942
optionSetMap map[string]OptionSets
4043
optionsStack [][]Option
4144
}
4245

4346
func (c *Command) writeDoc(w *docWriter) error {
44-
commandLength := len(strings.Split(c.FullName, " "))
4547
w.processOptions(c)
4648

4749
// If this is a root command, write a new file
48-
if commandLength == 2 {
50+
depth := c.depth()
51+
if depth == 1 {
4952
w.writeCommand(c)
50-
} else if commandLength > 2 {
53+
} else if depth > 1 {
5154
w.writeSubcommand(c)
5255
}
5356
return nil
5457
}
5558

5659
func (w *docWriter) writeCommand(c *Command) {
57-
fileName := strings.Split(c.FullName, " ")[1]
60+
fileName := c.fileName()
5861
w.fileMap[fileName] = &bytes.Buffer{}
5962
w.fileMap[fileName].WriteString("---\n")
6063
w.fileMap[fileName].WriteString("id: " + fileName + "\n")
6164
w.fileMap[fileName].WriteString("title: " + c.FullName + "\n")
6265
w.fileMap[fileName].WriteString("sidebar_label: " + c.FullName + "\n")
6366
w.fileMap[fileName].WriteString("description: " + c.Docs.DescriptionHeader + "\n")
6467
w.fileMap[fileName].WriteString("toc_max_heading_level: 4\n")
68+
6569
w.fileMap[fileName].WriteString("keywords:\n")
6670
for _, keyword := range c.Docs.Keywords {
6771
w.fileMap[fileName].WriteString(" - " + keyword + "\n")
@@ -75,27 +79,75 @@ func (w *docWriter) writeCommand(c *Command) {
7579
}
7680

7781
func (w *docWriter) writeSubcommand(c *Command) {
78-
fileName := strings.Split(c.FullName, " ")[1]
79-
subCommand := strings.Join(strings.Split(c.FullName, " ")[2:], "")
80-
w.fileMap[fileName].WriteString("## " + subCommand + "\n\n")
82+
fileName := c.fileName()
83+
prefix := strings.Repeat("#", c.depth())
84+
w.fileMap[fileName].WriteString(prefix + " " + c.leafName() + "\n\n")
8185
w.fileMap[fileName].WriteString(c.Description + "\n\n")
82-
w.fileMap[fileName].WriteString("Use the following options to change the behavior of this command.\n\n")
8386

84-
// gather options from command and all options aviailable from parent commands
85-
var allOptions = make([]Option, 0)
86-
for _, options := range w.optionsStack {
87-
allOptions = append(allOptions, options...)
87+
if w.isLeafCommand(c) {
88+
w.fileMap[fileName].WriteString("Use the following options to change the behavior of this command.\n\n")
89+
90+
// gather options from command and all options aviailable from parent commands
91+
var options = make([]Option, 0)
92+
var globalOptions = make([]Option, 0)
93+
for i, o := range w.optionsStack {
94+
if i == len(w.optionsStack)-1 {
95+
options = append(options, o...)
96+
} else {
97+
globalOptions = append(globalOptions, o...)
98+
}
99+
}
100+
101+
// alphabetize options
102+
sort.Slice(options, func(i, j int) bool {
103+
return options[i].Name < options[j].Name
104+
})
105+
106+
sort.Slice(globalOptions, func(i, j int) bool {
107+
return globalOptions[i].Name < globalOptions[j].Name
108+
})
109+
110+
w.writeOptions("Flags", options, c)
111+
w.writeOptions("Global Flags", globalOptions, c)
112+
113+
}
114+
}
115+
116+
func (w *docWriter) writeOptions(prefix string, options []Option, c *Command) {
117+
if len(options) == 0 {
118+
return
88119
}
89120

90-
// alphabetize options
91-
sort.Slice(allOptions, func(i, j int) bool {
92-
return allOptions[i].Name < allOptions[j].Name
93-
})
121+
fileName := c.fileName()
94122

95-
for _, option := range allOptions {
96-
w.fileMap[fileName].WriteString(fmt.Sprintf("## %s\n\n", option.Name))
97-
w.fileMap[fileName].WriteString(option.Description + "\n\n")
123+
w.fileMap[fileName].WriteString(fmt.Sprintf("**%s:**\n\n", prefix))
98124

125+
for _, o := range options {
126+
// option name and alias
127+
w.fileMap[fileName].WriteString(fmt.Sprintf("**--%s**", o.Name))
128+
if len(o.Short) > 0 {
129+
w.fileMap[fileName].WriteString(fmt.Sprintf(", **-%s**", o.Short))
130+
}
131+
w.fileMap[fileName].WriteString(fmt.Sprintf(" _%s_\n\n", o.Type))
132+
133+
// description
134+
w.fileMap[fileName].WriteString(encodeJSONExample(o.Description))
135+
if o.Required {
136+
w.fileMap[fileName].WriteString(" Required.")
137+
}
138+
if len(o.EnumValues) > 0 {
139+
w.fileMap[fileName].WriteString(fmt.Sprintf(" Accepted values: %s.", strings.Join(o.EnumValues, ", ")))
140+
}
141+
if len(o.Default) > 0 {
142+
w.fileMap[fileName].WriteString(fmt.Sprintf(` (default "%s")`, o.Default))
143+
}
144+
w.fileMap[fileName].WriteString("\n\n")
145+
146+
if o.Experimental {
147+
w.fileMap[fileName].WriteString(":::note" + "\n\n")
148+
w.fileMap[fileName].WriteString("Option is experimental." + "\n\n")
149+
w.fileMap[fileName].WriteString(":::" + "\n\n")
150+
}
99151
}
100152
}
101153

@@ -115,3 +167,21 @@ func (w *docWriter) processOptions(c *Command) {
115167

116168
w.optionsStack = append(w.optionsStack, options)
117169
}
170+
171+
func (w *docWriter) isLeafCommand(c *Command) bool {
172+
for _, maybeSubCmd := range w.allCommands {
173+
if maybeSubCmd.isSubCommand(c) {
174+
return false
175+
}
176+
}
177+
return true
178+
}
179+
180+
func encodeJSONExample(v string) string {
181+
// example: 'YourKey={"your": "value"}'
182+
// results in an mdx acorn rendering error
183+
// and wrapping in backticks lets it render
184+
re := regexp.MustCompile(`('[a-zA-Z0-9]*={.*}')`)
185+
v = re.ReplaceAllString(v, "`$1`")
186+
return v
187+
}

temporalcli/commandsgen/parse.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"regexp"
1010
"slices"
11+
"sort"
1112
"strings"
1213

1314
"gopkg.in/yaml.v3"
@@ -19,15 +20,16 @@ var CommandsYAML []byte
1920
type (
2021
// Option represents the structure of an option within option sets.
2122
Option struct {
22-
Name string `yaml:"name"`
23-
Type string `yaml:"type"`
24-
Description string `yaml:"description"`
25-
Short string `yaml:"short,omitempty"`
26-
Default string `yaml:"default,omitempty"`
27-
Env string `yaml:"env,omitempty"`
28-
Required bool `yaml:"required,omitempty"`
29-
Aliases []string `yaml:"aliases,omitempty"`
30-
EnumValues []string `yaml:"enum-values,omitempty"`
23+
Name string `yaml:"name"`
24+
Type string `yaml:"type"`
25+
Description string `yaml:"description"`
26+
Short string `yaml:"short,omitempty"`
27+
Default string `yaml:"default,omitempty"`
28+
Env string `yaml:"env,omitempty"`
29+
Required bool `yaml:"required,omitempty"`
30+
Aliases []string `yaml:"aliases,omitempty"`
31+
EnumValues []string `yaml:"enum-values,omitempty"`
32+
Experimental bool `yaml:"experimental,omitempty"`
3133
}
3234

3335
// Command represents the structure of each command in the commands map.
@@ -55,8 +57,9 @@ type (
5557

5658
// OptionSets represents the structure of option sets.
5759
OptionSets struct {
58-
Name string `yaml:"name"`
59-
Options []Option `yaml:"options"`
60+
Name string `yaml:"name"`
61+
Description string `yaml:"description"`
62+
Options []Option `yaml:"options"`
6063
}
6164

6265
// Commands represents the top-level structure holding commands and option sets.
@@ -87,6 +90,12 @@ func ParseCommands() (Commands, error) {
8790
return Commands{}, fmt.Errorf("failed parsing command section %q: %w", command.FullName, err)
8891
}
8992
}
93+
94+
// alphabetize commands
95+
sort.Slice(m.CommandList, func(i, j int) bool {
96+
return m.CommandList[i].FullName < m.CommandList[j].FullName
97+
})
98+
9099
return m, nil
91100
}
92101

@@ -170,6 +179,25 @@ func (c *Command) processSection() error {
170179
return nil
171180
}
172181

182+
func (c *Command) isSubCommand(maybeParent *Command) bool {
183+
return len(c.NamePath) == len(maybeParent.NamePath)+1 && strings.HasPrefix(c.FullName, maybeParent.FullName+" ")
184+
}
185+
186+
func (c *Command) leafName() string {
187+
return strings.Join(strings.Split(c.FullName, " ")[c.depth():], "")
188+
}
189+
190+
func (c *Command) fileName() string {
191+
if c.depth() <= 0 {
192+
return ""
193+
}
194+
return strings.Split(c.FullName, " ")[1]
195+
}
196+
197+
func (c *Command) depth() int {
198+
return len(strings.Split(c.FullName, " ")) - 1
199+
}
200+
173201
func (o *Option) processSection() error {
174202
if o.Name == "" {
175203
return fmt.Errorf("missing option name")

0 commit comments

Comments
 (0)