Skip to content

Commit

Permalink
docs: Automatically check that all flags are documented
Browse files Browse the repository at this point in the history
  • Loading branch information
KapJI authored and twpayne committed Oct 22, 2024
1 parent 0fb4392 commit 56729ce
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 23 deletions.
4 changes: 2 additions & 2 deletions assets/chezmoi.io/docs/reference/commands/add.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ destination directory.

## Flags

### `--autotemplate`
### `-a`, `--autotemplate`

Automatically generate a template by replacing strings that match variable
values from the `data` section of the config file with their respective config
Expand Down Expand Up @@ -46,7 +46,7 @@ Interactively prompt before adding each file.

Suppress warnings about adding ignored entries.

### `-s`, `--secrets` `ignore`|`warning`|`error`
### `--secrets` `ignore`|`warning`|`error`

> Configuration: `add.secrets`
Expand Down
2 changes: 1 addition & 1 deletion assets/chezmoi.io/docs/reference/commands/destroy.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Remove *target* from the source state, the destination directory, and the state.

## Common flags

### `-f`, `--force`
### `--force`

Destroy without prompting.

Expand Down
4 changes: 2 additions & 2 deletions assets/chezmoi.io/docs/reference/commands/execute-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ specified, the template is read from stdin.

## Flags

### `--init`, `-i`
### `-i`, `--init`

Include simulated functions only available during `chezmoi init`.

Expand Down Expand Up @@ -37,7 +37,7 @@ from *pairs*. *pairs* is a comma-separated list of *prompt*`=`*value* pairs. If
`promptInt` is called with a *prompt* that does not match any of *pairs*, then
it returns zero.

### `--promptString`, `-p` *pairs*
### `-p`, `--promptString` *pairs*

Simulate the `promptString` template function with a function that returns
values from *pairs*. *pairs* is a comma-separated list of *prompt*`=`*value*
Expand Down
2 changes: 1 addition & 1 deletion assets/chezmoi.io/docs/reference/commands/import.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The supported archive formats are `tar`, `tar.gz`, `tgz`, `tar.bz2`, `tbz2`,

## Flags

### `--destination` *directory*
### `-d`, `--destination` *directory*

Set the destination (in the source state) where the archive will be imported.

Expand Down
12 changes: 6 additions & 6 deletions assets/chezmoi.io/docs/reference/commands/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ own binary.

## Flags

### `--apply`
### `-a`, `--apply`

Run `chezmoi apply` after checking out the repo and creating the config file.

### `--branch` *branch*

Check out *branch* instead of the default branch.

### `--config-path` *path*
### `-C`, `--config-path` *path*

Write the generated config file to *path* instead of the default location.

Expand All @@ -53,11 +53,11 @@ Include existing template data when creating the config file. This defaults to
`true`. Set this to `false` to simulate creating the config file with no
existing template data.

### `--depth` *depth*
### `-d`, `--depth` *depth*

Clone the repo with depth *depth*.

### `--guess-repo-url` *bool*
### `-g`, `--guess-repo-url` *bool*

Guess the repo URL from the *repo* argument. This defaults to `true`.

Expand Down Expand Up @@ -105,11 +105,11 @@ a comma-separated list of *prompt*`=`*value* pairs. If `promptString` is called
with a *prompt* that does not match any of *pairs*, then it prompts the user for
a value.

### `--purge`
### `-p`, `--purge`

Remove the source and config directories after applying.

### `--purge-binary`
### `-P`, `--purge-binary`

Attempt to remove the chezmoi binary after applying.

Expand Down
2 changes: 1 addition & 1 deletion assets/chezmoi.io/docs/reference/commands/purge.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Purge chezmoi binary.

## Common flags

### `-f`, `--force`
### `--force`

Remove without prompting.

Expand Down
6 changes: 5 additions & 1 deletion assets/chezmoi.io/docs/reference/commands/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ If `update.command` is set then chezmoi will run `update.command` with

## Flags

### `--apply`
### `-a`, `--apply`

Apply changes after pulling, `true` by default. Can be disabled with `--apply=false`.

Expand All @@ -33,6 +33,10 @@ defaults to `all`.

Recreate config file from template.

### `-P`, `--parent-dirs`

Also perform command on all parent directories of *target*.

### `-r`, `--recursive`

Recurse into subdirectories, `true` by default. Can be disabled with `--recursive=false`.
Expand Down
4 changes: 4 additions & 0 deletions assets/chezmoi.io/docs/reference/commands/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ defaults to `all`.

Recreate config file from template.

### `-P`, `--parent-dirs`

Also perform command on all parent directories of *target*.

### `-r`, `--recursive`

Recurse into subdirectories, `true` by default. Can be disabled with `--recursive=false`.
Expand Down
70 changes: 62 additions & 8 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/glamour/styles"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.etcd.io/bbolt"

