mirror of
https://github.com/kopia/kopia.git
synced 2026-01-06 05:27:59 -05:00
348 lines
8.0 KiB
Go
348 lines
8.0 KiB
Go
// Command cli2md generates documentation pages from CLI flags.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/alecthomas/kingpin/v2"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/cli"
|
|
_ "github.com/kopia/kopia/internal/logfile"
|
|
)
|
|
|
|
//nolint:gochecknoglobals
|
|
var baseDir = flag.String("base-dir", "content/docs/Reference/Command-Line", "Base directory")
|
|
|
|
const (
|
|
advancedSection = "Advanced"
|
|
advancedCommandsWeight = 6
|
|
|
|
commonSection = "Common"
|
|
commonCommandsWeight = 5
|
|
|
|
dirMode = 0o750
|
|
)
|
|
|
|
// The group at the start is to avoid matching things like "in-place".
|
|
var escapeFlagsRe = regexp.MustCompile(`(^|\s)(--?\w+[-\w]*)`)
|
|
|
|
// escapeFlags escapes command-line flag references in help text by wrapping them in backticks.
|
|
func escapeFlags(text string) string {
|
|
return escapeFlagsRe.ReplaceAllString(text, "$1`$2`")
|
|
}
|
|
|
|
//nolint:gochecknoglobals
|
|
var overrideDefault = map[string]string{
|
|
"config-file": "repository.config",
|
|
"log-dir": "kopia",
|
|
}
|
|
|
|
func emitFlags(w io.Writer, flags []*kingpin.FlagModel) {
|
|
if len(flags) == 0 {
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(w, "| Flag | Short | Default | Help |\n") //nolint:errcheck
|
|
fmt.Fprintf(w, "| ---- | ----- | --- | --- |\n") //nolint:errcheck
|
|
|
|
for _, f := range sortFlags(flags) {
|
|
maybeAdvanced := ""
|
|
|
|
if f.Hidden {
|
|
maybeAdvanced = "[ADV] "
|
|
}
|
|
|
|
shortFlag := ""
|
|
if f.Short != 0 {
|
|
shortFlag = "`-" + string([]byte{byte(f.Short)}) + "`"
|
|
}
|
|
|
|
defaultValue := ""
|
|
if len(f.Default) > 0 {
|
|
defaultValue = f.Default[0]
|
|
}
|
|
|
|
if def, ok := overrideDefault[f.Name]; ok {
|
|
defaultValue = def
|
|
}
|
|
|
|
if defaultValue != "" {
|
|
defaultValue = "`" + defaultValue + "`"
|
|
}
|
|
|
|
if f.IsBoolFlag() {
|
|
if defaultValue == "" {
|
|
defaultValue = "`false`"
|
|
}
|
|
|
|
fmt.Fprintf(w, "| `--[no-]%v` | %v | %v | %v%v |\n", f.Name, shortFlag, defaultValue, maybeAdvanced, escapeFlags(f.Help)) //nolint:errcheck
|
|
} else {
|
|
fmt.Fprintf(w, "| `--%v` | %v | %v | %v%v |\n", f.Name, shortFlag, defaultValue, maybeAdvanced, escapeFlags(f.Help)) //nolint:errcheck
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n") //nolint:errcheck
|
|
}
|
|
|
|
func combineFlags(lists ...[]*kingpin.FlagModel) []*kingpin.FlagModel {
|
|
var result []*kingpin.FlagModel
|
|
|
|
for _, list := range lists {
|
|
result = append(result, list...)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func sortFlags(f []*kingpin.FlagModel) []*kingpin.FlagModel {
|
|
sort.Slice(f, func(i, j int) bool {
|
|
a, b := f[i], f[j]
|
|
|
|
if l, r := a.Hidden, b.Hidden; l != r {
|
|
return !l
|
|
}
|
|
|
|
return a.Name < b.Name
|
|
})
|
|
|
|
return f
|
|
}
|
|
|
|
func emitArgs(w io.Writer, args []*kingpin.ArgModel) {
|
|
if len(args) == 0 {
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(w, "| Argument | Help |\n") //nolint:errcheck
|
|
fmt.Fprintf(w, "| -------- | --- |\n") //nolint:errcheck
|
|
|
|
args2 := append([]*kingpin.ArgModel(nil), args...)
|
|
sort.Slice(args2, func(i, j int) bool {
|
|
return args2[i].Name < args2[j].Name
|
|
})
|
|
|
|
for _, f := range args2 {
|
|
fmt.Fprintf(w, "| `%v` | %v |\n", f.Name, escapeFlags(f.Help)) //nolint:errcheck
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n") //nolint:errcheck
|
|
}
|
|
|
|
func generateAppFlags(app *kingpin.ApplicationModel) error {
|
|
f, err := os.Create(filepath.Join(*baseDir, "flags.md"))
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to create common flags file")
|
|
}
|
|
defer f.Close() //nolint:errcheck
|
|
|
|
title := "Flags"
|
|
|
|
//nolint:errcheck
|
|
fmt.Fprintf(f, `---
|
|
title: %q
|
|
linkTitle: %q
|
|
weight: 3
|
|
---
|
|
`, title, title)
|
|
emitFlags(f, app.Flags)
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateCommands(app *kingpin.ApplicationModel, section string, weight int, advanced bool) error {
|
|
dir := filepath.Join(*baseDir, section)
|
|
|
|
if err := os.MkdirAll(dir, dirMode); err != nil {
|
|
return errors.Wrapf(err, "error creating section directory for %v", section)
|
|
}
|
|
|
|
f, err := os.Create(filepath.Join(dir, "_index.md")) //nolint:gosec
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to create common flags file")
|
|
}
|
|
defer f.Close() //nolint:errcheck
|
|
|
|
title := section + " Commands"
|
|
|
|
//nolint:errcheck
|
|
fmt.Fprintf(f, `---
|
|
title: %q
|
|
linkTitle: %q
|
|
weight: %v
|
|
hide_summary: true
|
|
no_list: true
|
|
---
|
|
`, title, title, weight)
|
|
|
|
flat := flattenCommands(app.Commands)
|
|
for _, c := range flat {
|
|
generateSubcommands(f, dir, c.Help, c.Commands, advanced)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func flattenCommands(cmds []*kingpin.CmdModel) []*kingpin.CmdModel {
|
|
var result []*kingpin.CmdModel
|
|
|
|
commonRoot := &kingpin.CmdModel{
|
|
Name: "Common Commands",
|
|
Help: "Common Commands",
|
|
CmdGroupModel: &kingpin.CmdGroupModel{},
|
|
}
|
|
result = append(result, commonRoot)
|
|
|
|
for _, c := range cmds {
|
|
if len(c.Commands) == 0 {
|
|
commonRoot.Commands = append(commonRoot.Commands, c)
|
|
continue
|
|
}
|
|
|
|
root := &kingpin.CmdModel{
|
|
Name: c.Name,
|
|
FullCommand: c.FullCommand,
|
|
Help: c.Help,
|
|
Hidden: c.Hidden,
|
|
CmdGroupModel: &kingpin.CmdGroupModel{},
|
|
}
|
|
result = append(result, root)
|
|
root.Commands = flattenChildren(c, nil, c.Hidden)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func flattenChildren(cmd *kingpin.CmdModel, parentFlags []*kingpin.FlagModel, forceHidden bool) []*kingpin.CmdModel {
|
|
var result []*kingpin.CmdModel
|
|
|
|
cmdFlags := combineFlags(parentFlags, cmd.Flags)
|
|
|
|
if len(cmd.Commands) == 0 {
|
|
cmdClone := *cmd
|
|
if forceHidden {
|
|
cmdClone.Hidden = true
|
|
}
|
|
|
|
cmdClone.Flags = cmdFlags
|
|
|
|
result = append(result, &cmdClone)
|
|
} else {
|
|
for _, c := range cmd.Commands {
|
|
result = append(result, flattenChildren(c, cmdFlags, c.Hidden || forceHidden)...)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func generateSubcommands(w io.Writer, dir, sectionTitle string, cmds []*kingpin.CmdModel, advanced bool) {
|
|
cmds = append([]*kingpin.CmdModel(nil), cmds...)
|
|
|
|
first := true
|
|
|
|
for _, c := range cmds {
|
|
if c.Hidden != advanced {
|
|
continue
|
|
}
|
|
|
|
if first {
|
|
fmt.Fprintf(w, "\n### %v\n\n", strings.TrimSuffix(sectionTitle, ".")) //nolint:errcheck
|
|
|
|
first = false
|
|
}
|
|
|
|
subcommandSlug := strings.ReplaceAll(c.FullCommand, " ", "-")
|
|
helpSummary := strings.SplitN(c.Help, "\n", 2)[0] //nolint:mnd
|
|
helpSummary = strings.TrimSuffix(helpSummary, ".")
|
|
fmt.Fprintf(w, "* [`%v`](%v) - %v\n", c.FullCommand, subcommandSlug+"/", helpSummary) //nolint:errcheck
|
|
generateSubcommandPage(filepath.Join(dir, subcommandSlug+".md"), c)
|
|
}
|
|
}
|
|
|
|
func generateSubcommandPage(fname string, cmd *kingpin.CmdModel) {
|
|
f, err := os.Create(fname) //nolint:gosec
|
|
if err != nil {
|
|
log.Fatalf("unable to create page: %v", err)
|
|
}
|
|
defer f.Close() //nolint:errcheck
|
|
|
|
title := cmd.FullCommand
|
|
|
|
//nolint:errcheck
|
|
fmt.Fprintf(f, `---
|
|
title: %q
|
|
linkTitle: %q
|
|
weight: 10
|
|
toc_hide: true
|
|
hide_summary: true
|
|
---
|
|
|
|
`, title, title)
|
|
|
|
var (
|
|
argSummary strings.Builder
|
|
flagSummary strings.Builder
|
|
)
|
|
|
|
for _, a := range cmd.Args {
|
|
if a.Required {
|
|
argSummary.WriteString(" <")
|
|
argSummary.WriteString(a.Name)
|
|
argSummary.WriteRune('>')
|
|
} else {
|
|
argSummary.WriteString(" [")
|
|
argSummary.WriteString(a.Name)
|
|
argSummary.WriteRune(']')
|
|
}
|
|
}
|
|
|
|
for _, fl := range cmd.Flags {
|
|
if fl.Required {
|
|
flagSummary.WriteString(" \\\n --" + fl.Name + "=...")
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(f, "```shell\n$ kopia %v%v%v\n```\n\n", cmd.FullCommand, flagSummary.String(), argSummary.String()) //nolint:errcheck
|
|
fmt.Fprintf(f, "%v\n\n", escapeFlags(cmd.Help)) //nolint:errcheck
|
|
|
|
emitFlags(f, cmd.Flags)
|
|
emitArgs(f, cmd.Args)
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if _, err := os.Stat(*baseDir); err != nil {
|
|
log.Fatalf("invalid base directory: %v", err)
|
|
}
|
|
|
|
_ = os.RemoveAll(filepath.Join(*baseDir, commonSection))
|
|
_ = os.RemoveAll(filepath.Join(*baseDir, advancedSection))
|
|
|
|
kingpinApp := kingpin.New("kopia", "Kopia - Fast And Secure Open-Source Backup").Author("http://kopia.github.io/")
|
|
cli.NewApp().Attach(kingpinApp)
|
|
|
|
app := kingpinApp.Model()
|
|
|
|
if err := generateAppFlags(app); err != nil {
|
|
log.Fatalf("unable to generate common flags: %v", err)
|
|
}
|
|
|
|
if err := generateCommands(app, commonSection, commonCommandsWeight, false); err != nil {
|
|
log.Fatalf("unable to generate common commands: %v", err)
|
|
}
|
|
|
|
if err := generateCommands(app, advancedSection, advancedCommandsWeight, true); err != nil {
|
|
log.Fatalf("unable to generate advanced commands: %v", err)
|
|
}
|
|
}
|