@optique/core 1.4.0-dev.2618 → 1.4.0-dev.2619

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ import { inheritAnnotations } from "./internal/annotations.js";
2
+ import { extractOptionNames } from "./usage.js";
3
+ import { mapModeValue } from "./internal/mode-dispatch.js";
4
+ import { withChildExecPath } from "./execution-context.js";
5
+ import { collectOptionNamesAtCurrentCommandDepth } from "./usage-internals.js";
6
+
7
+ //#region src/short-option.ts
8
+ const optionScopeKey = Symbol("optionScope");
9
+ const optionPathKey = Symbol("optionScopePath");
10
+ const scopeSourceKey = Symbol("optionScopeSource");
11
+ const scopeLocations = /* @__PURE__ */ new WeakMap();
12
+ const scopeBindings = /* @__PURE__ */ new WeakMap();
13
+ function getScopeBindings(selections) {
14
+ let bindings = scopeBindings.get(selections);
15
+ if (bindings == null) {
16
+ bindings = /* @__PURE__ */ new WeakMap();
17
+ scopeBindings.set(selections, bindings);
18
+ }
19
+ return bindings;
20
+ }
21
+ function bindScope(source, path, selections) {
22
+ const boundScopes = getScopeBindings(selections);
23
+ let node = boundScopes.get(source);
24
+ if (node == null) {
25
+ node = {
26
+ children: /* @__PURE__ */ new Map(),
27
+ bound: (selections$1, arities, context) => source(selections$1, arities, context, [])
28
+ };
29
+ boundScopes.set(source, node);
30
+ scopeLocations.set(node.bound, []);
31
+ }
32
+ for (let i = 0; i < path.length; i++) {
33
+ let child = node.children.get(path[i]);
34
+ if (child == null) {
35
+ const occurrence = path.slice(0, i + 1);
36
+ child = {
37
+ children: /* @__PURE__ */ new Map(),
38
+ bound: (selections$1, arities, context) => source(selections$1, arities, context, occurrence)
39
+ };
40
+ node.children.set(path[i], child);
41
+ scopeLocations.set(child.bound, occurrence);
42
+ }
43
+ node = child;
44
+ }
45
+ return node.bound;
46
+ }
47
+ function scopePath(context) {
48
+ return context.exec?.path ?? context[optionPathKey] ?? [];
49
+ }
50
+ /** Tracks child occurrences even for contexts without execution metadata. @internal */
51
+ function withOptionScopeChild(context, segment) {
52
+ return {
53
+ ...context,
54
+ [optionPathKey]: [...scopePath(context), segment]
55
+ };
56
+ }
57
+ const primitiveScopes = /* @__PURE__ */ new WeakMap();
58
+ function parserScope(parser) {
59
+ const declared = parser[scopeSourceKey];
60
+ if (declared != null) return declared;
61
+ let source = primitiveScopes.get(parser);
62
+ if (source == null) {
63
+ source = (_selections, arities) => {
64
+ if (arities != null) collectOptionNamesAtCurrentCommandDepth(parser.usage, arities, false);
65
+ return parser.leadingNames;
66
+ };
67
+ primitiveScopes.set(parser, source);
68
+ }
69
+ return source;
70
+ }
71
+ /** Preserves dynamic option ownership through a transparent wrapper. @internal */
72
+ function inheritOptionScope(target, inner) {
73
+ Object.defineProperty(target, scopeSourceKey, {
74
+ value: parserScope(inner),
75
+ enumerable: true,
76
+ configurable: true
77
+ });
78
+ }
79
+ /** Combines declarations without flattening conditional ownership. @internal */
80
+ function combinedOptionScope(parsers, segments) {
81
+ return (selections, arities, context, path = []) => new Set(parsers.flatMap((parser, index) => [...parserScope(parser)(selections, arities, context, segments == null ? path : [...path, segments[index]])]));
82
+ }
83
+ /** Evaluates repeated declarations at their actual item occurrences. @internal */
84
+ function repeatedOptionScope(parser) {
85
+ return (selections, arities, context, path = []) => {
86
+ const indices = /* @__PURE__ */ new Set();
87
+ const collect = (location) => {
88
+ if (location != null && path.every((segment, index) => location[index] === segment)) {
89
+ const index = location[path.length];
90
+ if (typeof index === "number") indices.add(index);
91
+ }
92
+ };
93
+ for (const selected of selections.keys()) collect(scopeLocations.get(selected));
94
+ if (context != null) collect(scopePath(context));
95
+ if (indices.size === 0) indices.add(0);
96
+ return new Set([...indices].flatMap((index) => [...parserScope(parser)(selections, arities, context, [...path, index])]));
97
+ };
98
+ }
99
+ /** Tracks declarations after a command or discriminator routes input. @internal */
100
+ function selectableOptionScope(initial) {
101
+ const source = (selections, arities, context, path = []) => (selections.get(bindScope(source, path, selections)) ?? initial)(selections, arities, context, path);
102
+ return {
103
+ source,
104
+ select(context, selected) {
105
+ const selections = context[optionScopeKey]?.selections;
106
+ if (selections == null) return;
107
+ selections.set(bindScope(source, scopePath(context), selections), selected);
108
+ }
109
+ };
110
+ }
111
+ /** Keeps selected conditional names distinct from sibling declarations. @internal */
112
+ function conditionalOptionScope(discriminator, fallback, candidates = []) {
113
+ const scope = selectableOptionScope((selections, arities, context, path) => {
114
+ const canSkip = discriminator.canSkip?.(context == null ? discriminator.initialState : inheritAnnotations(context.state, discriminator.initialState), context?.exec == null ? void 0 : withChildExecPath({
115
+ ...context.exec,
116
+ path: path ?? scopePath(context)
117
+ }, "_discriminator")) === true;
118
+ return combinedOptionScope([
119
+ discriminator,
120
+ ...canSkip ? candidates : [],
121
+ ...fallback == null ? [] : [fallback]
122
+ ], [
123
+ "_discriminator",
124
+ ...canSkip ? candidates.map(() => "_branch") : [],
125
+ ...fallback == null ? [] : ["_branch"]
126
+ ])(selections, arities, context, path);
127
+ });
128
+ return {
129
+ source: scope.source,
130
+ select(context, branch) {
131
+ scope.select(context, combinedOptionScope([discriminator, branch], ["_discriminator", "_branch"]));
132
+ }
133
+ };
134
+ }
135
+ /** Returns the names reachable in this parse scope. @internal */
136
+ function getOptionScope(context) {
137
+ const scoped = context[optionScopeKey];
138
+ const names = scoped == null ? extractOptionNames(context.usage, true) : new Set([...scoped.names, ...[...scoped.sources].flatMap((source) => [...source(scoped.selections, void 0, context)])]);
139
+ return names;
140
+ }
141
+ /** Adds reachable names without consulting the flattened help tree. @internal */
142
+ function withOptionScope(context, names) {
143
+ const inherited = context[optionScopeKey];
144
+ return {
145
+ ...context,
146
+ [optionScopeKey]: {
147
+ names: new Set([...inherited?.names ?? [], ...names]),
148
+ sources: inherited?.sources ?? /* @__PURE__ */ new Set(),
149
+ selections: inherited?.selections ?? /* @__PURE__ */ new Map()
150
+ }
151
+ };
152
+ }
153
+ /** Adds a parser's owned declarations, retaining modifier metadata. @internal */
154
+ function withParserOptionScope(context, parser, segment) {
155
+ const inherited = context[optionScopeKey];
156
+ const selections = inherited?.selections ?? /* @__PURE__ */ new Map();
157
+ return {
158
+ ...context,
159
+ [optionScopeKey]: {
160
+ names: inherited?.names ?? /* @__PURE__ */ new Set(),
161
+ sources: new Set([...inherited?.sources ?? [], bindScope(parserScope(parser), segment == null ? scopePath(context) : [...scopePath(context), segment], selections)]),
162
+ selections
163
+ }
164
+ };
165
+ }
166
+ /** Isolates route selections while probing a competing branch. @internal */
167
+ function forkOptionScope(context) {
168
+ const scoped = context[optionScopeKey];
169
+ if (scoped == null) return context;
170
+ const selections = new Map(scoped.selections);
171
+ scopeBindings.set(selections, getScopeBindings(scoped.selections));
172
+ return {
173
+ ...context,
174
+ [optionScopeKey]: {
175
+ ...scoped,
176
+ selections
177
+ }
178
+ };
179
+ }
180
+ /** Publishes winning routes without persisting temporary candidate names. @internal */
181
+ function adoptOptionScope(context, next) {
182
+ const own = context[optionScopeKey];
183
+ const child = next[optionScopeKey];
184
+ if (own != null && child != null) for (const [source, selected] of child.selections) own.selections.set(source, selected);
185
+ return context;
186
+ }
187
+ /** Removes names reserved only for a speculative probe. @internal */
188
+ function restoreOptionScope(next, parent) {
189
+ adoptOptionScope(parent, next);
190
+ const own = parent[optionScopeKey];
191
+ const child = next[optionScopeKey];
192
+ return own == null || child == null ? next : {
193
+ ...next,
194
+ [optionScopeKey]: {
195
+ ...child,
196
+ names: own.names,
197
+ selections: own.selections
198
+ }
199
+ };
200
+ }
201
+ /** Decorates construct entry points while retaining internal metadata. @internal */
202
+ function scopeParser(parser, source = () => parser.leadingNames, prepare, isolate = false) {
203
+ const parse = parser.parse;
204
+ const suggest = parser.suggest;
205
+ Object.defineProperties(parser, {
206
+ [scopeSourceKey]: {
207
+ value: source,
208
+ enumerable: true
209
+ },
210
+ parse: {
211
+ enumerable: true,
212
+ configurable: true,
213
+ writable: true,
214
+ value: (context) => {
215
+ const scoped = withParserOptionScope(isolate ? forkOptionScope(context) : context, parser);
216
+ prepare?.(scoped);
217
+ return mapModeValue(parser.mode, parse.call(parser, scoped), (result) => {
218
+ if (!result.success) return result;
219
+ let next = result.next;
220
+ if (isolate) {
221
+ adoptOptionScope(context, next);
222
+ const parent = context[optionScopeKey];
223
+ const child = next[optionScopeKey];
224
+ if (parent != null && child != null) next = {
225
+ ...next,
226
+ [optionScopeKey]: {
227
+ ...child,
228
+ selections: parent.selections
229
+ }
230
+ };
231
+ }
232
+ return {
233
+ ...result,
234
+ next: {
235
+ ...next,
236
+ [optionPathKey]: scopePath(context)
237
+ }
238
+ };
239
+ });
240
+ }
241
+ },
242
+ suggest: {
243
+ enumerable: true,
244
+ configurable: true,
245
+ writable: true,
246
+ value: (context, prefix) => {
247
+ const scoped = withParserOptionScope(context, parser);
248
+ prepare?.(scoped);
249
+ return suggest.call(parser, scoped, prefix);
250
+ }
251
+ }
252
+ });
253
+ return parser;
254
+ }
255
+ /** Tests full-name ownership before splitting a short prefix. @internal */
256
+ function fullNameOwnsToken(context, token) {
257
+ if (!/^-[^-]/.test(token)) return false;
258
+ for (const name of getOptionScope(context)) if (name.startsWith("-") && !name.startsWith("--") && name.length > 2 && !/^-[^-]$/u.test(name) && (token === name || token.startsWith(`${name}=`))) return true;
259
+ return false;
260
+ }
261
+ /** Finds the value part of an attached short-option completion. @internal */
262
+ function attachedValuePrefix(context, prefix, optionNames) {
263
+ if (!/^-[^-]/.test(prefix)) return void 0;
264
+ const scope = getOptionScope(context);
265
+ const names = {
266
+ value: /* @__PURE__ */ new Set(),
267
+ flag: /* @__PURE__ */ new Set()
268
+ };
269
+ const scoped = context[optionScopeKey];
270
+ if (scoped == null) collectOptionNamesAtCurrentCommandDepth(context.usage, names, false);
271
+ else for (const source of scoped.sources) source(scoped.selections, names, context);
272
+ for (let index = 1; index < prefix.length;) {
273
+ const character = String.fromCodePoint(prefix.codePointAt(index));
274
+ const nextIndex = index + character.length;
275
+ const remainder = `-${prefix.slice(index)}`;
276
+ for (const name of scope) if (name.startsWith("-") && !name.startsWith("--") && name.length > 2 && !/^-[^-]$/u.test(name) && (name.startsWith(remainder) || remainder.startsWith(`${name}=`))) return void 0;
277
+ const short = `-${character}`;
278
+ if (optionNames.includes(short)) return {
279
+ head: prefix.slice(0, nextIndex),
280
+ value: prefix.slice(nextIndex)
281
+ };
282
+ if (!scope.has(short) || !names.flag.has(short) || names.value.has(short)) return void 0;
283
+ index = nextIndex;
284
+ }
285
+ return void 0;
286
+ }
287
+ /** Preserves the prefix when embedding value suggestions in one token. @internal */
288
+ function prefixSuggestion(head, suggestion) {
289
+ return {
290
+ kind: "literal",
291
+ text: head + (suggestion.kind === "literal" ? suggestion.text : suggestion.pattern ?? ""),
292
+ ...suggestion.description != null ? { description: suggestion.description } : {}
293
+ };
294
+ }
295
+
296
+ //#endregion
297
+ export { adoptOptionScope, attachedValuePrefix, combinedOptionScope, conditionalOptionScope, forkOptionScope, fullNameOwnsToken, inheritOptionScope, prefixSuggestion, repeatedOptionScope, restoreOptionScope, scopeParser, selectableOptionScope, withOptionScope, withOptionScopeChild, withParserOptionScope };
@@ -76,7 +76,80 @@ function extractLeadingCommandNames(usage) {
76
76
  collectLeadingCandidates(usage, options, commands);
77
77
  return commands;
78
78
  }
