diff --git a/go/README.md b/go/README.md index 0ac104103..6cd6cb60b 100644 --- a/go/README.md +++ b/go/README.md @@ -96,9 +96,16 @@ a derive macro: //go:generate usage generate go -f mycli.usage.kdl -o tables.go ``` -The generated file exports `Root` to pass to `argv.New`, and a key constant per -command, flag and argument. Dispatch on those rather than on `Name`: it costs no -string comparison, and a flag renamed in the spec then fails to compile instead of +The generated file exports `Root` to pass to `argv.New`, `Meta` for the rules +decided after the last token, and a key constant per command, flag and argument. + +`Meta` costs nothing if you do not use it: Go's linker drops an unreferenced +package-level table entirely, so a CLI that only binds does not carry it. mise's +is 217 KB when something does reference it. That is the same split Rust gets from +a feature flag, without needing one. + +Dispatch on the key constants rather than on `Name`: it costs no string +comparison, and a flag renamed in the spec then fails to compile instead of silently never matching. Writing tables by hand is supported too, and is what @@ -111,9 +118,6 @@ var ( ) ``` -Dispatch on `Key` rather than `Name` in generated code: it is what the field -identifiers are for, and it costs no string comparison. - ## Conformance The [corpus](../corpus) is the definition of correct, and it is plain JSON so that @@ -146,10 +150,6 @@ claim is measured at real scale rather than against a fixture with four flags: ## What is missing -- **The cold table in generated code.** `usage generate go` emits the parse tables - but not the `Meta` ones yet, so the post-binding rules are reachable today from a - spec lowered at run time rather than from a generated package. Proving them - against the corpus came first, the way the binder did before the generator. - **Typed values.** Binding collects text. Something still has to turn `"8"` into an `int` and `"1m"` into a `time.Duration`, and report the ones that will not convert. diff --git a/go/internal/shadow/mise/meta_test.go b/go/internal/shadow/mise/meta_test.go new file mode 100644 index 000000000..175bb67ac --- /dev/null +++ b/go/internal/shadow/mise/meta_test.go @@ -0,0 +1,207 @@ +package mise + +import ( + "testing" + + "github.com/jdx/usage/go/argv" +) + +// The generated cold table, driving the rules it exists for. +// +// The unit tests in `argv` prove the rules against tables written by hand; the +// corpus proves them against tables built from a spec at run time. Neither +// exercises the emitter's half, which is where a field can be dropped, misnamed, +// or filed under the wrong key without anything noticing. These are the join. + +// resolve runs a parse and applies the rules to one named entry, returning what +// it ended up with. +func resolve(t *testing.T, name string, args []string, + environ map[string]string) (values []string, source argv.Source, err *argv.Error) { + t.Helper() + + // A value-less flag that was given has no values, and nil would read as "the + // command line said nothing" — so it is recorded as an empty slice, which is + // the distinction Fill draws. Getting this wrong makes a typed boolean fall + // through to env and default. + given := map[uint64][]string{} + occurrences := map[uint64]int{} + path := []*argv.Command{Root} + + p := argv.New(Root, args) + for p.Next() { + ev := p.Event() + switch ev.Kind { + case argv.KindCommand: + path = append(path, ev.Command) + case argv.KindFlag: + occurrences[ev.Flag.Key]++ + if ev.HasValue { + given[ev.Flag.Key] = append(given[ev.Flag.Key], ev.Value) + } else if given[ev.Flag.Key] == nil { + given[ev.Flag.Key] = []string{} + } + case argv.KindArg: + occurrences[ev.Arg.Key]++ + given[ev.Arg.Key] = append(given[ev.Arg.Key], ev.Value) + } + } + if e := p.Err(); e != nil { + t.Fatalf("binding failed: %v", e) + } + + lookup := func(k string) (string, bool) { v, ok := environ[k]; return v, ok } + + for _, cmd := range path { + for _, f := range cmd.Flags { + v, src := argv.Fill(Meta.Lookup(f.Key), given[f.Key], lookup) + if e := argv.Check(Meta.Lookup(f.Key), v, occurrences[f.Key]); e != nil && err == nil { + err = e + } + if f.Name == name { + values, source = v, src + } + } + for _, a := range cmd.Args { + v, src := argv.Fill(Meta.Lookup(a.Key), given[a.Key], lookup) + if e := argv.Check(Meta.Lookup(a.Key), v, 0); e != nil && err == nil { + err = e + } + if a.Name == name { + values, source = v, src + } + } + } + return values, source, err +} + +// mise declares `--manager` on `bootstrap packages import` with both a default +// and the single choice that default names, which makes it the one entry that +// exercises the whole cold table at once. +func TestAGeneratedDefaultFills(t *testing.T) { + values, source, err := resolve(t, "manager", + []string{"bootstrap", "packages", "import"}, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if source != argv.FromDefault { + t.Errorf("want the default, got %v", source) + } + if len(values) != 1 || values[0] != "brew" { + t.Errorf("want [brew], got %q", values) + } +} + +// The choices came from a `choices` block on the flag's *value*, which is a level +// of nesting the emitter has to read through. +func TestGeneratedChoicesAreEnforced(t *testing.T) { + if _, _, err := resolve(t, "log-level", []string{"--log-level", "debug"}, nil); err != nil { + t.Fatalf("a declared choice should be accepted: %v", err) + } + _, _, err := resolve(t, "log-level", []string{"--log-level", "chatty"}, nil) + if err == nil { + t.Fatal("a value outside the choices should be refused") + } + if err.Code != argv.CodeInvalidChoice || err.Name != "log-level" { + t.Errorf("want invalid_choice for log-level, got %q for %q", err.Code, err.Name) + } +} + +// The command line beats the default, which is the ordering the whole fallback +// rests on. +func TestTheCommandLineBeatsAGeneratedDefault(t *testing.T) { + values, source, err := resolve(t, "manager", + []string{"bootstrap", "packages", "import", "--manager", "brew"}, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if source != argv.FromArgv || len(values) != 1 || values[0] != "brew" { + t.Errorf("want brew from argv, got %q from %v", values, source) + } +} + +// The two tables are separate data joined only by key, and the emitter writes +// them in two passes. If those ever disagree, every rule reads the wrong +// declaration — so it is checked across all 989 entries rather than sampled. +func TestEveryEntryHasMetadataDescribingItself(t *testing.T) { + var checked int + var walk func(*argv.Command) + walk = func(c *argv.Command) { + // A command takes a key and has no cold half, so its slot is empty and + // Lookup should report nothing rather than a neighbour's entry. + if m := Meta.Lookup(c.Key); m != nil { + t.Errorf("command %q has metadata %+v", c.Name, m) + } + for _, f := range c.Flags { + m := Meta.Lookup(f.Key) + if m == nil { + t.Errorf("flag %q of %q has no metadata", f.Name, c.Name) + continue + } + if m.Name != f.Name || !m.Flag { + t.Errorf("flag %q of %q got metadata for %q (flag=%v)", + f.Name, c.Name, m.Name, m.Flag) + } + checked++ + } + for _, a := range c.Args { + m := Meta.Lookup(a.Key) + if m == nil { + t.Errorf("arg %q of %q has no metadata", a.Name, c.Name) + continue + } + if m.Name != a.Name || m.Flag { + t.Errorf("arg %q of %q got metadata for %q (flag=%v)", + a.Name, c.Name, m.Name, m.Flag) + } + checked++ + } + for _, sub := range c.Subcommands { + walk(sub) + } + } + walk(Root) + + if checked < 800 { + t.Errorf("only %d entries checked: the tables are probably truncated", checked) + } +} + +// Every key a relationship points at must exist, or the rule silently does +// nothing. The emitter drops names it cannot resolve, so this is where a spec +// that names a flag which does not exist would show up. +func TestRelationshipsPointAtRealEntries(t *testing.T) { + for i := range Meta { + m := &Meta[i] + for _, group := range [][]uint64{ + m.Conflicts, m.Overrides, m.RequiredUnless, m.RequiredIf, + } { + for _, key := range group { + if Meta.Lookup(key) == nil { + t.Errorf("%q points at key %d, which is not an entry", m.Name, key) + } + } + } + } +} + +// A value-less flag typed on the command line must not fall through to the +// fallbacks. mise's `--quiet` is one, and the distinction is invisible unless +// something asks: `Fill` reads a nil `given` as "the command line said nothing", +// so a helper that only records values reports a typed boolean as unset. +func TestATypedBooleanCountsAsGiven(t *testing.T) { + _, source, err := resolve(t, "quiet", []string{"--quiet"}, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if source != argv.FromArgv { + t.Errorf("a typed boolean should come from argv, got %v", source) + } + + _, source, err = resolve(t, "quiet", nil, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if source != argv.Unset { + t.Errorf("untyped, with nothing declared to fill it, should be unset: %v", source) + } +} diff --git a/go/internal/shadow/mise/tables.go b/go/internal/shadow/mise/tables.go index e75d010a7..f071cba44 100644 --- a/go/internal/shadow/mise/tables.go +++ b/go/internal/shadow/mise/tables.go @@ -3774,3 +3774,1063 @@ var cmdWhich = &argv.Command{ {Key: ArgWhichBinName, Name: "BIN_NAME"}, }, } + +// Meta is the cold table, read only by the rules that are decided once the +// last token has been read: required, choices, the env-then-default fallback, +// the var bounds, and the four that compare one entry against another. A parse +// never touches it. +// +// Indexed by key, so entry Key sits at Meta[Key-1]. A command's slot is empty: +// commands take keys too, and have no cold half. +var Meta = argv.Metadata{ + {}, + {Key: FlagContinueOnError, Name: "continue-on-error", Flag: true}, + {Key: FlagCd, Name: "cd", Flag: true}, + {Key: FlagEnv, Name: "env", Flag: true}, + {Key: FlagForce, Name: "force", Flag: true}, + {Key: FlagJobs, Name: "jobs", Flag: true}, + {Key: FlagDryRun, Name: "dry-run", Flag: true}, + {Key: FlagProfile, Name: "profile", Flag: true}, + {Key: FlagQuiet, Name: "quiet", Flag: true}, + {Key: FlagShell, Name: "shell", Flag: true}, + {Key: FlagTool, Name: "tool", Flag: true}, + {Key: FlagVerbose, Name: "verbose", Flag: true}, + {Key: FlagVersion, Name: "version", Flag: true}, + {Key: FlagYes, Name: "yes", Flag: true}, + {Key: FlagDebug, Name: "debug", Flag: true}, + {Key: FlagLogLevel, Name: "log-level", Flag: true, Choices: []string{"trace", "debug", "info", "warning", "error"}}, + {Key: FlagNoConfig, Name: "no-config", Flag: true}, + {Key: FlagNoEnv, Name: "no-env", Flag: true}, + {Key: FlagNoHooks, Name: "no-hooks", Flag: true}, + {Key: FlagNoTimings, Name: "no-timings", Flag: true}, + {Key: FlagOutput, Name: "output", Flag: true}, + {Key: FlagRaw, Name: "raw", Flag: true}, + {Key: FlagLocked, Name: "locked", Flag: true}, + {Key: FlagSilent, Name: "silent", Flag: true}, + {Key: FlagTimings, Name: "timings", Flag: true}, + {Key: FlagTrace, Name: "trace", Flag: true}, + {Key: ArgTask, Name: "TASK"}, + {Key: ArgTaskArgs, Name: "TASK_ARGS"}, + {Key: ArgTaskArgsLast, Name: "TASK_ARGS_LAST"}, + {}, + {Key: FlagActivateQuiet, Name: "quiet", Flag: true}, + {Key: FlagActivateShell, Name: "shell", Flag: true, Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: FlagActivateNoHookEnv, Name: "no-hook-env", Flag: true}, + {Key: FlagActivateShims, Name: "shims", Flag: true}, + {Key: FlagActivateStatus, Name: "status", Flag: true}, + {Key: ArgActivateShellType, Name: "SHELL_TYPE", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {}, + {Key: FlagToolAliasTool, Name: "tool", Flag: true}, + {Key: FlagToolAliasNoHeader, Name: "no-header", Flag: true}, + {}, + {Key: ArgToolAliasGetTool, Name: "TOOL", Required: true}, + {Key: ArgToolAliasGetAlias, Name: "ALIAS", Required: true}, + {}, + {Key: FlagToolAliasLsNoHeader, Name: "no-header", Flag: true}, + {Key: ArgToolAliasLsTool, Name: "TOOL"}, + {}, + {Key: ArgToolAliasSetTool, Name: "TOOL", Required: true}, + {Key: ArgToolAliasSetAlias, Name: "ALIAS", Required: true}, + {Key: ArgToolAliasSetValue, Name: "VALUE"}, + {}, + {Key: ArgToolAliasUnsetTool, Name: "TOOL", Required: true}, + {Key: ArgToolAliasUnsetAlias, Name: "ALIAS"}, + {}, + {Key: ArgAsdfArgs, Name: "ARGS"}, + {}, + {}, + {}, + {Key: FlagBinPathsBinNames, Name: "bin-names", Flag: true}, + {Key: FlagBinPathsJson, Name: "json", Flag: true}, + {Key: ArgBinPathsToolVersion, Name: "TOOL@VERSION"}, + {}, + {Key: FlagBootstrapDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapYes, Name: "yes", Flag: true}, + {Key: FlagBootstrapForceDotfiles, Name: "force-dotfiles", Flag: true}, + {Key: FlagBootstrapOnly, Name: "only", Flag: true, Choices: []string{"plugins", "packages", "accounts", "files", "services", "firewall", "compose", "repos", "dotfiles", "mise-shell-activate", "shell", "macos-defaults", "defaults", "macos-launchd-agents", "launchd", "linux-systemd-units", "systemd", "user", "tools", "task", "final-hook"}}, + {Key: FlagBootstrapPromptSecrets, Name: "prompt-secrets", Flag: true}, + {Key: FlagBootstrapSkip, Name: "skip", Flag: true, Choices: []string{"plugins", "packages", "accounts", "files", "services", "firewall", "compose", "repos", "dotfiles", "mise-shell-activate", "shell", "macos-defaults", "defaults", "macos-launchd-agents", "launchd", "linux-systemd-units", "systemd", "user", "tools", "task", "final-hook"}}, + {Key: FlagBootstrapUpdate, Name: "update", Flag: true}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {Key: FlagBootstrapAccountsApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapAccountsApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapAccountsStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapAccountsStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapComposeApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapComposeApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapComposeStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapComposeStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapDotfilesAddForce, Name: "force", Flag: true}, + {Key: FlagBootstrapDotfilesAddGlobal, Name: "global", Flag: true}, + {Key: FlagBootstrapDotfilesAddLocal, Name: "local", Flag: true}, + {Key: FlagBootstrapDotfilesAddMode, Name: "mode", Flag: true}, + {Key: FlagBootstrapDotfilesAddDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapDotfilesAddNoApply, Name: "no-apply", Flag: true}, + {Key: FlagBootstrapDotfilesAddPath, Name: "path", Flag: true}, + {Key: FlagBootstrapDotfilesAddSource, Name: "source", Flag: true}, + {Key: FlagBootstrapDotfilesAddYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapDotfilesAddTarget, Name: "TARGET", Required: true}, + {}, + {Key: FlagBootstrapDotfilesApplyForce, Name: "force", Flag: true}, + {Key: FlagBootstrapDotfilesApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapDotfilesApplyYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapDotfilesApplyTarget, Name: "TARGET"}, + {}, + {Key: FlagBootstrapDotfilesEditApply, Name: "apply", Flag: true}, + {Key: FlagBootstrapDotfilesEditMode, Name: "mode", Flag: true}, + {Key: FlagBootstrapDotfilesEditSource, Name: "source", Flag: true}, + {Key: FlagBootstrapDotfilesEditYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapDotfilesEditTarget, Name: "TARGET", Required: true}, + {}, + {Key: FlagBootstrapDotfilesStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapDotfilesStatusMissing, Name: "missing", Flag: true}, + {Key: ArgBootstrapDotfilesStatusTarget, Name: "TARGET"}, + {}, + {Key: FlagBootstrapDotfilesUnapplyForce, Name: "force", Flag: true}, + {Key: FlagBootstrapDotfilesUnapplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapDotfilesUnapplyYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapDotfilesUnapplyTarget, Name: "TARGET"}, + {}, + {}, + {Key: FlagBootstrapFilesApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapFilesApplyYes, Name: "yes", Flag: true}, + {Key: FlagBootstrapFilesApplyPromptSecrets, Name: "prompt-secrets", Flag: true}, + {}, + {Key: FlagBootstrapFilesStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapFilesStatusMissing, Name: "missing", Flag: true}, + {Key: FlagBootstrapFilesStatusPromptSecrets, Name: "prompt-secrets", Flag: true}, + {}, + {}, + {Key: FlagBootstrapFirewallApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapFirewallApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapFirewallStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapFirewallStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapLaunchdApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapLaunchdApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapLaunchdStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapLaunchdStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {}, + {Key: FlagBootstrapLinuxSystemdUnitsApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapLinuxSystemdUnitsApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapLinuxSystemdUnitsStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapLinuxSystemdUnitsStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {}, + {Key: FlagBootstrapMacosDefaultsApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapMacosDefaultsApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapMacosDefaultsStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapMacosDefaultsStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapMacosLaunchdAgentsApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapMacosLaunchdAgentsApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapMacosLaunchdAgentsStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapMacosLaunchdAgentsStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapMacosDefaultsApplyDryRun2, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapMacosDefaultsApplyYes2, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapMacosDefaultsStatusJson2, Name: "json", Flag: true}, + {Key: FlagBootstrapMacosDefaultsStatusMissing2, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapMiseShellActivateApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapMiseShellActivateApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapMiseShellActivateStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapMiseShellActivateStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapPackagesApplyManager, Name: "manager", Flag: true}, + {Key: FlagBootstrapPackagesApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapPackagesApplyYes, Name: "yes", Flag: true}, + {Key: FlagBootstrapPackagesApplyUpdate, Name: "update", Flag: true}, + {Key: ArgBootstrapPackagesApplyPackage, Name: "PACKAGE"}, + {}, + {}, + {Key: FlagBootstrapPackagesBrewTapLocal, Name: "local", Flag: true}, + {Key: FlagBootstrapPackagesBrewTapDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapPackagesBrewTapPath, Name: "path", Flag: true}, + {Key: ArgBootstrapPackagesBrewTapTap, Name: "TAP", Required: true}, + {Key: ArgBootstrapPackagesBrewTapUrl, Name: "URL"}, + {}, + {Key: FlagBootstrapPackagesBrewUntapLocal, Name: "local", Flag: true}, + {Key: FlagBootstrapPackagesBrewUntapDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapPackagesBrewUntapPath, Name: "path", Flag: true}, + {Key: ArgBootstrapPackagesBrewUntapTaps, Name: "TAPS", Required: true}, + {}, + {Key: FlagBootstrapPackagesImportEnv, Name: "env", Flag: true}, + {Key: FlagBootstrapPackagesImportGlobal, Name: "global", Flag: true}, + {Key: FlagBootstrapPackagesImportManager, Name: "manager", Flag: true, Choices: []string{"brew"}, Default: []string{"brew"}}, + {Key: FlagBootstrapPackagesImportAll, Name: "all", Flag: true}, + {Key: FlagBootstrapPackagesImportDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapPackagesImportPath, Name: "path", Flag: true}, + {}, + {Key: FlagBootstrapPackagesPruneManager, Name: "manager", Flag: true, Choices: []string{"brew"}, Default: []string{"brew"}}, + {Key: FlagBootstrapPackagesPruneDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapPackagesPruneYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapPackagesStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapPackagesStatusMissing, Name: "missing", Flag: true}, + {}, + {Key: FlagBootstrapPackagesUpgradeManager, Name: "manager", Flag: true}, + {Key: FlagBootstrapPackagesUpgradeDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapPackagesUpgradeYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapPackagesUpgradePackage, Name: "PACKAGE"}, + {}, + {Key: FlagBootstrapPackagesUseEnv, Name: "env", Flag: true}, + {Key: FlagBootstrapPackagesUseGlobal, Name: "global", Flag: true}, + {Key: FlagBootstrapPackagesUseDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapPackagesUsePath, Name: "path", Flag: true}, + {Key: FlagBootstrapPackagesUseYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapPackagesUsePackage, Name: "PACKAGE", Required: true}, + {}, + {Key: FlagBootstrapPlanJson, Name: "json", Flag: true}, + {Key: FlagBootstrapPlanDetailedExitcode, Name: "detailed-exitcode", Flag: true}, + {Key: FlagBootstrapPlanPromptSecrets, Name: "prompt-secrets", Flag: true}, + {}, + {}, + {Key: FlagBootstrapPluginsApplyDryRun, Name: "dry-run", Flag: true}, + {}, + {Key: FlagBootstrapPluginsStatusMissing, Name: "missing", Flag: true}, + {}, + {Key: FlagBootstrapRemoteAll, Name: "all", Flag: true}, + {Key: FlagBootstrapRemoteBootstrapCommand, Name: "bootstrap-command", Flag: true}, + {Key: FlagBootstrapRemoteConnectTimeout, Name: "connect-timeout", Flag: true, Default: []string{"10"}}, + {Key: FlagBootstrapRemoteExclude, Name: "exclude", Flag: true}, + {Key: FlagBootstrapRemoteFailFast, Name: "fail-fast", Flag: true}, + {Key: FlagBootstrapRemoteForceDotfiles, Name: "force-dotfiles", Flag: true}, + {Key: FlagBootstrapRemoteHost, Name: "host", Flag: true}, + {Key: FlagBootstrapRemoteIdentityFile, Name: "identity-file", Flag: true}, + {Key: FlagBootstrapRemoteDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapRemoteKeepStaging, Name: "keep-staging", Flag: true}, + {Key: FlagBootstrapRemoteMiseBin, Name: "mise-bin", Flag: true}, + {Key: FlagBootstrapRemoteOnly, Name: "only", Flag: true, Choices: []string{"plugins", "packages", "accounts", "files", "services", "firewall", "compose", "repos", "dotfiles", "mise-shell-activate", "shell", "macos-defaults", "defaults", "macos-launchd-agents", "launchd", "linux-systemd-units", "systemd", "user", "tools", "task", "final-hook"}}, + {Key: FlagBootstrapRemotePort, Name: "port", Flag: true}, + {Key: FlagBootstrapRemotePromptSecrets, Name: "prompt-secrets", Flag: true}, + {Key: FlagBootstrapRemoteRemoteMise, Name: "remote-mise", Flag: true}, + {Key: FlagBootstrapRemoteSkip, Name: "skip", Flag: true, Choices: []string{"plugins", "packages", "accounts", "files", "services", "firewall", "compose", "repos", "dotfiles", "mise-shell-activate", "shell", "macos-defaults", "defaults", "macos-launchd-agents", "launchd", "linux-systemd-units", "systemd", "user", "tools", "task", "final-hook"}}, + {Key: FlagBootstrapRemoteSource, Name: "source", Flag: true}, + {Key: FlagBootstrapRemoteSshOption, Name: "ssh-option", Flag: true}, + {Key: FlagBootstrapRemoteTag, Name: "tag", Flag: true}, + {Key: FlagBootstrapRemoteUpdate, Name: "update", Flag: true}, + {Key: FlagBootstrapRemoteYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapRemoteTarget, Name: "TARGET"}, + {}, + {}, + {Key: FlagBootstrapReposApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapReposApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapReposExecContinueOnError, Name: "continue-on-error", Flag: true}, + {Key: FlagBootstrapReposExecDryRun, Name: "dry-run", Flag: true}, + {Key: ArgBootstrapReposExecPath, Name: "PATH"}, + {Key: ArgBootstrapReposExecCommand, Name: "COMMAND", Required: true}, + {}, + {Key: FlagBootstrapReposStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapReposStatusMissing, Name: "missing", Flag: true}, + {}, + {Key: FlagBootstrapReposUpdateDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapReposUpdateYes, Name: "yes", Flag: true}, + {Key: ArgBootstrapReposUpdatePath, Name: "PATH"}, + {}, + {}, + {Key: FlagBootstrapSecretsStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapSecretsStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapServicesApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapServicesApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapServicesStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapServicesStatusMissing, Name: "missing", Flag: true}, + {}, + {Key: FlagBootstrapStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapStatusMissing, Name: "missing", Flag: true}, + {Key: FlagBootstrapStatusPromptSecrets, Name: "prompt-secrets", Flag: true}, + {}, + {}, + {Key: FlagBootstrapSystemdApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapSystemdApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapSystemdStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapSystemdStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagBootstrapUserApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagBootstrapUserApplyYes, Name: "yes", Flag: true}, + {}, + {Key: FlagBootstrapUserStatusJson, Name: "json", Flag: true}, + {Key: FlagBootstrapUserStatusMissing, Name: "missing", Flag: true}, + {}, + {}, + {Key: FlagCacheClearOutdate, Name: "outdate", Flag: true}, + {Key: FlagCacheClearTask, Name: "task", Flag: true}, + {Key: ArgCacheClearTool, Name: "TOOL"}, + {}, + {}, + {Key: FlagCachePruneVerbose, Name: "verbose", Flag: true}, + {Key: FlagCachePruneDryRun, Name: "dry-run", Flag: true}, + {Key: ArgCachePruneTool, Name: "TOOL"}, + {}, + {Key: FlagCacheTaskJson, Name: "json", Flag: true}, + {Key: ArgCacheTaskTask, Name: "TASK", Required: true}, + {}, + {Key: FlagCompletionShell, Name: "shell", Flag: true, Choices: []string{"bash", "fish", "powershell", "zsh"}}, + {Key: FlagCompletionIncludeBashCompletionLib, Name: "include-bash-completion-lib", Flag: true}, + {Key: FlagCompletionUsage, Name: "usage", Flag: true}, + {Key: ArgCompletionShell, Name: "SHELL", Choices: []string{"bash", "fish", "powershell", "zsh"}}, + {}, + {Key: FlagConfigJson, Name: "json", Flag: true}, + {Key: FlagConfigNoHeader, Name: "no-header", Flag: true}, + {Key: FlagConfigTrackedConfigs, Name: "tracked-configs", Flag: true}, + {}, + {Key: FlagConfigGetFile, Name: "file", Flag: true}, + {Key: ArgConfigGetKey, Name: "KEY"}, + {}, + {Key: FlagConfigLsJson, Name: "json", Flag: true}, + {Key: FlagConfigLsNoHeader, Name: "no-header", Flag: true}, + {Key: FlagConfigLsTrackedConfigs, Name: "tracked-configs", Flag: true}, + {}, + {Key: FlagConfigSetFile, Name: "file", Flag: true}, + {Key: FlagConfigSetType, Name: "type", Flag: true, Choices: []string{"infer", "string", "integer", "float", "bool", "list", "set"}, Default: []string{"infer"}}, + {Key: ArgConfigSetKey, Name: "KEY", Required: true}, + {Key: ArgConfigSetValue, Name: "VALUE"}, + {}, + {Key: ArgCurrentPlugin, Name: "PLUGIN"}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {Key: FlagDotfilesAddForce, Name: "force", Flag: true}, + {Key: FlagDotfilesAddGlobal, Name: "global", Flag: true}, + {Key: FlagDotfilesAddLocal, Name: "local", Flag: true}, + {Key: FlagDotfilesAddMode, Name: "mode", Flag: true}, + {Key: FlagDotfilesAddDryRun, Name: "dry-run", Flag: true}, + {Key: FlagDotfilesAddNoApply, Name: "no-apply", Flag: true}, + {Key: FlagDotfilesAddPath, Name: "path", Flag: true}, + {Key: FlagDotfilesAddSource, Name: "source", Flag: true}, + {Key: FlagDotfilesAddYes, Name: "yes", Flag: true}, + {Key: ArgDotfilesAddTarget, Name: "TARGET", Required: true}, + {}, + {Key: FlagDotfilesApplyForce, Name: "force", Flag: true}, + {Key: FlagDotfilesApplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagDotfilesApplyYes, Name: "yes", Flag: true}, + {Key: ArgDotfilesApplyTarget, Name: "TARGET"}, + {}, + {Key: FlagDotfilesEditApply, Name: "apply", Flag: true}, + {Key: FlagDotfilesEditMode, Name: "mode", Flag: true}, + {Key: FlagDotfilesEditSource, Name: "source", Flag: true}, + {Key: FlagDotfilesEditYes, Name: "yes", Flag: true}, + {Key: ArgDotfilesEditTarget, Name: "TARGET", Required: true}, + {}, + {Key: FlagDotfilesStatusJson, Name: "json", Flag: true}, + {Key: FlagDotfilesStatusMissing, Name: "missing", Flag: true}, + {Key: ArgDotfilesStatusTarget, Name: "TARGET"}, + {}, + {Key: FlagDotfilesUnapplyForce, Name: "force", Flag: true}, + {Key: FlagDotfilesUnapplyDryRun, Name: "dry-run", Flag: true}, + {Key: FlagDotfilesUnapplyYes, Name: "yes", Flag: true}, + {Key: ArgDotfilesUnapplyTarget, Name: "TARGET"}, + {}, + {Key: FlagDoctorJson, Name: "json", Flag: true}, + {}, + {Key: FlagDoctorPathFull, Name: "full", Flag: true}, + {}, + {Key: FlagEnShell, Name: "shell", Flag: true}, + {Key: ArgEnDir, Name: "DIR", Default: []string{"."}}, + {}, + {Key: FlagEnvDotenv, Name: "dotenv", Flag: true}, + {Key: FlagEnvJson, Name: "json", Flag: true}, + {Key: FlagEnvShell, Name: "shell", Flag: true, Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: FlagEnvJsonExtended, Name: "json-extended", Flag: true}, + {Key: FlagEnvRedacted, Name: "redacted", Flag: true}, + {Key: FlagEnvValues, Name: "values", Flag: true}, + {Key: ArgEnvToolVersion, Name: "TOOL@VERSION"}, + {}, + {Key: FlagExecCommand, Name: "command", Flag: true}, + {Key: FlagExecJobs, Name: "jobs", Flag: true}, + {Key: FlagExecAllowEnv, Name: "allow-env", Flag: true}, + {Key: FlagExecAllowNet, Name: "allow-net", Flag: true}, + {Key: FlagExecAllowRead, Name: "allow-read", Flag: true}, + {Key: FlagExecAllowWrite, Name: "allow-write", Flag: true}, + {Key: FlagExecDenyAll, Name: "deny-all", Flag: true}, + {Key: FlagExecDenyEnv, Name: "deny-env", Flag: true}, + {Key: FlagExecDenyNet, Name: "deny-net", Flag: true}, + {Key: FlagExecDenyRead, Name: "deny-read", Flag: true}, + {Key: FlagExecDenyWrite, Name: "deny-write", Flag: true}, + {Key: FlagExecFreshEnv, Name: "fresh-env", Flag: true}, + {Key: FlagExecNoDeps, Name: "no-deps", Flag: true}, + {Key: FlagExecRaw, Name: "raw", Flag: true}, + {Key: ArgExecToolVersion, Name: "TOOL@VERSION"}, + {Key: ArgExecCommand, Name: "COMMAND"}, + {}, + {Key: FlagFmtAll, Name: "all", Flag: true}, + {Key: FlagFmtCheck, Name: "check", Flag: true}, + {Key: FlagFmtStdin, Name: "stdin", Flag: true}, + {}, + {}, + {Key: FlagGenerateBootstrapLocalize, Name: "localize", Flag: true}, + {Key: FlagGenerateBootstrapVersion, Name: "version", Flag: true}, + {Key: FlagGenerateBootstrapWrite, Name: "write", Flag: true}, + {Key: FlagGenerateBootstrapLocalizedDir, Name: "localized-dir", Flag: true, Default: []string{".mise"}}, + {}, + {Key: FlagGenerateConfigGlobal, Name: "global", Flag: true}, + {Key: FlagGenerateConfigDryRun, Name: "dry-run", Flag: true}, + {Key: FlagGenerateConfigToolVersions, Name: "tool-versions", Flag: true}, + {Key: ArgGenerateConfigPath, Name: "PATH"}, + {}, + {Key: FlagGenerateDevcontainerImage, Name: "image", Flag: true}, + {Key: FlagGenerateDevcontainerMountMiseData, Name: "mount-mise-data", Flag: true}, + {Key: FlagGenerateDevcontainerName, Name: "name", Flag: true}, + {Key: FlagGenerateDevcontainerWrite, Name: "write", Flag: true}, + {}, + {Key: FlagGenerateGitPreCommitTask, Name: "task", Flag: true, Default: []string{"pre-commit"}}, + {Key: FlagGenerateGitPreCommitWrite, Name: "write", Flag: true}, + {Key: FlagGenerateGitPreCommitHook, Name: "hook", Flag: true, Default: []string{"pre-commit"}}, + {}, + {Key: FlagGenerateGithubActionTask, Name: "task", Flag: true, Default: []string{"ci"}}, + {Key: FlagGenerateGithubActionWrite, Name: "write", Flag: true}, + {Key: FlagGenerateGithubActionName, Name: "name", Flag: true, Default: []string{"ci"}}, + {}, + {Key: FlagGenerateTaskDocsInject, Name: "inject", Flag: true}, + {Key: FlagGenerateTaskDocsIndex, Name: "index", Flag: true}, + {Key: FlagGenerateTaskDocsMulti, Name: "multi", Flag: true}, + {Key: FlagGenerateTaskDocsOutput, Name: "output", Flag: true}, + {Key: FlagGenerateTaskDocsRoot, Name: "root", Flag: true}, + {Key: FlagGenerateTaskDocsStyle, Name: "style", Flag: true, Choices: []string{"simple", "detailed"}, Default: []string{"simple"}}, + {}, + {Key: FlagGenerateTaskStubsDir, Name: "dir", Flag: true, Default: []string{"bin"}}, + {Key: FlagGenerateTaskStubsMiseBin, Name: "mise-bin", Flag: true, Default: []string{"mise"}}, + {}, + {Key: FlagGenerateToolStubBin, Name: "bin", Flag: true}, + {Key: FlagGenerateToolStubBootstrap, Name: "bootstrap", Flag: true}, + {Key: FlagGenerateToolStubBootstrapVersion, Name: "bootstrap-version", Flag: true}, + {Key: FlagGenerateToolStubFetch, Name: "fetch", Flag: true}, + {Key: FlagGenerateToolStubHttp, Name: "http", Flag: true, Default: []string{"http"}}, + {Key: FlagGenerateToolStubLock, Name: "lock", Flag: true}, + {Key: FlagGenerateToolStubPlatformBin, Name: "platform-bin", Flag: true}, + {Key: FlagGenerateToolStubPlatformUrl, Name: "platform-url", Flag: true}, + {Key: FlagGenerateToolStubSkipDownload, Name: "skip-download", Flag: true}, + {Key: FlagGenerateToolStubUrl, Name: "url", Flag: true}, + {Key: FlagGenerateToolStubVersion, Name: "version", Flag: true, Default: []string{"latest"}}, + {Key: ArgGenerateToolStubOutput, Name: "OUTPUT", Required: true}, + {}, + {}, + {Key: FlagGithubTokenOauth, Name: "oauth", Flag: true}, + {Key: FlagGithubTokenRaw, Name: "raw", Flag: true}, + {Key: FlagGithubTokenRefresh, Name: "refresh", Flag: true}, + {Key: FlagGithubTokenUnmask, Name: "unmask", Flag: true}, + {Key: ArgGithubTokenHost, Name: "HOST", Default: []string{"github.com"}}, + {}, + {Key: FlagGlobalFuzzy, Name: "fuzzy", Flag: true}, + {Key: FlagGlobalPath, Name: "path", Flag: true}, + {Key: FlagGlobalPin, Name: "pin", Flag: true}, + {Key: FlagGlobalRemove, Name: "remove", Flag: true}, + {Key: ArgGlobalToolVersion, Name: "TOOL@VERSION"}, + {}, + {Key: FlagHookEnvForce, Name: "force", Flag: true}, + {Key: FlagHookEnvQuiet, Name: "quiet", Flag: true}, + {Key: FlagHookEnvShell, Name: "shell", Flag: true, Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: FlagHookEnvReason, Name: "reason", Flag: true, Choices: []string{"precmd", "chpwd"}}, + {Key: FlagHookEnvStatus, Name: "status", Flag: true}, + {}, + {Key: FlagHookNotFoundShell, Name: "shell", Flag: true, Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: ArgHookNotFoundBin, Name: "BIN", Required: true}, + {}, + {Key: FlagImplodeDryRun, Name: "dry-run", Flag: true}, + {Key: FlagImplodeConfig, Name: "config", Flag: true}, + {}, + {Key: FlagEditGlobal, Name: "global", Flag: true}, + {Key: FlagEditDryRun, Name: "dry-run", Flag: true}, + {Key: FlagEditToolVersions, Name: "tool-versions", Flag: true}, + {Key: ArgEditPath, Name: "PATH"}, + {}, + {Key: FlagInstallForce, Name: "force", Flag: true}, + {Key: FlagInstallJobs, Name: "jobs", Flag: true}, + {Key: FlagInstallDryRun, Name: "dry-run", Flag: true}, + {Key: FlagInstallVerbose, Name: "verbose", Flag: true}, + {Key: FlagInstallDryRunCode, Name: "dry-run-code", Flag: true}, + {Key: FlagInstallMinimumReleaseAge, Name: "minimum-release-age", Flag: true}, + {Key: FlagInstallMonorepo, Name: "monorepo", Flag: true}, + {Key: FlagInstallRaw, Name: "raw", Flag: true}, + {Key: FlagInstallShared, Name: "shared", Flag: true}, + {Key: FlagInstallSystem, Name: "system", Flag: true}, + {Key: ArgInstallToolVersion, Name: "TOOL@VERSION"}, + {}, + {Key: ArgInstallIntoToolVersion, Name: "TOOL@VERSION", Required: true}, + {Key: ArgInstallIntoPath, Name: "PATH", Required: true}, + {}, + {Key: FlagLatestInstalled, Name: "installed", Flag: true}, + {Key: FlagLatestMinimumReleaseAge, Name: "minimum-release-age", Flag: true}, + {Key: ArgLatestToolVersion, Name: "TOOL@VERSION", Required: true}, + {Key: ArgLatestAsdfVersion, Name: "ASDF_VERSION"}, + {}, + {Key: FlagLinkForce, Name: "force", Flag: true}, + {Key: ArgLinkToolVersion, Name: "TOOL@VERSION", Required: true}, + {Key: ArgLinkPath, Name: "PATH", Required: true}, + {}, + {Key: FlagLocalParent, Name: "parent", Flag: true}, + {Key: FlagLocalFuzzy, Name: "fuzzy", Flag: true}, + {Key: FlagLocalPath, Name: "path", Flag: true}, + {Key: FlagLocalPin, Name: "pin", Flag: true}, + {Key: FlagLocalRemove, Name: "remove", Flag: true}, + {Key: ArgLocalToolVersion, Name: "TOOL@VERSION"}, + {}, + {Key: FlagLockGlobal, Name: "global", Flag: true}, + {Key: FlagLockJobs, Name: "jobs", Flag: true}, + {Key: FlagLockDryRun, Name: "dry-run", Flag: true}, + {Key: FlagLockPlatform, Name: "platform", Flag: true}, + {Key: FlagLockBump, Name: "bump", Flag: true}, + {Key: FlagLockJson, Name: "json", Flag: true}, + {Key: FlagLockLocal, Name: "local", Flag: true}, + {Key: FlagLockMinimumReleaseAge, Name: "minimum-release-age", Flag: true}, + {Key: ArgLockTool, Name: "TOOL"}, + {}, + {Key: FlagLsCurrent, Name: "current", Flag: true}, + {Key: FlagLsGlobal, Name: "global", Flag: true}, + {Key: FlagLsInstalled, Name: "installed", Flag: true}, + {Key: FlagLsJson, Name: "json", Flag: true}, + {Key: FlagLsLocal, Name: "local", Flag: true}, + {Key: FlagLsMissing, Name: "missing", Flag: true}, + {Key: FlagLsOffline, Name: "offline", Flag: true}, + {Key: FlagLsPlugin, Name: "plugin", Flag: true}, + {Key: FlagLsAllSources, Name: "all-sources", Flag: true}, + {Key: FlagLsMonorepo, Name: "monorepo", Flag: true}, + {Key: FlagLsNoHeader, Name: "no-header", Flag: true}, + {Key: FlagLsOutdated, Name: "outdated", Flag: true}, + {Key: FlagLsPrefix, Name: "prefix", Flag: true}, + {Key: FlagLsPrunable, Name: "prunable", Flag: true}, + {Key: ArgLsInstalledTool, Name: "INSTALLED_TOOL"}, + {}, + {Key: FlagLsRemoteAll, Name: "all", Flag: true}, + {Key: FlagLsRemoteMinimumReleaseAge, Name: "minimum-release-age", Flag: true}, + {Key: FlagLsRemoteJson, Name: "json", Flag: true}, + {Key: FlagLsRemoteNoVersionsHost, Name: "no-versions-host", Flag: true}, + {Key: FlagLsRemotePrerelease, Name: "prerelease", Flag: true}, + {Key: FlagLsRemoteStrictMetadata, Name: "strict-metadata", Flag: true}, + {Key: ArgLsRemoteToolVersion, Name: "TOOL@VERSION"}, + {Key: ArgLsRemotePrefix, Name: "PREFIX"}, + {}, + {}, + {}, + {Key: FlagOciBuildCopy, Name: "copy", Flag: true}, + {Key: FlagOciBuildOutput, Name: "output", Flag: true, Default: []string{"./mise-oci"}}, + {Key: FlagOciBuildFrom, Name: "from", Flag: true}, + {Key: FlagOciBuildIncludeGlobal, Name: "include-global", Flag: true}, + {Key: FlagOciBuildTag, Name: "tag", Flag: true}, + {Key: FlagOciBuildMountPoint, Name: "mount-point", Flag: true}, + {Key: FlagOciBuildNoMise, Name: "no-mise", Flag: true}, + {Key: FlagOciBuildOwner, Name: "owner", Flag: true}, + {}, + {Key: FlagOciPushCacheFrom, Name: "cache-from", Flag: true}, + {Key: FlagOciPushFrom, Name: "from", Flag: true}, + {Key: FlagOciPushImageDir, Name: "image-dir", Flag: true}, + {Key: FlagOciPushIncludeGlobal, Name: "include-global", Flag: true}, + {Key: FlagOciPushMountPoint, Name: "mount-point", Flag: true}, + {Key: FlagOciPushNoCache, Name: "no-cache", Flag: true}, + {Key: FlagOciPushNoMise, Name: "no-mise", Flag: true}, + {Key: FlagOciPushOwner, Name: "owner", Flag: true}, + {Key: FlagOciPushUpdateIndex, Name: "update-index", Flag: true}, + {Key: ArgOciPushRef, Name: "REF", Required: true}, + {}, + {Key: FlagOciRunEngine, Name: "engine", Flag: true, Choices: []string{"auto", "podman", "docker"}, Default: []string{"auto"}}, + {Key: FlagOciRunFrom, Name: "from", Flag: true}, + {Key: FlagOciRunImageDir, Name: "image-dir", Flag: true}, + {Key: FlagOciRunIncludeGlobal, Name: "include-global", Flag: true}, + {Key: FlagOciRunKeep, Name: "keep", Flag: true}, + {Key: FlagOciRunMountPoint, Name: "mount-point", Flag: true}, + {Key: FlagOciRunNoMise, Name: "no-mise", Flag: true}, + {Key: FlagOciRunOwner, Name: "owner", Flag: true}, + {Key: FlagOciRunVolume, Name: "volume", Flag: true}, + {Key: FlagOciRunEnv, Name: "env", Flag: true}, + {Key: FlagOciRunInteractive, Name: "interactive", Flag: true}, + {Key: FlagOciRunTty, Name: "tty", Flag: true}, + {Key: FlagOciRunWorkdir, Name: "workdir", Flag: true}, + {Key: ArgOciRunCmd, Name: "CMD"}, + {}, + {Key: FlagOutdatedJson, Name: "json", Flag: true}, + {Key: FlagOutdatedBump, Name: "bump", Flag: true}, + {Key: FlagOutdatedInactive, Name: "inactive", Flag: true}, + {Key: FlagOutdatedLocal, Name: "local", Flag: true}, + {Key: FlagOutdatedMonorepo, Name: "monorepo", Flag: true}, + {Key: FlagOutdatedNoHeader, Name: "no-header", Flag: true}, + {Key: ArgOutdatedToolVersion, Name: "TOOL@VERSION"}, + {}, + {Key: FlagPatronsJson, Name: "json", Flag: true}, + {Key: FlagPatronsRefresh, Name: "refresh", Flag: true}, + {}, + {Key: FlagPluginsAll, Name: "all", Flag: true}, + {Key: FlagPluginsCore, Name: "core", Flag: true}, + {Key: FlagPluginsUrls, Name: "urls", Flag: true}, + {Key: FlagPluginsRefs, Name: "refs", Flag: true}, + {Key: FlagPluginsUser, Name: "user", Flag: true}, + {}, + {Key: FlagPluginsInstallAll, Name: "all", Flag: true}, + {Key: FlagPluginsInstallForce, Name: "force", Flag: true}, + {Key: FlagPluginsInstallJobs, Name: "jobs", Flag: true}, + {Key: FlagPluginsInstallVerbose, Name: "verbose", Flag: true}, + {Key: ArgPluginsInstallNewPlugin, Name: "NEW_PLUGIN"}, + {Key: ArgPluginsInstallGitUrl, Name: "GIT_URL"}, + {Key: ArgPluginsInstallRest, Name: "REST"}, + {}, + {Key: FlagPluginsLinkForce, Name: "force", Flag: true}, + {Key: ArgPluginsLinkName, Name: "NAME", Required: true}, + {Key: ArgPluginsLinkDir, Name: "DIR"}, + {}, + {Key: FlagPluginsLsAll, Name: "all", Flag: true}, + {Key: FlagPluginsLsCore, Name: "core", Flag: true}, + {Key: FlagPluginsLsOutdated, Name: "outdated", Flag: true}, + {Key: FlagPluginsLsUrls, Name: "urls", Flag: true}, + {Key: FlagPluginsLsRefs, Name: "refs", Flag: true}, + {Key: FlagPluginsLsUser, Name: "user", Flag: true}, + {}, + {Key: FlagPluginsLsRemoteUrls, Name: "urls", Flag: true}, + {Key: FlagPluginsLsRemoteOnlyNames, Name: "only-names", Flag: true}, + {}, + {Key: FlagPluginsUninstallAll, Name: "all", Flag: true}, + {Key: FlagPluginsUninstallPurge, Name: "purge", Flag: true}, + {Key: ArgPluginsUninstallPlugin, Name: "PLUGIN"}, + {}, + {Key: FlagPluginsUpdateJobs, Name: "jobs", Flag: true}, + {Key: ArgPluginsUpdatePlugin, Name: "PLUGIN"}, + {}, + {Key: FlagDepsExplain, Name: "explain", Flag: true}, + {Key: FlagDepsForce, Name: "force", Flag: true}, + {Key: FlagDepsDryRun, Name: "dry-run", Flag: true}, + {Key: FlagDepsList, Name: "list", Flag: true}, + {Key: FlagDepsMonorepo, Name: "monorepo", Flag: true}, + {Key: FlagDepsOnly, Name: "only", Flag: true}, + {Key: FlagDepsSkip, Name: "skip", Flag: true}, + {Key: ArgDepsProvider, Name: "PROVIDER"}, + {}, + {Key: FlagDepsAddDev, Name: "dev", Flag: true}, + {Key: ArgDepsAddPackages, Name: "PACKAGES", Required: true}, + {}, + {Key: FlagDepsInstallExplain, Name: "explain", Flag: true}, + {Key: FlagDepsInstallForce, Name: "force", Flag: true}, + {Key: FlagDepsInstallDryRun, Name: "dry-run", Flag: true}, + {Key: FlagDepsInstallList, Name: "list", Flag: true}, + {Key: FlagDepsInstallMonorepo, Name: "monorepo", Flag: true}, + {Key: FlagDepsInstallOnly, Name: "only", Flag: true}, + {Key: FlagDepsInstallSkip, Name: "skip", Flag: true}, + {Key: ArgDepsInstallProvider, Name: "PROVIDER"}, + {}, + {Key: ArgDepsRemovePackages, Name: "PACKAGES", Required: true}, + {}, + {Key: FlagPruneDryRun, Name: "dry-run", Flag: true}, + {Key: FlagPruneConfigs, Name: "configs", Flag: true}, + {Key: FlagPruneDryRunCode, Name: "dry-run-code", Flag: true}, + {Key: FlagPruneMonorepo, Name: "monorepo", Flag: true}, + {Key: FlagPruneTools, Name: "tools", Flag: true}, + {Key: ArgPruneInstalledTool, Name: "INSTALLED_TOOL"}, + {}, + {Key: FlagRegistryBackend, Name: "backend", Flag: true}, + {Key: FlagRegistryComplete, Name: "complete", Flag: true}, + {Key: FlagRegistryHideAliased, Name: "hide-aliased", Flag: true}, + {Key: FlagRegistryJson, Name: "json", Flag: true}, + {Key: FlagRegistrySecurity, Name: "security", Flag: true}, + {Key: ArgRegistryName, Name: "NAME"}, + {}, + {}, + {Key: FlagReshimForce, Name: "force", Flag: true}, + {Key: ArgReshimTool, Name: "TOOL"}, + {Key: ArgReshimVersion, Name: "VERSION"}, + {}, + {Key: FlagRunAffected, Name: "affected", Flag: true}, + {Key: FlagRunAffectedBase, Name: "affected-base", Flag: true}, + {Key: FlagRunAffectedExplain, Name: "affected-explain", Flag: true}, + {Key: FlagRunAffectedHead, Name: "affected-head", Flag: true}, + {Key: FlagRunAffectedJson, Name: "affected-json", Flag: true}, + {Key: FlagRunContinueOnError, Name: "continue-on-error", Flag: true}, + {Key: FlagRunCd, Name: "cd", Flag: true}, + {Key: FlagRunForce, Name: "force", Flag: true}, + {Key: FlagRunJobs, Name: "jobs", Flag: true}, + {Key: FlagRunDryRun, Name: "dry-run", Flag: true}, + {Key: FlagRunOutput, Name: "output", Flag: true}, + {Key: FlagRunQuiet, Name: "quiet", Flag: true}, + {Key: FlagRunRaw, Name: "raw", Flag: true}, + {Key: FlagRunShell, Name: "shell", Flag: true}, + {Key: FlagRunSilent, Name: "silent", Flag: true}, + {Key: FlagRunTool, Name: "tool", Flag: true}, + {Key: FlagRunAllowEnv, Name: "allow-env", Flag: true}, + {Key: FlagRunAllowNet, Name: "allow-net", Flag: true}, + {Key: FlagRunAllowRead, Name: "allow-read", Flag: true}, + {Key: FlagRunAllowWrite, Name: "allow-write", Flag: true}, + {Key: FlagRunDenyAll, Name: "deny-all", Flag: true}, + {Key: FlagRunDenyEnv, Name: "deny-env", Flag: true}, + {Key: FlagRunDenyNet, Name: "deny-net", Flag: true}, + {Key: FlagRunDenyRead, Name: "deny-read", Flag: true}, + {Key: FlagRunDenyWrite, Name: "deny-write", Flag: true}, + {Key: FlagRunFreshEnv, Name: "fresh-env", Flag: true}, + {Key: FlagRunNoCache, Name: "no-cache", Flag: true}, + {Key: FlagRunNoDeps, Name: "no-deps", Flag: true}, + {Key: FlagRunNoTimings, Name: "no-timings", Flag: true}, + {Key: FlagRunSkipDeps, Name: "skip-deps", Flag: true}, + {Key: FlagRunSkipTools, Name: "skip-tools", Flag: true}, + {Key: FlagRunTaskCache, Name: "task-cache", Flag: true, Choices: []string{"read-write", "read-only", "write-only", "off", "local-only"}, Default: []string{"read-write"}}, + {Key: FlagRunTaskCacheExplain, Name: "task-cache-explain", Flag: true}, + {Key: FlagRunTaskCacheExplainJson, Name: "task-cache-explain-json", Flag: true}, + {Key: FlagRunTaskCacheStats, Name: "task-cache-stats", Flag: true}, + {Key: FlagRunTimeout, Name: "timeout", Flag: true}, + {Key: FlagRunTimings, Name: "timings", Flag: true}, + {}, + {Key: FlagSearchInteractive, Name: "interactive", Flag: true}, + {Key: FlagSearchMatchType, Name: "match-type", Flag: true, Choices: []string{"equal", "contains", "fuzzy"}, Default: []string{"fuzzy"}}, + {Key: FlagSearchNoHeader, Name: "no-header", Flag: true}, + {Key: ArgSearchName, Name: "NAME"}, + {}, + {Key: FlagSelfUpdateForce, Name: "force", Flag: true}, + {Key: FlagSelfUpdateYes, Name: "yes", Flag: true}, + {Key: FlagSelfUpdateNoPlugins, Name: "no-plugins", Flag: true}, + {Key: ArgSelfUpdateVersion, Name: "VERSION"}, + {}, + {Key: FlagSetEnv, Name: "env", Flag: true}, + {Key: FlagSetGlobal, Name: "global", Flag: true}, + {Key: FlagSetAgeEncrypt, Name: "age-encrypt", Flag: true}, + {Key: FlagSetAgeKeyFile, Name: "age-key-file", Flag: true}, + {Key: FlagSetAgeRecipient, Name: "age-recipient", Flag: true}, + {Key: FlagSetAgeSshRecipient, Name: "age-ssh-recipient", Flag: true}, + {Key: FlagSetComplete, Name: "complete", Flag: true}, + {Key: FlagSetFile, Name: "file", Flag: true}, + {Key: FlagSetNoRedact, Name: "no-redact", Flag: true}, + {Key: FlagSetPrompt, Name: "prompt", Flag: true}, + {Key: FlagSetRemove, Name: "remove", Flag: true}, + {Key: FlagSetStdin, Name: "stdin", Flag: true}, + {Key: ArgSetEnvVar, Name: "ENV_VAR"}, + {}, + {Key: FlagSettingsAll, Name: "all", Flag: true}, + {Key: FlagSettingsJson, Name: "json", Flag: true}, + {Key: FlagSettingsLocal, Name: "local", Flag: true}, + {Key: FlagSettingsToml, Name: "toml", Flag: true}, + {Key: FlagSettingsComplete, Name: "complete", Flag: true}, + {Key: FlagSettingsJsonExtended, Name: "json-extended", Flag: true}, + {Key: ArgSettingsSetting, Name: "SETTING"}, + {Key: ArgSettingsValue, Name: "VALUE"}, + {}, + {Key: FlagSettingsAddLocal, Name: "local", Flag: true}, + {Key: ArgSettingsAddSetting, Name: "SETTING", Required: true}, + {Key: ArgSettingsAddValue, Name: "VALUE"}, + {}, + {Key: FlagSettingsGetLocal, Name: "local", Flag: true}, + {Key: ArgSettingsGetSetting, Name: "SETTING", Required: true}, + {}, + {Key: FlagSettingsLsAll, Name: "all", Flag: true}, + {Key: FlagSettingsLsJson, Name: "json", Flag: true}, + {Key: FlagSettingsLsLocal, Name: "local", Flag: true}, + {Key: FlagSettingsLsToml, Name: "toml", Flag: true}, + {Key: FlagSettingsLsComplete, Name: "complete", Flag: true}, + {Key: FlagSettingsLsJsonExtended, Name: "json-extended", Flag: true}, + {Key: ArgSettingsLsSetting, Name: "SETTING"}, + {}, + {Key: FlagSettingsSetLocal, Name: "local", Flag: true}, + {Key: ArgSettingsSetSetting, Name: "SETTING", Required: true}, + {Key: ArgSettingsSetValue, Name: "VALUE"}, + {}, + {Key: FlagSettingsUnsetLocal, Name: "local", Flag: true}, + {Key: ArgSettingsUnsetKey, Name: "KEY", Required: true}, + {}, + {Key: FlagShellJobs, Name: "jobs", Flag: true}, + {Key: FlagShellUnset, Name: "unset", Flag: true}, + {Key: FlagShellRaw, Name: "raw", Flag: true}, + {Key: ArgShellToolVersion, Name: "TOOL@VERSION", Required: true}, + {}, + {Key: FlagShellAliasNoHeader, Name: "no-header", Flag: true}, + {}, + {Key: ArgShellAliasGetShellAlias, Name: "shell_alias", Required: true}, + {}, + {Key: FlagShellAliasLsNoHeader, Name: "no-header", Flag: true}, + {}, + {Key: ArgShellAliasSetShellAlias, Name: "shell_alias", Required: true}, + {Key: ArgShellAliasSetCommand, Name: "COMMAND"}, + {}, + {Key: ArgShellAliasUnsetShellAlias, Name: "shell_alias", Required: true}, + {}, + {}, + {}, + {Key: FlagSyncNodeBrew, Name: "brew", Flag: true}, + {Key: FlagSyncNodeNodenv, Name: "nodenv", Flag: true}, + {Key: FlagSyncNodeNvm, Name: "nvm", Flag: true}, + {}, + {Key: FlagSyncPythonPyenv, Name: "pyenv", Flag: true}, + {Key: FlagSyncPythonUv, Name: "uv", Flag: true}, + {}, + {Key: FlagSyncRubyBrew, Name: "brew", Flag: true}, + {}, + {Key: FlagTasksGlobal, Name: "global", Flag: true}, + {Key: FlagTasksJson, Name: "json", Flag: true}, + {Key: FlagTasksLocal, Name: "local", Flag: true}, + {Key: FlagTasksExtended, Name: "extended", Flag: true}, + {Key: FlagTasksAll, Name: "all", Flag: true}, + {Key: FlagTasksComplete, Name: "complete", Flag: true}, + {Key: FlagTasksHidden, Name: "hidden", Flag: true}, + {Key: FlagTasksNameOnly, Name: "name-only", Flag: true}, + {Key: FlagTasksNoHeader, Name: "no-header", Flag: true}, + {Key: FlagTasksSort, Name: "sort", Flag: true, Choices: []string{"name", "alias", "description", "source"}}, + {Key: FlagTasksSortOrder, Name: "sort-order", Flag: true, Choices: []string{"asc", "desc"}}, + {Key: FlagTasksUsage, Name: "usage", Flag: true}, + {Key: ArgTasksTask, Name: "TASK"}, + {}, + {Key: FlagTasksAddAlias, Name: "alias", Flag: true}, + {Key: FlagTasksAddDepends, Name: "depends", Flag: true}, + {Key: FlagTasksAddDir, Name: "dir", Flag: true}, + {Key: FlagTasksAddFile, Name: "file", Flag: true}, + {Key: FlagTasksAddHide, Name: "hide", Flag: true}, + {Key: FlagTasksAddQuiet, Name: "quiet", Flag: true}, + {Key: FlagTasksAddRaw, Name: "raw", Flag: true}, + {Key: FlagTasksAddSources, Name: "sources", Flag: true}, + {Key: FlagTasksAddWaitFor, Name: "wait-for", Flag: true}, + {Key: FlagTasksAddDependsPost, Name: "depends-post", Flag: true}, + {Key: FlagTasksAddDescription, Name: "description", Flag: true}, + {Key: FlagTasksAddOutputs, Name: "outputs", Flag: true}, + {Key: FlagTasksAddRunWindows, Name: "run-windows", Flag: true}, + {Key: FlagTasksAddShell, Name: "shell", Flag: true}, + {Key: FlagTasksAddSilent, Name: "silent", Flag: true}, + {Key: ArgTasksAddTask, Name: "TASK", Required: true}, + {Key: ArgTasksAddRun, Name: "RUN"}, + {}, + {Key: FlagTasksDepsCompact, Name: "compact", Flag: true}, + {Key: FlagTasksDepsDot, Name: "dot", Flag: true}, + {Key: FlagTasksDepsHidden, Name: "hidden", Flag: true}, + {Key: ArgTasksDepsTasks, Name: "TASKS"}, + {}, + {Key: FlagTasksEditPath, Name: "path", Flag: true}, + {Key: ArgTasksEditTask, Name: "TASK", Required: true}, + {}, + {Key: FlagTasksGraphJson, Name: "json", Flag: true}, + {Key: FlagTasksGraphExplain, Name: "explain", Flag: true}, + {Key: FlagTasksGraphNoHeader, Name: "no-header", Flag: true}, + {}, + {Key: FlagTasksInfoJson, Name: "json", Flag: true}, + {Key: ArgTasksInfoTask, Name: "TASK", Required: true}, + {}, + {Key: FlagTasksLsGlobal, Name: "global", Flag: true}, + {Key: FlagTasksLsJson, Name: "json", Flag: true}, + {Key: FlagTasksLsLocal, Name: "local", Flag: true}, + {Key: FlagTasksLsExtended, Name: "extended", Flag: true}, + {Key: FlagTasksLsAll, Name: "all", Flag: true}, + {Key: FlagTasksLsComplete, Name: "complete", Flag: true}, + {Key: FlagTasksLsHidden, Name: "hidden", Flag: true}, + {Key: FlagTasksLsNameOnly, Name: "name-only", Flag: true}, + {Key: FlagTasksLsNoHeader, Name: "no-header", Flag: true}, + {Key: FlagTasksLsSort, Name: "sort", Flag: true, Choices: []string{"name", "alias", "description", "source"}}, + {Key: FlagTasksLsSortOrder, Name: "sort-order", Flag: true, Choices: []string{"asc", "desc"}}, + {Key: FlagTasksLsUsage, Name: "usage", Flag: true}, + {}, + {Key: FlagTasksRunAffected, Name: "affected", Flag: true}, + {Key: FlagTasksRunAffectedBase, Name: "affected-base", Flag: true}, + {Key: FlagTasksRunAffectedExplain, Name: "affected-explain", Flag: true}, + {Key: FlagTasksRunAffectedHead, Name: "affected-head", Flag: true}, + {Key: FlagTasksRunAffectedJson, Name: "affected-json", Flag: true}, + {Key: FlagTasksRunContinueOnError, Name: "continue-on-error", Flag: true}, + {Key: FlagTasksRunCd, Name: "cd", Flag: true}, + {Key: FlagTasksRunForce, Name: "force", Flag: true}, + {Key: FlagTasksRunJobs, Name: "jobs", Flag: true}, + {Key: FlagTasksRunDryRun, Name: "dry-run", Flag: true}, + {Key: FlagTasksRunOutput, Name: "output", Flag: true}, + {Key: FlagTasksRunQuiet, Name: "quiet", Flag: true}, + {Key: FlagTasksRunRaw, Name: "raw", Flag: true}, + {Key: FlagTasksRunShell, Name: "shell", Flag: true}, + {Key: FlagTasksRunSilent, Name: "silent", Flag: true}, + {Key: FlagTasksRunTool, Name: "tool", Flag: true}, + {Key: FlagTasksRunAllowEnv, Name: "allow-env", Flag: true}, + {Key: FlagTasksRunAllowNet, Name: "allow-net", Flag: true}, + {Key: FlagTasksRunAllowRead, Name: "allow-read", Flag: true}, + {Key: FlagTasksRunAllowWrite, Name: "allow-write", Flag: true}, + {Key: FlagTasksRunDenyAll, Name: "deny-all", Flag: true}, + {Key: FlagTasksRunDenyEnv, Name: "deny-env", Flag: true}, + {Key: FlagTasksRunDenyNet, Name: "deny-net", Flag: true}, + {Key: FlagTasksRunDenyRead, Name: "deny-read", Flag: true}, + {Key: FlagTasksRunDenyWrite, Name: "deny-write", Flag: true}, + {Key: FlagTasksRunFreshEnv, Name: "fresh-env", Flag: true}, + {Key: FlagTasksRunNoCache, Name: "no-cache", Flag: true}, + {Key: FlagTasksRunNoDeps, Name: "no-deps", Flag: true}, + {Key: FlagTasksRunNoTimings, Name: "no-timings", Flag: true}, + {Key: FlagTasksRunSkipDeps, Name: "skip-deps", Flag: true}, + {Key: FlagTasksRunSkipTools, Name: "skip-tools", Flag: true}, + {Key: FlagTasksRunTaskCache, Name: "task-cache", Flag: true, Choices: []string{"read-write", "read-only", "write-only", "off", "local-only"}, Default: []string{"read-write"}}, + {Key: FlagTasksRunTaskCacheExplain, Name: "task-cache-explain", Flag: true}, + {Key: FlagTasksRunTaskCacheExplainJson, Name: "task-cache-explain-json", Flag: true}, + {Key: FlagTasksRunTaskCacheStats, Name: "task-cache-stats", Flag: true}, + {Key: FlagTasksRunTimeout, Name: "timeout", Flag: true}, + {Key: FlagTasksRunTimings, Name: "timings", Flag: true}, + {Key: ArgTasksRunTask, Name: "TASK", Default: []string{"default"}}, + {Key: ArgTasksRunArgs, Name: "ARGS"}, + {Key: ArgTasksRunArgsLast, Name: "ARGS_LAST"}, + {}, + {Key: FlagTasksValidateErrorsOnly, Name: "errors-only", Flag: true}, + {Key: FlagTasksValidateJson, Name: "json", Flag: true}, + {Key: ArgTasksValidateTasks, Name: "TASKS"}, + {}, + {Key: FlagTestToolAll, Name: "all", Flag: true}, + {Key: FlagTestToolJobs, Name: "jobs", Flag: true}, + {Key: FlagTestToolAllConfig, Name: "all-config", Flag: true}, + {Key: FlagTestToolIncludeNonDefined, Name: "include-non-defined", Flag: true}, + {Key: FlagTestToolRaw, Name: "raw", Flag: true}, + {Key: ArgTestToolTools, Name: "TOOLS"}, + {}, + {}, + {Key: FlagTokenForgejoUnmask, Name: "unmask", Flag: true}, + {Key: ArgTokenForgejoHost, Name: "HOST", Default: []string{"codeberg.org"}}, + {}, + {Key: FlagTokenGithubOauth, Name: "oauth", Flag: true}, + {Key: FlagTokenGithubRaw, Name: "raw", Flag: true}, + {Key: FlagTokenGithubRefresh, Name: "refresh", Flag: true}, + {Key: FlagTokenGithubUnmask, Name: "unmask", Flag: true}, + {Key: ArgTokenGithubHost, Name: "HOST", Default: []string{"github.com"}}, + {}, + {Key: FlagTokenGitlabUnmask, Name: "unmask", Flag: true}, + {Key: ArgTokenGitlabHost, Name: "HOST", Default: []string{"gitlab.com"}}, + {}, + {Key: FlagToolJson, Name: "json", Flag: true}, + {Key: FlagToolActive, Name: "active", Flag: true}, + {Key: FlagToolBackend, Name: "backend", Flag: true}, + {Key: FlagToolConfigSource, Name: "config-source", Flag: true}, + {Key: FlagToolDescription, Name: "description", Flag: true}, + {Key: FlagToolInstalled, Name: "installed", Flag: true}, + {Key: FlagToolRequested, Name: "requested", Flag: true}, + {Key: FlagToolToolOptions, Name: "tool-options", Flag: true}, + {Key: ArgToolTool, Name: "TOOL", Required: true}, + {}, + {Key: ArgToolStubFile, Name: "FILE", Required: true}, + {Key: ArgToolStubArgs, Name: "ARGS"}, + {}, + {Key: FlagTrustAll, Name: "all", Flag: true}, + {Key: FlagTrustIgnore, Name: "ignore", Flag: true}, + {Key: FlagTrustShow, Name: "show", Flag: true}, + {Key: FlagTrustUntrust, Name: "untrust", Flag: true}, + {Key: ArgTrustConfigFile, Name: "CONFIG_FILE"}, + {}, + {Key: FlagUninstallAll, Name: "all", Flag: true}, + {Key: FlagUninstallDryRun, Name: "dry-run", Flag: true}, + {Key: FlagUninstallDryRunCode, Name: "dry-run-code", Flag: true}, + {Key: ArgUninstallInstalledToolVersion, Name: "INSTALLED_TOOL@VERSION"}, + {}, + {Key: FlagUnsetFile, Name: "file", Flag: true}, + {Key: FlagUnsetGlobal, Name: "global", Flag: true}, + {Key: ArgUnsetEnvKey, Name: "ENV_KEY"}, + {}, + {Key: ArgUntrustConfigFile, Name: "CONFIG_FILE"}, + {}, + {Key: FlagUnuseEnv, Name: "env", Flag: true}, + {Key: FlagUnuseGlobal, Name: "global", Flag: true}, + {Key: FlagUnusePath, Name: "path", Flag: true}, + {Key: FlagUnuseNoPrune, Name: "no-prune", Flag: true}, + {Key: ArgUnuseInstalledToolVersion, Name: "INSTALLED_TOOL@VERSION", Required: true}, + {}, + {Key: FlagUpgradeInteractive, Name: "interactive", Flag: true}, + {Key: FlagUpgradeJobs, Name: "jobs", Flag: true}, + {Key: FlagUpgradeBump, Name: "bump", Flag: true}, + {Key: FlagUpgradeDryRun, Name: "dry-run", Flag: true}, + {Key: FlagUpgradeExclude, Name: "exclude", Flag: true}, + {Key: FlagUpgradeDryRunCode, Name: "dry-run-code", Flag: true}, + {Key: FlagUpgradeInactive, Name: "inactive", Flag: true}, + {Key: FlagUpgradeLocal, Name: "local", Flag: true}, + {Key: FlagUpgradeMinimumReleaseAge, Name: "minimum-release-age", Flag: true}, + {Key: FlagUpgradeMonorepo, Name: "monorepo", Flag: true}, + {Key: FlagUpgradeNoPrune, Name: "no-prune", Flag: true}, + {Key: FlagUpgradeRaw, Name: "raw", Flag: true}, + {Key: ArgUpgradeInstalledToolVersion, Name: "INSTALLED_TOOL@VERSION"}, + {}, + {}, + {Key: FlagUseEnv, Name: "env", Flag: true}, + {Key: FlagUseForce, Name: "force", Flag: true}, + {Key: FlagUseGlobal, Name: "global", Flag: true}, + {Key: FlagUseJobs, Name: "jobs", Flag: true}, + {Key: FlagUseDryRun, Name: "dry-run", Flag: true}, + {Key: FlagUsePath, Name: "path", Flag: true}, + {Key: FlagUseDryRunCode, Name: "dry-run-code", Flag: true}, + {Key: FlagUseFuzzy, Name: "fuzzy", Flag: true}, + {Key: FlagUseMinimumReleaseAge, Name: "minimum-release-age", Flag: true}, + {Key: FlagUsePin, Name: "pin", Flag: true}, + {Key: FlagUseRaw, Name: "raw", Flag: true}, + {Key: FlagUseRemove, Name: "remove", Flag: true}, + {Key: ArgUseToolVersion, Name: "TOOL@VERSION"}, + {}, + {Key: FlagVersionJson, Name: "json", Flag: true}, + {}, + {Key: FlagWatchTaskFlag, Name: "task-flag", Flag: true}, + {Key: FlagWatchGlob, Name: "glob", Flag: true}, + {Key: FlagWatchSkipDeps, Name: "skip-deps", Flag: true}, + {Key: FlagWatchWatch, Name: "watch", Flag: true}, + {Key: FlagWatchWatchNonRecursive, Name: "watch-non-recursive", Flag: true}, + {Key: FlagWatchWatchFile, Name: "watch-file", Flag: true}, + {Key: FlagWatchClear, Name: "clear", Flag: true, Choices: []string{"clear", "reset"}}, + {Key: FlagWatchOnBusyUpdate, Name: "on-busy-update", Flag: true, Choices: []string{"queue", "do-nothing", "restart", "signal"}, Default: []string{"do-nothing"}}, + {Key: FlagWatchRestart, Name: "restart", Flag: true}, + {Key: FlagWatchSignal, Name: "signal", Flag: true}, + {Key: FlagWatchStopSignal, Name: "stop-signal", Flag: true}, + {Key: FlagWatchStopTimeout, Name: "stop-timeout", Flag: true, Default: []string{"10s"}}, + {Key: FlagWatchMapSignal, Name: "map-signal", Flag: true}, + {Key: FlagWatchDebounce, Name: "debounce", Flag: true, Default: []string{"50ms"}}, + {Key: FlagWatchStdinQuit, Name: "stdin-quit", Flag: true}, + {Key: FlagWatchNoVcsIgnore, Name: "no-vcs-ignore", Flag: true}, + {Key: FlagWatchNoProjectIgnore, Name: "no-project-ignore", Flag: true}, + {Key: FlagWatchNoGlobalIgnore, Name: "no-global-ignore", Flag: true}, + {Key: FlagWatchNoDefaultIgnore, Name: "no-default-ignore", Flag: true}, + {Key: FlagWatchNoDiscoverIgnore, Name: "no-discover-ignore", Flag: true}, + {Key: FlagWatchIgnoreNothing, Name: "ignore-nothing", Flag: true}, + {Key: FlagWatchPostpone, Name: "postpone", Flag: true}, + {Key: FlagWatchDelayRun, Name: "delay-run", Flag: true}, + {Key: FlagWatchPoll, Name: "poll", Flag: true}, + {Key: FlagWatchShell, Name: "shell", Flag: true}, + {Key: FlagWatchN, Name: "n", Flag: true}, + {Key: FlagWatchEmitEventsTo, Name: "emit-events-to", Flag: true, Choices: []string{"environment", "stdio", "file", "json-stdio", "json-file", "none"}, Default: []string{"none"}}, + {Key: FlagWatchOnlyEmitEvents, Name: "only-emit-events", Flag: true}, + {Key: FlagWatchEnv, Name: "env", Flag: true}, + {Key: FlagWatchWrapProcess, Name: "wrap-process", Flag: true, Choices: []string{"group", "session", "none"}}, + {Key: FlagWatchNotify, Name: "notify", Flag: true}, + {Key: FlagWatchColor, Name: "color", Flag: true, Choices: []string{"auto", "always", "never"}, Default: []string{"auto"}}, + {Key: FlagWatchTimings, Name: "timings", Flag: true}, + {Key: FlagWatchQuiet, Name: "quiet", Flag: true}, + {Key: FlagWatchBell, Name: "bell", Flag: true}, + {Key: FlagWatchProjectOrigin, Name: "project-origin", Flag: true}, + {Key: FlagWatchWorkdir, Name: "workdir", Flag: true}, + {Key: FlagWatchExts, Name: "exts", Flag: true}, + {Key: FlagWatchFilter, Name: "filter", Flag: true}, + {Key: FlagWatchFilterFile, Name: "filter-file", Flag: true}, + {Key: FlagWatchFilterProg, Name: "filter-prog", Flag: true}, + {Key: FlagWatchIgnore, Name: "ignore", Flag: true}, + {Key: FlagWatchIgnoreFile, Name: "ignore-file", Flag: true}, + {Key: FlagWatchFsEvents, Name: "fs-events", Flag: true, Choices: []string{"access", "create", "remove", "rename", "modify", "metadata"}, Default: []string{"create", "remove", "rename", "modify", "metadata"}}, + {Key: FlagWatchNoMeta, Name: "no-meta", Flag: true}, + {Key: FlagWatchPrintEvents, Name: "print-events", Flag: true}, + {Key: FlagWatchManual, Name: "manual", Flag: true}, + {Key: ArgWatchTask, Name: "TASK"}, + {Key: ArgWatchArgs, Name: "ARGS"}, + {}, + {Key: ArgWhereToolVersion, Name: "TOOL@VERSION", Required: true}, + {Key: ArgWhereAsdfVersion, Name: "ASDF_VERSION"}, + {}, + {Key: FlagWhichTool, Name: "tool", Flag: true}, + {Key: FlagWhichComplete, Name: "complete", Flag: true}, + {Key: FlagWhichPlugin, Name: "plugin", Flag: true}, + {Key: FlagWhichVersion, Name: "version", Flag: true}, + {Key: ArgWhichBinName, Name: "BIN_NAME"}, +} diff --git a/go/internal/spec/spec.go b/go/internal/spec/spec.go index 2785be49f..5ba32003d 100644 --- a/go/internal/spec/spec.go +++ b/go/internal/spec/spec.go @@ -356,12 +356,19 @@ func (b *builder) resolveRelationships(c *Cmd, out *argv.Command) { // whichever of the two spellings was typed. The relationship is between entries // rather than between tokens, which is what this key model already assumes. func (b *builder) matchFlag(flags []*argv.Flag, name string, globalsOnly bool) (uint64, bool) { - // The form is part of the name. `--q` does not reach the short `-q`, and - // `-color` does not reach the long `--color`: usage-lib resolves neither, and - // resolving them here would have a generated CLI enforcing a rule the - // reference does not. A declaration that names a flag by the wrong form is a - // typo, and the useful failure is the rule not existing rather than a rule - // nobody wrote. + // Two passes, in the order the parser itself looks: every ordinary form + // first, then negations. + // + // That order is not a nicety. `longFlag` tries `findLong` across all flags + // before it tries `findNegation`, so with `--a` declaring `negate="--zap"` + // and a separate `--zap`, typing `--zap` binds *zap*. A per-candidate search + // would hand the relationship to `a`, and the table would then enforce a rule + // against a flag the command line never binds. The table has to agree with + // the binder it feeds. + eligible := func(f *argv.Flag) bool { return !globalsOnly || f.Global } + + // The form is part of the name: `--q` does not reach the short `-q`, and + // `-color` does not reach the long `--color`. usage-lib resolves neither. long, short, bare := "", byte(0), "" switch { case strings.HasPrefix(name, "--"): @@ -373,25 +380,15 @@ func (b *builder) matchFlag(flags []*argv.Flag, name string, globalsOnly bool) ( // it can be typed as. bare = name } - if long == "" && short == 0 && bare == "" { - return 0, false - } for _, f := range flags { - if globalsOnly && !f.Global { + if !eligible(f) { continue } if bare != "" && f.Name == bare { return f.Key, true } if long != "" { - // The negation is a form of the flag it belongs to and names the same - // entry: usage-lib reports a conflict declared against `--no-color` - // whichever of the two spellings was typed. Compared as written, so a - // `negate="-no-color"` is not reached by `--no-color`. - if b.negation[f.Key] == name { - return f.Key, true - } for _, l := range f.Longs { if l == long { return f.Key, true @@ -406,9 +403,17 @@ func (b *builder) matchFlag(flags []*argv.Flag, name string, globalsOnly bool) ( } } } + + // Negations, compared exactly as both sides were written — dashes included. + // `negate="-no-tint"` is named by `-no-tint` and not by `--no-tint`, and + // usage-lib resolves it that way round too. + for _, f := range flags { + if eligible(f) && b.negation[f.Key] != "" && b.negation[f.Key] == name { + return f.Key, true + } + } return 0, false } - func (b *builder) flag(f *Flag) *argv.Flag { out := &argv.Flag{ Key: b.next(), diff --git a/go/internal/spec/spec_test.go b/go/internal/spec/spec_test.go index 929880de8..c08328b20 100644 --- a/go/internal/spec/spec_test.go +++ b/go/internal/spec/spec_test.go @@ -313,3 +313,53 @@ func TestANegationIsMatchedAsWritten(t *testing.T) { t.Errorf("--no-tint is not the form `-no-tint`, so nothing: got %v", got) } } + +// The table has to agree with the binder it feeds. +// +// The parser tries every long form before any negation, so with `--a` declaring +// `negate="--zap"` and a separate `--zap`, typing `--zap` binds *zap*. Resolving +// per candidate handed the relationship to `a`, and the rule would then have been +// enforced against a flag the command line never binds. +func TestAnOrdinaryFormBeatsAnotherFlagsNegation(t *testing.T) { + root, meta := build(&Spec{ + Name: "ex", Bin: "ex", + Cmd: Cmd{Name: "ex", Flags: []Flag{ + {Name: "a", Long: []string{"a"}, Negate: "--zap"}, + {Name: "zap", Long: []string{"zap"}}, + {Name: "p", Long: []string{"p"}, Conflicts: []string{"--zap"}}, + }}, + }) + var zap uint64 + for _, f := range root.Flags { + if f.Name == "zap" { + zap = f.Key + } + } + if got := metaFor(t, meta, root, "p").Conflicts; len(got) != 1 || got[0] != zap { + t.Errorf("should name the flag --zap binds (%d), got %v", zap, got) + } +} + +// A negation is named by the form it was written as, whatever the dashes. +func TestASingleDashNegationIsNamedByItsOwnForm(t *testing.T) { + root, meta := build(&Spec{ + Name: "ex", Bin: "ex", + Cmd: Cmd{Name: "ex", Flags: []Flag{ + {Name: "tint", Long: []string{"tint"}, Negate: "-no-tint"}, + {Name: "plain", Long: []string{"plain"}, Conflicts: []string{"-no-tint"}}, + {Name: "other", Long: []string{"other"}, Conflicts: []string{"--no-tint"}}, + }}, + }) + var tint uint64 + for _, f := range root.Flags { + if f.Name == "tint" { + tint = f.Key + } + } + if got := metaFor(t, meta, root, "plain").Conflicts; len(got) != 1 || got[0] != tint { + t.Errorf("the exact form should resolve to tint (%d), got %v", tint, got) + } + if got := metaFor(t, meta, root, "other").Conflicts; len(got) != 0 { + t.Errorf("--no-tint is not how it was declared, got %v", got) + } +} diff --git a/lib/src/go/mod.rs b/lib/src/go/mod.rs index b1c11f403..8cd9ed307 100644 --- a/lib/src/go/mod.rs +++ b/lib/src/go/mod.rs @@ -7,12 +7,15 @@ //! //! # What it emits, and what it does not //! -//! Binding tables only — which token becomes which flag or argument. Help text, -//! choices, defaults, `env`, and every other thing that needs a value's type are -//! deliberately absent, for the same reason they are absent from the Rust hot -//! path: a successful parse never touches them, and a table that carried them -//! would put mise's several hundred kilobytes of help strings in front of the -//! parser. They belong in a second, cold table, which is a separate piece of work. +//! Two tables, kept apart on purpose. The hot one is what binding reads: which +//! token becomes which flag or argument, and nothing else. The cold one — `Meta` — +//! carries what the rules decided after the last token need: `required`, +//! `choices`, `default`, `env`, the var bounds, and the four that compare one +//! entry against another. A parse never touches the second. +//! +//! Help text is in neither. mise's runs to several hundred kilobytes, and a table +//! carrying it would put all of that in front of the parser; rendering help is its +//! own cold table and its own piece of work. //! //! # Why package-level `var` and not `const` //! @@ -27,7 +30,7 @@ //! because `default_subcommand` has to point at a node inside the tree, and a //! composite literal cannot refer to its own interior. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt::Write as _; use heck::AsPascalCase; @@ -145,6 +148,7 @@ impl<'a> Emitter<'a> { self.header(); self.constants(&commands); self.tables(&commands); + self.metadata(&commands); // Each command is followed by a blank line, which leaves one at the end of // the file. gofmt strips it, and a generated file that is not gofmt-clean @@ -380,6 +384,251 @@ impl<'a> Emitter<'a> { } } +impl Emitter<'_> { + /// Emit the cold table: everything binding deliberately does not know. + /// + /// Indexed by key, which is what makes a lookup an index rather than a map — + /// and a Go map would have to be built at init, which is the one thing these + /// tables are for avoiding. Keys are handed out to commands as well as to + /// flags and arguments, and a command has no cold half, so its slot is an + /// empty entry rather than a gap: `Metadata.Lookup` checks the key it finds + /// and reports nothing when it does not match, so an empty slot answers + /// correctly and the index stays dense. + fn metadata(&mut self, commands: &[Emitted]) { + // By key, so the slice can be written in one pass in index order. + let mut by_key: BTreeMap = BTreeMap::new(); + for e in commands { + for (flag, named) in &e.flags { + by_key.insert(named.number, self.flag_meta(flag, named, e, commands)); + } + for (arg, named) in &e.args { + by_key.insert(named.number, arg_meta(arg, named)); + } + } + + let total = commands + .iter() + .map(|e| 1 + e.flags.len() + e.args.len()) + .sum::() as u64; + + let _ = writeln!( + self.out, + "// Meta is the cold table, read only by the rules that are decided once the\n\ + // last token has been read: required, choices, the env-then-default fallback,\n\ + // the var bounds, and the four that compare one entry against another. A parse\n\ + // never touches it.\n\ + //\n\ + // Indexed by key, so entry Key sits at Meta[Key-1]. A command's slot is empty:\n\ + // commands take keys too, and have no cold half.\n\ + var Meta = argv.Metadata{{" + ); + for key in 1..=total { + match by_key.get(&key) { + Some(entry) => { + let _ = writeln!(self.out, "\t{entry},"); + } + None => { + let _ = writeln!(self.out, "\t{{}},"); + } + } + } + let _ = writeln!(self.out, "}}\n"); + } + + /// The cold half of a flag. + fn flag_meta( + &self, + flag: &SpecFlag, + named: &Named, + owner: &Emitted, + commands: &[Emitted], + ) -> String { + let mut fields = vec![ + format!("Key: {}", named.key), + format!("Name: {}", go_string(&flag.name)), + "Flag: true".to_string(), + ]; + if flag.required { + fields.push("Required: true".to_string()); + } + // Written on the value a flag takes, never on the flag. + if let Some(choices) = flag.arg.as_ref().and_then(|a| a.choices.as_ref()) { + fields.push(format!("Choices: {}", string_slice(&choices.choices))); + } + // A default can be written in either place, and usage-lib falls back to + // the one on the value. `env` deliberately does not follow the same + // nesting, because usage-lib does not read it there either. + let default = if !flag.default.is_empty() { + &flag.default + } else { + flag.arg + .as_ref() + .map(|a| &a.default) + .unwrap_or(&flag.default) + }; + if !default.is_empty() { + fields.push(format!("Default: {}", string_slice(default))); + } + if let Some(env) = &flag.env { + fields.push(format!("Env: {}", go_string(env))); + } + if let Some(min) = flag.var_min { + fields.push(format!("VarMin: {}", clamp_var_max(min))); + } + // Occurrences. The per-occurrence value bound is a limit binding applies + // and lives on the parse table. + if let Some(max) = flag.var_max { + fields.push(format!("VarMax: {}", clamp_var_max(max))); + } + + for (label, names) in [ + ("Conflicts", &flag.conflicts), + ("Overrides", &flag.overrides), + ("RequiredUnless", &flag.required_unless), + ("RequiredIf", &flag.required_if), + ] { + let keys = resolve_relationship(names, owner, commands); + if !keys.is_empty() { + fields.push(format!("{label}: {}", key_slice(&keys))); + } + } + + format!("{{{}}}", fields.join(", ")) + } +} + +/// The cold half of a positional argument. +fn arg_meta(arg: &SpecArg, named: &Named) -> String { + let mut fields = vec![ + format!("Key: {}", named.key), + format!("Name: {}", go_string(&arg.name)), + ]; + if arg.required { + fields.push("Required: true".to_string()); + } + if let Some(choices) = &arg.choices { + fields.push(format!("Choices: {}", string_slice(&choices.choices))); + } + if !arg.default.is_empty() { + fields.push(format!("Default: {}", string_slice(&arg.default))); + } + if let Some(env) = &arg.env { + fields.push(format!("Env: {}", go_string(env))); + } + if let Some(min) = arg.var_min { + fields.push(format!("VarMin: {}", clamp_var_max(min))); + } + // No VarMax: for an argument the bound is a limit binding applies, which is + // what makes `[a]… [b]` fillable at all, so judging it again would fail an + // invocation that never broke it. + format!("{{{}}}", fields.join(", ")) +} + +/// Turn the names in a relationship into the keys they refer to. +/// +/// Resolved here, where the whole command is visible, so that nothing downstream +/// searches by name on a path it would repeat per parse. The names arrive as +/// written — `--stdin`, dashes and all — so they are matched against a flag's long +/// forms, its shorts, and the name the spec gives it. +/// +/// A name nothing answers to is dropped. That is a spec bug worth reporting, but +/// this function has no way to; the check belongs beside the duplicate-form and +/// duplicate-key checks that already run where the whole tree is visible. +fn resolve_relationship(names: &[String], owner: &Emitted, commands: &[Emitted]) -> Vec { + let mut out = Vec::new(); + for name in names { + // The declaring command's own flags first, then any ancestor's globals — + // the scope a token has, in the order a token gets it, so a subcommand + // redeclaring an inherited name shadows it here as it does at parse time. + let mut found = match_flag(owner, name, false); + if found.is_none() { + let path = &owner.cmd.full_cmd; + for depth in (0..path.len()).rev() { + let ancestor = commands + .iter() + .find(|e| e.cmd.full_cmd.len() == depth && e.cmd.full_cmd[..] == path[..depth]); + if let Some(key) = ancestor.and_then(|a| match_flag(a, name, true)) { + found = Some(key); + break; + } + } + } + if let Some(key) = found { + out.push(key); + } + } + out +} + +/// Find a flag by any spelling a declaration may use for it. +/// +/// The negation counts, and resolves to the same entry: usage-lib treats +/// `conflicts = "--no-color"` as naming the `color` flag and reports the conflict +/// whichever of the two spellings was typed. The relationship is between entries +/// rather than between tokens, which is what the key model already assumes. +fn match_flag(cmd: &Emitted, name: &str, globals_only: bool) -> Option { + // Two passes, in the order the parser itself looks: every ordinary form + // first, then negations. + // + // That order is not a nicety. The parser tries every long form before it + // tries any negation, so with `--a` declaring `negate = "--zap"` and a + // separate `--zap`, typing `--zap` binds *zap*. A per-candidate search hands + // the relationship to `a`, and the table then enforces a rule against a flag + // the command line never binds. The table has to agree with the binder it + // feeds. + let eligible = |flag: &SpecFlag| !globals_only || flag.global; + + // The form is part of the name: `--q` does not reach the short `-q`, and + // `-color` does not reach the long `--color`. usage-lib resolves neither. + let (long, short, bare) = if let Some(rest) = name.strip_prefix("--") { + (Some(rest), None, None) + } else if let Some(rest) = name.strip_prefix('-') { + let mut chars = rest.chars(); + match (chars.next(), chars.next()) { + (Some(c), None) => (None, Some(c), None), + _ => (None, None, None), + } + } else { + (None, None, Some(name)) + }; + + let ordinary = cmd.flags.iter().find(|(flag, _)| { + if !eligible(flag) { + return false; + } + if let Some(bare) = bare { + return flag.name == bare; + } + if let Some(long) = long { + return flag.long.iter().any(|l| l == long); + } + short.is_some_and(|c| flag.short.contains(&c)) + }); + if let Some((_, named)) = ordinary { + return Some(named.key.clone()); + } + + // Negations, compared exactly as both sides were written — dashes included. + // `negate = "-no-tint"` is named by `-no-tint` and not by `--no-tint`, and + // usage-lib resolves it that way round too. + cmd.flags + .iter() + .find(|(flag, _)| eligible(flag) && flag.negate.as_deref() == Some(name)) + .map(|(_, named)| named.key.clone()) +} +fn string_slice(values: &[String]) -> String { + let list = values + .iter() + .map(|v| go_string(v)) + .collect::>() + .join(", "); + format!("[]string{{{list}}}") +} + +fn key_slice(keys: &[String]) -> String { + format!("[]uint64{{{}}}", keys.join(", ")) +} + /// A line inside a `const` block or a composite literal. /// /// The distinction exists only to reproduce gofmt's alignment, which pads within @@ -899,6 +1148,142 @@ flag "--tag " var=#true var_max=1 assert!(!tag.contains("VarMax"), "occurrence bound leaked: {tag}"); } + /// A relationship names a flag by any spelling that reaches it, and from + /// anywhere the flag is in scope. + /// + /// Both halves were silently resolving to nothing, which is worse than an + /// error: the rule simply never fired, while usage-lib enforced it. + #[test] + fn a_relationship_resolves_through_scope_and_negation() { + let out = go(r#" +name "ex" +bin "ex" +flag "--quiet" global=#true +flag "--color" negate="--no-color" +flag "--plain" conflicts="--no-color" +cmd "run" { + flag "--loud" conflicts="--quiet" + flag "--solo" conflicts="--plain" +} +"#); + // A negation names the flag it belongs to. + assert!( + out.contains("Name: \"plain\", Flag: true, Conflicts: []uint64{FlagColor}"), + "{out}" + ); + // An inherited global is in scope from below. + assert!( + out.contains("Name: \"loud\", Flag: true, Conflicts: []uint64{FlagQuiet}"), + "{out}" + ); + // `--plain` is not global, so from a subcommand it names nothing — the + // other half, and the one a looser search would get wrong. + assert!( + out.contains("{Key: FlagRunSolo, Name: \"solo\", Flag: true},"), + "a non-global should not resolve from below:\n{out}" + ); + } + + /// The form is part of the name, and usage-lib resolves neither of the + /// mismatched ones — so resolving them would have a generated CLI enforcing a + /// rule the reference does not. + #[test] + fn a_relationship_needs_the_right_form() { + let out = go(r#" +name "ex" +bin "ex" +flag "-q --quiet" +flag "--color" +flag "--a" conflicts="--q" +flag "--b" conflicts="-color" +flag "--c" conflicts="-q" +flag "--d" conflicts="--color" +"#); + // `--q` is not a long form of anything, and `-color` is not a short. + assert!( + out.contains("{Key: FlagA, Name: \"a\", Flag: true},"), + "{out}" + ); + assert!( + out.contains("{Key: FlagB, Name: \"b\", Flag: true},"), + "{out}" + ); + // The forms the flags actually have. + assert!( + out.contains("Name: \"c\", Flag: true, Conflicts: []uint64{FlagQuiet}"), + "{out}" + ); + assert!( + out.contains("Name: \"d\", Flag: true, Conflicts: []uint64{FlagColor}"), + "{out}" + ); + } + + /// The table has to agree with the binder it feeds. + /// + /// The parser tries every long form before any negation, so with `--a` + /// declaring `negate="--zap"` and a separate `--zap`, typing `--zap` binds + /// *zap*. A per-candidate search handed the relationship to `a`, which would + /// have enforced the rule against a flag the command line never binds. + #[test] + fn an_ordinary_form_beats_another_flags_negation() { + let out = go(r#" +name "ex" +bin "ex" +flag "--a" negate="--zap" +flag "--zap" +flag "--p" conflicts="--zap" +"#); + assert!( + out.contains("Name: \"p\", Flag: true, Conflicts: []uint64{FlagZap}"), + "should name the flag `--zap` binds, not the one negating to it:\n{out}" + ); + } + + /// A negation is named by the form it was written as, whatever the dashes. + #[test] + fn a_single_dash_negation_is_named_by_its_own_form() { + let out = go(r#" +name "ex" +bin "ex" +flag "--tint" negate="-no-tint" +flag "--plain" conflicts="-no-tint" +flag "--other" conflicts="--no-tint" +"#); + assert!( + out.contains("Name: \"plain\", Flag: true, Conflicts: []uint64{FlagTint}"), + "the exact form should resolve:\n{out}" + ); + // And the form it was not written as does not. + assert!( + out.contains("{Key: FlagOther, Name: \"other\", Flag: true},"), + "`--no-tint` is not how it was declared:\n{out}" + ); + } + + /// A negation is matched as the spec wrote it, dashes and all. + #[test] + fn a_negation_is_matched_as_written() { + let out = go(r#" +name "ex" +bin "ex" +flag "--color" negate="--no-color" +flag "--tint" negate="-no-tint" +flag "--a" conflicts="--no-color" +flag "--b" conflicts="--no-tint" +"#); + assert!( + out.contains("Name: \"a\", Flag: true, Conflicts: []uint64{FlagColor}"), + "{out}" + ); + // `--no-tint` is not the form `-no-tint`, so it names nothing — as in + // usage-lib, which does not resolve it either. + assert!( + out.contains("{Key: FlagB, Name: \"b\", Flag: true},"), + "{out}" + ); + } + #[test] fn strings_are_escaped_to_go_rules() { assert_eq!(go_string(r#"a"b\c"#), r#""a\"b\\c""#); diff --git a/lib/src/go/snapshots/usage__go__tests__a_default_subcommand_points_into_the_tree.snap b/lib/src/go/snapshots/usage__go__tests__a_default_subcommand_points_into_the_tree.snap index 19dbb90ad..b1381bd72 100644 --- a/lib/src/go/snapshots/usage__go__tests__a_default_subcommand_points_into_the_tree.snap +++ b/lib/src/go/snapshots/usage__go__tests__a_default_subcommand_points_into_the_tree.snap @@ -43,3 +43,17 @@ var cmdRun = &argv.Command{ {Key: ArgRunArgs, Name: "args", Var: true}, }, } + +// Meta is the cold table, read only by the rules that are decided once the +// last token has been read: required, choices, the env-then-default fallback, +// the var bounds, and the four that compare one entry against another. A parse +// never touches it. +// +// Indexed by key, so entry Key sits at Meta[Key-1]. A command's slot is empty: +// commands take keys too, and have no cold half. +var Meta = argv.Metadata{ + {}, + {Key: ArgTask, Name: "task"}, + {}, + {Key: ArgRunArgs, Name: "args"}, +} diff --git a/lib/src/go/snapshots/usage__go__tests__a_whole_cli.snap b/lib/src/go/snapshots/usage__go__tests__a_whole_cli.snap index 070023e75..d0e91c791 100644 --- a/lib/src/go/snapshots/usage__go__tests__a_whole_cli.snap +++ b/lib/src/go/snapshots/usage__go__tests__a_whole_cli.snap @@ -83,3 +83,26 @@ var cmdConfigLs = &argv.Command{ {Key: FlagConfigLsNoHeader, Name: "no-header", Longs: []string{"no-header"}}, }, } + +// Meta is the cold table, read only by the rules that are decided once the +// last token has been read: required, choices, the env-then-default fallback, +// the var bounds, and the four that compare one entry against another. A parse +// never touches it. +// +// Indexed by key, so entry Key sits at Meta[Key-1]. A command's slot is empty: +// commands take keys too, and have no cold half. +var Meta = argv.Metadata{ + {}, + {Key: FlagVerbose, Name: "verbose", Flag: true}, + {Key: FlagColor, Name: "color", Flag: true}, + {Key: FlagJobs, Name: "jobs", Flag: true}, + {Key: FlagInclude, Name: "include", Flag: true, VarMax: 3}, + {Key: ArgFile, Name: "file", Required: true}, + {Key: ArgRest, Name: "rest"}, + {}, + {Key: FlagInstallForce, Name: "force", Flag: true}, + {Key: ArgInstallPkg, Name: "pkg", Required: true}, + {}, + {}, + {Key: FlagConfigLsNoHeader, Name: "no-header", Flag: true}, +} diff --git a/lib/src/go/snapshots/usage__go__tests__colliding_names_get_distinct_identifiers.snap b/lib/src/go/snapshots/usage__go__tests__colliding_names_get_distinct_identifiers.snap index 7a100cc9e..0a5f32d40 100644 --- a/lib/src/go/snapshots/usage__go__tests__colliding_names_get_distinct_identifiers.snap +++ b/lib/src/go/snapshots/usage__go__tests__colliding_names_get_distinct_identifiers.snap @@ -57,3 +57,19 @@ var cmdMacosDefaults2 = &argv.Command{ {Key: FlagMacosDefaultsApply2, Name: "apply", Longs: []string{"apply"}}, }, } + +// Meta is the cold table, read only by the rules that are decided once the +// last token has been read: required, choices, the env-then-default fallback, +// the var bounds, and the four that compare one entry against another. A parse +// never touches it. +// +// Indexed by key, so entry Key sits at Meta[Key-1]. A command's slot is empty: +// commands take keys too, and have no cold half. +var Meta = argv.Metadata{ + {}, + {}, + {Key: FlagMacosDefaultsApply, Name: "apply", Flag: true}, + {}, + {}, + {Key: FlagMacosDefaultsApply2, Name: "apply", Flag: true}, +} diff --git a/lib/src/go/snapshots/usage__go__tests__unknown_flags_are_inherited_and_overridable.snap b/lib/src/go/snapshots/usage__go__tests__unknown_flags_are_inherited_and_overridable.snap index c5536ba52..012f6aa63 100644 --- a/lib/src/go/snapshots/usage__go__tests__unknown_flags_are_inherited_and_overridable.snap +++ b/lib/src/go/snapshots/usage__go__tests__unknown_flags_are_inherited_and_overridable.snap @@ -60,3 +60,18 @@ var cmdExecNested = &argv.Command{ Name: "nested", Key: CmdExecNested, } + +// Meta is the cold table, read only by the rules that are decided once the +// last token has been read: required, choices, the env-then-default fallback, +// the var bounds, and the four that compare one entry against another. A parse +// never touches it. +// +// Indexed by key, so entry Key sits at Meta[Key-1]. A command's slot is empty: +// commands take keys too, and have no cold half. +var Meta = argv.Metadata{ + {}, + {}, + {}, + {}, + {}, +}