Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced docs gen to more closely match existing docs (legacy) #692

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
194 changes: 150 additions & 44 deletions temporalcli/commandsgen/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@ package commandsgen
import (
"bytes"
"fmt"
"regexp"
"sort"
"strings"
)

type DocsFile struct {
FileName string
}

func GenerateDocsFiles(commands Commands) (map[string][]byte, error) {

optionSetMap := make(map[string]OptionSets)
for i, optionSet := range commands.OptionSets {
optionSetMap[optionSet.Name] = commands.OptionSets[i]
}

w := &docWriter{fileMap: make(map[string]*bytes.Buffer), optionSetMap: optionSetMap}
w := &docWriter{
fileMap: make(map[string]*bytes.Buffer),
optionSetMap: optionSetMap,
usages: commands.Usages,
}

// cmd-options.mdx
w.writeCommandOptions()

// sort by parent command (activity, batch, etc)
// sorted ascending by full name of command (activity complete, batch list, etc)
for _, cmd := range commands.CommandList {
if err := cmd.writeDoc(w); err != nil {
return nil, fmt.Errorf("failed writing docs for command %s: %w", cmd.FullName, err)
Expand All @@ -34,34 +39,52 @@ func GenerateDocsFiles(commands Commands) (map[string][]byte, error) {
return finalMap, nil
}

type docWriter struct {
fileMap map[string]*bytes.Buffer
optionSetMap map[string]OptionSets
optionsStack [][]Option
}

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

// If this is a root command, write a new file
if commandLength == 2 {
if c.Depth == 1 {
w.writeCommand(c)
} else if commandLength > 2 {
} else if c.Depth > 1 {
w.writeSubcommand(c)
}
return nil
}

type docWriter struct {
fileMap map[string]*bytes.Buffer
optionSetMap map[string]OptionSets
optionsStack [][]Option
usages Usages
}

func (w *docWriter) processOptions(c *Command) {
// Pop options from stack if we are moving up a level
if len(w.optionsStack) >= len(strings.Split(c.FullName, " ")) {
w.optionsStack = w.optionsStack[:len(w.optionsStack)-1]
}
var options []Option
options = append(options, c.Options...)

// Maintain stack of options available from parent commands
for _, set := range c.OptionSets {
optionSetOptions := w.optionSetMap[set].Options
options = append(options, optionSetOptions...)
}

w.optionsStack = append(w.optionsStack, options)
}

func (w *docWriter) writeCommand(c *Command) {
fileName := strings.Split(c.FullName, " ")[1]
fileName := c.FileName
w.fileMap[fileName] = &bytes.Buffer{}
w.fileMap[fileName].WriteString("---\n")
w.fileMap[fileName].WriteString("id: " + fileName + "\n")
w.fileMap[fileName].WriteString("title: " + c.FullName + "\n")
w.fileMap[fileName].WriteString("sidebar_label: " + c.FullName + "\n")
w.fileMap[fileName].WriteString("description: " + c.Docs.DescriptionHeader + "\n")
w.fileMap[fileName].WriteString("toc_max_heading_level: 4\n")

w.fileMap[fileName].WriteString("keywords:\n")
for _, keyword := range c.Docs.Keywords {
w.fileMap[fileName].WriteString(" - " + keyword + "\n")
Expand All @@ -75,43 +98,126 @@ func (w *docWriter) writeCommand(c *Command) {
}

func (w *docWriter) writeSubcommand(c *Command) {
fileName := strings.Split(c.FullName, " ")[1]
subCommand := strings.Join(strings.Split(c.FullName, " ")[2:], "")
w.fileMap[fileName].WriteString("## " + subCommand + "\n\n")
fileName := c.FileName
prefix := strings.Repeat("#", c.Depth)
w.fileMap[fileName].WriteString(prefix + " " + c.LeafName + "\n\n")
w.fileMap[fileName].WriteString(c.Description + "\n\n")
w.fileMap[fileName].WriteString("Use the following options to change the behavior of this command.\n\n")

// gather options from command and all options aviailable from parent commands
var allOptions = make([]Option, 0)
for _, options := range w.optionsStack {
allOptions = append(allOptions, options...)
}
if len(c.Children) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great improvement here! The old code fails to handle a few edge cases with more complex subcommands.

How are Parent and Children used? I only see Children being used to check if len(c.Children) == 0 {, and I don't see the Parent information used anywhere. If that's the case, I feel like a simpler approach would be to keep a IsLeafCommand from enrich.go and use that for this condition here

Copy link
Contributor Author

@prasek prasek Oct 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuandrew Thanks! Parent is used in EnrichCommands() for various things including setting Children on the Parent. Are you thinking an IsLeafCommand() helper or something on the Command struct?

Children gives docs.go more flexibility in how the docs are rendered, for example the more hierarchical docs gen style used in this tcld PR which we probably want to move to for temporal at some point.

w.fileMap[fileName].WriteString("Use the following options to change the behavior of this command.\n\n")

// alphabetize options
sort.Slice(allOptions, func(i, j int) bool {
return allOptions[i].Name < allOptions[j].Name
})
// gather options from command and all options aviailable from parent commands
var allOptions = make([]Option, 0)
for _, options := range w.optionsStack {
allOptions = append(allOptions, options...)
}

for _, option := range allOptions {
w.fileMap[fileName].WriteString(fmt.Sprintf("## %s\n\n", option.Name))
w.fileMap[fileName].WriteString(option.Description + "\n\n")
// alphabetize options
sort.Slice(allOptions, func(i, j int) bool {
return allOptions[i].Name < allOptions[j].Name
})

for _, option := range allOptions {
w.fileMap[fileName].WriteString(fmt.Sprintf("- [--%s](/cli/cmd-options#%s)\n\n", option.Name, option.Name))
}
}
}

func (w *docWriter) processOptions(c *Command) {
// Pop options from stack if we are moving up a level
if len(w.optionsStack) >= len(strings.Split(c.FullName, " ")) {
w.optionsStack = w.optionsStack[:len(w.optionsStack)-1]
}
var options []Option
options = append(options, c.Options...)
func (w *docWriter) writeCommandOptions() {
fileName := "cmd-options"
w.fileMap[fileName] = &bytes.Buffer{}
w.fileMap[fileName].WriteString("---\n")
w.fileMap[fileName].WriteString("id: " + fileName + "\n")
w.fileMap[fileName].WriteString("title: Temporal CLI command options reference\n")
w.fileMap[fileName].WriteString("sidebar_label: cmd options\n")
w.fileMap[fileName].WriteString("description: Discover how to manage Temporal Workflows, from Activity Execution to Workflow Ids, using clusters, cron schedules, dynamic configurations, and logging. Perfect for developers.\n")
w.fileMap[fileName].WriteString("toc_max_heading_level: 4\n")

// Maintain stack of options available from parent commands
for _, set := range c.OptionSets {
optionSetOptions := w.optionSetMap[set].Options
options = append(options, optionSetOptions...)
w.fileMap[fileName].WriteString("keywords:\n")
w.fileMap[fileName].WriteString(" - " + "cli reference" + "\n")
w.fileMap[fileName].WriteString(" - " + "command line interface cli" + "\n")
w.fileMap[fileName].WriteString(" - " + "temporal cli" + "\n")

w.fileMap[fileName].WriteString("tags:\n")
w.fileMap[fileName].WriteString(" - " + "cli-reference" + "\n")
w.fileMap[fileName].WriteString(" - " + "command-line-interface-cli" + "\n")
w.fileMap[fileName].WriteString(" - " + "temporal-cli" + "\n")

w.fileMap[fileName].WriteString("---\n\n")

/////// option a
for _, option := range w.usages.OptionUsagesByOptionDescription {
w.fileMap[fileName].WriteString(fmt.Sprintf("## %s\n\n", option.OptionName))

if len(option.Usages) == 1 {
usageDescription := option.Usages[0]
usage := usageDescription.UsageSites[0]
w.fileMap[fileName].WriteString(encodeJSONExample(usage.Option.Description) + "\n\n")

if usage.Option.Experimental {
w.fileMap[fileName].WriteString(":::note" + "\n\n")
w.fileMap[fileName].WriteString("Option is experimental." + "\n\n")
w.fileMap[fileName].WriteString(":::" + "\n\n")
}
} else {
for i, usageDescription := range option.Usages {
if i > 0 {
w.fileMap[fileName].WriteString("\n")

}
w.fileMap[fileName].WriteString(encodeJSONExample(usageDescription.OptionDescription) + "\n\n")

for _, usage := range usageDescription.UsageSites {
experimentalDescr := ""
if usage.Option.Experimental {
experimentalDescr = " (option usage is EXPERIMENTAL)"
}
if usage.UsageSiteType == UsageTypeCommand {
w.fileMap[fileName].WriteString("- `" + usage.UsageSiteDescription + "`" + experimentalDescr + "\n")
} else {
w.fileMap[fileName].WriteString("- " + usage.UsageSiteDescription + experimentalDescr + "\n")
}
}

}
}
}

w.optionsStack = append(w.optionsStack, options)
/////// option b

/*

for _, option := range w.usages.OptionUsages {
w.fileMap[fileName].WriteString(fmt.Sprintf("## %s\n\n", option.OptionName))

if len(option.Usages) == 1 {
usage := option.Usages[0]
w.fileMap[fileName].WriteString(usage.Option.Description + "\n\n")

if usage.Option.Experimental {
w.fileMap[fileName].WriteString(":::note" + "\n\n")
w.fileMap[fileName].WriteString("Option is experimental and may be removed at a future date." + "\n\n")
w.fileMap[fileName].WriteString(":::" + "\n\n")
}
} else {
for _, usage := range option.Usages {
w.fileMap[fileName].WriteString("**" + usage.UsageDescription + "**\n")
w.fileMap[fileName].WriteString(usage.Option.Description + "\n\n")

if usage.Option.Experimental {
w.fileMap[fileName].WriteString(":::note" + "\n\n")
w.fileMap[fileName].WriteString("Option is experimental and may be removed at a future date." + "\n\n")
w.fileMap[fileName].WriteString(":::" + "\n\n")
}
}
}
}
*/
}

func encodeJSONExample(v string) string {
// example: `'YourKey={"your": "value"}'`
re := regexp.MustCompile(`('[a-zA-Z0-9]*={.*}')`)
v = re.ReplaceAllString(v, "`$1`")
return v
}
Loading
Loading