79
+ /** Collects arities along an entered command path. @internal */
80
+ function collectActiveOptionNames(usage, commandPath, names, fromExclusive, includeDirectAfterCommandOptions) {
81
+ if (commandPath.length === 0) {
82
+ collectOptionNamesAtCurrentCommandDepth(usage, names, false);
83
+ return;
84
+ }
85
+ const [commandName, ...rest] = commandPath;
86
+ for (let i = 0; i < usage.length; i++) {
87
+ const term = usage[i];
88
+ if (term.type === "command" && term.name === commandName) {
89
+ const remainingUsage = usage.slice(i + 1);
90
+ if (rest.length === 0) collectOptionNamesAtCurrentCommandDepth(remainingUsage, names, !fromExclusive && includeDirectAfterCommandOptions);
91
+ else collectActiveOptionNames(remainingUsage, rest, names, fromExclusive, includeDirectAfterCommandOptions);
92
+ } else if (term.type === "exclusive") for (const branch of term.terms) collectActiveOptionNames(branch, commandPath, names, true, includeDirectAfterCommandOptions);
93
+ else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") collectActiveOptionNames(term.terms, commandPath, names, fromExclusive, includeDirectAfterCommandOptions);
94
+ }
95
+ }
96
+ /** Collects arities without entering further commands. @internal */
97
+ function collectOptionNamesAtCurrentCommandDepth(usage, names, afterMatchedCommand) {
98
+ for (const term of usage) if (collectOptionNamesAtCurrentCommandDepthFromTerm(term, names, afterMatchedCommand)) return;
99
+ }
100
+ function collectOptionNamesAtCurrentCommandDepthFromTerm(term, names, afterMatchedCommand) {
101
+ switch (term.type) {
102
+ case "command": return !afterMatchedCommand;
103
+ case "option":
104
+ if (term.metavar != null) for (const name of term.names) names.value.add(name);
105
+ else for (const name of term.names) names.flag.add(name);
106
+ return false;
107
+ case "optional":
108
+ case "multiple":
109
+ case "sequence":
110
+ collectOptionNamesAtCurrentCommandDepth(term.terms, names, afterMatchedCommand);
111
+ return false;
112
+ case "exclusive":
113
+ for (const branch of term.terms) collectOptionNamesAtCurrentCommandDepth(branch, names, false);
114
+ return false;
115
+ case "argument":
116
+ case "literal":
117
+ case "passthrough":
118
+ case "ellipsis": return false;
119
+ }
120
+ }
121
+ /** Collects scoped root arities across sibling commands. @internal */
122
+ function collectRootOptionNames(usage, names, rootLeadingNames) {
123
+ for (const term of usage) collectRootOptionNamesFromTerm(term, names, rootLeadingNames);
124
+ }
125
+ function collectRootOptionNamesFromTerm(term, names, rootLeadingNames) {
126
+ switch (term.type) {
127
+ case "option":
128
+ for (const name of term.names) {
129
+ if (!rootLeadingNames.has(name)) continue;
130
+ if (term.metavar != null) names.value.add(name);
131
+ else names.flag.add(name);
132
+ }
133
+ return;
134
+ case "optional":
135
+ case "multiple":
136
+ case "sequence":
137
+ collectRootOptionNames(term.terms, names, rootLeadingNames);
138
+ return;
139
+ case "exclusive":
140
+ for (const branch of term.terms) collectRootOptionNames(branch, names, rootLeadingNames);
141
+ return;
142
+ case "argument":
143
+ case "command":
144
+ case "literal":
145
+ case "passthrough":
146
+ case "ellipsis": return;
147
+ }
148
+ }
79
149
 
