package wand import ( "fmt" "github.com/iancoleman/strcase" "io" "strings" ) func header(level int) string { s := make([]string, level+2) return strings.Join(s, "#") } func formatMarkdownCommand(printf func(string, ...any), println func(...any), doc doc, level int) { printf("%s Synopsis\n\n", header(level)) println("```") printf(doc.synopsis.command) if doc.synopsis.hasOptions { printf(" [options...]") } for i := range doc.synopsis.arguments.names { printf( " [%s %s]", doc.synopsis.arguments.names[i], doc.synopsis.arguments.types[i], ) } if doc.synopsis.arguments.variadic { printf("...") } println() println("```") min := doc.synopsis.arguments.minPositional max := doc.synopsis.arguments.maxPositional if doc.synopsis.arguments.variadic { if min > 0 || max > 0 { println() } switch { case min > 0 && max > 0: printf("min %d and max %d total positional arguments\n", min, max) case min > 0: printf("min %d total positional arguments\n", min) case max > 0: printf("max %d total positional arguments\n", max) } } if doc.synopsis.hasSubcommands { println() println("```") for _, sc := range doc.subcommands { printf("%s %s\n", escapeMD(doc.name), sc.name) } println("```") } if doc.description != "" { printf("\n%s Description\n\n", header(level)) println(escapeMD(paragraphs(doc.description))) } if len(doc.options) > 0 { printf("\n%s Options\n\n", header(level)) if doc.hasBoolOptions { printf("- [b]: booelan flag, true or false, or no argument means true\n") } if doc.hasListOptions { printf("- [*]: accepts multiple instances of the same option\n") } if doc.hasBoolOptions || doc.hasListOptions { println() } names, descriptions := prepareOptions(doc.options) for _, n := range names { printf("- **%s**: %s\n", escapeMD(n), escapeMD(lines(descriptions[n]))) } } if len(doc.options) > 0 && commandNameExpression.MatchString(doc.appName) { printf("\n.%s Environment Variables\n\n", header(level)) println(escapeMD(paragraphs(envDocs))) println() println("Example environment variable:") println() o := doc.options[0] println("```") printf(strcase.ToSnake(fmt.Sprintf("%s-%s", doc.appName, o.name))) printf("=") if o.isBool { printf("true") } else { printf("42") } println() println("```") } if len(doc.options) > 0 && len(doc.configFiles) > 0 { printf("\n.Configuration Files\n\n") println(escapeMD(paragraphs(configDocs))) println() println("Config files:") println() for _, cf := range doc.configFiles { if cf.fromOption { println("- zero or more configuration files defined by the --config option\n") continue } if cf.fn != "" { printf("- %s", cf.fn) if cf.optional { printf(" (optional)") } println() continue } } println() println("Example configuration entry:") println() o := doc.options[0] println("```") printf("# default for --%s:\n", o.name) printf(strcase.ToSnake(o.name)) printf(" = ") if o.isBool { printf("true") } else { printf("42") } println() println("```") println() println("Example for discarding an inherited entry:") println() println("```") println("# discarding an inherited entry:") println(strcase.ToSnake(o.name)) println("```") } } func formatMarkdownMultiCommand(out io.Writer, doc doc, level int) error { printf, println, finish := printer(out) printf("%s %s\n\n", header(level), escapeMD(doc.appName)) println("Provides several commands:") println() allCommands := allCommands(doc) for _, c := range allCommands { printf("- %s\n", escapeMD(c.fullCommand)) } println() for _, c := range allCommands { printf("%s %s\n\n", header(level+1), escapeMD(c.fullCommand)) formatMarkdownCommand(printf, println, c, level+2) } return finish() } func formatMarkdownSingleCommand(out io.Writer, doc doc, level int) error { printf, println, finish := printer(out) printf("%s %s\n\n", header(level), doc.appName) formatMarkdownCommand(printf, println, doc, level+1) return finish() } func formatMarkdown(out io.Writer, doc doc, level int) error { var hasSubcommands bool for _, sc := range doc.subcommands { if !sc.isHelp && !sc.isVersion { continue } hasSubcommands = true break } if hasSubcommands { return formatMarkdownMultiCommand(out, doc, level) } return formatMarkdownSingleCommand(out, doc, level) }