From 006960e80b741ff65c14f78e5f4e01964805780f Mon Sep 17 00:00:00 2001 From: default <216188+jdx@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:33:06 +0000 Subject: [PATCH 1/3] feat(go): answer the completion request a shell sends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A shell hands over a line and a cursor, not argv: the words do not exist yet, the one being typed is half-written, and it may sit inside quotes that are not closed. So this recovers them — `Split`, ported rule for rule from usage-argv, including the per-shell parts, since PowerShell escapes with a backtick and writes a quote by doubling it while the others do neither. The request is usage-argv's, spelled the same way on purpose: mycli __complete_word__ --shell zsh --line "mycli install no" One convention, so a script written for either framework says the same thing, and so a spec's `complete "x" run="mycli __complete_word__ …"` means one thing whichever language answers it. It is recognized before the parse rather than inside it: a completion is not a command this CLI runs, and putting it in the tables would make it one — visible to the grammar, the help and the spec. `Respond` is the whole of what an adopter writes: hand it argv, and either it answers or it says this was an ordinary invocation. Whether the shell should also offer paths is decided the way the reference decides it, which needed two things the tables did not carry — what a flag's value is called, and the type a `complete` block names. Both are on the cold metadata now, from both producers, so the comparison between them covers it. Co-Authored-By: Claude Opus 5 --- go/README.md | 8 +- go/argv/complete_shell.go | 29 ++ go/argv/post.go | 10 + go/argv/request.go | 205 +++++++++ go/argv/request_test.go | 162 +++++++ go/argv/split.go | 233 ++++++++++ go/argv/split_test.go | 116 +++++ go/internal/shadow/mise/tables.go | 426 +++++++++--------- go/internal/spec/spec.go | 77 +++- lib/src/go/mod.rs | 35 +- .../usage__go__tests__a_whole_cli.snap | 4 +- 11 files changed, 1065 insertions(+), 240 deletions(-) create mode 100644 go/argv/request.go create mode 100644 go/argv/request_test.go create mode 100644 go/argv/split.go create mode 100644 go/argv/split_test.go diff --git a/go/README.md b/go/README.md index 754eb7cf8..367cc573b 100644 --- a/go/README.md +++ b/go/README.md @@ -256,6 +256,8 @@ claim is measured at real scale rather than against a fixture with four flags: - **A typed front door.** The conversions exist; what is missing is generated code that calls them, so a CLI author gets a struct rather than events. -- **Per-shell completion output.** `Walk` and `Candidates` answer _what_ could go - at the cursor; turning that into the text bash, zsh, fish or PowerShell expect - is still to do, as is running the `complete` scripts a spec can declare. +- **The shell scripts themselves.** A CLI can now answer a completion request — + `mycli __complete_word__ --shell zsh --line "…"` — in the text each shell reads. + What is missing is the handful of lines that register that callback with bash, + zsh, fish, nushell and PowerShell, and running the `run=` scripts a spec can + declare, which needs a subprocess this package has no business starting. diff --git a/go/argv/complete_shell.go b/go/argv/complete_shell.go index 96fc0cf2e..414136879 100644 --- a/go/argv/complete_shell.go +++ b/go/argv/complete_shell.go @@ -21,6 +21,35 @@ const ( PowerShell ) +// doublesQuotes reports whether a quote inside a quoted string is written by +// doubling it, which is PowerShell's rule and nobody else's. +func (s Shell) doublesQuotes() bool { return s == PowerShell } + +// backtickEscapes reports whether an escape is written with a backtick rather +// than a backslash — PowerShell again. +func (s Shell) backtickEscapes() bool { return s == PowerShell } + +// ShellNamed is the shell a `--shell` argument names, and whether it named one. +// +// A completion request comes from a script this package wrote, so the name is one +// of five — but it arrives as text off a command line, and a shell that sends +// something else should get an answer rather than a crash. +func ShellNamed(name string) (Shell, bool) { + switch name { + case "bash": + return Bash, true + case "zsh": + return Zsh, true + case "fish": + return Fish, true + case "nu", "nushell": + return Nu, true + case "powershell", "pwsh": + return PowerShell, true + } + return Bash, false +} + // Files says whether paths belong at this position as well as the candidates. type Files uint8 diff --git a/go/argv/post.go b/go/argv/post.go index 928ee40ed..6aa5c8492 100644 --- a/go/argv/post.go +++ b/go/argv/post.go @@ -39,6 +39,16 @@ type Meta struct { // between them can name a different flag entirely. Empty for an argument, // which is typed as its value rather than as a form. Spelling string + // ValueName is what a flag's value is called — the `DIR` of `--into ` — + // and empty where the entry is an argument, which is named by its value + // already. Read by completion rather than by any rule here: what a value is + // called is what says whether a path belongs there. + ValueName string + // CompleteType is the type a spec's `complete` block names for this entry, + // where it names one. Also completion's, and carried for the same reason: an + // author who wrote `complete "input" type="file"` said what the position + // takes, and the alternative is inferring it from a name they did not choose. + CompleteType string // Flag distinguishes a missing flag from a missing argument, which the // grammar reports as different classes. Flag bool diff --git a/go/argv/request.go b/go/argv/request.go new file mode 100644 index 000000000..3397eac7b --- /dev/null +++ b/go/argv/request.go @@ -0,0 +1,205 @@ +package argv + +import "strings" + +// The hidden command a shell calls, and what it answers. +// +// A completion is not something the CLI runs. It is recognized before the parse +// rather than inside it: putting it in the tables would make it a command — +// visible to the grammar, to the help, to the spec — and every CLI would grow a +// subcommand nobody typed. +// +// The request is usage-argv's, spelled the same way on purpose: +// +// mycli __complete_word__ --shell zsh --line "mycli install no" +// +// One convention, so a shell script written for either framework says the same +// thing, and so a spec's `complete "x" run="mycli __complete_word__ …"` means one +// thing whichever language answers it. + +// RequestName is the argument that marks a completion request. +// +// Long and ugly on purpose: it is typed by a script, never by a person, and it +// has to be a word no CLI would want for itself. +const RequestName = "__complete_word__" + +// Request is a completion request as a shell sent it. +type Request struct { + Shell Shell + // Line is the command line as typed, and Cursor a byte offset into it. + Line string + Cursor int +} + +// ParseRequest reads a completion request out of argv, and reports whether this +// was one at all. +// +// `argv` is what the program was given, without its own name — the same slice +// [New] takes, so a caller asks this first and parses only if the answer is no. +// +// The flags are read by hand. There are three, and reading them with the parser +// would mean putting them in the tables this is deliberately outside of. Anything +// unrecognized is ignored rather than refused: a completion that errors out is a +// shell that beeps at every keystroke. +func ParseRequest(argv []string) (Request, bool) { + if len(argv) == 0 || argv[0] != RequestName { + return Request{}, false + } + req := Request{Shell: Bash, Cursor: -1} + for i := 1; i < len(argv); i++ { + switch argv[i] { + case "--shell": + if i+1 < len(argv) { + i++ + if shell, ok := ShellNamed(argv[i]); ok { + req.Shell = shell + } + } + case "--line": + if i+1 < len(argv) { + i++ + req.Line = argv[i] + } + case "--cursor": + if i+1 < len(argv) { + i++ + if n, err := atoi(argv[i]); err == nil { + req.Cursor = n + } + } + } + } + // No cursor means the end of the line, which is where a shell puts it when it + // has no way to say — nushell, whose completer only ever sees the words. + if req.Cursor < 0 { + req.Cursor = len(req.Line) + } + return req, true +} + +// Answer works out what could be typed at the cursor. +// +// The candidates come from the tables, and so does the question of whether paths +// belong here — a shell can complete those itself, and asking it to is how a CLI +// says "anything at all goes here" without listing the filesystem. +func (r Request) Answer(root *Command, help HelpTable, meta Metadata) Answer { + split := Split(r.Line, r.Cursor, r.Shell) + pos := Walk(root, split.Argv()) + candidates := Candidates(pos, split.Prefix, help, meta) + return Answer{Candidates: candidates, Files: filesAt(pos, split, candidates, meta)} +} + +// Respond is the whole of what a CLI has to do: recognize the request, answer it, +// and write the text its shell reads. +// +// Returns false where argv is an ordinary invocation, which is the caller's cue +// to parse it as one. +func Respond(argv []string, root *Command, help HelpTable, meta Metadata) (string, bool) { + req, ok := ParseRequest(argv) + if !ok { + return "", false + } + return RenderAnswer(req.Answer(root, help, meta), req.Shell), true +} + +// filesAt decides whether the shell should offer paths here as well. +// +// Four questions, in the order the reference asks them: +// +// - A dash-prefixed word is a flag or nothing. No path starts with one. +// - An argument that is not readable until after a `--` is not fillable yet, so +// nothing belongs there — not even a path, which the parser would refuse +// exactly as it refuses a value. +// - Did the spec say what this position takes? A `complete` block naming a type +// answers outright, and so does a name like `` or ``: the reference +// resolves a completer by name and falls back to reading the name as the type. +// - Otherwise: is the position *closed*? Something was offered, or the entry +// declares its own set, in which case an unmatched prefix means "no matches" +// rather than "ask somebody else". Offering the working directory for a +// mistyped choice answers the second as though it were the first. +func filesAt(pos Position, split SplitLine, candidates []Candidate, meta Metadata) Files { + if pos.FlagsPossible && strings.HasPrefix(split.Prefix, "-") { + return NoFiles + } + // Only when the cursor is *at* that argument. A flag waiting for its value is + // a different position that happens to have an unfilled positional behind it, + // and a rule about the positional has nothing to say about the flag: `ex + // --from ⌶` takes a path whatever the argument after it needs. + if pos.AwaitingValue == nil && pos.NextArg != nil && + pos.NextArg.DoubleDash == DoubleDashRequired && !pos.SeparatorSeen { + return NoFiles + } + + var m *Meta + named := "" + switch { + case pos.AwaitingValue != nil: + m = meta.Lookup(pos.AwaitingValue.Key) + named = pos.AwaitingValue.Name + case pos.Collecting != nil: + m = meta.Lookup(pos.Collecting.Key) + named = pos.Collecting.Name + case pos.NextArg != nil: + m = meta.Lookup(pos.NextArg.Key) + named = pos.NextArg.Name + } + if m != nil { + if m.ValueName != "" { + named = m.ValueName + } + if asked := filesFor(m.CompleteType); asked != NoFiles { + return asked + } + } + if asked := filesFor(named); asked != NoFiles { + return asked + } + + declaresChoices := m != nil && len(m.Choices) > 0 + if len(candidates) > 0 || declaresChoices || pos.HelpTopic { + return NoFiles + } + return AnyFile +} + +// filesFor is the paths a name asks for, by the name itself. +// +// The same names the reference reads, compared without case: an argument called +// `` completes files and `` directories without a spec saying so. +func filesFor(name string) Files { + switch { + case name == "": + return NoFiles + case strings.EqualFold(name, "file"), strings.EqualFold(name, "path"), + strings.EqualFold(name, "config_file"): + return AnyFile + case strings.EqualFold(name, "dir"), strings.EqualFold(name, "directory"): + return Dirs + } + return NoFiles +} + +// atoi is [strconv.Atoi] for a non-negative number, written out because this +// package takes no imports it can avoid. +func atoi(s string) (int, error) { + if s == "" { + return 0, errNotANumber + } + n := 0 + for i := 0; i < len(s); i++ { + if s[i] < '0' || s[i] > '9' { + return 0, errNotANumber + } + n = n*10 + int(s[i]-'0') + if n < 0 { + return 0, errNotANumber + } + } + return n, nil +} + +type numberError struct{} + +func (numberError) Error() string { return "not a number" } + +var errNotANumber = numberError{} diff --git a/go/argv/request_test.go b/go/argv/request_test.go new file mode 100644 index 000000000..affd0c5ad --- /dev/null +++ b/go/argv/request_test.go @@ -0,0 +1,162 @@ +package argv + +import ( + "strings" + "testing" +) + +// A CLI with the shapes the files rule turns on: a named path, a named +// directory, a declared set, and an argument that reads only after a separator. +func requestFixture() (*Command, HelpTable, Metadata) { + into := &Flag{Key: 2, Name: "into", Longs: []string{"into"}, TakesValue: true} + tool := &Flag{Key: 3, Name: "tool", Longs: []string{"tool"}, TakesValue: true} + file := &Arg{Key: 4, Name: "FILE"} + edit := &Command{Key: 5, Name: "edit", Flags: []*Flag{into}, Args: []*Arg{file}} + use := &Command{Key: 6, Name: "use", Flags: []*Flag{tool}, + Args: []*Arg{{Key: 7, Name: "TOOL"}}} + after := &Command{Key: 8, Name: "run", + Args: []*Arg{{Key: 9, Name: "TASK", DoubleDash: DoubleDashRequired}}} + root := &Command{Key: 1, Name: "ex", Subcommands: []*Command{edit, use, after}} + + help := HelpTable{{Key: 1}, {Key: 2}, {Key: 3}, {Key: 4}, {Key: 5, Short: "Edit a file"}, + {Key: 6, Short: "Use a tool"}, {Key: 7}, {Key: 8, Short: "Run a task"}, {Key: 9}} + meta := Metadata{ + {Key: 1}, + {Key: 2, Name: "into", Flag: true, ValueName: "DIR"}, + {Key: 3, Name: "tool", Flag: true, ValueName: "TOOL", + Choices: []string{"node", "python"}}, + {Key: 4, Name: "FILE"}, + {Key: 5}, {Key: 6}, + {Key: 7, Name: "TOOL", Choices: []string{"node", "python"}}, + {Key: 8}, + {Key: 9, Name: "TASK"}, + } + return root, help, meta +} + +func ask(t *testing.T, shell Shell, line string) (Answer, string) { + t.Helper() + root, help, meta := requestFixture() + cursor := strings.Index(line, "⌶") + if cursor < 0 { + cursor = len(line) + } + line = strings.Replace(line, "⌶", "", 1) + + argv := []string{RequestName, "--shell", shellName(shell), "--line", line} + out, ok := Respond(argv, root, help, meta) + if !ok { + t.Fatalf("%q should be a completion request", argv) + } + req, _ := ParseRequest(argv) + req.Cursor = cursor + return req.Answer(root, help, meta), out +} + +func shellName(s Shell) string { + switch s { + case Zsh: + return "zsh" + case Fish: + return "fish" + case Nu: + return "nu" + case PowerShell: + return "powershell" + } + return "bash" +} + +// An ordinary invocation is left alone. The request is recognized before the +// parse, and a CLI that answered completions for `ex --help` would be one nobody +// could run. +func TestAnOrdinaryInvocationIsNotARequest(t *testing.T) { + root, help, meta := requestFixture() + for _, argv := range [][]string{{}, {"edit"}, {"--into", "x"}, {"_complete"}} { + if _, ok := Respond(argv, root, help, meta); ok { + t.Errorf("%v is a command line, not a completion request", argv) + } + } +} + +// The three arguments a shell passes, and what a missing one means. +func TestReadingTheRequest(t *testing.T) { + req, ok := ParseRequest([]string{RequestName, "--shell", "zsh", + "--line", "ex use no", "--cursor", "6"}) + if !ok || req.Shell != Zsh || req.Line != "ex use no" || req.Cursor != 6 { + t.Fatalf("read back wrong: %+v ok=%v", req, ok) + } + + // No cursor is the end of the line, which is where a shell puts it when it has + // no way to say — nushell, whose completer only ever sees the words. + req, _ = ParseRequest([]string{RequestName, "--line", "ex use"}) + if req.Cursor != len("ex use") { + t.Errorf("want the end of the line, got %d", req.Cursor) + } + + // A shell passing something this version does not know about is answered, not + // refused: a completion that errors out beeps at every keystroke. + req, ok = ParseRequest([]string{RequestName, "--wat", "1", "--shell", "klingon", + "--line", "ex ", "--cursor", "nope"}) + if !ok || req.Shell != Bash || req.Line != "ex " { + t.Errorf("unknown arguments should be ignored: %+v", req) + } +} + +// What the answer says, end to end, in the shape the shell reads. +func TestAnsweringARequest(t *testing.T) { + _, out := ask(t, Bash, "ex ⌶") + for _, want := range []string{"edit", "use", "run"} { + if !strings.Contains(out, want+"\n") { + t.Errorf("want %q offered:\n%s", want, out) + } + } + // zsh takes three fields, and the description comes from the same table the + // page uses. + _, out = ask(t, Zsh, "ex ed⌶") + if !strings.HasPrefix(out, "edit\tEdit a file\tedit\n") { + t.Errorf("want the zsh triple, got %q", out) + } +} + +// Whether the shell should offer paths as well — the question a CLI answers by +// saying nothing about it. +func TestWhenPathsBelongAtTheCursor(t *testing.T) { + for _, c := range []struct { + line string + want Files + why string + }{ + {"ex ⌶", NoFiles, "subcommands were offered, so the position is answered"}, + {"ex -⌶", NoFiles, "a dash-prefixed word is a flag or nothing"}, + {"ex edit ⌶", AnyFile, "the argument is called FILE"}, + {"ex edit --into ⌶", Dirs, "the value is called DIR"}, + {"ex use ⌶", NoFiles, "the argument declares its own set"}, + {"ex use --tool ⌶", NoFiles, "so does the value"}, + {"ex run ⌶", NoFiles, "that argument is not readable until after a --"}, + {"ex run -- ⌶", AnyFile, "and past the separator it is anything at all"}, + } { + answer, _ := ask(t, Bash, c.line) + if answer.Files != c.want { + t.Errorf("%q: want %v, got %v — %s", c.line, c.want, answer.Files, c.why) + } + } +} + +// A completer's declared type outranks the name, because the author wrote it. +func TestADeclaredCompleterTypeDecidesIt(t *testing.T) { + root := &Command{Key: 1, Name: "ex", Args: []*Arg{{Key: 2, Name: "INPUT"}}} + help := HelpTable{{Key: 1}, {Key: 2}} + meta := Metadata{{Key: 1}, {Key: 2, Name: "INPUT", CompleteType: "dir"}} + + req := Request{Shell: Bash, Line: "ex ", Cursor: 3} + if got := req.Answer(root, help, meta).Files; got != Dirs { + t.Errorf("`complete type=\"dir\"` says directories, got %v", got) + } + // And with nothing declared and nothing known, the position is open: a name + // nobody described could be anything, so the shell should offer paths. + meta[1].CompleteType = "" + if got := req.Answer(root, help, meta).Files; got != AnyFile { + t.Errorf("an undescribed position takes anything, got %v", got) + } +} diff --git a/go/argv/split.go b/go/argv/split.go new file mode 100644 index 000000000..f01aa0aa0 --- /dev/null +++ b/go/argv/split.go @@ -0,0 +1,233 @@ +package argv + +import "unicode/utf8" + +// Turning a command line back into the words a shell would have passed. +// +// A completion request arrives as text, not as argv: the shell hands over the +// line and where the cursor is in it, because the words do not exist yet — the +// one being typed is half-written and may be inside quotes that are not closed. +// So the words have to be recovered here, by the same rules the shell would have +// applied had the line been run. +// +// Ported from usage-argv's `split`, which is where the rules were worked out. +// Both sides answer the same request from the same kind of shell script, so a +// line that means one thing to a Rust CLI and another to a Go one would be the +// two frameworks disagreeing about the shell rather than about the spec. + +// SplitLine is a command line as the shell would have passed it, plus where the +// cursor was. +type SplitLine struct { + // Words are the words, unquoted — what argv would hold had the line been run. + // + // Always at least one: a cursor sitting after a space is completing a word + // that does not exist yet, and an empty word is how that is said. Candidates + // for "anything at all" and candidates for "something starting with `no`" are + // the same question with a different prefix, and a caller should not have to + // special-case the empty one. + Words []string + // Cword is which of Words the cursor is in. + Cword int + // Prefix is the part of that word before the cursor, unquoted — what a + // candidate must start with. + Prefix string +} + +// Argv is the words a parser should walk: after the program name, before the +// cursor's word. +// +// Two things dropped for two reasons. The program name, because argv does not +// contain it and the parse tables describe what comes *after* it. The word being +// completed, because it is half-typed by definition — feeding it in would ask +// what can follow a word the user has not finished, when the question is what +// that word could be. +func (s SplitLine) Argv() []string { + start := min(1, s.Cword) + return s.Words[start:s.Cword] +} + +// Split splits a line at a byte cursor, the way `shell` would have split it. +// +// `cursor` is a byte offset into `line`; anything past the end is treated as the +// end, and an offset landing inside a multi-byte character is moved back to that +// character's start rather than being taken literally — a completion request is +// not a place to be strict about a shell's arithmetic. +func Split(line string, cursor int, shell Shell) SplitLine { + if cursor > len(line) { + cursor = len(line) + } + if cursor < 0 { + cursor = 0 + } + cursor = floorCharBoundary(line, cursor) + + var words []string + var word []rune + // Whether anything has been written into `word` — including a quote that so + // far contains nothing, so that `ex ""` is a word and not a gap between two. + started := false + cword, prefix, found := 0, "", false + // Whether the cursor sat inside a word rather than in the gap before one. A + // gap is a word the user is about to type, so one has to be made for them. + cursorInWord := false + + // The cursor is reached before the character it sits in front of is read, so + // the word in hand is the word being completed and what is in it is the + // prefix. Checked at the top of each character and again before an escape + // swallows the one after it: only checking the top left a cursor sitting on an + // escaped character unnoticed, and the split then described the last word of + // the line rather than the one being typed. + reached := func(i int) { + if i == cursor && !found { + cword, prefix, found = len(words), string(word), true + cursorInWord = started + } + } + + var quote rune + runes := []rune(line) + // Byte offsets alongside, because the cursor is one. + offsets := make([]int, len(runes)+1) + at := 0 + for i, r := range runes { + offsets[i] = at + at += utf8.RuneLen(r) + } + offsets[len(runes)] = at + + for i := 0; i < len(runes); i++ { + c := runes[i] + reached(offsets[i]) + + peek := func() (rune, bool) { + if i+1 < len(runes) { + return runes[i+1], true + } + return 0, false + } + + switch { + case quote == '\'': + if c == '\'' { + // PowerShell writes a quote inside a quoted string by doubling it; + // the POSIX-shaped shells have no such rule, and there a second + // quote always ends the string. + if next, ok := peek(); shell.doublesQuotes() && ok && next == '\'' { + reached(offsets[i+1]) + word = append(word, '\'') + i++ + } else { + quote = 0 + } + } else { + word = append(word, c) + } + + case quote != 0: + if c == quote { + if next, ok := peek(); shell.doublesQuotes() && ok && next == quote { + reached(offsets[i+1]) + word = append(word, quote) + i++ + } else { + quote = 0 + } + } else if isEscape(c, shell) { + // Inside double quotes an escape is only an escape before a + // character it could mean something to; before anything else it is + // a literal, which is why a Windows path in double quotes survives. + if next, ok := peek(); ok && escapableInQuotes(next, shell) { + reached(offsets[i+1]) + word = append(word, next) + i++ + } else { + word = append(word, c) + } + } else { + word = append(word, c) + } + + default: + switch { + case c == '\'' || c == '"': + quote = c + started = true + case isEscape(c, shell): + // Before the check, because the escape has already started the + // word: a cursor on the escaped character is inside it, not in the + // gap before it. + started = true + if next, ok := peek(); ok { + reached(offsets[i+1]) + word = append(word, next) + i++ + } + // A trailing escape is a line the user is still typing, not a + // mistake to report: it escapes the character they have not typed. + case isSpace(c): + if started { + words = append(words, string(word)) + word = word[:0] + started = false + } + default: + word = append(word, c) + started = true + } + } + } + + // The cursor at the very end of the line: the loop above only sees positions it + // reads a character at, and there is no character there. + if !found { + cword, prefix = len(words), string(word) + cursorInWord = started + } + if started { + words = append(words, string(word)) + } + + // A cursor in a gap is completing a word that is not in the line yet — at the + // end of it, or between two that are already there. `ex ⌶use` is asking what + // can go *before* `use`, and answering about `use` itself would complete the + // wrong word. + if !cursorInWord { + words = append(words, "") + copy(words[cword+1:], words[cword:]) + words[cword] = "" + } + return SplitLine{Words: words, Cword: cword, Prefix: prefix} +} + +// isEscape reports whether a character starts an escape in this shell. +func isEscape(c rune, shell Shell) bool { + if shell.backtickEscapes() { + return c == '`' + } + return c == '\\' +} + +// escapableInQuotes reports whether an escape inside double quotes applies to the +// character after it. +func escapableInQuotes(c rune, shell Shell) bool { + if shell.backtickEscapes() { + return c == '"' || c == '`' || c == '$' + } + return c == '"' || c == '\\' || c == '$' || c == '`' +} + +// isSpace is the whitespace a shell splits words on. Written out rather than +// taken from `unicode`, which would pull the tables in for a package whose whole +// claim is that it costs nothing unused. +func isSpace(c rune) bool { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f' +} + +// floorCharBoundary is the start of the character containing `index`. +func floorCharBoundary(s string, index int) int { + // The end of the string is a boundary and has no byte to look at. + for index > 0 && index < len(s) && !utf8.RuneStart(s[index]) { + index-- + } + return index +} diff --git a/go/argv/split_test.go b/go/argv/split_test.go new file mode 100644 index 000000000..f95c7c6c6 --- /dev/null +++ b/go/argv/split_test.go @@ -0,0 +1,116 @@ +package argv + +import ( + "reflect" + "strings" + "testing" +) + +// The cursor is written as `⌶` in these cases, and cut out before the split, so a +// case reads as the line the user is looking at. +func at(line string, shell Shell) SplitLine { + cursor := strings.Index(line, "⌶") + if cursor < 0 { + cursor = len(line) + } + return Split(strings.Replace(line, "⌶", "", 1), cursor, shell) +} + +func TestSplittingALine(t *testing.T) { + for _, c := range []struct { + line string + words []string + cword int + prefix string + }{ + // The plain cases: a word being typed, and a gap after one. + {"ex inst⌶", []string{"ex", "inst"}, 1, "inst"}, + {"ex ⌶", []string{"ex", ""}, 1, ""}, + {"ex install ⌶", []string{"ex", "install", ""}, 2, ""}, + // A cursor inside a word ignores the rest of it: what follows is a tail the + // user has not decided on, and it should not narrow what can be typed. + {"ex ins⌶tall", []string{"ex", "install"}, 1, "ins"}, + // A cursor in the gap *before* a word is completing a word that is not + // there yet, so one is made for it. + {"ex ⌶install", []string{"ex", "", "install"}, 1, ""}, + // Quotes hold a word together, and an unclosed one is a word being typed. + {`ex "two words⌶`, []string{"ex", "two words"}, 1, "two words"}, + {`ex 'two words' ⌶`, []string{"ex", "two words", ""}, 2, ""}, + // An empty quoted string is a word, not a gap. + {`ex "" ⌶`, []string{"ex", "", ""}, 2, ""}, + // An escape takes the character after it, and a trailing one is a line + // still being typed rather than a mistake. + {`ex two\ wo⌶`, []string{"ex", "two wo"}, 1, "two wo"}, + {`ex two\⌶`, []string{"ex", "two"}, 1, "two"}, + // Nothing at all is still one word: the cursor is completing it. + {"", []string{""}, 0, ""}, + {"ex⌶", []string{"ex"}, 0, "ex"}, + } { + got := at(c.line, Bash) + want := SplitLine{Words: c.words, Cword: c.cword, Prefix: c.prefix} + if !reflect.DeepEqual(got, want) { + t.Errorf("%q:\n got %+v\nwant %+v", c.line, got, want) + } + } +} + +// Inside double quotes a backslash escapes only what it could mean something to, +// which is what lets a Windows path survive being quoted. +func TestAnEscapeInsideQuotes(t *testing.T) { + got := at(`ex "C:\Users\me⌶`, Bash) + if want := `C:\Users\me`; got.Prefix != want { + t.Errorf("want %q, got %q", want, got.Prefix) + } + got = at(`ex "say \"hi\"⌶`, Bash) + if want := `say "hi"`; got.Prefix != want { + t.Errorf("want %q, got %q", want, got.Prefix) + } +} + +// PowerShell escapes with a backtick and writes a quote by doubling it. Splitting +// its lines by POSIX rules turned a path into an escape sequence. +func TestPowerShellQuotesItsOwnWay(t *testing.T) { + if got := at("ex C:\\Users\\me⌶", PowerShell); got.Prefix != `C:\Users\me` { + t.Errorf("a backslash is not an escape in PowerShell: %q", got.Prefix) + } + if got := at("ex `\"quoted⌶", PowerShell); got.Prefix != `"quoted` { + t.Errorf("a backtick escapes: %q", got.Prefix) + } + if got := at("ex 'it''s'⌶", PowerShell); got.Prefix != "it's" { + t.Errorf("a doubled quote is one quote: %q", got.Prefix) + } + // And the same doubled quote is two words to bash, which is the point of + // asking which shell sent the line. + if got := at("ex 'it''s'⌶", Bash); got.Prefix != "its" { + t.Errorf("bash has no doubling rule: %q", got.Prefix) + } +} + +// A cursor is a byte offset from a shell's arithmetic, and a completion request is +// not the place to be strict about it. +func TestACursorOutsideTheLine(t *testing.T) { + if got := Split("ex run", 999, Bash); got.Prefix != "run" { + t.Errorf("past the end is the end: %+v", got) + } + if got := Split("ex ünïcode", 5, Bash); got.Prefix != "ü" { + // Byte 5 is inside `ï`; the character it belongs to starts at 4, and the + // word so far is `ü`. Moved back rather than cutting a rune in half. + t.Errorf("a cursor inside a character moves back to its start: %+v", got) + } +} + +// The words a parser walks: after the program name, before the word being typed. +func TestWhatTheParserIsGiven(t *testing.T) { + got := at("ex install no⌶", Bash) + if want := []string{"install"}; !reflect.DeepEqual(got.Argv(), want) { + t.Errorf("want %v, got %v", want, got.Argv()) + } + // Nothing but the program name yet, so nothing to walk. + if got := at("ex ⌶", Bash); len(got.Argv()) != 0 { + t.Errorf("want nothing, got %v", got.Argv()) + } + // Not even the program name: a line that is only a cursor. + if got := at("⌶", Bash); len(got.Argv()) != 0 { + t.Errorf("want nothing, got %v", got.Argv()) + } +} diff --git a/go/internal/shadow/mise/tables.go b/go/internal/shadow/mise/tables.go index 471c6bfb8..fd7698f2f 100644 --- a/go/internal/shadow/mise/tables.go +++ b/go/internal/shadow/mise/tables.go @@ -3785,25 +3785,25 @@ var cmdWhich = &argv.Command{ var Meta = argv.Metadata{ {}, {Key: FlagContinueOnError, Name: "continue-on-error", Flag: true, Spelling: "--continue-on-error"}, - {Key: FlagCd, Name: "cd", Flag: true, Spelling: "--cd"}, - {Key: FlagEnv, Name: "env", Flag: true, Spelling: "--env"}, + {Key: FlagCd, Name: "cd", Flag: true, Spelling: "--cd", ValueName: "DIR"}, + {Key: FlagEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "ENV"}, {Key: FlagForce, Name: "force", Flag: true, Spelling: "--force"}, - {Key: FlagJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagProfile, Name: "profile", Flag: true, Spelling: "--profile"}, + {Key: FlagProfile, Name: "profile", Flag: true, Spelling: "--profile", ValueName: "PROFILE"}, {Key: FlagQuiet, Name: "quiet", Flag: true, Spelling: "--quiet"}, - {Key: FlagShell, Name: "shell", Flag: true, Spelling: "--shell"}, - {Key: FlagTool, Name: "tool", Flag: true, Spelling: "--tool"}, + {Key: FlagShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL"}, + {Key: FlagTool, Name: "tool", Flag: true, Spelling: "--tool", ValueName: "TOOL@VERSION"}, {Key: FlagVerbose, Name: "verbose", Flag: true, Spelling: "--verbose"}, {Key: FlagVersion, Name: "version", Flag: true, Spelling: "--version"}, {Key: FlagYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: FlagDebug, Name: "debug", Flag: true, Spelling: "--debug"}, - {Key: FlagLogLevel, Name: "log-level", Flag: true, Spelling: "--log-level", Choices: []string{"trace", "debug", "info", "warning", "error"}}, + {Key: FlagLogLevel, Name: "log-level", Flag: true, Spelling: "--log-level", ValueName: "LEVEL", Choices: []string{"trace", "debug", "info", "warning", "error"}}, {Key: FlagNoConfig, Name: "no-config", Flag: true, Spelling: "--no-config"}, {Key: FlagNoEnv, Name: "no-env", Flag: true, Spelling: "--no-env"}, {Key: FlagNoHooks, Name: "no-hooks", Flag: true, Spelling: "--no-hooks"}, {Key: FlagNoTimings, Name: "no-timings", Flag: true, Spelling: "--no-timings"}, - {Key: FlagOutput, Name: "output", Flag: true, Spelling: "--output"}, + {Key: FlagOutput, Name: "output", Flag: true, Spelling: "--output", ValueName: "OUTPUT"}, {Key: FlagRaw, Name: "raw", Flag: true, Spelling: "--raw"}, {Key: FlagLocked, Name: "locked", Flag: true, Spelling: "--locked"}, {Key: FlagSilent, Name: "silent", Flag: true, Spelling: "--silent"}, @@ -3814,13 +3814,13 @@ var Meta = argv.Metadata{ {Key: ArgTaskArgsLast, Name: "TASK_ARGS_LAST"}, {}, {Key: FlagActivateQuiet, Name: "quiet", Flag: true, Spelling: "--quiet"}, - {Key: FlagActivateShell, Name: "shell", Flag: true, Spelling: "--shell", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: FlagActivateShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, {Key: FlagActivateNoHookEnv, Name: "no-hook-env", Flag: true, Spelling: "--no-hook-env"}, {Key: FlagActivateShims, Name: "shims", Flag: true, Spelling: "--shims"}, {Key: FlagActivateStatus, Name: "status", Flag: true, Spelling: "--status"}, {Key: ArgActivateShellType, Name: "SHELL_TYPE", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, {}, - {Key: FlagToolAliasTool, Name: "tool", Flag: true, Spelling: "--tool"}, + {Key: FlagToolAliasTool, Name: "tool", Flag: true, Spelling: "--tool", ValueName: "TOOL"}, {Key: FlagToolAliasNoHeader, Name: "no-header", Flag: true, Spelling: "--no-header"}, {}, {Key: ArgToolAliasGetTool, Name: "TOOL", Required: true}, @@ -3847,9 +3847,9 @@ var Meta = argv.Metadata{ {Key: FlagBootstrapDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagBootstrapYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: FlagBootstrapForceDotfiles, Name: "force-dotfiles", Flag: true, Spelling: "--force-dotfiles"}, - {Key: FlagBootstrapOnly, Name: "only", Flag: true, Spelling: "--only", 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: FlagBootstrapOnly, Name: "only", Flag: true, Spelling: "--only", ValueName: "ONLY", 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, Spelling: "--prompt-secrets"}, - {Key: FlagBootstrapSkip, Name: "skip", Flag: true, Spelling: "--skip", 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: FlagBootstrapSkip, Name: "skip", Flag: true, Spelling: "--skip", ValueName: "SKIP", 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, Spelling: "--update"}, {}, {}, @@ -3876,11 +3876,11 @@ var Meta = argv.Metadata{ {Key: FlagBootstrapDotfilesAddForce, Name: "force", Flag: true, Spelling: "--force"}, {Key: FlagBootstrapDotfilesAddGlobal, Name: "global", Flag: true, Spelling: "--global"}, {Key: FlagBootstrapDotfilesAddLocal, Name: "local", Flag: true, Spelling: "--local"}, - {Key: FlagBootstrapDotfilesAddMode, Name: "mode", Flag: true, Spelling: "--mode"}, + {Key: FlagBootstrapDotfilesAddMode, Name: "mode", Flag: true, Spelling: "--mode", ValueName: "MODE"}, {Key: FlagBootstrapDotfilesAddDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagBootstrapDotfilesAddNoApply, Name: "no-apply", Flag: true, Spelling: "--no-apply"}, - {Key: FlagBootstrapDotfilesAddPath, Name: "path", Flag: true, Spelling: "--path"}, - {Key: FlagBootstrapDotfilesAddSource, Name: "source", Flag: true, Spelling: "--source"}, + {Key: FlagBootstrapDotfilesAddPath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, + {Key: FlagBootstrapDotfilesAddSource, Name: "source", Flag: true, Spelling: "--source", ValueName: "PATH"}, {Key: FlagBootstrapDotfilesAddYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: ArgBootstrapDotfilesAddTarget, Name: "TARGET", Required: true}, {}, @@ -3890,8 +3890,8 @@ var Meta = argv.Metadata{ {Key: ArgBootstrapDotfilesApplyTarget, Name: "TARGET"}, {}, {Key: FlagBootstrapDotfilesEditApply, Name: "apply", Flag: true, Spelling: "--apply"}, - {Key: FlagBootstrapDotfilesEditMode, Name: "mode", Flag: true, Spelling: "--mode"}, - {Key: FlagBootstrapDotfilesEditSource, Name: "source", Flag: true, Spelling: "--source"}, + {Key: FlagBootstrapDotfilesEditMode, Name: "mode", Flag: true, Spelling: "--mode", ValueName: "MODE"}, + {Key: FlagBootstrapDotfilesEditSource, Name: "source", Flag: true, Spelling: "--source", ValueName: "PATH"}, {Key: FlagBootstrapDotfilesEditYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: ArgBootstrapDotfilesEditTarget, Name: "TARGET", Required: true}, {}, @@ -3965,7 +3965,7 @@ var Meta = argv.Metadata{ {Key: FlagBootstrapMiseShellActivateStatusMissing, Name: "missing", Flag: true, Spelling: "--missing"}, {}, {}, - {Key: FlagBootstrapPackagesApplyManager, Name: "manager", Flag: true, Spelling: "--manager"}, + {Key: FlagBootstrapPackagesApplyManager, Name: "manager", Flag: true, Spelling: "--manager", ValueName: "MANAGER"}, {Key: FlagBootstrapPackagesApplyDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagBootstrapPackagesApplyYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: FlagBootstrapPackagesApplyUpdate, Name: "update", Flag: true, Spelling: "--update"}, @@ -3974,38 +3974,38 @@ var Meta = argv.Metadata{ {}, {Key: FlagBootstrapPackagesBrewTapLocal, Name: "local", Flag: true, Spelling: "--local"}, {Key: FlagBootstrapPackagesBrewTapDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagBootstrapPackagesBrewTapPath, Name: "path", Flag: true, Spelling: "--path"}, + {Key: FlagBootstrapPackagesBrewTapPath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, {Key: ArgBootstrapPackagesBrewTapTap, Name: "TAP", Required: true}, {Key: ArgBootstrapPackagesBrewTapUrl, Name: "URL"}, {}, {Key: FlagBootstrapPackagesBrewUntapLocal, Name: "local", Flag: true, Spelling: "--local"}, {Key: FlagBootstrapPackagesBrewUntapDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagBootstrapPackagesBrewUntapPath, Name: "path", Flag: true, Spelling: "--path"}, + {Key: FlagBootstrapPackagesBrewUntapPath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, {Key: ArgBootstrapPackagesBrewUntapTaps, Name: "TAPS", Required: true}, {}, - {Key: FlagBootstrapPackagesImportEnv, Name: "env", Flag: true, Spelling: "--env"}, + {Key: FlagBootstrapPackagesImportEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "ENV"}, {Key: FlagBootstrapPackagesImportGlobal, Name: "global", Flag: true, Spelling: "--global"}, - {Key: FlagBootstrapPackagesImportManager, Name: "manager", Flag: true, Spelling: "--manager", Choices: []string{"brew"}, Default: []string{"brew"}}, + {Key: FlagBootstrapPackagesImportManager, Name: "manager", Flag: true, Spelling: "--manager", ValueName: "MANAGER", Choices: []string{"brew"}, Default: []string{"brew"}}, {Key: FlagBootstrapPackagesImportAll, Name: "all", Flag: true, Spelling: "--all"}, {Key: FlagBootstrapPackagesImportDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagBootstrapPackagesImportPath, Name: "path", Flag: true, Spelling: "--path"}, + {Key: FlagBootstrapPackagesImportPath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, {}, - {Key: FlagBootstrapPackagesPruneManager, Name: "manager", Flag: true, Spelling: "--manager", Choices: []string{"brew"}, Default: []string{"brew"}}, + {Key: FlagBootstrapPackagesPruneManager, Name: "manager", Flag: true, Spelling: "--manager", ValueName: "MANAGER", Choices: []string{"brew"}, Default: []string{"brew"}}, {Key: FlagBootstrapPackagesPruneDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagBootstrapPackagesPruneYes, Name: "yes", Flag: true, Spelling: "--yes"}, {}, {Key: FlagBootstrapPackagesStatusJson, Name: "json", Flag: true, Spelling: "--json"}, {Key: FlagBootstrapPackagesStatusMissing, Name: "missing", Flag: true, Spelling: "--missing"}, {}, - {Key: FlagBootstrapPackagesUpgradeManager, Name: "manager", Flag: true, Spelling: "--manager"}, + {Key: FlagBootstrapPackagesUpgradeManager, Name: "manager", Flag: true, Spelling: "--manager", ValueName: "MANAGER"}, {Key: FlagBootstrapPackagesUpgradeDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagBootstrapPackagesUpgradeYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: ArgBootstrapPackagesUpgradePackage, Name: "PACKAGE"}, {}, - {Key: FlagBootstrapPackagesUseEnv, Name: "env", Flag: true, Spelling: "--env"}, + {Key: FlagBootstrapPackagesUseEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "ENV"}, {Key: FlagBootstrapPackagesUseGlobal, Name: "global", Flag: true, Spelling: "--global"}, {Key: FlagBootstrapPackagesUseDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagBootstrapPackagesUsePath, Name: "path", Flag: true, Spelling: "--path"}, + {Key: FlagBootstrapPackagesUsePath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, {Key: FlagBootstrapPackagesUseYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: ArgBootstrapPackagesUsePackage, Name: "PACKAGE", Required: true}, {}, @@ -4019,24 +4019,24 @@ var Meta = argv.Metadata{ {Key: FlagBootstrapPluginsStatusMissing, Name: "missing", Flag: true, Spelling: "--missing"}, {}, {Key: FlagBootstrapRemoteAll, Name: "all", Flag: true, Spelling: "--all"}, - {Key: FlagBootstrapRemoteBootstrapCommand, Name: "bootstrap-command", Flag: true, Spelling: "--bootstrap-command"}, - {Key: FlagBootstrapRemoteConnectTimeout, Name: "connect-timeout", Flag: true, Spelling: "--connect-timeout", Default: []string{"10"}}, - {Key: FlagBootstrapRemoteExclude, Name: "exclude", Flag: true, Spelling: "--exclude"}, + {Key: FlagBootstrapRemoteBootstrapCommand, Name: "bootstrap-command", Flag: true, Spelling: "--bootstrap-command", ValueName: "COMMAND"}, + {Key: FlagBootstrapRemoteConnectTimeout, Name: "connect-timeout", Flag: true, Spelling: "--connect-timeout", ValueName: "CONNECT_TIMEOUT", Default: []string{"10"}}, + {Key: FlagBootstrapRemoteExclude, Name: "exclude", Flag: true, Spelling: "--exclude", ValueName: "PATTERN"}, {Key: FlagBootstrapRemoteFailFast, Name: "fail-fast", Flag: true, Spelling: "--fail-fast"}, {Key: FlagBootstrapRemoteForceDotfiles, Name: "force-dotfiles", Flag: true, Spelling: "--force-dotfiles"}, - {Key: FlagBootstrapRemoteHost, Name: "host", Flag: true, Spelling: "--host"}, - {Key: FlagBootstrapRemoteIdentityFile, Name: "identity-file", Flag: true, Spelling: "--identity-file"}, + {Key: FlagBootstrapRemoteHost, Name: "host", Flag: true, Spelling: "--host", ValueName: "[USER@]HOST"}, + {Key: FlagBootstrapRemoteIdentityFile, Name: "identity-file", Flag: true, Spelling: "--identity-file", ValueName: "IDENTITY_FILE"}, {Key: FlagBootstrapRemoteDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagBootstrapRemoteKeepStaging, Name: "keep-staging", Flag: true, Spelling: "--keep-staging"}, - {Key: FlagBootstrapRemoteMiseBin, Name: "mise-bin", Flag: true, Spelling: "--mise-bin"}, - {Key: FlagBootstrapRemoteOnly, Name: "only", Flag: true, Spelling: "--only", 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, Spelling: "--port"}, + {Key: FlagBootstrapRemoteMiseBin, Name: "mise-bin", Flag: true, Spelling: "--mise-bin", ValueName: "MISE_BIN"}, + {Key: FlagBootstrapRemoteOnly, Name: "only", Flag: true, Spelling: "--only", ValueName: "ONLY", 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, Spelling: "--port", ValueName: "PORT"}, {Key: FlagBootstrapRemotePromptSecrets, Name: "prompt-secrets", Flag: true, Spelling: "--prompt-secrets"}, - {Key: FlagBootstrapRemoteRemoteMise, Name: "remote-mise", Flag: true, Spelling: "--remote-mise"}, - {Key: FlagBootstrapRemoteSkip, Name: "skip", Flag: true, Spelling: "--skip", 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, Spelling: "--source"}, - {Key: FlagBootstrapRemoteSshOption, Name: "ssh-option", Flag: true, Spelling: "--ssh-option"}, - {Key: FlagBootstrapRemoteTag, Name: "tag", Flag: true, Spelling: "--tag"}, + {Key: FlagBootstrapRemoteRemoteMise, Name: "remote-mise", Flag: true, Spelling: "--remote-mise", ValueName: "COMMAND"}, + {Key: FlagBootstrapRemoteSkip, Name: "skip", Flag: true, Spelling: "--skip", ValueName: "SKIP", 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, Spelling: "--source", ValueName: "SOURCE"}, + {Key: FlagBootstrapRemoteSshOption, Name: "ssh-option", Flag: true, Spelling: "--ssh-option", ValueName: "OPTION"}, + {Key: FlagBootstrapRemoteTag, Name: "tag", Flag: true, Spelling: "--tag", ValueName: "TAG"}, {Key: FlagBootstrapRemoteUpdate, Name: "update", Flag: true, Spelling: "--update"}, {Key: FlagBootstrapRemoteYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: ArgBootstrapRemoteTarget, Name: "TARGET"}, @@ -4088,7 +4088,7 @@ var Meta = argv.Metadata{ {}, {}, {Key: FlagCacheClearOutdate, Name: "outdate", Flag: true, Spelling: "--outdate"}, - {Key: FlagCacheClearTask, Name: "task", Flag: true, Spelling: "--task"}, + {Key: FlagCacheClearTask, Name: "task", Flag: true, Spelling: "--task", ValueName: "TASK"}, {Key: ArgCacheClearTool, Name: "TOOL"}, {}, {}, @@ -4099,7 +4099,7 @@ var Meta = argv.Metadata{ {Key: FlagCacheTaskJson, Name: "json", Flag: true, Spelling: "--json"}, {Key: ArgCacheTaskTask, Name: "TASK", Required: true}, {}, - {Key: FlagCompletionShell, Name: "shell", Flag: true, Spelling: "--shell", Choices: []string{"bash", "fish", "powershell", "zsh"}}, + {Key: FlagCompletionShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL_TYPE", Choices: []string{"bash", "fish", "powershell", "zsh"}}, {Key: FlagCompletionIncludeBashCompletionLib, Name: "include-bash-completion-lib", Flag: true, Spelling: "--include-bash-completion-lib"}, {Key: FlagCompletionUsage, Name: "usage", Flag: true, Spelling: "--usage"}, {Key: ArgCompletionShell, Name: "SHELL", Choices: []string{"bash", "fish", "powershell", "zsh"}}, @@ -4108,15 +4108,15 @@ var Meta = argv.Metadata{ {Key: FlagConfigNoHeader, Name: "no-header", Flag: true, Spelling: "--no-header"}, {Key: FlagConfigTrackedConfigs, Name: "tracked-configs", Flag: true, Spelling: "--tracked-configs"}, {}, - {Key: FlagConfigGetFile, Name: "file", Flag: true, Spelling: "--file"}, + {Key: FlagConfigGetFile, Name: "file", Flag: true, Spelling: "--file", ValueName: "FILE"}, {Key: ArgConfigGetKey, Name: "KEY"}, {}, {Key: FlagConfigLsJson, Name: "json", Flag: true, Spelling: "--json"}, {Key: FlagConfigLsNoHeader, Name: "no-header", Flag: true, Spelling: "--no-header"}, {Key: FlagConfigLsTrackedConfigs, Name: "tracked-configs", Flag: true, Spelling: "--tracked-configs"}, {}, - {Key: FlagConfigSetFile, Name: "file", Flag: true, Spelling: "--file"}, - {Key: FlagConfigSetType, Name: "type", Flag: true, Spelling: "--type", Choices: []string{"infer", "string", "integer", "float", "bool", "list", "set"}, Default: []string{"infer"}}, + {Key: FlagConfigSetFile, Name: "file", Flag: true, Spelling: "--file", ValueName: "FILE"}, + {Key: FlagConfigSetType, Name: "type", Flag: true, Spelling: "--type", ValueName: "TYPE", Choices: []string{"infer", "string", "integer", "float", "bool", "list", "set"}, Default: []string{"infer"}}, {Key: ArgConfigSetKey, Name: "KEY", Required: true}, {Key: ArgConfigSetValue, Name: "VALUE"}, {}, @@ -4131,11 +4131,11 @@ var Meta = argv.Metadata{ {Key: FlagDotfilesAddForce, Name: "force", Flag: true, Spelling: "--force"}, {Key: FlagDotfilesAddGlobal, Name: "global", Flag: true, Spelling: "--global"}, {Key: FlagDotfilesAddLocal, Name: "local", Flag: true, Spelling: "--local"}, - {Key: FlagDotfilesAddMode, Name: "mode", Flag: true, Spelling: "--mode"}, + {Key: FlagDotfilesAddMode, Name: "mode", Flag: true, Spelling: "--mode", ValueName: "MODE"}, {Key: FlagDotfilesAddDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagDotfilesAddNoApply, Name: "no-apply", Flag: true, Spelling: "--no-apply"}, - {Key: FlagDotfilesAddPath, Name: "path", Flag: true, Spelling: "--path"}, - {Key: FlagDotfilesAddSource, Name: "source", Flag: true, Spelling: "--source"}, + {Key: FlagDotfilesAddPath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, + {Key: FlagDotfilesAddSource, Name: "source", Flag: true, Spelling: "--source", ValueName: "PATH"}, {Key: FlagDotfilesAddYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: ArgDotfilesAddTarget, Name: "TARGET", Required: true}, {}, @@ -4145,8 +4145,8 @@ var Meta = argv.Metadata{ {Key: ArgDotfilesApplyTarget, Name: "TARGET"}, {}, {Key: FlagDotfilesEditApply, Name: "apply", Flag: true, Spelling: "--apply"}, - {Key: FlagDotfilesEditMode, Name: "mode", Flag: true, Spelling: "--mode"}, - {Key: FlagDotfilesEditSource, Name: "source", Flag: true, Spelling: "--source"}, + {Key: FlagDotfilesEditMode, Name: "mode", Flag: true, Spelling: "--mode", ValueName: "MODE"}, + {Key: FlagDotfilesEditSource, Name: "source", Flag: true, Spelling: "--source", ValueName: "PATH"}, {Key: FlagDotfilesEditYes, Name: "yes", Flag: true, Spelling: "--yes"}, {Key: ArgDotfilesEditTarget, Name: "TARGET", Required: true}, {}, @@ -4163,23 +4163,23 @@ var Meta = argv.Metadata{ {}, {Key: FlagDoctorPathFull, Name: "full", Flag: true, Spelling: "--full"}, {}, - {Key: FlagEnShell, Name: "shell", Flag: true, Spelling: "--shell"}, + {Key: FlagEnShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL"}, {Key: ArgEnDir, Name: "DIR", Default: []string{"."}}, {}, {Key: FlagEnvDotenv, Name: "dotenv", Flag: true, Spelling: "--dotenv"}, {Key: FlagEnvJson, Name: "json", Flag: true, Spelling: "--json"}, - {Key: FlagEnvShell, Name: "shell", Flag: true, Spelling: "--shell", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: FlagEnvShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, {Key: FlagEnvJsonExtended, Name: "json-extended", Flag: true, Spelling: "--json-extended"}, {Key: FlagEnvRedacted, Name: "redacted", Flag: true, Spelling: "--redacted"}, {Key: FlagEnvValues, Name: "values", Flag: true, Spelling: "--values"}, {Key: ArgEnvToolVersion, Name: "TOOL@VERSION"}, {}, - {Key: FlagExecCommand, Name: "command", Flag: true, Spelling: "--command"}, - {Key: FlagExecJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, - {Key: FlagExecAllowEnv, Name: "allow-env", Flag: true, Spelling: "--allow-env"}, - {Key: FlagExecAllowNet, Name: "allow-net", Flag: true, Spelling: "--allow-net"}, - {Key: FlagExecAllowRead, Name: "allow-read", Flag: true, Spelling: "--allow-read"}, - {Key: FlagExecAllowWrite, Name: "allow-write", Flag: true, Spelling: "--allow-write"}, + {Key: FlagExecCommand, Name: "command", Flag: true, Spelling: "--command", ValueName: "C"}, + {Key: FlagExecJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, + {Key: FlagExecAllowEnv, Name: "allow-env", Flag: true, Spelling: "--allow-env", ValueName: "VAR"}, + {Key: FlagExecAllowNet, Name: "allow-net", Flag: true, Spelling: "--allow-net", ValueName: "HOST"}, + {Key: FlagExecAllowRead, Name: "allow-read", Flag: true, Spelling: "--allow-read", ValueName: "PATH"}, + {Key: FlagExecAllowWrite, Name: "allow-write", Flag: true, Spelling: "--allow-write", ValueName: "PATH"}, {Key: FlagExecDenyAll, Name: "deny-all", Flag: true, Spelling: "--deny-all"}, {Key: FlagExecDenyEnv, Name: "deny-env", Flag: true, Spelling: "--deny-env"}, {Key: FlagExecDenyNet, Name: "deny-net", Flag: true, Spelling: "--deny-net"}, @@ -4197,49 +4197,49 @@ var Meta = argv.Metadata{ {}, {}, {Key: FlagGenerateBootstrapLocalize, Name: "localize", Flag: true, Spelling: "--localize"}, - {Key: FlagGenerateBootstrapVersion, Name: "version", Flag: true, Spelling: "--version"}, - {Key: FlagGenerateBootstrapWrite, Name: "write", Flag: true, Spelling: "--write"}, - {Key: FlagGenerateBootstrapLocalizedDir, Name: "localized-dir", Flag: true, Spelling: "--localized-dir", Default: []string{".mise"}}, + {Key: FlagGenerateBootstrapVersion, Name: "version", Flag: true, Spelling: "--version", ValueName: "VERSION"}, + {Key: FlagGenerateBootstrapWrite, Name: "write", Flag: true, Spelling: "--write", ValueName: "WRITE"}, + {Key: FlagGenerateBootstrapLocalizedDir, Name: "localized-dir", Flag: true, Spelling: "--localized-dir", ValueName: "LOCALIZED_DIR", Default: []string{".mise"}}, {}, {Key: FlagGenerateConfigGlobal, Name: "global", Flag: true, Spelling: "--global"}, {Key: FlagGenerateConfigDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagGenerateConfigToolVersions, Name: "tool-versions", Flag: true, Spelling: "--tool-versions"}, + {Key: FlagGenerateConfigToolVersions, Name: "tool-versions", Flag: true, Spelling: "--tool-versions", ValueName: "TOOL_VERSIONS"}, {Key: ArgGenerateConfigPath, Name: "PATH"}, {}, - {Key: FlagGenerateDevcontainerImage, Name: "image", Flag: true, Spelling: "--image"}, + {Key: FlagGenerateDevcontainerImage, Name: "image", Flag: true, Spelling: "--image", ValueName: "IMAGE"}, {Key: FlagGenerateDevcontainerMountMiseData, Name: "mount-mise-data", Flag: true, Spelling: "--mount-mise-data"}, - {Key: FlagGenerateDevcontainerName, Name: "name", Flag: true, Spelling: "--name"}, + {Key: FlagGenerateDevcontainerName, Name: "name", Flag: true, Spelling: "--name", ValueName: "NAME"}, {Key: FlagGenerateDevcontainerWrite, Name: "write", Flag: true, Spelling: "--write"}, {}, - {Key: FlagGenerateGitPreCommitTask, Name: "task", Flag: true, Spelling: "--task", Default: []string{"pre-commit"}}, + {Key: FlagGenerateGitPreCommitTask, Name: "task", Flag: true, Spelling: "--task", ValueName: "TASK", Default: []string{"pre-commit"}}, {Key: FlagGenerateGitPreCommitWrite, Name: "write", Flag: true, Spelling: "--write"}, - {Key: FlagGenerateGitPreCommitHook, Name: "hook", Flag: true, Spelling: "--hook", Default: []string{"pre-commit"}}, + {Key: FlagGenerateGitPreCommitHook, Name: "hook", Flag: true, Spelling: "--hook", ValueName: "HOOK", Default: []string{"pre-commit"}}, {}, - {Key: FlagGenerateGithubActionTask, Name: "task", Flag: true, Spelling: "--task", Default: []string{"ci"}}, + {Key: FlagGenerateGithubActionTask, Name: "task", Flag: true, Spelling: "--task", ValueName: "TASK", Default: []string{"ci"}}, {Key: FlagGenerateGithubActionWrite, Name: "write", Flag: true, Spelling: "--write"}, - {Key: FlagGenerateGithubActionName, Name: "name", Flag: true, Spelling: "--name", Default: []string{"ci"}}, + {Key: FlagGenerateGithubActionName, Name: "name", Flag: true, Spelling: "--name", ValueName: "NAME", Default: []string{"ci"}}, {}, {Key: FlagGenerateTaskDocsInject, Name: "inject", Flag: true, Spelling: "--inject"}, {Key: FlagGenerateTaskDocsIndex, Name: "index", Flag: true, Spelling: "--index"}, {Key: FlagGenerateTaskDocsMulti, Name: "multi", Flag: true, Spelling: "--multi"}, - {Key: FlagGenerateTaskDocsOutput, Name: "output", Flag: true, Spelling: "--output"}, - {Key: FlagGenerateTaskDocsRoot, Name: "root", Flag: true, Spelling: "--root"}, - {Key: FlagGenerateTaskDocsStyle, Name: "style", Flag: true, Spelling: "--style", Choices: []string{"simple", "detailed"}, Default: []string{"simple"}}, + {Key: FlagGenerateTaskDocsOutput, Name: "output", Flag: true, Spelling: "--output", ValueName: "OUTPUT"}, + {Key: FlagGenerateTaskDocsRoot, Name: "root", Flag: true, Spelling: "--root", ValueName: "ROOT"}, + {Key: FlagGenerateTaskDocsStyle, Name: "style", Flag: true, Spelling: "--style", ValueName: "STYLE", Choices: []string{"simple", "detailed"}, Default: []string{"simple"}}, {}, - {Key: FlagGenerateTaskStubsDir, Name: "dir", Flag: true, Spelling: "--dir", Default: []string{"bin"}}, - {Key: FlagGenerateTaskStubsMiseBin, Name: "mise-bin", Flag: true, Spelling: "--mise-bin", Default: []string{"mise"}}, + {Key: FlagGenerateTaskStubsDir, Name: "dir", Flag: true, Spelling: "--dir", ValueName: "DIR", Default: []string{"bin"}}, + {Key: FlagGenerateTaskStubsMiseBin, Name: "mise-bin", Flag: true, Spelling: "--mise-bin", ValueName: "MISE_BIN", Default: []string{"mise"}}, {}, - {Key: FlagGenerateToolStubBin, Name: "bin", Flag: true, Spelling: "--bin"}, + {Key: FlagGenerateToolStubBin, Name: "bin", Flag: true, Spelling: "--bin", ValueName: "BIN"}, {Key: FlagGenerateToolStubBootstrap, Name: "bootstrap", Flag: true, Spelling: "--bootstrap"}, - {Key: FlagGenerateToolStubBootstrapVersion, Name: "bootstrap-version", Flag: true, Spelling: "--bootstrap-version"}, + {Key: FlagGenerateToolStubBootstrapVersion, Name: "bootstrap-version", Flag: true, Spelling: "--bootstrap-version", ValueName: "BOOTSTRAP_VERSION"}, {Key: FlagGenerateToolStubFetch, Name: "fetch", Flag: true, Spelling: "--fetch"}, - {Key: FlagGenerateToolStubHttp, Name: "http", Flag: true, Spelling: "--http", Default: []string{"http"}}, + {Key: FlagGenerateToolStubHttp, Name: "http", Flag: true, Spelling: "--http", ValueName: "HTTP", Default: []string{"http"}}, {Key: FlagGenerateToolStubLock, Name: "lock", Flag: true, Spelling: "--lock"}, - {Key: FlagGenerateToolStubPlatformBin, Name: "platform-bin", Flag: true, Spelling: "--platform-bin"}, - {Key: FlagGenerateToolStubPlatformUrl, Name: "platform-url", Flag: true, Spelling: "--platform-url"}, + {Key: FlagGenerateToolStubPlatformBin, Name: "platform-bin", Flag: true, Spelling: "--platform-bin", ValueName: "PLATFORM_BIN"}, + {Key: FlagGenerateToolStubPlatformUrl, Name: "platform-url", Flag: true, Spelling: "--platform-url", ValueName: "PLATFORM_URL"}, {Key: FlagGenerateToolStubSkipDownload, Name: "skip-download", Flag: true, Spelling: "--skip-download"}, - {Key: FlagGenerateToolStubUrl, Name: "url", Flag: true, Spelling: "--url"}, - {Key: FlagGenerateToolStubVersion, Name: "version", Flag: true, Spelling: "--version", Default: []string{"latest"}}, + {Key: FlagGenerateToolStubUrl, Name: "url", Flag: true, Spelling: "--url", ValueName: "URL"}, + {Key: FlagGenerateToolStubVersion, Name: "version", Flag: true, Spelling: "--version", ValueName: "VERSION", Default: []string{"latest"}}, {Key: ArgGenerateToolStubOutput, Name: "OUTPUT", Required: true}, {}, {}, @@ -4252,16 +4252,16 @@ var Meta = argv.Metadata{ {Key: FlagGlobalFuzzy, Name: "fuzzy", Flag: true, Spelling: "--fuzzy"}, {Key: FlagGlobalPath, Name: "path", Flag: true, Spelling: "--path"}, {Key: FlagGlobalPin, Name: "pin", Flag: true, Spelling: "--pin"}, - {Key: FlagGlobalRemove, Name: "remove", Flag: true, Spelling: "--remove"}, + {Key: FlagGlobalRemove, Name: "remove", Flag: true, Spelling: "--remove", ValueName: "TOOL"}, {Key: ArgGlobalToolVersion, Name: "TOOL@VERSION"}, {}, {Key: FlagHookEnvForce, Name: "force", Flag: true, Spelling: "--force"}, {Key: FlagHookEnvQuiet, Name: "quiet", Flag: true, Spelling: "--quiet"}, - {Key: FlagHookEnvShell, Name: "shell", Flag: true, Spelling: "--shell", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, - {Key: FlagHookEnvReason, Name: "reason", Flag: true, Spelling: "--reason", Choices: []string{"precmd", "chpwd"}}, + {Key: FlagHookEnvShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: FlagHookEnvReason, Name: "reason", Flag: true, Spelling: "--reason", ValueName: "REASON", Choices: []string{"precmd", "chpwd"}}, {Key: FlagHookEnvStatus, Name: "status", Flag: true, Spelling: "--status"}, {}, - {Key: FlagHookNotFoundShell, Name: "shell", Flag: true, Spelling: "--shell", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, + {Key: FlagHookNotFoundShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL", Choices: []string{"bash", "elvish", "fish", "nu", "xonsh", "zsh", "pwsh"}}, {Key: ArgHookNotFoundBin, Name: "BIN", Required: true}, {}, {Key: FlagImplodeDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, @@ -4269,18 +4269,18 @@ var Meta = argv.Metadata{ {}, {Key: FlagEditGlobal, Name: "global", Flag: true, Spelling: "--global"}, {Key: FlagEditDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagEditToolVersions, Name: "tool-versions", Flag: true, Spelling: "--tool-versions"}, + {Key: FlagEditToolVersions, Name: "tool-versions", Flag: true, Spelling: "--tool-versions", ValueName: "TOOL_VERSIONS"}, {Key: ArgEditPath, Name: "PATH"}, {}, {Key: FlagInstallForce, Name: "force", Flag: true, Spelling: "--force"}, - {Key: FlagInstallJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagInstallJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagInstallDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagInstallVerbose, Name: "verbose", Flag: true, Spelling: "--verbose"}, {Key: FlagInstallDryRunCode, Name: "dry-run-code", Flag: true, Spelling: "--dry-run-code"}, - {Key: FlagInstallMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age"}, + {Key: FlagInstallMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age", ValueName: "MINIMUM_RELEASE_AGE"}, {Key: FlagInstallMonorepo, Name: "monorepo", Flag: true, Spelling: "--monorepo"}, {Key: FlagInstallRaw, Name: "raw", Flag: true, Spelling: "--raw"}, - {Key: FlagInstallShared, Name: "shared", Flag: true, Spelling: "--shared"}, + {Key: FlagInstallShared, Name: "shared", Flag: true, Spelling: "--shared", ValueName: "SHARED"}, {Key: FlagInstallSystem, Name: "system", Flag: true, Spelling: "--system"}, {Key: ArgInstallToolVersion, Name: "TOOL@VERSION"}, {}, @@ -4288,7 +4288,7 @@ var Meta = argv.Metadata{ {Key: ArgInstallIntoPath, Name: "PATH", Required: true}, {}, {Key: FlagLatestInstalled, Name: "installed", Flag: true, Spelling: "--installed"}, - {Key: FlagLatestMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age"}, + {Key: FlagLatestMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age", ValueName: "MINIMUM_RELEASE_AGE"}, {Key: ArgLatestToolVersion, Name: "TOOL@VERSION", Required: true}, {Key: ArgLatestAsdfVersion, Name: "ASDF_VERSION"}, {}, @@ -4300,17 +4300,17 @@ var Meta = argv.Metadata{ {Key: FlagLocalFuzzy, Name: "fuzzy", Flag: true, Spelling: "--fuzzy"}, {Key: FlagLocalPath, Name: "path", Flag: true, Spelling: "--path"}, {Key: FlagLocalPin, Name: "pin", Flag: true, Spelling: "--pin"}, - {Key: FlagLocalRemove, Name: "remove", Flag: true, Spelling: "--remove"}, + {Key: FlagLocalRemove, Name: "remove", Flag: true, Spelling: "--remove", ValueName: "TOOL"}, {Key: ArgLocalToolVersion, Name: "TOOL@VERSION"}, {}, {Key: FlagLockGlobal, Name: "global", Flag: true, Spelling: "--global"}, - {Key: FlagLockJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagLockJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagLockDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagLockPlatform, Name: "platform", Flag: true, Spelling: "--platform"}, + {Key: FlagLockPlatform, Name: "platform", Flag: true, Spelling: "--platform", ValueName: "PLATFORM"}, {Key: FlagLockBump, Name: "bump", Flag: true, Spelling: "--bump"}, {Key: FlagLockJson, Name: "json", Flag: true, Spelling: "--json"}, {Key: FlagLockLocal, Name: "local", Flag: true, Spelling: "--local"}, - {Key: FlagLockMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age"}, + {Key: FlagLockMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age", ValueName: "MINIMUM_RELEASE_AGE"}, {Key: ArgLockTool, Name: "TOOL"}, {}, {Key: FlagLsCurrent, Name: "current", Flag: true, Spelling: "--current"}, @@ -4320,17 +4320,17 @@ var Meta = argv.Metadata{ {Key: FlagLsLocal, Name: "local", Flag: true, Spelling: "--local"}, {Key: FlagLsMissing, Name: "missing", Flag: true, Spelling: "--missing"}, {Key: FlagLsOffline, Name: "offline", Flag: true, Spelling: "--offline"}, - {Key: FlagLsPlugin, Name: "plugin", Flag: true, Spelling: "--plugin"}, + {Key: FlagLsPlugin, Name: "plugin", Flag: true, Spelling: "--plugin", ValueName: "TOOL_FLAG"}, {Key: FlagLsAllSources, Name: "all-sources", Flag: true, Spelling: "--all-sources"}, {Key: FlagLsMonorepo, Name: "monorepo", Flag: true, Spelling: "--monorepo"}, {Key: FlagLsNoHeader, Name: "no-header", Flag: true, Spelling: "--no-header"}, {Key: FlagLsOutdated, Name: "outdated", Flag: true, Spelling: "--outdated"}, - {Key: FlagLsPrefix, Name: "prefix", Flag: true, Spelling: "--prefix"}, + {Key: FlagLsPrefix, Name: "prefix", Flag: true, Spelling: "--prefix", ValueName: "PREFIX"}, {Key: FlagLsPrunable, Name: "prunable", Flag: true, Spelling: "--prunable"}, {Key: ArgLsInstalledTool, Name: "INSTALLED_TOOL"}, {}, {Key: FlagLsRemoteAll, Name: "all", Flag: true, Spelling: "--all"}, - {Key: FlagLsRemoteMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age"}, + {Key: FlagLsRemoteMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age", ValueName: "MINIMUM_RELEASE_AGE"}, {Key: FlagLsRemoteJson, Name: "json", Flag: true, Spelling: "--json"}, {Key: FlagLsRemoteNoVersionsHost, Name: "no-versions-host", Flag: true, Spelling: "--no-versions-host"}, {Key: FlagLsRemotePrerelease, Name: "prerelease", Flag: true, Spelling: "--prerelease"}, @@ -4340,39 +4340,39 @@ var Meta = argv.Metadata{ {}, {}, {}, - {Key: FlagOciBuildCopy, Name: "copy", Flag: true, Spelling: "--copy"}, - {Key: FlagOciBuildOutput, Name: "output", Flag: true, Spelling: "--output", Default: []string{"./mise-oci"}}, - {Key: FlagOciBuildFrom, Name: "from", Flag: true, Spelling: "--from"}, + {Key: FlagOciBuildCopy, Name: "copy", Flag: true, Spelling: "--copy", ValueName: "HOST_PATH:IMAGE_PATH"}, + {Key: FlagOciBuildOutput, Name: "output", Flag: true, Spelling: "--output", ValueName: "OUTPUT", Default: []string{"./mise-oci"}}, + {Key: FlagOciBuildFrom, Name: "from", Flag: true, Spelling: "--from", ValueName: "FROM"}, {Key: FlagOciBuildIncludeGlobal, Name: "include-global", Flag: true, Spelling: "--include-global"}, - {Key: FlagOciBuildTag, Name: "tag", Flag: true, Spelling: "--tag"}, - {Key: FlagOciBuildMountPoint, Name: "mount-point", Flag: true, Spelling: "--mount-point"}, + {Key: FlagOciBuildTag, Name: "tag", Flag: true, Spelling: "--tag", ValueName: "TAG"}, + {Key: FlagOciBuildMountPoint, Name: "mount-point", Flag: true, Spelling: "--mount-point", ValueName: "MOUNT_POINT"}, {Key: FlagOciBuildNoMise, Name: "no-mise", Flag: true, Spelling: "--no-mise"}, - {Key: FlagOciBuildOwner, Name: "owner", Flag: true, Spelling: "--owner"}, + {Key: FlagOciBuildOwner, Name: "owner", Flag: true, Spelling: "--owner", ValueName: "UID[:GID]"}, {}, - {Key: FlagOciPushCacheFrom, Name: "cache-from", Flag: true, Spelling: "--cache-from"}, - {Key: FlagOciPushFrom, Name: "from", Flag: true, Spelling: "--from"}, - {Key: FlagOciPushImageDir, Name: "image-dir", Flag: true, Spelling: "--image-dir"}, + {Key: FlagOciPushCacheFrom, Name: "cache-from", Flag: true, Spelling: "--cache-from", ValueName: "REF"}, + {Key: FlagOciPushFrom, Name: "from", Flag: true, Spelling: "--from", ValueName: "FROM"}, + {Key: FlagOciPushImageDir, Name: "image-dir", Flag: true, Spelling: "--image-dir", ValueName: "IMAGE_DIR"}, {Key: FlagOciPushIncludeGlobal, Name: "include-global", Flag: true, Spelling: "--include-global"}, - {Key: FlagOciPushMountPoint, Name: "mount-point", Flag: true, Spelling: "--mount-point"}, + {Key: FlagOciPushMountPoint, Name: "mount-point", Flag: true, Spelling: "--mount-point", ValueName: "MOUNT_POINT"}, {Key: FlagOciPushNoCache, Name: "no-cache", Flag: true, Spelling: "--no-cache"}, {Key: FlagOciPushNoMise, Name: "no-mise", Flag: true, Spelling: "--no-mise"}, - {Key: FlagOciPushOwner, Name: "owner", Flag: true, Spelling: "--owner"}, + {Key: FlagOciPushOwner, Name: "owner", Flag: true, Spelling: "--owner", ValueName: "UID[:GID]"}, {Key: FlagOciPushUpdateIndex, Name: "update-index", Flag: true, Spelling: "--update-index"}, {Key: ArgOciPushRef, Name: "REF", Required: true}, {}, - {Key: FlagOciRunEngine, Name: "engine", Flag: true, Spelling: "--engine", Choices: []string{"auto", "podman", "docker"}, Default: []string{"auto"}}, - {Key: FlagOciRunFrom, Name: "from", Flag: true, Spelling: "--from"}, - {Key: FlagOciRunImageDir, Name: "image-dir", Flag: true, Spelling: "--image-dir"}, + {Key: FlagOciRunEngine, Name: "engine", Flag: true, Spelling: "--engine", ValueName: "ENGINE", Choices: []string{"auto", "podman", "docker"}, Default: []string{"auto"}}, + {Key: FlagOciRunFrom, Name: "from", Flag: true, Spelling: "--from", ValueName: "FROM"}, + {Key: FlagOciRunImageDir, Name: "image-dir", Flag: true, Spelling: "--image-dir", ValueName: "IMAGE_DIR"}, {Key: FlagOciRunIncludeGlobal, Name: "include-global", Flag: true, Spelling: "--include-global"}, {Key: FlagOciRunKeep, Name: "keep", Flag: true, Spelling: "--keep"}, - {Key: FlagOciRunMountPoint, Name: "mount-point", Flag: true, Spelling: "--mount-point"}, + {Key: FlagOciRunMountPoint, Name: "mount-point", Flag: true, Spelling: "--mount-point", ValueName: "MOUNT_POINT"}, {Key: FlagOciRunNoMise, Name: "no-mise", Flag: true, Spelling: "--no-mise"}, - {Key: FlagOciRunOwner, Name: "owner", Flag: true, Spelling: "--owner"}, - {Key: FlagOciRunVolume, Name: "volume", Flag: true, Spelling: "--volume"}, - {Key: FlagOciRunEnv, Name: "env", Flag: true, Spelling: "--env"}, + {Key: FlagOciRunOwner, Name: "owner", Flag: true, Spelling: "--owner", ValueName: "UID[:GID]"}, + {Key: FlagOciRunVolume, Name: "volume", Flag: true, Spelling: "--volume", ValueName: "HOST:CONTAINER"}, + {Key: FlagOciRunEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "KEY=VAL"}, {Key: FlagOciRunInteractive, Name: "interactive", Flag: true, Spelling: "--interactive"}, {Key: FlagOciRunTty, Name: "tty", Flag: true, Spelling: "--tty"}, - {Key: FlagOciRunWorkdir, Name: "workdir", Flag: true, Spelling: "--workdir"}, + {Key: FlagOciRunWorkdir, Name: "workdir", Flag: true, Spelling: "--workdir", ValueName: "WORKDIR"}, {Key: ArgOciRunCmd, Name: "CMD"}, {}, {Key: FlagOutdatedJson, Name: "json", Flag: true, Spelling: "--json"}, @@ -4394,7 +4394,7 @@ var Meta = argv.Metadata{ {}, {Key: FlagPluginsInstallAll, Name: "all", Flag: true, Spelling: "--all"}, {Key: FlagPluginsInstallForce, Name: "force", Flag: true, Spelling: "--force"}, - {Key: FlagPluginsInstallJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagPluginsInstallJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagPluginsInstallVerbose, Name: "verbose", Flag: true, Spelling: "--verbose"}, {Key: ArgPluginsInstallNewPlugin, Name: "NEW_PLUGIN"}, {Key: ArgPluginsInstallGitUrl, Name: "GIT_URL"}, @@ -4418,7 +4418,7 @@ var Meta = argv.Metadata{ {Key: FlagPluginsUninstallPurge, Name: "purge", Flag: true, Spelling: "--purge"}, {Key: ArgPluginsUninstallPlugin, Name: "PLUGIN"}, {}, - {Key: FlagPluginsUpdateJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagPluginsUpdateJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: ArgPluginsUpdatePlugin, Name: "PLUGIN"}, {}, {Key: FlagDepsExplain, Name: "explain", Flag: true, Spelling: "--explain"}, @@ -4426,8 +4426,8 @@ var Meta = argv.Metadata{ {Key: FlagDepsDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagDepsList, Name: "list", Flag: true, Spelling: "--list"}, {Key: FlagDepsMonorepo, Name: "monorepo", Flag: true, Spelling: "--monorepo"}, - {Key: FlagDepsOnly, Name: "only", Flag: true, Spelling: "--only"}, - {Key: FlagDepsSkip, Name: "skip", Flag: true, Spelling: "--skip"}, + {Key: FlagDepsOnly, Name: "only", Flag: true, Spelling: "--only", ValueName: "ONLY"}, + {Key: FlagDepsSkip, Name: "skip", Flag: true, Spelling: "--skip", ValueName: "SKIP"}, {Key: ArgDepsProvider, Name: "PROVIDER"}, {}, {Key: FlagDepsAddDev, Name: "dev", Flag: true, Spelling: "--dev"}, @@ -4438,8 +4438,8 @@ var Meta = argv.Metadata{ {Key: FlagDepsInstallDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagDepsInstallList, Name: "list", Flag: true, Spelling: "--list"}, {Key: FlagDepsInstallMonorepo, Name: "monorepo", Flag: true, Spelling: "--monorepo"}, - {Key: FlagDepsInstallOnly, Name: "only", Flag: true, Spelling: "--only"}, - {Key: FlagDepsInstallSkip, Name: "skip", Flag: true, Spelling: "--skip"}, + {Key: FlagDepsInstallOnly, Name: "only", Flag: true, Spelling: "--only", ValueName: "ONLY"}, + {Key: FlagDepsInstallSkip, Name: "skip", Flag: true, Spelling: "--skip", ValueName: "SKIP"}, {Key: ArgDepsInstallProvider, Name: "PROVIDER"}, {}, {Key: ArgDepsRemovePackages, Name: "PACKAGES", Required: true}, @@ -4451,7 +4451,7 @@ var Meta = argv.Metadata{ {Key: FlagPruneTools, Name: "tools", Flag: true, Spelling: "--tools"}, {Key: ArgPruneInstalledTool, Name: "INSTALLED_TOOL"}, {}, - {Key: FlagRegistryBackend, Name: "backend", Flag: true, Spelling: "--backend"}, + {Key: FlagRegistryBackend, Name: "backend", Flag: true, Spelling: "--backend", ValueName: "BACKEND"}, {Key: FlagRegistryComplete, Name: "complete", Flag: true, Spelling: "--complete"}, {Key: FlagRegistryHideAliased, Name: "hide-aliased", Flag: true, Spelling: "--hide-aliased"}, {Key: FlagRegistryJson, Name: "json", Flag: true, Spelling: "--json"}, @@ -4464,25 +4464,25 @@ var Meta = argv.Metadata{ {Key: ArgReshimVersion, Name: "VERSION"}, {}, {Key: FlagRunAffected, Name: "affected", Flag: true, Spelling: "--affected"}, - {Key: FlagRunAffectedBase, Name: "affected-base", Flag: true, Spelling: "--affected-base"}, + {Key: FlagRunAffectedBase, Name: "affected-base", Flag: true, Spelling: "--affected-base", ValueName: "REV"}, {Key: FlagRunAffectedExplain, Name: "affected-explain", Flag: true, Spelling: "--affected-explain"}, - {Key: FlagRunAffectedHead, Name: "affected-head", Flag: true, Spelling: "--affected-head"}, + {Key: FlagRunAffectedHead, Name: "affected-head", Flag: true, Spelling: "--affected-head", ValueName: "REV"}, {Key: FlagRunAffectedJson, Name: "affected-json", Flag: true, Spelling: "--affected-json"}, {Key: FlagRunContinueOnError, Name: "continue-on-error", Flag: true, Spelling: "--continue-on-error"}, - {Key: FlagRunCd, Name: "cd", Flag: true, Spelling: "--cd"}, + {Key: FlagRunCd, Name: "cd", Flag: true, Spelling: "--cd", ValueName: "CD"}, {Key: FlagRunForce, Name: "force", Flag: true, Spelling: "--force"}, - {Key: FlagRunJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagRunJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagRunDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagRunOutput, Name: "output", Flag: true, Spelling: "--output"}, + {Key: FlagRunOutput, Name: "output", Flag: true, Spelling: "--output", ValueName: "OUTPUT"}, {Key: FlagRunQuiet, Name: "quiet", Flag: true, Spelling: "--quiet"}, {Key: FlagRunRaw, Name: "raw", Flag: true, Spelling: "--raw"}, - {Key: FlagRunShell, Name: "shell", Flag: true, Spelling: "--shell"}, + {Key: FlagRunShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL"}, {Key: FlagRunSilent, Name: "silent", Flag: true, Spelling: "--silent"}, - {Key: FlagRunTool, Name: "tool", Flag: true, Spelling: "--tool"}, - {Key: FlagRunAllowEnv, Name: "allow-env", Flag: true, Spelling: "--allow-env"}, - {Key: FlagRunAllowNet, Name: "allow-net", Flag: true, Spelling: "--allow-net"}, - {Key: FlagRunAllowRead, Name: "allow-read", Flag: true, Spelling: "--allow-read"}, - {Key: FlagRunAllowWrite, Name: "allow-write", Flag: true, Spelling: "--allow-write"}, + {Key: FlagRunTool, Name: "tool", Flag: true, Spelling: "--tool", ValueName: "TOOL@VERSION"}, + {Key: FlagRunAllowEnv, Name: "allow-env", Flag: true, Spelling: "--allow-env", ValueName: "VAR"}, + {Key: FlagRunAllowNet, Name: "allow-net", Flag: true, Spelling: "--allow-net", ValueName: "HOST"}, + {Key: FlagRunAllowRead, Name: "allow-read", Flag: true, Spelling: "--allow-read", ValueName: "PATH"}, + {Key: FlagRunAllowWrite, Name: "allow-write", Flag: true, Spelling: "--allow-write", ValueName: "PATH"}, {Key: FlagRunDenyAll, Name: "deny-all", Flag: true, Spelling: "--deny-all"}, {Key: FlagRunDenyEnv, Name: "deny-env", Flag: true, Spelling: "--deny-env"}, {Key: FlagRunDenyNet, Name: "deny-net", Flag: true, Spelling: "--deny-net"}, @@ -4494,15 +4494,15 @@ var Meta = argv.Metadata{ {Key: FlagRunNoTimings, Name: "no-timings", Flag: true, Spelling: "--no-timings"}, {Key: FlagRunSkipDeps, Name: "skip-deps", Flag: true, Spelling: "--skip-deps"}, {Key: FlagRunSkipTools, Name: "skip-tools", Flag: true, Spelling: "--skip-tools"}, - {Key: FlagRunTaskCache, Name: "task-cache", Flag: true, Spelling: "--task-cache", Choices: []string{"read-write", "read-only", "write-only", "off", "local-only"}, Default: []string{"read-write"}}, + {Key: FlagRunTaskCache, Name: "task-cache", Flag: true, Spelling: "--task-cache", ValueName: "TASK_CACHE", Choices: []string{"read-write", "read-only", "write-only", "off", "local-only"}, Default: []string{"read-write"}}, {Key: FlagRunTaskCacheExplain, Name: "task-cache-explain", Flag: true, Spelling: "--task-cache-explain"}, {Key: FlagRunTaskCacheExplainJson, Name: "task-cache-explain-json", Flag: true, Spelling: "--task-cache-explain-json"}, {Key: FlagRunTaskCacheStats, Name: "task-cache-stats", Flag: true, Spelling: "--task-cache-stats"}, - {Key: FlagRunTimeout, Name: "timeout", Flag: true, Spelling: "--timeout"}, + {Key: FlagRunTimeout, Name: "timeout", Flag: true, Spelling: "--timeout", ValueName: "TIMEOUT"}, {Key: FlagRunTimings, Name: "timings", Flag: true, Spelling: "--timings"}, {}, {Key: FlagSearchInteractive, Name: "interactive", Flag: true, Spelling: "--interactive"}, - {Key: FlagSearchMatchType, Name: "match-type", Flag: true, Spelling: "--match-type", Choices: []string{"equal", "contains", "fuzzy"}, Default: []string{"fuzzy"}}, + {Key: FlagSearchMatchType, Name: "match-type", Flag: true, Spelling: "--match-type", ValueName: "MATCH_TYPE", Choices: []string{"equal", "contains", "fuzzy"}, Default: []string{"fuzzy"}}, {Key: FlagSearchNoHeader, Name: "no-header", Flag: true, Spelling: "--no-header"}, {Key: ArgSearchName, Name: "NAME"}, {}, @@ -4511,17 +4511,17 @@ var Meta = argv.Metadata{ {Key: FlagSelfUpdateNoPlugins, Name: "no-plugins", Flag: true, Spelling: "--no-plugins"}, {Key: ArgSelfUpdateVersion, Name: "VERSION"}, {}, - {Key: FlagSetEnv, Name: "env", Flag: true, Spelling: "--env"}, + {Key: FlagSetEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "ENV"}, {Key: FlagSetGlobal, Name: "global", Flag: true, Spelling: "--global"}, {Key: FlagSetAgeEncrypt, Name: "age-encrypt", Flag: true, Spelling: "--age-encrypt"}, - {Key: FlagSetAgeKeyFile, Name: "age-key-file", Flag: true, Spelling: "--age-key-file"}, - {Key: FlagSetAgeRecipient, Name: "age-recipient", Flag: true, Spelling: "--age-recipient"}, - {Key: FlagSetAgeSshRecipient, Name: "age-ssh-recipient", Flag: true, Spelling: "--age-ssh-recipient"}, + {Key: FlagSetAgeKeyFile, Name: "age-key-file", Flag: true, Spelling: "--age-key-file", ValueName: "PATH"}, + {Key: FlagSetAgeRecipient, Name: "age-recipient", Flag: true, Spelling: "--age-recipient", ValueName: "RECIPIENT"}, + {Key: FlagSetAgeSshRecipient, Name: "age-ssh-recipient", Flag: true, Spelling: "--age-ssh-recipient", ValueName: "PATH_OR_PUBKEY"}, {Key: FlagSetComplete, Name: "complete", Flag: true, Spelling: "--complete"}, - {Key: FlagSetFile, Name: "file", Flag: true, Spelling: "--file"}, + {Key: FlagSetFile, Name: "file", Flag: true, Spelling: "--file", ValueName: "FILE"}, {Key: FlagSetNoRedact, Name: "no-redact", Flag: true, Spelling: "--no-redact"}, {Key: FlagSetPrompt, Name: "prompt", Flag: true, Spelling: "--prompt"}, - {Key: FlagSetRemove, Name: "remove", Flag: true, Spelling: "--remove"}, + {Key: FlagSetRemove, Name: "remove", Flag: true, Spelling: "--remove", ValueName: "ENV_KEY"}, {Key: FlagSetStdin, Name: "stdin", Flag: true, Spelling: "--stdin"}, {Key: ArgSetEnvVar, Name: "ENV_VAR"}, {}, @@ -4556,7 +4556,7 @@ var Meta = argv.Metadata{ {Key: FlagSettingsUnsetLocal, Name: "local", Flag: true, Spelling: "--local"}, {Key: ArgSettingsUnsetKey, Name: "KEY", Required: true}, {}, - {Key: FlagShellJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagShellJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagShellUnset, Name: "unset", Flag: true, Spelling: "--unset"}, {Key: FlagShellRaw, Name: "raw", Flag: true, Spelling: "--raw"}, {Key: ArgShellToolVersion, Name: "TOOL@VERSION", Required: true}, @@ -4592,25 +4592,25 @@ var Meta = argv.Metadata{ {Key: FlagTasksHidden, Name: "hidden", Flag: true, Spelling: "--hidden"}, {Key: FlagTasksNameOnly, Name: "name-only", Flag: true, Spelling: "--name-only"}, {Key: FlagTasksNoHeader, Name: "no-header", Flag: true, Spelling: "--no-header"}, - {Key: FlagTasksSort, Name: "sort", Flag: true, Spelling: "--sort", Choices: []string{"name", "alias", "description", "source"}}, - {Key: FlagTasksSortOrder, Name: "sort-order", Flag: true, Spelling: "--sort-order", Choices: []string{"asc", "desc"}}, + {Key: FlagTasksSort, Name: "sort", Flag: true, Spelling: "--sort", ValueName: "COLUMN", Choices: []string{"name", "alias", "description", "source"}}, + {Key: FlagTasksSortOrder, Name: "sort-order", Flag: true, Spelling: "--sort-order", ValueName: "SORT_ORDER", Choices: []string{"asc", "desc"}}, {Key: FlagTasksUsage, Name: "usage", Flag: true, Spelling: "--usage"}, {Key: ArgTasksTask, Name: "TASK"}, {}, - {Key: FlagTasksAddAlias, Name: "alias", Flag: true, Spelling: "--alias"}, - {Key: FlagTasksAddDepends, Name: "depends", Flag: true, Spelling: "--depends"}, - {Key: FlagTasksAddDir, Name: "dir", Flag: true, Spelling: "--dir"}, + {Key: FlagTasksAddAlias, Name: "alias", Flag: true, Spelling: "--alias", ValueName: "ALIAS"}, + {Key: FlagTasksAddDepends, Name: "depends", Flag: true, Spelling: "--depends", ValueName: "DEPENDS"}, + {Key: FlagTasksAddDir, Name: "dir", Flag: true, Spelling: "--dir", ValueName: "DIR"}, {Key: FlagTasksAddFile, Name: "file", Flag: true, Spelling: "--file"}, {Key: FlagTasksAddHide, Name: "hide", Flag: true, Spelling: "--hide"}, {Key: FlagTasksAddQuiet, Name: "quiet", Flag: true, Spelling: "--quiet"}, {Key: FlagTasksAddRaw, Name: "raw", Flag: true, Spelling: "--raw"}, - {Key: FlagTasksAddSources, Name: "sources", Flag: true, Spelling: "--sources"}, - {Key: FlagTasksAddWaitFor, Name: "wait-for", Flag: true, Spelling: "--wait-for"}, - {Key: FlagTasksAddDependsPost, Name: "depends-post", Flag: true, Spelling: "--depends-post"}, - {Key: FlagTasksAddDescription, Name: "description", Flag: true, Spelling: "--description"}, - {Key: FlagTasksAddOutputs, Name: "outputs", Flag: true, Spelling: "--outputs"}, - {Key: FlagTasksAddRunWindows, Name: "run-windows", Flag: true, Spelling: "--run-windows"}, - {Key: FlagTasksAddShell, Name: "shell", Flag: true, Spelling: "--shell"}, + {Key: FlagTasksAddSources, Name: "sources", Flag: true, Spelling: "--sources", ValueName: "SOURCES"}, + {Key: FlagTasksAddWaitFor, Name: "wait-for", Flag: true, Spelling: "--wait-for", ValueName: "WAIT_FOR"}, + {Key: FlagTasksAddDependsPost, Name: "depends-post", Flag: true, Spelling: "--depends-post", ValueName: "DEPENDS_POST"}, + {Key: FlagTasksAddDescription, Name: "description", Flag: true, Spelling: "--description", ValueName: "DESCRIPTION"}, + {Key: FlagTasksAddOutputs, Name: "outputs", Flag: true, Spelling: "--outputs", ValueName: "OUTPUTS"}, + {Key: FlagTasksAddRunWindows, Name: "run-windows", Flag: true, Spelling: "--run-windows", ValueName: "RUN_WINDOWS"}, + {Key: FlagTasksAddShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL"}, {Key: FlagTasksAddSilent, Name: "silent", Flag: true, Spelling: "--silent"}, {Key: ArgTasksAddTask, Name: "TASK", Required: true}, {Key: ArgTasksAddRun, Name: "RUN"}, @@ -4639,30 +4639,30 @@ var Meta = argv.Metadata{ {Key: FlagTasksLsHidden, Name: "hidden", Flag: true, Spelling: "--hidden"}, {Key: FlagTasksLsNameOnly, Name: "name-only", Flag: true, Spelling: "--name-only"}, {Key: FlagTasksLsNoHeader, Name: "no-header", Flag: true, Spelling: "--no-header"}, - {Key: FlagTasksLsSort, Name: "sort", Flag: true, Spelling: "--sort", Choices: []string{"name", "alias", "description", "source"}}, - {Key: FlagTasksLsSortOrder, Name: "sort-order", Flag: true, Spelling: "--sort-order", Choices: []string{"asc", "desc"}}, + {Key: FlagTasksLsSort, Name: "sort", Flag: true, Spelling: "--sort", ValueName: "COLUMN", Choices: []string{"name", "alias", "description", "source"}}, + {Key: FlagTasksLsSortOrder, Name: "sort-order", Flag: true, Spelling: "--sort-order", ValueName: "SORT_ORDER", Choices: []string{"asc", "desc"}}, {Key: FlagTasksLsUsage, Name: "usage", Flag: true, Spelling: "--usage"}, {}, {Key: FlagTasksRunAffected, Name: "affected", Flag: true, Spelling: "--affected"}, - {Key: FlagTasksRunAffectedBase, Name: "affected-base", Flag: true, Spelling: "--affected-base"}, + {Key: FlagTasksRunAffectedBase, Name: "affected-base", Flag: true, Spelling: "--affected-base", ValueName: "REV"}, {Key: FlagTasksRunAffectedExplain, Name: "affected-explain", Flag: true, Spelling: "--affected-explain"}, - {Key: FlagTasksRunAffectedHead, Name: "affected-head", Flag: true, Spelling: "--affected-head"}, + {Key: FlagTasksRunAffectedHead, Name: "affected-head", Flag: true, Spelling: "--affected-head", ValueName: "REV"}, {Key: FlagTasksRunAffectedJson, Name: "affected-json", Flag: true, Spelling: "--affected-json"}, {Key: FlagTasksRunContinueOnError, Name: "continue-on-error", Flag: true, Spelling: "--continue-on-error"}, - {Key: FlagTasksRunCd, Name: "cd", Flag: true, Spelling: "--cd"}, + {Key: FlagTasksRunCd, Name: "cd", Flag: true, Spelling: "--cd", ValueName: "CD"}, {Key: FlagTasksRunForce, Name: "force", Flag: true, Spelling: "--force"}, - {Key: FlagTasksRunJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagTasksRunJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagTasksRunDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagTasksRunOutput, Name: "output", Flag: true, Spelling: "--output"}, + {Key: FlagTasksRunOutput, Name: "output", Flag: true, Spelling: "--output", ValueName: "OUTPUT"}, {Key: FlagTasksRunQuiet, Name: "quiet", Flag: true, Spelling: "--quiet"}, {Key: FlagTasksRunRaw, Name: "raw", Flag: true, Spelling: "--raw"}, - {Key: FlagTasksRunShell, Name: "shell", Flag: true, Spelling: "--shell"}, + {Key: FlagTasksRunShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL"}, {Key: FlagTasksRunSilent, Name: "silent", Flag: true, Spelling: "--silent"}, - {Key: FlagTasksRunTool, Name: "tool", Flag: true, Spelling: "--tool"}, - {Key: FlagTasksRunAllowEnv, Name: "allow-env", Flag: true, Spelling: "--allow-env"}, - {Key: FlagTasksRunAllowNet, Name: "allow-net", Flag: true, Spelling: "--allow-net"}, - {Key: FlagTasksRunAllowRead, Name: "allow-read", Flag: true, Spelling: "--allow-read"}, - {Key: FlagTasksRunAllowWrite, Name: "allow-write", Flag: true, Spelling: "--allow-write"}, + {Key: FlagTasksRunTool, Name: "tool", Flag: true, Spelling: "--tool", ValueName: "TOOL@VERSION"}, + {Key: FlagTasksRunAllowEnv, Name: "allow-env", Flag: true, Spelling: "--allow-env", ValueName: "VAR"}, + {Key: FlagTasksRunAllowNet, Name: "allow-net", Flag: true, Spelling: "--allow-net", ValueName: "HOST"}, + {Key: FlagTasksRunAllowRead, Name: "allow-read", Flag: true, Spelling: "--allow-read", ValueName: "PATH"}, + {Key: FlagTasksRunAllowWrite, Name: "allow-write", Flag: true, Spelling: "--allow-write", ValueName: "PATH"}, {Key: FlagTasksRunDenyAll, Name: "deny-all", Flag: true, Spelling: "--deny-all"}, {Key: FlagTasksRunDenyEnv, Name: "deny-env", Flag: true, Spelling: "--deny-env"}, {Key: FlagTasksRunDenyNet, Name: "deny-net", Flag: true, Spelling: "--deny-net"}, @@ -4674,11 +4674,11 @@ var Meta = argv.Metadata{ {Key: FlagTasksRunNoTimings, Name: "no-timings", Flag: true, Spelling: "--no-timings"}, {Key: FlagTasksRunSkipDeps, Name: "skip-deps", Flag: true, Spelling: "--skip-deps"}, {Key: FlagTasksRunSkipTools, Name: "skip-tools", Flag: true, Spelling: "--skip-tools"}, - {Key: FlagTasksRunTaskCache, Name: "task-cache", Flag: true, Spelling: "--task-cache", Choices: []string{"read-write", "read-only", "write-only", "off", "local-only"}, Default: []string{"read-write"}}, + {Key: FlagTasksRunTaskCache, Name: "task-cache", Flag: true, Spelling: "--task-cache", ValueName: "TASK_CACHE", Choices: []string{"read-write", "read-only", "write-only", "off", "local-only"}, Default: []string{"read-write"}}, {Key: FlagTasksRunTaskCacheExplain, Name: "task-cache-explain", Flag: true, Spelling: "--task-cache-explain"}, {Key: FlagTasksRunTaskCacheExplainJson, Name: "task-cache-explain-json", Flag: true, Spelling: "--task-cache-explain-json"}, {Key: FlagTasksRunTaskCacheStats, Name: "task-cache-stats", Flag: true, Spelling: "--task-cache-stats"}, - {Key: FlagTasksRunTimeout, Name: "timeout", Flag: true, Spelling: "--timeout"}, + {Key: FlagTasksRunTimeout, Name: "timeout", Flag: true, Spelling: "--timeout", ValueName: "TIMEOUT"}, {Key: FlagTasksRunTimings, Name: "timings", Flag: true, Spelling: "--timings"}, {Key: ArgTasksRunTask, Name: "TASK", Default: []string{"default"}}, {Key: ArgTasksRunArgs, Name: "ARGS"}, @@ -4689,7 +4689,7 @@ var Meta = argv.Metadata{ {Key: ArgTasksValidateTasks, Name: "TASKS"}, {}, {Key: FlagTestToolAll, Name: "all", Flag: true, Spelling: "--all"}, - {Key: FlagTestToolJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagTestToolJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagTestToolAllConfig, Name: "all-config", Flag: true, Spelling: "--all-config"}, {Key: FlagTestToolIncludeNonDefined, Name: "include-non-defined", Flag: true, Spelling: "--include-non-defined"}, {Key: FlagTestToolRaw, Name: "raw", Flag: true, Spelling: "--raw"}, @@ -4725,70 +4725,70 @@ var Meta = argv.Metadata{ {Key: FlagTrustIgnore, Name: "ignore", Flag: true, Spelling: "--ignore"}, {Key: FlagTrustShow, Name: "show", Flag: true, Spelling: "--show"}, {Key: FlagTrustUntrust, Name: "untrust", Flag: true, Spelling: "--untrust"}, - {Key: ArgTrustConfigFile, Name: "CONFIG_FILE"}, + {Key: ArgTrustConfigFile, Name: "CONFIG_FILE", CompleteType: "file"}, {}, {Key: FlagUninstallAll, Name: "all", Flag: true, Spelling: "--all"}, {Key: FlagUninstallDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, {Key: FlagUninstallDryRunCode, Name: "dry-run-code", Flag: true, Spelling: "--dry-run-code"}, {Key: ArgUninstallInstalledToolVersion, Name: "INSTALLED_TOOL@VERSION"}, {}, - {Key: FlagUnsetFile, Name: "file", Flag: true, Spelling: "--file"}, + {Key: FlagUnsetFile, Name: "file", Flag: true, Spelling: "--file", ValueName: "FILE"}, {Key: FlagUnsetGlobal, Name: "global", Flag: true, Spelling: "--global"}, {Key: ArgUnsetEnvKey, Name: "ENV_KEY"}, {}, - {Key: ArgUntrustConfigFile, Name: "CONFIG_FILE"}, + {Key: ArgUntrustConfigFile, Name: "CONFIG_FILE", CompleteType: "file"}, {}, - {Key: FlagUnuseEnv, Name: "env", Flag: true, Spelling: "--env"}, + {Key: FlagUnuseEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "ENV"}, {Key: FlagUnuseGlobal, Name: "global", Flag: true, Spelling: "--global"}, - {Key: FlagUnusePath, Name: "path", Flag: true, Spelling: "--path"}, + {Key: FlagUnusePath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, {Key: FlagUnuseNoPrune, Name: "no-prune", Flag: true, Spelling: "--no-prune"}, {Key: ArgUnuseInstalledToolVersion, Name: "INSTALLED_TOOL@VERSION", Required: true}, {}, {Key: FlagUpgradeInteractive, Name: "interactive", Flag: true, Spelling: "--interactive"}, - {Key: FlagUpgradeJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagUpgradeJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagUpgradeBump, Name: "bump", Flag: true, Spelling: "--bump"}, {Key: FlagUpgradeDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagUpgradeExclude, Name: "exclude", Flag: true, Spelling: "--exclude"}, + {Key: FlagUpgradeExclude, Name: "exclude", Flag: true, Spelling: "--exclude", ValueName: "INSTALLED_TOOL"}, {Key: FlagUpgradeDryRunCode, Name: "dry-run-code", Flag: true, Spelling: "--dry-run-code"}, {Key: FlagUpgradeInactive, Name: "inactive", Flag: true, Spelling: "--inactive"}, {Key: FlagUpgradeLocal, Name: "local", Flag: true, Spelling: "--local"}, - {Key: FlagUpgradeMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age"}, + {Key: FlagUpgradeMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age", ValueName: "MINIMUM_RELEASE_AGE"}, {Key: FlagUpgradeMonorepo, Name: "monorepo", Flag: true, Spelling: "--monorepo"}, {Key: FlagUpgradeNoPrune, Name: "no-prune", Flag: true, Spelling: "--no-prune"}, {Key: FlagUpgradeRaw, Name: "raw", Flag: true, Spelling: "--raw"}, {Key: ArgUpgradeInstalledToolVersion, Name: "INSTALLED_TOOL@VERSION"}, {}, {}, - {Key: FlagUseEnv, Name: "env", Flag: true, Spelling: "--env"}, + {Key: FlagUseEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "ENV"}, {Key: FlagUseForce, Name: "force", Flag: true, Spelling: "--force"}, {Key: FlagUseGlobal, Name: "global", Flag: true, Spelling: "--global"}, - {Key: FlagUseJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, + {Key: FlagUseJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "JOBS"}, {Key: FlagUseDryRun, Name: "dry-run", Flag: true, Spelling: "--dry-run"}, - {Key: FlagUsePath, Name: "path", Flag: true, Spelling: "--path"}, + {Key: FlagUsePath, Name: "path", Flag: true, Spelling: "--path", ValueName: "PATH"}, {Key: FlagUseDryRunCode, Name: "dry-run-code", Flag: true, Spelling: "--dry-run-code"}, {Key: FlagUseFuzzy, Name: "fuzzy", Flag: true, Spelling: "--fuzzy"}, - {Key: FlagUseMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age"}, + {Key: FlagUseMinimumReleaseAge, Name: "minimum-release-age", Flag: true, Spelling: "--minimum-release-age", ValueName: "MINIMUM_RELEASE_AGE"}, {Key: FlagUsePin, Name: "pin", Flag: true, Spelling: "--pin"}, {Key: FlagUseRaw, Name: "raw", Flag: true, Spelling: "--raw"}, - {Key: FlagUseRemove, Name: "remove", Flag: true, Spelling: "--remove"}, + {Key: FlagUseRemove, Name: "remove", Flag: true, Spelling: "--remove", ValueName: "TOOL"}, {Key: ArgUseToolVersion, Name: "TOOL@VERSION"}, {}, {Key: FlagVersionJson, Name: "json", Flag: true, Spelling: "--json"}, {}, - {Key: FlagWatchTaskFlag, Name: "task-flag", Flag: true, Spelling: "--task-flag"}, - {Key: FlagWatchGlob, Name: "glob", Flag: true, Spelling: "--glob"}, + {Key: FlagWatchTaskFlag, Name: "task-flag", Flag: true, Spelling: "--task-flag", ValueName: "TASK_FLAG"}, + {Key: FlagWatchGlob, Name: "glob", Flag: true, Spelling: "--glob", ValueName: "GLOB"}, {Key: FlagWatchSkipDeps, Name: "skip-deps", Flag: true, Spelling: "--skip-deps"}, - {Key: FlagWatchWatch, Name: "watch", Flag: true, Spelling: "--watch"}, - {Key: FlagWatchWatchNonRecursive, Name: "watch-non-recursive", Flag: true, Spelling: "--watch-non-recursive"}, - {Key: FlagWatchWatchFile, Name: "watch-file", Flag: true, Spelling: "--watch-file"}, - {Key: FlagWatchClear, Name: "clear", Flag: true, Spelling: "--clear", Choices: []string{"clear", "reset"}}, - {Key: FlagWatchOnBusyUpdate, Name: "on-busy-update", Flag: true, Spelling: "--on-busy-update", Choices: []string{"queue", "do-nothing", "restart", "signal"}, Default: []string{"do-nothing"}}, + {Key: FlagWatchWatch, Name: "watch", Flag: true, Spelling: "--watch", ValueName: "PATH"}, + {Key: FlagWatchWatchNonRecursive, Name: "watch-non-recursive", Flag: true, Spelling: "--watch-non-recursive", ValueName: "PATH"}, + {Key: FlagWatchWatchFile, Name: "watch-file", Flag: true, Spelling: "--watch-file", ValueName: "PATH"}, + {Key: FlagWatchClear, Name: "clear", Flag: true, Spelling: "--clear", ValueName: "MODE", Choices: []string{"clear", "reset"}}, + {Key: FlagWatchOnBusyUpdate, Name: "on-busy-update", Flag: true, Spelling: "--on-busy-update", ValueName: "MODE", Choices: []string{"queue", "do-nothing", "restart", "signal"}, Default: []string{"do-nothing"}}, {Key: FlagWatchRestart, Name: "restart", Flag: true, Spelling: "--restart"}, - {Key: FlagWatchSignal, Name: "signal", Flag: true, Spelling: "--signal"}, - {Key: FlagWatchStopSignal, Name: "stop-signal", Flag: true, Spelling: "--stop-signal"}, - {Key: FlagWatchStopTimeout, Name: "stop-timeout", Flag: true, Spelling: "--stop-timeout", Default: []string{"10s"}}, - {Key: FlagWatchMapSignal, Name: "map-signal", Flag: true, Spelling: "--map-signal"}, - {Key: FlagWatchDebounce, Name: "debounce", Flag: true, Spelling: "--debounce", Default: []string{"50ms"}}, + {Key: FlagWatchSignal, Name: "signal", Flag: true, Spelling: "--signal", ValueName: "SIGNAL"}, + {Key: FlagWatchStopSignal, Name: "stop-signal", Flag: true, Spelling: "--stop-signal", ValueName: "SIGNAL"}, + {Key: FlagWatchStopTimeout, Name: "stop-timeout", Flag: true, Spelling: "--stop-timeout", ValueName: "TIMEOUT", Default: []string{"10s"}}, + {Key: FlagWatchMapSignal, Name: "map-signal", Flag: true, Spelling: "--map-signal", ValueName: "SIGNAL:SIGNAL"}, + {Key: FlagWatchDebounce, Name: "debounce", Flag: true, Spelling: "--debounce", ValueName: "TIMEOUT", Default: []string{"50ms"}}, {Key: FlagWatchStdinQuit, Name: "stdin-quit", Flag: true, Spelling: "--stdin-quit"}, {Key: FlagWatchNoVcsIgnore, Name: "no-vcs-ignore", Flag: true, Spelling: "--no-vcs-ignore"}, {Key: FlagWatchNoProjectIgnore, Name: "no-project-ignore", Flag: true, Spelling: "--no-project-ignore"}, @@ -4797,28 +4797,28 @@ var Meta = argv.Metadata{ {Key: FlagWatchNoDiscoverIgnore, Name: "no-discover-ignore", Flag: true, Spelling: "--no-discover-ignore"}, {Key: FlagWatchIgnoreNothing, Name: "ignore-nothing", Flag: true, Spelling: "--ignore-nothing"}, {Key: FlagWatchPostpone, Name: "postpone", Flag: true, Spelling: "--postpone"}, - {Key: FlagWatchDelayRun, Name: "delay-run", Flag: true, Spelling: "--delay-run"}, - {Key: FlagWatchPoll, Name: "poll", Flag: true, Spelling: "--poll"}, - {Key: FlagWatchShell, Name: "shell", Flag: true, Spelling: "--shell"}, + {Key: FlagWatchDelayRun, Name: "delay-run", Flag: true, Spelling: "--delay-run", ValueName: "DURATION"}, + {Key: FlagWatchPoll, Name: "poll", Flag: true, Spelling: "--poll", ValueName: "INTERVAL"}, + {Key: FlagWatchShell, Name: "shell", Flag: true, Spelling: "--shell", ValueName: "SHELL"}, {Key: FlagWatchN, Name: "n", Flag: true, Spelling: "-n"}, - {Key: FlagWatchEmitEventsTo, Name: "emit-events-to", Flag: true, Spelling: "--emit-events-to", Choices: []string{"environment", "stdio", "file", "json-stdio", "json-file", "none"}, Default: []string{"none"}}, + {Key: FlagWatchEmitEventsTo, Name: "emit-events-to", Flag: true, Spelling: "--emit-events-to", ValueName: "MODE", Choices: []string{"environment", "stdio", "file", "json-stdio", "json-file", "none"}, Default: []string{"none"}}, {Key: FlagWatchOnlyEmitEvents, Name: "only-emit-events", Flag: true, Spelling: "--only-emit-events"}, - {Key: FlagWatchEnv, Name: "env", Flag: true, Spelling: "--env"}, - {Key: FlagWatchWrapProcess, Name: "wrap-process", Flag: true, Spelling: "--wrap-process", Choices: []string{"group", "session", "none"}}, + {Key: FlagWatchEnv, Name: "env", Flag: true, Spelling: "--env", ValueName: "KEY=VALUE"}, + {Key: FlagWatchWrapProcess, Name: "wrap-process", Flag: true, Spelling: "--wrap-process", ValueName: "MODE", Choices: []string{"group", "session", "none"}}, {Key: FlagWatchNotify, Name: "notify", Flag: true, Spelling: "--notify"}, - {Key: FlagWatchColor, Name: "color", Flag: true, Spelling: "--color", Choices: []string{"auto", "always", "never"}, Default: []string{"auto"}}, + {Key: FlagWatchColor, Name: "color", Flag: true, Spelling: "--color", ValueName: "MODE", Choices: []string{"auto", "always", "never"}, Default: []string{"auto"}}, {Key: FlagWatchTimings, Name: "timings", Flag: true, Spelling: "--timings"}, {Key: FlagWatchQuiet, Name: "quiet", Flag: true, Spelling: "--quiet"}, {Key: FlagWatchBell, Name: "bell", Flag: true, Spelling: "--bell"}, - {Key: FlagWatchProjectOrigin, Name: "project-origin", Flag: true, Spelling: "--project-origin"}, - {Key: FlagWatchWorkdir, Name: "workdir", Flag: true, Spelling: "--workdir"}, - {Key: FlagWatchExts, Name: "exts", Flag: true, Spelling: "--exts"}, - {Key: FlagWatchFilter, Name: "filter", Flag: true, Spelling: "--filter"}, - {Key: FlagWatchFilterFile, Name: "filter-file", Flag: true, Spelling: "--filter-file"}, - {Key: FlagWatchFilterProg, Name: "filter-prog", Flag: true, Spelling: "--filter-prog"}, - {Key: FlagWatchIgnore, Name: "ignore", Flag: true, Spelling: "--ignore"}, - {Key: FlagWatchIgnoreFile, Name: "ignore-file", Flag: true, Spelling: "--ignore-file"}, - {Key: FlagWatchFsEvents, Name: "fs-events", Flag: true, Spelling: "--fs-events", Choices: []string{"access", "create", "remove", "rename", "modify", "metadata"}, Default: []string{"create", "remove", "rename", "modify", "metadata"}}, + {Key: FlagWatchProjectOrigin, Name: "project-origin", Flag: true, Spelling: "--project-origin", ValueName: "DIRECTORY"}, + {Key: FlagWatchWorkdir, Name: "workdir", Flag: true, Spelling: "--workdir", ValueName: "DIRECTORY"}, + {Key: FlagWatchExts, Name: "exts", Flag: true, Spelling: "--exts", ValueName: "EXTENSIONS"}, + {Key: FlagWatchFilter, Name: "filter", Flag: true, Spelling: "--filter", ValueName: "PATTERN"}, + {Key: FlagWatchFilterFile, Name: "filter-file", Flag: true, Spelling: "--filter-file", ValueName: "PATH"}, + {Key: FlagWatchFilterProg, Name: "filter-prog", Flag: true, Spelling: "--filter-prog", ValueName: "EXPRESSION"}, + {Key: FlagWatchIgnore, Name: "ignore", Flag: true, Spelling: "--ignore", ValueName: "PATTERN"}, + {Key: FlagWatchIgnoreFile, Name: "ignore-file", Flag: true, Spelling: "--ignore-file", ValueName: "PATH"}, + {Key: FlagWatchFsEvents, Name: "fs-events", Flag: true, Spelling: "--fs-events", ValueName: "EVENTS", Choices: []string{"access", "create", "remove", "rename", "modify", "metadata"}, Default: []string{"create", "remove", "rename", "modify", "metadata"}}, {Key: FlagWatchNoMeta, Name: "no-meta", Flag: true, Spelling: "--no-meta"}, {Key: FlagWatchPrintEvents, Name: "print-events", Flag: true, Spelling: "--print-events"}, {Key: FlagWatchManual, Name: "manual", Flag: true, Spelling: "--manual"}, @@ -4828,7 +4828,7 @@ var Meta = argv.Metadata{ {Key: ArgWhereToolVersion, Name: "TOOL@VERSION", Required: true}, {Key: ArgWhereAsdfVersion, Name: "ASDF_VERSION"}, {}, - {Key: FlagWhichTool, Name: "tool", Flag: true, Spelling: "--tool"}, + {Key: FlagWhichTool, Name: "tool", Flag: true, Spelling: "--tool", ValueName: "TOOL@VERSION"}, {Key: FlagWhichComplete, Name: "complete", Flag: true, Spelling: "--complete"}, {Key: FlagWhichPlugin, Name: "plugin", Flag: true, Spelling: "--plugin"}, {Key: FlagWhichVersion, Name: "version", Flag: true, Spelling: "--version"}, diff --git a/go/internal/spec/spec.go b/go/internal/spec/spec.go index c8b3adc9a..3160d508c 100644 --- a/go/internal/spec/spec.go +++ b/go/internal/spec/spec.go @@ -41,11 +41,15 @@ type Spec struct { Version string `json:"version"` About string `json:"about"` AboutLong string `json:"about_long"` - BeforeHelp string `json:"before_help"` - AfterHelp string `json:"after_help"` - BeforeHelpLong string `json:"before_help_long"` - AfterHelpLong string `json:"after_help_long"` - Usage string `json:"usage"` + // Complete is the completers a spec declares, keyed by the lowercased name of + // the argument or flag value they belong to — which is how usage-lib keys + // them, and how a lookup has to be spelled. + Complete map[string]Completer `json:"complete"` + BeforeHelp string `json:"before_help"` + AfterHelp string `json:"after_help"` + BeforeHelpLong string `json:"before_help_long"` + AfterHelpLong string `json:"after_help_long"` + Usage string `json:"usage"` } // HelpSpec is what a page needs from the spec's root rather than from a command. @@ -63,6 +67,14 @@ func (s *Spec) HelpSpec() argv.HelpSpec { } } +// Completer is one `complete` block. Only the type is read here: running the +// `run=` script is a completion's job at run time, and needs a subprocess this +// package has no business starting. +type Completer struct { + Name string `json:"name"` + Type string `json:"type_"` +} + // Cmd is one command in the lowered spec. type Cmd struct { Name string `json:"name"` @@ -319,7 +331,7 @@ func (s *Spec) Build() (*argv.Command, argv.Metadata) { // help one. They share the keys that tie them together, so they are built in one // pass for the same reason the first two were. func (s *Spec) BuildAll() (*argv.Command, argv.Metadata, argv.HelpTable) { - b := &builder{} + b := &builder{complete: s.Complete} root := b.command(&s.Cmd, unknownFlags(s.UnknownFlags, argv.UnknownFlagsValue)) // default_subcommand is a property of the spec rather than of a command, so it @@ -398,6 +410,8 @@ type builder struct { // it needs the original. A spec declaring `negate="-no-color"` is not named // by `--no-color`, and usage-lib does not resolve it either. negation map[uint64]string + // complete is the spec's `complete` blocks, keyed as usage-lib keys them. + complete map[string]Completer } // record files an entry's cold half at the position its key indexes. @@ -442,6 +456,26 @@ func visibleAliases(c *Cmd) []string { return out } +// valueOf is what a flag's value is called, or empty where the flag takes none. +func valueOf(f *Flag) string { + if f.Arg == nil { + return "" + } + return f.Arg.Name +} + +// completeType is the type the spec's `complete` block names for an entry. +// +// By lowercased name, which is how usage-lib files them: `complete "FILE"` and an +// argument written `` are the same position as far as the reference is +// concerned. +func (b *builder) completeType(name string) string { + if b.complete == nil || name == "" { + return "" + } + return b.complete[strings.ToLower(name)].Type +} + func first(values ...string) string { for _, v := range values { if v != "" { @@ -672,14 +706,16 @@ func (b *builder) flag(f *Flag) *argv.Flag { Default: f.defaults(), }) b.record(out.Key, argv.Meta{ - Name: f.Name, - Flag: true, - Spelling: spelling(f), - Required: f.Required, - Choices: f.choices(), - Default: f.defaults(), - Env: f.Env, - VarMin: clampVarMax(f.VarMin), + Name: f.Name, + Flag: true, + Spelling: spelling(f), + ValueName: valueOf(f), + CompleteType: b.completeType(first(valueOf(f), f.Name)), + Required: f.Required, + Choices: f.choices(), + Default: f.defaults(), + Env: f.Env, + VarMin: clampVarMax(f.VarMin), // Occurrences. The per-occurrence value bound is a limit binding applies, // and is set on the parse table below rather than here. VarMax: clampVarMax(f.VarMax), @@ -719,12 +755,13 @@ func (b *builder) arg(a *Arg) *argv.Arg { Default: a.Default, }) b.record(out.Key, argv.Meta{ - Name: a.Name, - Required: a.Required, - Choices: a.Choices.list(), - Default: a.Default, - Env: a.Env, - VarMin: clampVarMax(a.VarMin), + Name: a.Name, + Required: a.Required, + CompleteType: b.completeType(a.Name), + Choices: a.Choices.list(), + Default: a.Default, + Env: a.Env, + VarMin: clampVarMax(a.VarMin), // 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 here would // fail an invocation that never broke it. diff --git a/lib/src/go/mod.rs b/lib/src/go/mod.rs index 3d11b3169..35dd785fe 100644 --- a/lib/src/go/mod.rs +++ b/lib/src/go/mod.rs @@ -408,7 +408,7 @@ impl Emitter<'_> { 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)); + by_key.insert(named.number, arg_meta(self.spec, arg, named)); } } @@ -465,6 +465,19 @@ impl Emitter<'_> { } else if let Some(short) = flag.short.first() { fields.push(format!("Spelling: {}", go_string(&format!("-{short}")))); } + // What the value is called, which is what says whether a path belongs + // there — `--into ` completes directories because of the name. + if let Some(value) = flag.arg.as_ref() { + fields.push(format!("ValueName: {}", go_string(&value.name))); + } + let named_value = flag + .arg + .as_ref() + .map(|a| a.name.as_str()) + .unwrap_or(flag.name.as_str()); + if let Some(kind) = complete_type(self.spec, named_value) { + fields.push(format!("CompleteType: {}", go_string(kind))); + } // 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))); @@ -512,7 +525,18 @@ impl Emitter<'_> { } /// The cold half of a positional argument. -fn arg_meta(arg: &SpecArg, named: &Named) -> String { +/// The type a spec's `complete` block names for an entry, if it names one. +/// +/// By lowercased name, which is how usage-lib files them: `complete "FILE"` and +/// an argument written `` are the same position as far as the reference is +/// concerned. +fn complete_type<'a>(spec: &'a Spec, name: &str) -> Option<&'a str> { + spec.complete + .get(&name.to_lowercase()) + .and_then(|c| c.type_.as_deref()) +} + +fn arg_meta(spec: &Spec, arg: &SpecArg, named: &Named) -> String { let mut fields = vec![ format!("Key: {}", named.key), format!("Name: {}", go_string(&arg.name)), @@ -520,6 +544,13 @@ fn arg_meta(arg: &SpecArg, named: &Named) -> String { if arg.required { fields.push("Required: true".to_string()); } + // What the position takes, where the spec said so. Read by completion rather + // than by any post-binding rule: an author who wrote `complete "input" + // type="file"` named what belongs there, and the alternative is inferring it + // from a name they did not choose. + if let Some(kind) = complete_type(spec, &arg.name) { + fields.push(format!("CompleteType: {}", go_string(kind))); + } if let Some(choices) = &arg.choices { fields.push(format!("Choices: {}", string_slice(&choices.choices))); } 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 7865e9bcc..4e944f28f 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 @@ -95,8 +95,8 @@ var Meta = argv.Metadata{ {}, {Key: FlagVerbose, Name: "verbose", Flag: true, Spelling: "--verbose"}, {Key: FlagColor, Name: "color", Flag: true, Spelling: "--color"}, - {Key: FlagJobs, Name: "jobs", Flag: true, Spelling: "--jobs"}, - {Key: FlagInclude, Name: "include", Flag: true, Spelling: "--include", VarMax: 3}, + {Key: FlagJobs, Name: "jobs", Flag: true, Spelling: "--jobs", ValueName: "n"}, + {Key: FlagInclude, Name: "include", Flag: true, Spelling: "--include", ValueName: "pattern", VarMax: 3}, {Key: ArgFile, Name: "file", Required: true}, {Key: ArgRest, Name: "rest"}, {}, From 5a325cce3c9a59da127b53e57f95b217d5c00f90 Mon Sep 17 00:00:00 2001 From: default <216188+jdx@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:53:21 +0000 Subject: [PATCH 2/3] fix(go): offer flags only once a dash is typed, as the reference does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Candidates` offered every flag at a bare cursor. The reference offers none — `mise ⌶` lists commands, `mise -⌶` lists both forms, `mise --⌶` the longs — which was checked against `usage complete-word` rather than argued about. It matters twice. A shell showed flags where the CLI would rather show what to run, and the file fallback asks whether *anything* was offered: with every flag in the list, every position on every command with a flag looked answered, so an undescribed value never fell back to paths. Two smaller ones alongside. A variadic still collecting is the cursor's position, so a positional owing a `--` behind it has nothing to say about whether a path belongs. And a `--cursor` too large to hold is refused rather than wrapped: a wrapped one describes a position near the start of the line, and the answer would be confident and about the wrong word. The parity test is the point. These rules were reimplemented, and reimplemented rules drift; `usage complete-word` is the oracle a shell would have called before any of this existed, so the cursor's answer is now compared against it over mise's spec — the same standard the 211 pages are held to. It excludes the two kinds of position this side does not claim: where the reference lists the working directory, compared as a marker instead, and where it runs a spec's `complete` block, which shells out on a Tab and is deliberately not built. Co-Authored-By: Claude Opus 5 --- go/argv/complete.go | 14 +++- go/argv/complete_test.go | 45 ++++++++--- go/argv/request.go | 22 +++-- go/argv/request_test.go | 61 ++++++++++++++ go/conformance/complete_test.go | 138 ++++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 go/conformance/complete_test.go diff --git a/go/argv/complete.go b/go/argv/complete.go index e7be2f945..e9ac03f5c 100644 --- a/go/argv/complete.go +++ b/go/argv/complete.go @@ -191,10 +191,16 @@ func Candidates(pos Position, partial string, help HelpTable, meta Metadata) []C commands() } - // Flags, only where one could still be typed, and taken from the parser's own - // scope so that shadowing is respected: a subcommand redeclaring an inherited - // name offers its own. - if pos.FlagsPossible { + // Flags, only where one could still be typed *and* the user has started typing + // one. The reference offers no flags for a bare cursor — `ex ⌶` lists + // subcommands, `ex -⌶` lists both forms, `ex --⌶` the longs — and checked + // against `usage complete-word` rather than assumed. Offering them anyway made + // every position look answered, which is also what decides whether the shell + // should fall back to paths. + // + // The prefix filter below would narrow them to the same set; what this changes + // is the empty prefix, where it would not. + if pos.FlagsPossible && strings.HasPrefix(partial, "-") { for _, s := range flagsInScope(pos.Chain) { if h := help.Lookup(s.flag.Key); h != nil && h.Hide { continue diff --git a/go/argv/complete_test.go b/go/argv/complete_test.go index 12b9a0c31..0d9accdb5 100644 --- a/go/argv/complete_test.go +++ b/go/argv/complete_test.go @@ -61,8 +61,24 @@ func complete(words []string, partial string) []string { } func TestCompletionOffersCommandsFlagsAndAliases(t *testing.T) { + // A bare cursor is asking which command to run. The reference offers no flags + // there — checked against `usage complete-word`, which answers `install` and + // `run` for a spec whose root also has `-v --verbose`. got := complete(nil, "") - for _, want := range []string{"run", "list", "r", "--verbose", "-v", "--color", "--no-color"} { + for _, want := range []string{"run", "list", "r"} { + if !offered(got, want) { + t.Errorf("want %q offered, got %v", want, got) + } + } + for _, unwanted := range []string{"--verbose", "-v"} { + if offered(got, unwanted) { + t.Errorf("a bare cursor is not asking for flags: %v", got) + } + } + + // A dash is: a lone one offers both forms. + got = complete(nil, "-") + for _, want := range []string{"--verbose", "-v", "--color", "--no-color"} { if !offered(got, want) { t.Errorf("want %q offered, got %v", want, got) } @@ -87,7 +103,7 @@ func TestCompletionFiltersByThePartialWord(t *testing.T) { // A global is offered inside a subcommand, because the parser accepts it there. func TestAGlobalIsOfferedInsideASubcommand(t *testing.T) { - got := complete([]string{"run"}, "") + got := complete([]string{"run"}, "-") if !offered(got, "--verbose") { t.Errorf("an inherited global should be offered: %v", got) } @@ -213,7 +229,7 @@ func TestOnlyTheClaimedSpellingIsWithdrawn(t *testing.T) { help := HelpTable{{Key: 1}, {Key: 2}, {Key: 3}, {Key: 4}} meta := Metadata{{Key: 1}, {Key: 2}, {Key: 3}, {Key: 4}} - got := values(Candidates(Walk(root, []string{"run"}), "", help, meta)) + got := values(Candidates(Walk(root, []string{"run"}), "-", help, meta)) // `--jobs` is the subcommand's now, and still offered once. if n := count(got, "--jobs"); n != 1 { t.Errorf("--jobs should appear once, got %d: %v", n, got) @@ -264,7 +280,7 @@ func TestANegationLosesToALongOfTheSameSpelling(t *testing.T) { if f := binds(t, root, []string{"run", "--no-color"}); f != global { t.Fatalf("the parser binds --no-color to %v, so the premise is wrong", f) } - got := values(Candidates(Walk(root, []string{"run"}), "", help, meta)) + got := values(Candidates(Walk(root, []string{"run"}), "-", help, meta)) if n := count(got, "--no-color"); n != 1 { t.Errorf("--no-color should be offered once, by the flag that binds it, got %d: %v", n, got) @@ -288,7 +304,7 @@ func TestAnInheritedNegationSurvivesItsFlagsOtherSpellings(t *testing.T) { if f := binds(t, root, []string{"run", "--no-color"}); f != global { t.Fatalf("the parser binds --no-color to %v, so the premise is wrong", f) } - got := values(Candidates(Walk(root, []string{"run"}), "", help, meta)) + got := values(Candidates(Walk(root, []string{"run"}), "-", help, meta)) if !offered(got, "--no-color") { t.Errorf("--no-color still binds, so it should be offered: %v", got) } @@ -339,7 +355,7 @@ func TestANegationSpelledLikeItsOwnLongIsOfferedOnce(t *testing.T) { help := HelpTable{{Key: 1}, {Key: 2}} meta := Metadata{{Key: 1}, {Key: 2}} - got := values(Candidates(Walk(root, nil), "", help, meta)) + got := values(Candidates(Walk(root, nil), "-", help, meta)) if n := count(got, "--no-color"); n != 1 { t.Errorf("--no-color should be offered once, got %d: %v", n, got) } @@ -368,19 +384,24 @@ func TestAVariadicStillCollectingOffersFlagsToo(t *testing.T) { } got := values(Candidates(Walk(root, []string{"--tools", "a"}), "", help, meta)) - if !offered(got, "--force") { - t.Errorf("a flag ends the collection and binds, so it belongs here: %v", got) - } if !offered(got, "node") { t.Errorf("the variadic's own values belong here too: %v", got) } + // And once a dash is typed, the flags that would end the collection — the + // same rule as anywhere else, which is the point: this position is not the + // exclusive one a flag owed its value is. + dashed := values(Candidates(Walk(root, []string{"--tools", "a"}), "-", help, meta)) + if !offered(dashed, "--force") { + t.Errorf("a flag ends the collection and binds, so it belongs here: %v", dashed) + } // A plain word goes to the variadic, so nothing a plain word cannot be. if offered(got, "run") { t.Errorf("a subcommand name would be collected as a value, not bound: %v", got) } - // And a flag still owed its first value keeps the position to itself. - owed := values(Candidates(Walk(root, []string{"--tools"}), "", help, meta)) + // And a flag still owed its first value keeps the position to itself, dash or + // no dash: the parser refuses a flag-like token there. + owed := values(Candidates(Walk(root, []string{"--tools"}), "-", help, meta)) if offered(owed, "--force") { t.Errorf("a flag-like token is refused where a value is owed: %v", owed) } @@ -403,7 +424,7 @@ func TestANearerFlagTakesTheSpellingFromAnInheritedNegation(t *testing.T) { if f := binds(t, root, []string{"run", "--x"}); f != local { t.Fatalf("the parser binds --x to the nearer flag, got %v", f) } - got := values(Candidates(Walk(root, []string{"run"}), "", help, meta)) + got := values(Candidates(Walk(root, []string{"run"}), "-", help, meta)) if n := count(got, "--x"); n != 1 { t.Errorf("--x should be offered once, by the flag that binds it, got %d: %v", n, got) } diff --git a/go/argv/request.go b/go/argv/request.go index 3397eac7b..371c0fc84 100644 --- a/go/argv/request.go +++ b/go/argv/request.go @@ -121,11 +121,13 @@ func filesAt(pos Position, split SplitLine, candidates []Candidate, meta Metadat if pos.FlagsPossible && strings.HasPrefix(split.Prefix, "-") { return NoFiles } - // Only when the cursor is *at* that argument. A flag waiting for its value is - // a different position that happens to have an unfilled positional behind it, + // Only when the cursor is *at* that argument. A flag taking a value is a + // different position that happens to have an unfilled positional behind it, // and a rule about the positional has nothing to say about the flag: `ex - // --from ⌶` takes a path whatever the argument after it needs. - if pos.AwaitingValue == nil && pos.NextArg != nil && + // --from ⌶` takes a path whatever the argument after it needs. A variadic + // still collecting is the same position by a weaker claim, and the reference + // folds the two together here. + if pos.AwaitingValue == nil && pos.Collecting == nil && pos.NextArg != nil && pos.NextArg.DoubleDash == DoubleDashRequired && !pos.SeparatorSeen { return NoFiles } @@ -181,7 +183,14 @@ func filesFor(name string) Files { // atoi is [strconv.Atoi] for a non-negative number, written out because this // package takes no imports it can avoid. +// +// A number too large to hold is refused rather than wrapped. Wrapping is worse +// than refusing here: a cursor that came back as a small number would describe a +// position near the start of the line, and the completion would answer confidently +// about the wrong word — where a refusal falls back to the end of the line, which +// is where a shell means when it says nothing. func atoi(s string) (int, error) { + const maxInt = int(^uint(0) >> 1) if s == "" { return 0, errNotANumber } @@ -190,10 +199,11 @@ func atoi(s string) (int, error) { if s[i] < '0' || s[i] > '9' { return 0, errNotANumber } - n = n*10 + int(s[i]-'0') - if n < 0 { + digit := int(s[i] - '0') + if n > (maxInt-digit)/10 { return 0, errNotANumber } + n = n*10 + digit } return n, nil } diff --git a/go/argv/request_test.go b/go/argv/request_test.go index affd0c5ad..dfd08f9ef 100644 --- a/go/argv/request_test.go +++ b/go/argv/request_test.go @@ -143,6 +143,67 @@ func TestWhenPathsBelongAtTheCursor(t *testing.T) { } } +// A command having flags does not close the position. +// +// `Candidates` offers flags only for a dash-prefixed word, which is what the +// reference does — and what keeps the file fallback working, since the fallback +// asks whether *anything* was offered. Offering every flag at a bare cursor made +// every position on every command with a flag look answered. +func TestFlagsDoNotCloseThePositionToPaths(t *testing.T) { + root := &Command{Key: 1, Name: "ex", + Flags: []*Flag{{Key: 2, Name: "verbose", Longs: []string{"verbose"}}}, + Args: []*Arg{{Key: 3, Name: "INPUT"}}} + help := HelpTable{{Key: 1}, {Key: 2}, {Key: 3}} + meta := Metadata{{Key: 1}, {Key: 2, Name: "verbose", Flag: true}, {Key: 3, Name: "INPUT"}} + + answer := Request{Shell: Bash, Line: "ex ", Cursor: 3}.Answer(root, help, meta) + if answer.Files != AnyFile { + t.Errorf("nothing describes INPUT, so the shell should offer paths: %v", answer.Files) + } + if len(answer.Candidates) != 0 { + t.Errorf("a bare cursor is not asking for flags: %v", values(answer.Candidates)) + } + // And a dash asks for them, which closes the position — no path starts with + // one. + dashed := Request{Shell: Bash, Line: "ex -", Cursor: 4}.Answer(root, help, meta) + if dashed.Files != NoFiles || len(dashed.Candidates) == 0 { + t.Errorf("a dash asks for flags and nothing else: %v %v", + dashed.Files, values(dashed.Candidates)) + } +} + +// A variadic still collecting is on the flag's value, not on the argument behind +// it — so an argument owing a separator has nothing to say about this position. +func TestASeparatorOwedBehindACollectingFlag(t *testing.T) { + tools := &Flag{Key: 2, Name: "tools", Longs: []string{"tools"}, + TakesValue: true, Variadic: true} + root := &Command{Key: 1, Name: "ex", Flags: []*Flag{tools}, + Args: []*Arg{{Key: 3, Name: "TASK", DoubleDash: DoubleDashRequired}}} + help := HelpTable{{Key: 1}, {Key: 2}, {Key: 3}} + meta := Metadata{{Key: 1}, {Key: 2, Name: "tools", Flag: true, ValueName: "PATH"}, + {Key: 3, Name: "TASK"}} + + const line = "ex --tools a " + if got := (Request{Shell: Bash, Line: line, Cursor: len(line)}). + Answer(root, help, meta).Files; got != AnyFile { + t.Errorf("the cursor is on the flag's value, which is called PATH: %v", got) + } +} + +// A cursor too large to hold falls back to the end of the line. +// +// Wrapping would be worse than refusing: a small number describes a position near +// the start of the line, and the completion would answer confidently about the +// wrong word. +func TestACursorTooLargeToHold(t *testing.T) { + for _, huge := range []string{"18446744073709551616", "99999999999999999999"} { + req, ok := ParseRequest([]string{RequestName, "--line", "ex use", "--cursor", huge}) + if !ok || req.Cursor != len("ex use") { + t.Errorf("%s should fall back to the end of the line, got %d", huge, req.Cursor) + } + } +} + // A completer's declared type outranks the name, because the author wrote it. func TestADeclaredCompleterTypeDecidesIt(t *testing.T) { root := &Command{Key: 1, Name: "ex", Args: []*Arg{{Key: 2, Name: "INPUT"}}} diff --git a/go/conformance/complete_test.go b/go/conformance/complete_test.go new file mode 100644 index 000000000..7fc33ee57 --- /dev/null +++ b/go/conformance/complete_test.go @@ -0,0 +1,138 @@ +package conformance + +import ( + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/jdx/usage/go/argv" +) + +// Does the cursor get the same answer here as it does from usage-lib? +// +// The same standard the pages are held to, and for the same reason: these rules +// were reimplemented, and reimplemented rules drift. `usage complete-word` is the +// reference a shell would have called before any of this existed, so it is the +// oracle — over mise's real spec, which is the largest one there is. +// +// This exists because a divergence got through. `Candidates` offered every flag +// at a bare cursor; the reference offers none until a `-` is typed, and nothing +// noticed until a reviewer read the two. A hand-written test asserts what its +// author believed; this asks. + +// completeWord is the reference's answer for a partial word at a command path. +// +// Only the lines the CLI itself produced: where the reference has nothing to +// offer it falls back to listing the working directory, and those lines describe +// the machine rather than the spec. The Go side says `\x01files` there instead, +// which is compared separately below. +func completeWord(t *testing.T, usageBin, kdl string, words []string, cword int) []string { + t.Helper() + args := []string{"complete-word", "--shell", "bash", "-f", kdl, + "--cword", itoa(cword), "--"} + args = append(args, words...) + out, err := exec.Command(usageBin, args...).Output() + if err != nil { + t.Fatalf("the reference should answer %v: %v", words, err) + } + var lines []string + for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") { + if line != "" { + lines = append(lines, line) + } + } + return lines +} + +func TestTheCursorGetsTheReferencesAnswer(t *testing.T) { + usageBin := findUsage(t) + kdl := filepath.Join("..", "..", "benches", "mise.usage.kdl") + lowered := lowerFile(t, usageBin, kdl) + root, meta, help := lowered.BuildAll() + + // Lines where the reference answers from the spec alone. Two kinds are left + // out on purpose, because they are answers this side does not claim to give: + // a position where the reference lists the working directory (compared as a + // marker below), and one where it *runs* a spec's `complete` block — `mise ⌶` + // and `mise settings ⌶` both do, and shelling out on a Tab is the piece this + // package has deliberately not built. + // + // What is left covers the branches: subcommands, aliases, a nested command, + // both flag forms, a long that narrows, and a word that matches nothing. + for _, line := range []string{ + "mise plug", + "mise config ", + "mise config l", + "mise -", + "mise --", + "mise --log-", + "mise use -", + "mise wat", + "mise plugins ", + "mise plugins install -", + } { + split := argv.Split(line, len(line), argv.Bash) + want := completeWord(t, usageBin, kdl, split.Words, split.Cword) + + answer := argv.Request{Shell: argv.Bash, Line: line, Cursor: len(line)}. + Answer(root, help, meta) + var got []string + for _, c := range answer.Candidates { + got = append(got, c.Value) + } + + if !sameSet(got, want) { + t.Errorf("%q:\n ours: %v\n lib: %v", line, got, want) + } + } +} + +// And where the reference falls back to the filesystem, the Go side says so +// rather than listing it. +// +// The two are the same answer said differently: usage-lib prints the directory +// because it is answering a shell that has already asked; the tables say "paths +// belong here" and let the script call the shell's own path completion, which +// knows about `~`, variables and remote paths. +func TestAPathFallbackIsAMarkerRatherThanAListing(t *testing.T) { + usageBin := findUsage(t) + kdl := filepath.Join("..", "..", "benches", "mise.usage.kdl") + lowered := lowerFile(t, usageBin, kdl) + root, meta, help := lowered.BuildAll() + + // `mise edit` takes a file, and neither side has anything else to offer there. + const line = "mise edit " + split := argv.Split(line, len(line), argv.Bash) + want := completeWord(t, usageBin, kdl, split.Words, split.Cword) + if len(want) == 0 { + t.Skip("the reference listed nothing here, so there is no fallback to compare") + } + + answer := argv.Request{Shell: argv.Bash, Line: line, Cursor: len(line)}. + Answer(root, help, meta) + if answer.Files != argv.AnyFile { + t.Errorf("the reference fell back to paths at %q, and this did not: %v", + line, answer.Files) + } + if len(answer.Candidates) != 0 { + t.Errorf("nothing of the spec's own belongs there: %v", answer.Candidates) + } +} + +func sameSet(a, b []string) bool { + if len(a) != len(b) { + return false + } + seen := map[string]int{} + for _, s := range a { + seen[s]++ + } + for _, s := range b { + seen[s]-- + if seen[s] < 0 { + return false + } + } + return true +} From 1968b2e69433979da428219b6daff694512c1684 Mon Sep 17 00:00:00 2001 From: default <216188+jdx@users.noreply.github.com> Date: Tue, 18 Aug 2026 00:03:22 +0000 Subject: [PATCH 3/3] test(go): ask the reference only where the reference answers from the spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two of the lines sat on mise's root, whose default subcommand is `run` — so its `TASK` argument runs a completer, and even `mise plug⌶` was answered partly by a subprocess this side does not start. They passed because no task in this repository happens to begin with `plug` or `wat`, which is not a property of either implementation. Moved under a subcommand that has neither a completer nor a path fallback, and the helper's comment now says what it actually does: it returns every line the reference printed, and it is the *positions* that are chosen to avoid the two kinds of answer this side does not claim. Filtering the lines instead would quietly hide a real difference behind a rule about what a path looks like. The call is bounded too. It is another program, and one that hangs should fail this test rather than the suite. Co-Authored-By: Claude Opus 5 --- go/conformance/complete_test.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/go/conformance/complete_test.go b/go/conformance/complete_test.go index 7fc33ee57..7c6922dd1 100644 --- a/go/conformance/complete_test.go +++ b/go/conformance/complete_test.go @@ -1,10 +1,12 @@ package conformance import ( + "context" "os/exec" "path/filepath" "strings" "testing" + "time" "github.com/jdx/usage/go/argv" ) @@ -23,16 +25,23 @@ import ( // completeWord is the reference's answer for a partial word at a command path. // -// Only the lines the CLI itself produced: where the reference has nothing to -// offer it falls back to listing the working directory, and those lines describe -// the machine rather than the spec. The Go side says `\x01files` there instead, -// which is compared separately below. +// Every non-empty line it printed, whatever the line holds. Two of the things it +// can hold are not this side's to reproduce — a listing of the working directory +// where nothing else fit, and whatever a spec's `complete` block printed when the +// reference ran it — so the *positions* asked about are chosen to avoid both, +// rather than the lines being filtered afterwards. Filtering would quietly hide a +// real difference behind a rule about what a path looks like. +// +// Bounded, because it is another program: a reference that hangs should fail this +// test rather than the suite. func completeWord(t *testing.T, usageBin, kdl string, words []string, cword int) []string { t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() args := []string{"complete-word", "--shell", "bash", "-f", kdl, "--cword", itoa(cword), "--"} args = append(args, words...) - out, err := exec.Command(usageBin, args...).Output() + out, err := exec.CommandContext(ctx, usageBin, args...).Output() if err != nil { t.Fatalf("the reference should answer %v: %v", words, err) } @@ -58,18 +67,22 @@ func TestTheCursorGetsTheReferencesAnswer(t *testing.T) { // and `mise settings ⌶` both do, and shelling out on a Tab is the piece this // package has deliberately not built. // + // The root's own cursor is one of those: mise's default subcommand is `run`, + // whose `TASK` runs a completer, so even `mise plug⌶` is answered partly by a + // subprocess. The lines below sit under a subcommand that has neither. + // // What is left covers the branches: subcommands, aliases, a nested command, // both flag forms, a long that narrows, and a word that matches nothing. for _, line := range []string{ - "mise plug", "mise config ", "mise config l", "mise -", "mise --", "mise --log-", "mise use -", - "mise wat", "mise plugins ", + "mise plugins in", + "mise plugins wat", "mise plugins install -", } { split := argv.Split(line, len(line), argv.Bash)