80
150
  //#endregion
151
+ exports.collectActiveOptionNames = collectActiveOptionNames;
81
152
  exports.collectLeadingCandidates = collectLeadingCandidates;
153
+ exports.collectOptionNamesAtCurrentCommandDepth = collectOptionNamesAtCurrentCommandDepth;
154
+ exports.collectRootOptionNames = collectRootOptionNames;
82
155
  exports.extractLeadingCommandNames = extractLeadingCommandNames;
@@ -76,6 +76,76 @@ function extractLeadingCommandNames(usage) {
76
76
  collectLeadingCandidates(usage, options, commands);
77
77
  return commands;
78
78
  }
79
+ /** Collects arities along an entered command path. @internal */
80
+ function collectActiveOptionNames(usage, commandPath, names, fromExclusive, includeDirectAfterCommandOptions) {
81
+ if (commandPath.length === 0) {
82
+ collectOptionNamesAtCurrentCommandDepth(usage, names, false);
83
+ return;
84
+ }
85
+ const [commandName, ...rest] = commandPath;
86
+ for (let i = 0; i < usage.length; i++) {
87
+ const term = usage[i];
88
+ if (term.type === "command" && term.name === commandName) {
89
+ const remainingUsage = usage.slice(i + 1);
90
+ if (rest.length === 0) collectOptionNamesAtCurrentCommandDepth(remainingUsage, names, !fromExclusive && includeDirectAfterCommandOptions);
91
+ else collectActiveOptionNames(remainingUsage, rest, names, fromExclusive, includeDirectAfterCommandOptions);
92
+ } else if (term.type === "exclusive") for (const branch of term.terms) collectActiveOptionNames(branch, commandPath, names, true, includeDirectAfterCommandOptions);
93
+ else if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") collectActiveOptionNames(term.terms, commandPath, names, fromExclusive, includeDirectAfterCommandOptions);
94
+ }
95
+ }
96
+ /** Collects arities without entering further commands. @internal */
97
+ function collectOptionNamesAtCurrentCommandDepth(usage, names, afterMatchedCommand) {
98
+ for (const term of usage) if (collectOptionNamesAtCurrentCommandDepthFromTerm(term, names, afterMatchedCommand)) return;
99
+ }
100
+ function collectOptionNamesAtCurrentCommandDepthFromTerm(term, names, afterMatchedCommand) {
101
+ switch (term.type) {
102
+ case "command": return !afterMatchedCommand;
103
+ case "option":
104
+ if (term.metavar != null) for (const name of term.names) names.value.add(name);
105
+ else for (const name of term.names) names.flag.add(name);
106
+ return false;
107
+ case "optional":
108
+ case "multiple":
109
+ case "sequence":
110
+ collectOptionNamesAtCurrentCommandDepth(term.terms, names, afterMatchedCommand);
111
+ return false;
112
+ case "exclusive":
113
+ for (const branch of term.terms) collectOptionNamesAtCurrentCommandDepth(branch, names, false);
114
+ return false;
115
+ case "argument":
116
+ case "literal":
117
+ case "passthrough":
118
+ case "ellipsis": return false;
119
+ }
120
+ }
121
+ /** Collects scoped root arities across sibling commands. @internal */
122
+ function collectRootOptionNames(usage, names, rootLeadingNames) {
123
+ for (const term of usage) collectRootOptionNamesFromTerm(term, names, rootLeadingNames);
124
+ }
125
+ function collectRootOptionNamesFromTerm(term, names, rootLeadingNames) {
126
+ switch (term.type) {
127
+ case "option":
128
+ for (const name of term.names) {
129
+ if (!rootLeadingNames.has(name)) continue;
130
+ if (term.metavar != null) names.value.add(name);
131
+ else names.flag.add(name);
132
+ }
133
+ return;
134
+ case "optional":
135
+ case "multiple":
136
+ case "sequence":
137
+ collectRootOptionNames(term.terms, names, rootLeadingNames);
138
+ return;
139
+ case "exclusive":
140
+ for (const branch of term.terms) collectRootOptionNames(branch, names, rootLeadingNames);
141
+ return;
142
+ case "argument":
143
+ case "command":
144
+ case "literal":
145
+ case "passthrough":
146
+ case "ellipsis": return;
147
+ }
148
+ }
79
149
 
