diff --git a/PLAN.md b/PLAN.md index d2bec1666..7ef066e48 100644 --- a/PLAN.md +++ b/PLAN.md @@ -181,7 +181,7 @@ manpages, and SDKs — never a runtime dependency of somebody else's program. an unbounded variadic on one side of a flatten and a later positional on the other — and the flag-form collision. Both are invisible to either expansion and visible where the tables are joined. -- [ ] **`usage-derive` v1** — everything mise needs. `conflicts`, `overrides`, +- [x] **`usage-derive` v1** — everything mise needs. `conflicts`, `overrides`, `required_if`, `required_unless`, `var`, `count`, `env`, defaults, the four `double_dash` modes, global flags, flatten, boxed subcommand variants, headings and `cfg`-gated variants have all landed since this was written. @@ -192,13 +192,11 @@ manpages, and SDKs — never a runtime dependency of somebody else's program. `Arg::requires` as a setter with no getter, so a `Command` cannot be asked what it requires. That is a clap limitation, recorded in `lib/src/spec/flag.rs`, not an item to close here. - What is left is **`requires_if` / `requires_ifs`**, the conditional forms, - which need something the spec does not have: a selector that names a - _value_ rather than a presence. `selector_is_explicit` answers only "was - that flag given", so "required when `--format` is `json`" cannot be said - from either end — `required_if` has the same limit. And - **delimiters**, where a value's `,` is split at parse time rather than only - in a clap default. Both are in the clap-parity list below. + **`requires_if` / `requires_ifs` have since landed too**: the spec records + repeated value/selector pairs, usage-lib and the derive enforce the same + explicit-value rule clap does, and the cold tables emit the relationship + without touching binding. Delimiters landed alongside them, so the original + v1 list is closed. - [x] **The post-binding layer** — `required`, `choices`, `env` fallback, defaults, `var_min`, `conflicts`, `required_if`, `required_unless`, and `overrides` — `var_max` moved to the binder, see the decision below. All of them need a value's type, so they belong with the derive @@ -268,10 +266,8 @@ tasks --usage"`, so task names are meant to come from running that. usage-argv d descended into `config ls` when an unrelated `config` happened to have an `ls`. A spec declares one name, once, at the top. The corpus vector that recorded the difference is now an ordinary agreeing vector — the reference test refused to let the label stay, which is what it is for. -- [ ] **A mount on the root command** — the spec accepts `mount` only inside a - `cmd` block, so a CLI whose _top-level_ subcommands are discovered by running - something cannot say so. Worth deciding whether that is a gap or a deliberate - restriction. +- [x] **A mount on the root command** — top-level discovery is represented and + usage-lib keeps completion and execution consistent about when the mount runs. - [x] **`subcommand_required`, which the derive knew and did not say** — a bare `T` subcommand field requires a subcommand and an `Option` does not, and the parser has always refused the invocation accordingly. `Spec::to_kdl` wrote neither, so the @@ -293,11 +289,8 @@ tasks --usage"`, so task names are meant to come from running that. usage-argv d descends, and the corpus's own table builder stops resolving it — one implementation of the rule instead of two, and it was the second one that hid the parser not having it. Costs **160 instructions per parse, 72,272 against 72,112** at mise's scale. -- [ ] **`subcommand_required` on the root command** — the same restriction as the root - mount, and found beside it: the spec accepts the property only inside a `cmd` block, - so a CLI whose _root_ cannot be run alone has no way to say so. The clap bridge could - not say it either, so nothing regressed — but a bare `T` subcommand field on the root - is now a thing the derive knows and the spec has nowhere to put. +- [x] **`subcommand_required` on the root command** — a bare root subcommand field + now reaches the spec and both parsers report the missing command consistently. - [x] **Three things a spec could say that the derive could not** — a flag's value name (`--tool ` came back as `--tool `, since the flag's own name was all there @@ -363,26 +356,21 @@ Groups are the opposite case: `Command::get_groups`, `ArgGroup::get_args` and **Changes what a CLI does** -- [ ] **`requires` / `requires_if` / `requires_ifs`** — "this flag needs that - one". The spec has `conflicts`, `overrides`, `required_if` and - `required_unless`, and no way to say the positive form. Nothing in - `lib/src/spec/flag.rs` parses it and the derive has no attribute. The one - true blocker in the original v1 list. The bridge will not carry it, per the - note above, so this is a reason to declare in usage rather than a bridge - bug: a CLI that moves its declaration here gains a constraint its generated - spec never had. -- [ ] **`ArgGroup`, and `exclusive`** _(bridge too)_ — "exactly one of these - three", "at least one of these". Only pairwise `conflicts` exists, which is - O(n²) declarations for what clap says once, and cannot express requiredness - across a set at all. Readable from a clap `Command`, so the bridge gains - these once the spec can hold them. -- [ ] **`value_delimiter`** — `--tags a,b,c` as three values. `lib/src/spec/arg.rs` - splits a clap _default_ by it and says the spec has a list rather than a - delimiter, which is true of a default and not of a command line: nothing - splits a value at parse time. -- [ ] **`default_missing_value`, and optional-value flags** — `--color` versus - `--color=always`, which is `Option>` in clap. `Shape` has no - variant for it. +- [x] **`requires` / `requires_if` / `requires_ifs`** — "this flag needs that + one". Plain and value-conditional forms now reach the spec, usage-lib and + generated checks. The bridge still cannot carry them, per the note above, + so this remains a reason to declare in usage rather than a bridge bug. +- [x] **`ArgGroup`, and `exclusive`** _(bridge too)_ — "exactly one of these + three", "at least one of these". Groups cross the clap bridge, spec and + derive, and exclusivity is enforced across globals, aliases, flatten and + subcommand boundaries. +- [x] **`value_delimiter`** — `--tags a,b,c` as three values. `lib/src/spec/arg.rs` + and both Rust parsers now split typed, environment and default values before + checking or converting them. +- [~] **`default_missing_value`, and optional-value flags** — the metadata and + help-rendering half has landed (`--bump [BUMP]`), but binding still requires + a value. Accepting bare `--color` as distinct from `--color=always` still + needs an `Option>`-shaped partial and default-missing semantics. - [ ] **`default_value_if` / `default_value_ifs`** — a default that depends on another flag. Ours are unconditional. - [ ] **`value_parser`** — clap takes an arbitrary parser function and range @@ -549,9 +537,9 @@ down and stop. Nothing gets integrated into mise before this point. parsers. Adoption is measured by what it lets mise delete, listed below. - [ ] **hk, pitchfork, fnox** — smaller, and all three already generate their spec from clap, so they are the natural second adopters. -- [ ] **Other languages** — the grammar and corpus are language-neutral on - purpose. A Go, JavaScript, or Python implementation is verified by running - the corpus, not by reading this repository's Rust. +- [~] **Other languages** — Go now parses, validates, renders help and answers + completions from generated static tables, verified against the shared corpus. + JavaScript and Python implementations remain open. ### What adoption should let mise delete diff --git a/corpus/08-choices-and-required.json b/corpus/08-choices-and-required.json index 027b4c68d..a3daf7766 100644 --- a/corpus/08-choices-and-required.json +++ b/corpus/08-choices-and-required.json @@ -243,6 +243,72 @@ "error": "var_too_many" }, "layer": "post-binding" + }, + { + "id": "requires-if-value-matches", + "doc": "A value-conditional requirement is enforced when the declaring flag explicitly has the named value.", + "spec": "name \"ex\"\nbin \"ex\"\nflag \"--format \" {\n requires_if \"json\" \"--schema\"\n}\nflag \"--schema \"\n", + "argv": ["--format", "json"], + "expect": { + "error": "missing_required_flag" + }, + "layer": "post-binding" + }, + { + "id": "requires-if-value-does-not-match", + "doc": "A different value does not activate the conditional requirement.", + "spec": "name \"ex\"\nbin \"ex\"\nflag \"--format \" {\n requires_if \"json\" \"--schema\"\n}\nflag \"--schema \"\n", + "argv": ["--format", "text"], + "expect": { + "ok": { + "flags": { + "format": "text" + } + } + }, + "layer": "post-binding" + }, + { + "id": "requires-if-is-satisfied", + "doc": "The conditional requirement disappears once the flag it names has a value.", + "spec": "name \"ex\"\nbin \"ex\"\nflag \"--format \" {\n requires_if \"json\" \"--schema\"\n}\nflag \"--schema \"\n", + "argv": ["--format", "json", "--schema", "schema.json"], + "expect": { + "ok": { + "flags": { + "format": "json", + "schema": "schema.json" + } + } + }, + "layer": "post-binding" + }, + { + "id": "requires-if-environment-is-explicit", + "doc": "An environment value is explicit and activates a matching conditional requirement, as it does in clap.", + "spec": "name \"ex\"\nbin \"ex\"\nflag \"--format \" env=\"EX_FORMAT\" {\n requires_if \"json\" \"--schema\"\n}\nflag \"--schema \"\n", + "argv": [], + "env": { + "EX_FORMAT": "json" + }, + "expect": { + "error": "missing_required_flag" + }, + "layer": "post-binding" + }, + { + "id": "requires-if-default-is-not-explicit", + "doc": "A default does not activate a value-conditional requirement because nobody supplied it.", + "spec": "name \"ex\"\nbin \"ex\"\nflag \"--format \" default=\"json\" {\n requires_if \"json\" \"--schema\"\n}\nflag \"--schema \"\n", + "argv": [], + "expect": { + "ok": { + "flags": { + "format": "json" + } + } + }, + "layer": "post-binding" } ] } diff --git a/go/argv/post.go b/go/argv/post.go index 6aa5c8492..7bc539d73 100644 --- a/go/argv/post.go +++ b/go/argv/post.go @@ -52,6 +52,9 @@ type Meta struct { // Flag distinguishes a missing flag from a missing argument, which the // grammar reports as different classes. Flag bool + // RequiresIfBoolean says this entry's conditional relationships compare a + // boolean rather than text, so their explicit values need normalization. + RequiresIfBoolean bool // Required means it must end up with a value, from anywhere. Required bool // Choices is the exact set of values allowed. Matching is case-sensitive: @@ -73,7 +76,7 @@ type Meta struct { // value bound here would fail an invocation that never broke it. VarMax uint32 - // The four that need a second entry to answer, all resolved to keys rather + // The relationships that need a second entry to answer, all resolved to keys rather // than left as the names the spec writes. Resolution happens where the whole // command is visible — the generator, or the table builder — so that nothing // downstream has to search by name, and a declaration naming a flag that does @@ -89,6 +92,15 @@ type Meta struct { RequiredUnless []uint64 // RequiredIf makes this required when any of them is present. RequiredIf []uint64 + // RequiresIf names entries required when this entry explicitly has Value. + // Defaults do not activate the condition, but may satisfy the requirement. + RequiresIf []ValueRequirement +} + +// ValueRequirement is one value-conditional relationship. +type ValueRequirement struct { + Value string + Key uint64 } // Metadata is the cold table, indexed by key. diff --git a/go/argv/relationships.go b/go/argv/relationships.go index 556f00623..312abf62a 100644 --- a/go/argv/relationships.go +++ b/go/argv/relationships.go @@ -1,14 +1,41 @@ package argv +// RelationshipValues canonicalizes the values used by value-conditional +// relationships. It leaves value-taking entries alone and turns every boolean +// source into the same "true" or "false" spelling. +func RelationshipValues(m *Meta, values []string, source Source, negated bool) []string { + if m == nil || !m.RequiresIfBoolean { + return values + } + switch source { + case FromArgv: + if negated { + return []string{"false"} + } + return []string{"true"} + case FromEnv: + if len(values) > 0 && EnvTruth(values[0]) { + return []string{"true"} + } + return []string{"false"} + case FromDefault: + if len(values) > 0 && values[0] == "true" { + return []string{"true"} + } + return []string{"false"} + } + return values +} + // The rules that compare one entry against another. // // Everything in post.go judges an entry on its own: is it there, is its value -// allowed, are there enough of them. These four need a second entry to answer at +// allowed, are there enough of them. These rules need a second entry to answer at // all, which is why they are separate — a name in the declaration has to be // resolved to the entry it refers to before any of it can be checked, and that // resolution happens where the whole command is visible rather than here. // -// `overrides` is the odd one. The other three are decided once the last token has +// `overrides` is the odd one. The others are decided once the last token has // been read; this one asks which of two flags came *last*, which only the // arriving tokens know. So it is applied first, on the order binding reports, and // what it removes is removed before anything else looks. @@ -86,6 +113,16 @@ func ApplyOverrides(meta Metadata, order map[uint64]int) map[uint64]bool { // A key removed by [ApplyOverrides] should not appear in `entries` at all: it // lost, so it is out of the running rather than merely absent. func CheckRelationships(meta Metadata, entries []uint64, sourceOf func(uint64) Source) *Error { + return CheckRelationshipsWithValues(meta, entries, sourceOf, nil) +} + +// CheckRelationshipsWithValues also enforces value-conditional requirements. +// +// `valuesOf` reports canonical values for an entry. Boolean flags should report +// "true" or "false" regardless of whether that value came from a spelling such +// as `--feature`, its negation, or a truthy environment value. +func CheckRelationshipsWithValues(meta Metadata, entries []uint64, + sourceOf func(uint64) Source, valuesOf func(uint64) []string) *Error { given := func(key uint64) bool { return sourceOf(key).Given() } for _, key := range entries { @@ -107,6 +144,17 @@ func CheckRelationships(meta Metadata, entries []uint64, sourceOf func(uint64) S return e } } + if valuesOf != nil { + values := valuesOf(key) + for _, condition := range m.RequiresIf { + if !containsValue(values, condition.Value) || sourceOf(condition.Key) != Unset { + continue + } + if required := meta.Lookup(condition.Key); required != nil { + return missingRequired(required) + } + } + } continue } @@ -142,6 +190,15 @@ func CheckRelationships(meta Metadata, entries []uint64, sourceOf func(uint64) S return nil } +func containsValue(values []string, expected string) bool { + for _, value := range values { + if value == expected { + return true + } + } + return false +} + func missingRequired(m *Meta) *Error { code := CodeMissingRequiredArg if m.Flag { diff --git a/go/argv/relationships_test.go b/go/argv/relationships_test.go index cf0bdfdcd..ec4bf2d0b 100644 --- a/go/argv/relationships_test.go +++ b/go/argv/relationships_test.go @@ -168,6 +168,79 @@ func TestConditionalRequirements(t *testing.T) { } } +func TestValueConditionalRequirements(t *testing.T) { + meta := pair(nil, nil, "") + meta[0].RequiresIf = []ValueRequirement{{Value: "json", Key: keyStdin}} + all := []uint64{keyFile, keyStdin, keyURL} + values := func(value string) func(uint64) []string { + return func(key uint64) []string { + if key == keyFile { + return []string{value} + } + return nil + } + } + + if err := CheckRelationshipsWithValues(meta, all, set(keyFile), values("json")); err == nil { + t.Error("the matching value should require stdin") + } else if err.Code != CodeMissingRequiredFlag || err.Name != "stdin" { + t.Errorf("want missing stdin, got %q %q", err.Code, err.Name) + } + if err := CheckRelationshipsWithValues(meta, all, set(keyFile), values("text")); err != nil { + t.Errorf("a different value should require nothing, got %q", err.Code) + } + if err := CheckRelationshipsWithValues(meta, all, set(keyFile, keyStdin), values("json")); err != nil { + t.Errorf("the required flag satisfies the condition, got %q", err.Code) + } + + fromDefault := func(key uint64) Source { + if key == keyFile { + return FromDefault + } + return Unset + } + if err := CheckRelationshipsWithValues(meta, all, fromDefault, values("json")); err != nil { + t.Errorf("a default should not activate the condition, got %q", err.Code) + } + + defaultSatisfies := func(key uint64) Source { + if key == keyFile { + return FromArgv + } + if key == keyStdin { + return FromDefault + } + return Unset + } + if err := CheckRelationshipsWithValues(meta, all, defaultSatisfies, values("json")); err != nil { + t.Errorf("a default on the required flag satisfies it, got %q", err.Code) + } +} + +func TestRelationshipValuesNormalizesBooleans(t *testing.T) { + m := &Meta{RequiresIfBoolean: true} + cases := []struct { + name string + values []string + source Source + negated bool + want string + }{ + {"typed flag", nil, FromArgv, false, "true"}, + {"typed negation", nil, FromArgv, true, "false"}, + {"truthy environment", []string{"TRUE"}, FromEnv, false, "true"}, + {"false environment", []string{"0"}, FromEnv, false, "false"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := RelationshipValues(m, c.values, c.source, c.negated) + if len(got) != 1 || got[0] != c.want { + t.Errorf("want %q, got %v", c.want, got) + } + }) + } +} + // An entry already marked Required has been answered by Check, and saying it // twice in two different voices helps nobody. func TestAnAlreadyRequiredEntryIsNotReportedTwice(t *testing.T) { diff --git a/go/conformance/conformance_test.go b/go/conformance/conformance_test.go index 4b7e73094..a416100a3 100644 --- a/go/conformance/conformance_test.go +++ b/go/conformance/conformance_test.go @@ -335,7 +335,14 @@ func run(s *spec.Spec, args []string, env map[string]string) (*Parsed, *argv.Err } return argv.Unset } - if err := argv.CheckRelationships(meta, scope, sourceOf); err != nil { + valuesOf := func(key uint64) []string { + r := final[key] + if r == nil { + return nil + } + return argv.RelationshipValues(meta.Lookup(key), r.values, r.source, r.negated) + } + if err := argv.CheckRelationshipsWithValues(meta, scope, sourceOf, valuesOf); err != nil { return nil, err } diff --git a/go/internal/shadow/mise/meta_test.go b/go/internal/shadow/mise/meta_test.go index 682ccec73..1fe8da0ab 100644 --- a/go/internal/shadow/mise/meta_test.go +++ b/go/internal/shadow/mise/meta_test.go @@ -182,6 +182,11 @@ func TestRelationshipsPointAtRealEntries(t *testing.T) { } } } + for _, condition := range m.RequiresIf { + if Meta.Lookup(condition.Key) == nil { + t.Errorf("%q conditionally points at key %d, which is not an entry", m.Name, condition.Key) + } + } } } diff --git a/go/internal/spec/spec.go b/go/internal/spec/spec.go index 3160d508c..fa9ab20b4 100644 --- a/go/internal/spec/spec.go +++ b/go/internal/spec/spec.go @@ -203,11 +203,18 @@ type Flag struct { HelpLong string `json:"help_long"` HelpHeading string `json:"help_heading"` // The four that name another flag. They arrive as written, dashes included. - Conflicts []string `json:"conflicts"` - Overrides []string `json:"overrides"` - RequiredIf []string `json:"required_if"` - RequiredUnless []string `json:"required_unless"` - Arg *Arg `json:"arg"` + Conflicts []string `json:"conflicts"` + Overrides []string `json:"overrides"` + RequiredIf []string `json:"required_if"` + RequiredUnless []string `json:"required_unless"` + RequiresIf []RequiresIf `json:"requires_if"` + Arg *Arg `json:"arg"` +} + +// RequiresIf is one explicit value and the flag that value requires. +type RequiresIf struct { + Value string `json:"value"` + Requires string `json:"requires"` } // spelling is how a user types a flag: its first long form, else its first short. @@ -589,6 +596,15 @@ func (b *builder) resolveRelationships(c *Cmd, out *argv.Command) { } return out } + resolveValues := func(requirements []RequiresIf) []argv.ValueRequirement { + var out []argv.ValueRequirement + for _, requirement := range requirements { + if key, ok := find(requirement.Requires); ok { + out = append(out, argv.ValueRequirement{Value: requirement.Value, Key: key}) + } + } + return out + } // `c.Flags` and `out.Flags` are built in step, so the index is the join. for i := range c.Flags { @@ -598,6 +614,7 @@ func (b *builder) resolveRelationships(c *Cmd, out *argv.Command) { m.Overrides = resolve(src.Overrides) m.RequiredUnless = resolve(src.RequiredUnless) m.RequiredIf = resolve(src.RequiredIf) + m.RequiresIf = resolveValues(src.RequiresIf) } } @@ -706,16 +723,17 @@ 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), - 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), + Name: f.Name, + Flag: true, + RequiresIfBoolean: len(f.RequiresIf) > 0 && f.Arg == nil, + 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), diff --git a/lib/src/go/mod.rs b/lib/src/go/mod.rs index 35dd785fe..c595bc846 100644 --- a/lib/src/go/mod.rs +++ b/lib/src/go/mod.rs @@ -457,6 +457,9 @@ impl Emitter<'_> { if flag.required { fields.push("Required: true".to_string()); } + if !flag.requires_if.is_empty() && flag.arg.is_none() { + fields.push("RequiresIfBoolean: true".to_string()); + } // How a user types it, worked out where the forms are visible: the rules // that judge an entry never see a flag, and guessing from the name gets a // one-letter long form and a short the wrong way round. @@ -519,6 +522,22 @@ impl Emitter<'_> { fields.push(format!("{label}: {}", key_slice(&keys))); } } + let requires_if = flag + .requires_if + .iter() + .filter_map(|condition| { + resolve_relationship(std::slice::from_ref(&condition.requires), owner, commands) + .into_iter() + .next() + .map(|key| format!("{{Value: {}, Key: {key}}}", go_string(&condition.value))) + }) + .collect::>(); + if !requires_if.is_empty() { + fields.push(format!( + "RequiresIf: []argv.ValueRequirement{{{}}}", + requires_if.join(", ") + )); + } format!("{{{}}}", fields.join(", ")) } @@ -1605,6 +1624,28 @@ cmd "run" { ); } + #[test] + fn a_value_conditional_requirement_reaches_go_metadata() { + let out = go(r#" +name "ex" +bin "ex" +flag "--format " { + requires_if "json" "--schema" +} +flag "--schema " +"#); + assert!( + entry_of(&out, "format").contains( + "RequiresIf: []argv.ValueRequirement{{Value: \"json\", Key: FlagSchema}}" + ), + "{out}" + ); + assert!( + out.contains("argv.CheckRelationshipsWithValues"), + "the emitted parser must enforce the metadata:\n{out}" + ); + } + /// The form is part of the name, and usage-lib resolves neither of the /// mismatched ones — so resolving them would have a generated CLI enforcing a /// rule the reference does not. diff --git a/lib/src/go/structs.rs b/lib/src/go/structs.rs index c182df01a..6e36bed93 100644 --- a/lib/src/go/structs.rs +++ b/lib/src/go/structs.rs @@ -100,6 +100,25 @@ pub(super) fn emit(out: &mut String, commands: &[Emitted]) { fn parse_fn(out: &mut String, commands: &[Emitted], assigned: &Fields) { let root = &commands[0]; + let has_requires_if = commands + .iter() + .flat_map(|command| command.flags.iter()) + .any(|(flag, _)| !flag.requires_if.is_empty()); + let conditional_state = if has_requires_if { + "\tnegated := map[uint64]bool{}\n" + } else { + "" + }; + let conditional_resolved = if has_requires_if { + "\tresolved := map[uint64][]string{}\n" + } else { + "" + }; + let conditional_value = if has_requires_if { + "\t\tresolved[key] = values\n" + } else { + "" + }; let _ = writeln!( out, "// Parse binds a command line and fills the structs above.\n\ @@ -129,6 +148,7 @@ fn parse_fn(out: &mut String, commands: &[Emitted], assigned: &Fields) { \t// before any of it is handed back.\n\ \tgiven := map[uint64][]string{{}}\n\ \tseen := map[uint64]int{{}}\n\ + {conditional_state}\ \tchain := []*argv.Command{{Root}}\n\ \n\tp := argv.New(Root, args)\n\ \tfor p.Next() {{\n\ @@ -153,9 +173,15 @@ fn parse_fn(out: &mut String, commands: &[Emitted], assigned: &Fields) { ); } + let conditional_event = if has_requires_if { + "\t\t\tnegated[ev.Flag.Key] = ev.Negated\n" + } else { + "" + }; let _ = writeln!( out, "\t\t\t}}\n\t\tcase argv.KindFlag:\n\t\t\tseen[ev.Flag.Key]++\n\ + {conditional_event}\ \t\t\tif ev.HasValue {{\n\ \t\t\t\tgiven[ev.Flag.Key] = append(given[ev.Flag.Key], ev.Value)\n\ \t\t\t}} else if given[ev.Flag.Key] == nil {{\n\ @@ -207,9 +233,11 @@ fn parse_fn(out: &mut String, commands: &[Emitted], assigned: &Fields) { \t\tfor _, a := range cmd.Args {{\n\t\t\tscope = append(scope, a.Key)\n\t\t}}\n\ \t}}\n\ \tsources := map[uint64]argv.Source{{}}\n\ + {conditional_resolved}\ \tfor _, key := range scope {{\n\ \t\tvalues, source := argv.Fill(Meta.Lookup(key), given[key], argv.LookupEnv)\n\ \t\tsources[key] = source\n\ + {conditional_value}\ \t\tif err := argv.Check(Meta.Lookup(key), values, seen[key]); err != nil {{\n\ \t\t\treturn nil, err\n\t\t}}\n\ \t\t// What the environment or a default supplied has to reach the field\n\ @@ -224,13 +252,23 @@ fn parse_fn(out: &mut String, commands: &[Emitted], assigned: &Fields) { \t\t\tswitch key {{" ); fallback_cases(out, commands, assigned); - let _ = writeln!( - out, - "\t\t\t}}\n\t\t}}\n\t}}\n\ - \tif err := argv.CheckRelationships(Meta, scope, func(k uint64) argv.Source {{\n\ - \t\treturn sources[k]\n\t}}); err != nil {{\n\t\treturn nil, err\n\t}}\n\ - \treturn out, nil\n}}\n" - ); + let _ = writeln!(out, "\t\t\t}}\n\t\t}}\n\t}}"); + if has_requires_if { + let _ = writeln!( + out, + "\tif err := argv.CheckRelationshipsWithValues(Meta, scope, func(k uint64) argv.Source {{\n\ + \t\treturn sources[k]\n\t}}, func(k uint64) []string {{\n\ + \t\treturn argv.RelationshipValues(Meta.Lookup(k), resolved[k], sources[k], negated[k])\n\ + \t}}); err != nil {{\n\t\treturn nil, err\n\t}}" + ); + } else { + let _ = writeln!( + out, + "\tif err := argv.CheckRelationships(Meta, scope, func(k uint64) argv.Source {{\n\ + \t\treturn sources[k]\n\t}}); err != nil {{\n\t\treturn nil, err\n\t}}" + ); + } + let _ = writeln!(out, "\treturn out, nil\n}}\n"); } /// The cases that put an `env` or `default` value into its field.