"github.com/twpayne/chezmoi/v2/assets/chezmoi.io/docs/reference/commands"
Expand All @@ -30,6 +31,7 @@ var (

deDuplicateErrorRx = regexp.MustCompile(`:\s+`)
trailingSpaceRx = regexp.MustCompile(` +\n`)
helpFlagsRx = regexp.MustCompile("^### (?:`-([a-zA-Z])`, )?`--([a-zA-Z-]+)`")

helps = make(map[string]*help)
)
Expand All @@ -43,8 +45,10 @@ type VersionInfo struct {
}

type help struct {
longHelp string
example string
longHelp string
example string
longFlags map[string]bool
shortFlags map[string]bool
}

func init() {
Expand Down Expand Up @@ -155,6 +159,9 @@ func extractHelp(command string, data []byte, longHelpTermRenderer, exampleTermR
state := stateReadTitle
var longHelpLines []string
var exampleLines []string
longFlags := make(map[string]bool)
shortFlags := make(map[string]bool)

stateChange := func(line string, state *stateType) bool {
switch {
case strings.HasPrefix(line, "## Flags") || strings.HasPrefix(line, "## Common flags"):
Expand All @@ -169,9 +176,6 @@ func extractHelp(command string, data []byte, longHelpTermRenderer, exampleTermR
case strings.HasPrefix(line, "## "):
*state = stateInUnknownSection
return true
case strings.HasPrefix(line, "!!! "):
*state = stateInAdmonition
return true
}
return false
}
Expand All @@ -189,13 +193,28 @@ func extractHelp(command string, data []byte, longHelpTermRenderer, exampleTermR
return nil, fmt.Errorf("expected title for '%s'", command)
}
case stateInLongHelp:
if !stateChange(line, &state) {
switch {
case stateChange(line, &state):
break
case strings.HasPrefix(line, "!!! "):
state = stateInAdmonition
default:
longHelpLines = append(longHelpLines, line)
}
case stateInExamples:
if !stateChange(line, &state) {
exampleLines = append(exampleLines, line)
}
case stateInOptions:
if !stateChange(line, &state) {
matches := helpFlagsRx.FindStringSubmatch(line)
if matches != nil {
if matches[1] != "" {
shortFlags[matches[1]] = true
}
longFlags[matches[2]] = true
}
}
default:
stateChange(line, &state)
}
Expand All @@ -210,8 +229,10 @@ func extractHelp(command string, data []byte, longHelpTermRenderer, exampleTermR
return nil, err
}
return &help{
longHelp: "Description:\n" + longHelp,
example: example,
longHelp: "Description:\n" + longHelp,
example: example,
longFlags: longFlags,
shortFlags: shortFlags,
}, nil
}

Expand Down Expand Up @@ -245,6 +266,39 @@ func mustLongHelp(command string) string {
return help.longHelp
}

func ensureAllFlagsDocumented(cmd *cobra.Command, persistentFlags *pflag.FlagSet) {
cmdName := cmd.Name()
help, ok := helps[cmdName]
if !ok && !cmd.Flags().HasFlags() {
return
}
if !ok {
panic(cmdName + ": missing flags")
}
// Check if all flags are documented.
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if _, ok := help.longFlags[flag.Name]; !ok {
panic(fmt.Sprintf("%s: undocumented long flag --%s", cmdName, flag.Name))
}
if flag.Shorthand != "" {
if _, ok := help.shortFlags[flag.Shorthand]; !ok {
panic(fmt.Sprintf("%s: undocumented short flag -%s", cmdName, flag.Shorthand))
}
}
})
// Check if all documented flags exist.
for flag := range help.longFlags {
if cmd.Flags().Lookup(flag) == nil && persistentFlags.Lookup(flag) == nil {
panic(fmt.Sprintf("%s: flag --%s documented but not implemented", cmdName, flag))
}
}
for flag := range help.shortFlags {
if cmd.Flags().ShorthandLookup(flag) == nil && persistentFlags.ShorthandLookup(flag) == nil {
panic(fmt.Sprintf("%s: flag -%s documented but not implemented", cmdName, flag))
}
}
}

// runMain runs chezmoi's main function.
func runMain(versionInfo VersionInfo, args []string) (err error) {
if versionInfo.Commit == "" || versionInfo.Date == "" {
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1740,6 +1740,7 @@ func (c *Config) newRootCmd() (*cobra.Command, error) {
c.newVerifyCmd(),
} {
if cmd != nil {
ensureAllFlagsDocumented(cmd, persistentFlags)
registerCommonFlagCompletionFuncs(cmd)
rootCmd.AddCommand(cmd)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/doctorcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (c *Config) newDoctorCmd() *cobra.Command {
),
}

doctorCmd.PersistentFlags().BoolVar(&c.doctor.noNetwork, "no-network", c.doctor.noNetwork, "do not use network connection")
doctorCmd.Flags().BoolVar(&c.doctor.noNetwork, "no-network", c.doctor.noNetwork, "do not use network connection")

return doctorCmd
}
Expand Down

0 comments on commit 56729ce

Please sign in to comment.