80
150
  //#endregion
81
- export { collectLeadingCandidates, extractLeadingCommandNames };
151
+ export { collectActiveOptionNames, collectLeadingCandidates, collectOptionNamesAtCurrentCommandDepth, collectRootOptionNames, extractLeadingCommandNames };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.4.0-dev.2618",
3
+ "version": "1.4.0-dev.2619",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -237,7 +237,7 @@
237
237
  },
238
238
  "sideEffects": false,
239
239
  "devDependencies": {
240
- "@optique/env": "1.4.0-dev.2618+2cb27efe",
240
+ "@optique/env": "1.4.0-dev.2619+6fa1e1d4",
241
241
  "@types/node": "^24.0.0",
242
242
  "fast-check": "^4.7.0",
243
243
  "tsdown": "^0.13.0",
@@ -11,7 +11,8 @@ description: >
11
11
  license: MIT
12
12
  ---
13
13
 
14
- Start at <https://optique.dev/llms.txt> online; these rules also work offline.
14
+ Start at <https://optique.dev/llms.txt> when online. These rules cover the
15
+ combinatorial parser model and common pitfalls offline.
15
16
 
16
17
 
17
18
  Core rules
@@ -29,15 +30,11 @@ Core rules
29
30
  Pin `colors`/`maxWidth` in tests; defaults use terminal and environment.
30
31
  - Compose parsers with `object()`, `tuple()`, `seq()`, `or()`, `merge()`, and
31
32
  modifiers. Do not hand-write argument scanners around Optique parsers.
32
- - For command/option help headings, set the runner's
33
- `helpSections: { commands: "Commands", options: "Options" }`.
34
- It groups untitled entries only on pages with visible commands. See
35
- <https://optique.dev/cookbook.md#command-and-option-headings-in-help>.
36
- - Let TypeScript infer results; write a separate result interface only when
37
- another API boundary requires it.
38
- - Parsers are usually required. `optional(p)` yields `undefined`, and
39
- `withDefault(p, value)` supplies a fallback. Use
40
- `withDefault(flag("--name"), false)` for optional Boolean flags.
33
+ - Let TypeScript infer parser result types. Only write a separate result
34
+ interface when another API boundary requires it.
35
+ - Most parsers are required. `optional(p)` yields `undefined`, and
36
+ `withDefault(p, value)` supplies a fallback. For absent Boolean flags,
37
+ use `withDefault(flag("--name"), false)`.
41
38
  - Use semantic `message` helpers for descriptions and errors. Since 1.3.0,
42
39
  customize terminal output with `theme` or `messageFormatter`, preserving
43
40
  `initialWidth`, quoting, and width options. Keep canonical errors unthemed.
@@ -50,6 +47,8 @@ Core rules
50
47
  `@optique/run/valueparser` for file-system paths. Write a custom
51
48
  `{ mode, metavar, parse, format }` value parser only when the catalog does
52
49
  not cover the domain.
50
+ - Since 1.4.0, `-p8080`/`-vp8080` accept attached values. Values consume
51
+ the literal suffix (`-p=5` gives `"=5"`); full single-dash names win.
53
52
  - Async value parsers like *@optique/git* make containing parsers async.
54
53
  Await `run()`/`parse()`/`runParser()` or, for `bindKeyring()`, `runAsync()`.
55
54
  - Use `dependency()` when one value parser controls another's valid values.
@@ -232,8 +231,9 @@ const parser = logLevel();
232
231
  Common mistakes checklist
233
232
  -------------------------
234
233
 
235
- - Pass parsers to `run()` for apps; use explicit argument arrays with
236
- `parse()` in tests and embedded use. Do not pre-parse `process.argv`.
234
+ - Do not parse `process.argv` manually before calling Optique. Pass the parser
235
+ to `run()` for applications, or pass explicit argument arrays to `parse()`
236
+ in tests and embedded use.
237
237
  - Do not treat `or(a, b)` as “zero or more alternatives.” It requires one
238
238
  matching branch unless the whole `or()` is wrapped in `optional()` or
239
239
  `withDefault()`.