Files
lotus/scripts/docsgen-cli/doc_generator.go

157 lines
4.0 KiB
Go

package main
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
)
// DocGenerator handles CLI documentation generation
type DocGenerator struct {
app *cli.App
outputDir string
writer io.Writer
}
// NewDocGenerator creates a new documentation generator
func NewDocGenerator(outputDir string, app *cli.App) *DocGenerator {
return &DocGenerator{
outputDir: outputDir,
app: app,
}
}
// Generate generates documentation for the CLI app
func (g *DocGenerator) Generate(name string) error {
file, err := g.createMarkdownFile(name)
if err != nil {
return fmt.Errorf("failed to create markdown file: %w", err)
}
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("failed to close markdown file: %v\n", err)
}
}()
return g.generateContent(file, name)
}
// createMarkdownFile creates a new markdown file for output.
func (g *DocGenerator) createMarkdownFile(name string) (*os.File, error) {
filePath := filepath.Join(g.outputDir, fmt.Sprintf("cli-%s.md", name))
return os.Create(filePath)
}
func (g *DocGenerator) generateContent(file *os.File, name string) error {
bufferedWriter := bufio.NewWriter(file)
g.writer = bufferedWriter
g.app.Writer = bufferedWriter
if err := g.generateDocs(name); err != nil {
return fmt.Errorf("failed to generate documentation: %w", err)
}
return bufferedWriter.Flush()
}
// generateDocs orchestrates the documentation generation process
func (g *DocGenerator) generateDocs(name string) error {
if err := g.writeAppHeader(); err != nil {
return fmt.Errorf("failed to write app header: %w", err)
}
return g.writeCommandDocs(g.app.Commands, name, 0)
}
// writeAppHeader writes the application header documentation
func (g *DocGenerator) writeAppHeader() error {
if _, err := g.writer.Write([]byte(fmt.Sprintf("# %s\n\n```\n", g.app.Name))); err != nil {
return err
}
if err := g.app.Run(getHelpArgs("", "")); err != nil {
return fmt.Errorf("failed to write command docs: %w", err)
}
if _, err := g.writer.Write([]byte("```\n")); err != nil {
return err
}
return nil
}
func (g *DocGenerator) writeCommandDocs(commands cli.Commands, rootName string, depth int) error {
uncategorizedCmds, categorizedCmds := separateCommands(commands)
// Write uncategorized commands first
if err := g.writeCommands(uncategorizedCmds, rootName, depth); err != nil {
return fmt.Errorf("failed to write uncategorized commands: %w", err)
}
// Write categorized commands next
if err := g.writeCommands(categorizedCmds, rootName, depth); err != nil {
return fmt.Errorf("failed to write categorized commands: %w", err)
}
return nil
}
// separateCommands separates commands into uncategorized and categorized
func separateCommands(commands []*cli.Command) (uncategorized cli.Commands, categorized cli.Commands) {
for _, cmd := range commands {
if cmd.Category == "" {
uncategorized = append(uncategorized, cmd)
} else {
categorized = append(categorized, cmd)
}
}
return uncategorized, categorized
}
// writeCommands writes documentation for all commands recursively
func (g *DocGenerator) writeCommands(commands cli.Commands, rootName string, depth int) error {
for _, cmd := range commands {
if cmd.Name == "help" || cmd.Hidden {
continue
}
cmdName := fmt.Sprintf("%s %s", rootName, cmd.Name)
if _, err := g.writer.Write([]byte(fmt.Sprintf("\n%s %s\n\n```\n", strings.Repeat("#", depth+2), cmdName))); err != nil {
return err
}
if err := g.app.Run(getHelpArgs(rootName, cmd.Name)); err != nil {
return fmt.Errorf("failed to write command docs: %w", err)
}
if _, err := g.writer.Write([]byte("```\n")); err != nil {
return err
}
if len(cmd.Subcommands) > 0 {
if err := g.writeCommands(cmd.Subcommands, rootName+" "+cmd.Name, depth+1); err != nil {
return err
}
}
}
return nil
}
func getHelpArgs(rootName string, cmdName string) []string {
if rootName == "" && cmdName == "" {
return []string{"-h"}
}
args := strings.Split(rootName, " ")
args = append(args, cmdName)
args = append(args, "-h")
return args
}