@optique/core 1.0.0-dev.908 → 1.0.0
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.
- package/dist/annotation-state.cjs +425 -0
- package/dist/annotation-state.d.cts +24 -0
- package/dist/annotation-state.d.ts +24 -0
- package/dist/annotation-state.js +414 -0
- package/dist/annotations.cjs +2 -248
- package/dist/annotations.d.cts +2 -137
- package/dist/annotations.d.ts +2 -137
- package/dist/annotations.js +2 -238
- package/dist/completion.cjs +611 -100
- package/dist/completion.d.cts +1 -1
- package/dist/completion.d.ts +1 -1
- package/dist/completion.js +611 -100
- package/dist/constructs.cjs +3338 -827
- package/dist/constructs.d.cts +48 -7
- package/dist/constructs.d.ts +48 -7
- package/dist/constructs.js +3338 -827
- package/dist/context.cjs +0 -23
- package/dist/context.d.cts +119 -53
- package/dist/context.d.ts +119 -53
- package/dist/context.js +0 -22
- package/dist/dependency-metadata.cjs +139 -0
- package/dist/dependency-metadata.d.cts +112 -0
- package/dist/dependency-metadata.d.ts +112 -0
- package/dist/dependency-metadata.js +138 -0
- package/dist/dependency-runtime.cjs +698 -0
- package/dist/dependency-runtime.d.cts +149 -0
- package/dist/dependency-runtime.d.ts +149 -0
- package/dist/dependency-runtime.js +687 -0
- package/dist/dependency.cjs +7 -928
- package/dist/dependency.d.cts +2 -794
- package/dist/dependency.d.ts +2 -794
- package/dist/dependency.js +2 -899
- package/dist/displaywidth.cjs +44 -0
- package/dist/displaywidth.js +43 -0
- package/dist/doc.cjs +285 -23
- package/dist/doc.d.cts +57 -2
- package/dist/doc.d.ts +57 -2
- package/dist/doc.js +283 -25
- package/dist/execution-context.cjs +56 -0
- package/dist/execution-context.js +53 -0
- package/dist/extension.cjs +87 -0
- package/dist/extension.d.cts +97 -0
- package/dist/extension.d.ts +97 -0
- package/dist/extension.js +76 -0
- package/dist/facade.cjs +718 -523
- package/dist/facade.d.cts +87 -18
- package/dist/facade.d.ts +87 -18
- package/dist/facade.js +718 -523
- package/dist/index.cjs +14 -29
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +7 -7
- package/dist/input-trace.cjs +56 -0
- package/dist/input-trace.d.cts +77 -0
- package/dist/input-trace.d.ts +77 -0
- package/dist/input-trace.js +55 -0
- package/dist/internal/annotations.cjs +316 -0
- package/dist/internal/annotations.d.cts +140 -0
- package/dist/internal/annotations.d.ts +140 -0
- package/dist/internal/annotations.js +306 -0
- package/dist/internal/dependency.cjs +984 -0
- package/dist/internal/dependency.d.cts +539 -0
- package/dist/internal/dependency.d.ts +539 -0
- package/dist/internal/dependency.js +964 -0
- package/dist/{mode-dispatch.cjs → internal/mode-dispatch.cjs} +1 -3
- package/dist/{mode-dispatch.d.cts → internal/mode-dispatch.d.cts} +3 -7
- package/dist/{mode-dispatch.d.ts → internal/mode-dispatch.d.ts} +3 -7
- package/dist/{mode-dispatch.js → internal/mode-dispatch.js} +1 -3
- package/dist/internal/parser.cjs +728 -0
- package/dist/internal/parser.d.cts +947 -0
- package/dist/internal/parser.d.ts +947 -0
- package/dist/internal/parser.js +711 -0
- package/dist/message.cjs +84 -26
- package/dist/message.d.cts +49 -9
- package/dist/message.d.ts +49 -9
- package/dist/message.js +84 -27
- package/dist/modifiers.cjs +1023 -240
- package/dist/modifiers.d.cts +42 -1
- package/dist/modifiers.d.ts +42 -1
- package/dist/modifiers.js +1023 -240
- package/dist/parser.cjs +11 -463
- package/dist/parser.d.cts +3 -537
- package/dist/parser.d.ts +3 -537
- package/dist/parser.js +2 -433
- package/dist/phase2-seed.cjs +59 -0
- package/dist/phase2-seed.js +56 -0
- package/dist/primitives.cjs +557 -208
- package/dist/primitives.d.cts +10 -14
- package/dist/primitives.d.ts +10 -14
- package/dist/primitives.js +557 -208
- package/dist/program.cjs +5 -1
- package/dist/program.d.cts +5 -3
- package/dist/program.d.ts +5 -3
- package/dist/program.js +6 -1
- package/dist/suggestion.cjs +22 -8
- package/dist/suggestion.js +22 -8
- package/dist/usage-internals.cjs +3 -2
- package/dist/usage-internals.js +4 -2
- package/dist/usage.cjs +195 -40
- package/dist/usage.d.cts +92 -11
- package/dist/usage.d.ts +92 -11
- package/dist/usage.js +194 -41
- package/dist/validate.cjs +170 -0
- package/dist/validate.js +164 -0
- package/dist/valueparser.cjs +1278 -191
- package/dist/valueparser.d.cts +330 -20
- package/dist/valueparser.d.ts +330 -20
- package/dist/valueparser.js +1277 -192
- package/package.json +9 -9
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { hasMeaningfulAnnotations, inheritAnnotations, injectAnnotations, isInjectedAnnotationWrapper, unwrapInjectedAnnotationWrapper } from "./annotations.js";
|
|
2
|
+
import { cloneMessage, message } from "../message.js";
|
|
3
|
+
import { cloneUsage, normalizeUsage } from "../usage.js";
|
|
4
|
+
import { cloneDocEntry, isDocEntryHidden } from "../doc.js";
|
|
5
|
+
import { dispatchByMode } from "./mode-dispatch.js";
|
|
6
|
+
import { collectExplicitSourceValues, collectExplicitSourceValuesAsync, createDependencyRuntimeContext } from "../dependency-runtime.js";
|
|
7
|
+
import { createInputTrace } from "../input-trace.js";
|
|
8
|
+
|
|
9
|
+
//#region src/internal/parser.ts
|
|
10
|
+
/**
|
|
11
|
+
* Internal marker for wrappers whose `{ hasCliValue: false }` states should
|
|
12
|
+
* be treated as unmatched dependency-source states during completion-time
|
|
13
|
+
* Phase 1.
|
|
14
|
+
*
|
|
15
|
+
* Wrappers like `bindEnv()` and `bindConfig()` opt in because their missing
|
|
16
|
+
* CLI states still carry enough fallback context to pre-complete exactly
|
|
17
|
+
* once. Wrappers like `prompt()` intentionally do not opt in because
|
|
18
|
+
* prompted values are not yet registered as dependency sources.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const unmatchedNonCliDependencySourceStateMarker = Symbol.for("@optique/core/parser/unmatchedNonCliDependencySourceStateMarker");
|
|
23
|
+
/**
|
|
24
|
+
* Internal marker for parsers that want parent-state annotations injected
|
|
25
|
+
* directly into rebuilt child states instead of relying on structural
|
|
26
|
+
* inheritance.
|
|
27
|
+
*
|
|
28
|
+
* Wrappers like `bindConfig()`, `bindEnv()`, and `prompt()` opt in because
|
|
29
|
+
* their fallback contracts depend on carrying annotations through wrapper
|
|
30
|
+
* state objects during parse, complete, and suggest.
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
const inheritParentAnnotationsKey = Symbol.for("@optique/core/inheritParentAnnotations");
|
|
35
|
+
/**
|
|
36
|
+
* Internal marker for wrapper parsers that should only treat annotation-only
|
|
37
|
+
* primitive wrapper states as completable when a nested source-binding wrapper
|
|
38
|
+
* produced them.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
const annotationWrapperRequiresSourceBindingKey = Symbol.for("@optique/core/annotationWrapperRequiresSourceBinding");
|
|
43
|
+
/**
|
|
44
|
+
* Creates a {@link ParserContext} from a {@link ParseFrame} and an
|
|
45
|
+
* {@link ExecutionContext}. The returned object provides both structured
|
|
46
|
+
* access (`frame`, `exec`) and flat access (`buffer`, `state`, etc.)
|
|
47
|
+
* for backward compatibility.
|
|
48
|
+
*
|
|
49
|
+
* @template TState The type of the state used during parsing.
|
|
50
|
+
* @param frame Parser-local frame data.
|
|
51
|
+
* @param exec Shared execution context.
|
|
52
|
+
* @returns A {@link ParserContext} instance.
|
|
53
|
+
* @since 1.0.0
|
|
54
|
+
*/
|
|
55
|
+
function createParserContext(frame, exec) {
|
|
56
|
+
return {
|
|
57
|
+
exec,
|
|
58
|
+
trace: exec.trace,
|
|
59
|
+
buffer: frame.buffer,
|
|
60
|
+
state: frame.state,
|
|
61
|
+
optionsTerminated: frame.optionsTerminated,
|
|
62
|
+
usage: exec.usage,
|
|
63
|
+
dependencyRegistry: exec.dependencyRegistry
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function injectAnnotationsIntoState(state, options) {
|
|
67
|
+
return injectAnnotations(state, options?.annotations);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parses an array of command-line arguments using the provided combined parser.
|
|
71
|
+
* This function processes the input arguments, applying the parser to each
|
|
72
|
+
* argument until all arguments are consumed or an error occurs.
|
|
73
|
+
*
|
|
74
|
+
* This function only accepts synchronous parsers. For asynchronous parsers,
|
|
75
|
+
* use {@link parseAsync}.
|
|
76
|
+
*
|
|
77
|
+
* @template T The type of the value produced by the parser.
|
|
78
|
+
* @param parser The combined {@link Parser} to use for parsing the input
|
|
79
|
+
* arguments. Must be a synchronous parser.
|
|
80
|
+
* @param args The array of command-line arguments to parse. Usually this is
|
|
81
|
+
* `process.argv.slice(2)` in Node.js or `Deno.args` in Deno.
|
|
82
|
+
* @param options Optional {@link ParseOptions} for customizing parsing behavior.
|
|
83
|
+
* @returns A {@link Result} object indicating whether the parsing was
|
|
84
|
+
* successful or not. If successful, it contains the parsed value of
|
|
85
|
+
* type `T`. If not, it contains an error message describing the
|
|
86
|
+
* failure.
|
|
87
|
+
* @throws {TypeError} When a synchronous dependency source extractor returns a
|
|
88
|
+
* thenable during completion-time dependency seeding.
|
|
89
|
+
* @since 0.9.0 Renamed from the original `parse` function which now delegates
|
|
90
|
+
* to this for sync parsers.
|
|
91
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
92
|
+
*/
|
|
93
|
+
function parseSync(parser, args, options) {
|
|
94
|
+
const initialState = injectAnnotationsIntoState(parser.initialState, options);
|
|
95
|
+
const shouldUnwrapAnnotatedValue = hasMeaningfulAnnotations(options?.annotations) || isInjectedAnnotationWrapper(parser.initialState);
|
|
96
|
+
const exec = {
|
|
97
|
+
usage: parser.usage,
|
|
98
|
+
phase: "parse",
|
|
99
|
+
path: [],
|
|
100
|
+
trace: createInputTrace()
|
|
101
|
+
};
|
|
102
|
+
let context = createParserContext({
|
|
103
|
+
buffer: args,
|
|
104
|
+
state: initialState,
|
|
105
|
+
optionsTerminated: false
|
|
106
|
+
}, exec);
|
|
107
|
+
do {
|
|
108
|
+
const result = parser.parse(context);
|
|
109
|
+
if (!result.success) return {
|
|
110
|
+
success: false,
|
|
111
|
+
error: result.error
|
|
112
|
+
};
|
|
113
|
+
const previousBuffer = context.buffer;
|
|
114
|
+
context = result.next;
|
|
115
|
+
if (isBufferUnchanged(previousBuffer, context.buffer)) return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: message`Unexpected option or argument: ${context.buffer[0]}.`
|
|
118
|
+
};
|
|
119
|
+
} while (context.buffer.length > 0);
|
|
120
|
+
const runtime = createDependencyRuntimeContext();
|
|
121
|
+
const completeExec = {
|
|
122
|
+
...exec,
|
|
123
|
+
phase: "complete",
|
|
124
|
+
dependencyRuntime: runtime,
|
|
125
|
+
dependencyRegistry: runtime.registry,
|
|
126
|
+
trace: context.exec?.trace ?? context.trace ?? exec.trace
|
|
127
|
+
};
|
|
128
|
+
const endResult = parser.complete(context.state, completeExec);
|
|
129
|
+
return endResult.success ? {
|
|
130
|
+
success: true,
|
|
131
|
+
value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
|
|
132
|
+
...endResult.deferred ? { deferred: true } : {},
|
|
133
|
+
...endResult.deferredKeys ? { deferredKeys: endResult.deferredKeys } : {}
|
|
134
|
+
} : {
|
|
135
|
+
success: false,
|
|
136
|
+
error: endResult.error
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Returns `true` when the buffer has not changed between iterations,
|
|
141
|
+
* indicating a parser is stalling without consuming input.
|
|
142
|
+
*/
|
|
143
|
+
function isBufferUnchanged(previous, current) {
|
|
144
|
+
return current.length > 0 && current.length === previous.length && current.every((item, i) => item === previous[i]);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Parses an array of command-line arguments using the provided combined parser.
|
|
148
|
+
* This function processes the input arguments, applying the parser to each
|
|
149
|
+
* argument until all arguments are consumed or an error occurs.
|
|
150
|
+
*
|
|
151
|
+
* This function accepts any parser (sync or async) and always returns a Promise.
|
|
152
|
+
* For synchronous parsing with sync parsers, use {@link parseSync} instead.
|
|
153
|
+
*
|
|
154
|
+
* @template T The type of the value produced by the parser.
|
|
155
|
+
* @param parser The combined {@link Parser} to use for parsing the input
|
|
156
|
+
* arguments.
|
|
157
|
+
* @param args The array of command-line arguments to parse. Usually this is
|
|
158
|
+
* `process.argv.slice(2)` in Node.js or `Deno.args` in Deno.
|
|
159
|
+
* @param options Optional {@link ParseOptions} for customizing parsing behavior.
|
|
160
|
+
* @returns A Promise that resolves to a {@link Result} object indicating
|
|
161
|
+
* whether the parsing was successful or not.
|
|
162
|
+
* @since 0.9.0
|
|
163
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
164
|
+
*/
|
|
165
|
+
async function parseAsync(parser, args, options) {
|
|
166
|
+
const initialState = injectAnnotationsIntoState(parser.initialState, options);
|
|
167
|
+
const shouldUnwrapAnnotatedValue = hasMeaningfulAnnotations(options?.annotations) || isInjectedAnnotationWrapper(parser.initialState);
|
|
168
|
+
const exec = {
|
|
169
|
+
usage: parser.usage,
|
|
170
|
+
phase: "parse",
|
|
171
|
+
path: [],
|
|
172
|
+
trace: createInputTrace()
|
|
173
|
+
};
|
|
174
|
+
let context = createParserContext({
|
|
175
|
+
buffer: args,
|
|
176
|
+
state: initialState,
|
|
177
|
+
optionsTerminated: false
|
|
178
|
+
}, exec);
|
|
179
|
+
do {
|
|
180
|
+
const result = await parser.parse(context);
|
|
181
|
+
if (!result.success) return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: result.error
|
|
184
|
+
};
|
|
185
|
+
const previousBuffer = context.buffer;
|
|
186
|
+
context = result.next;
|
|
187
|
+
if (isBufferUnchanged(previousBuffer, context.buffer)) return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: message`Unexpected option or argument: ${context.buffer[0]}.`
|
|
190
|
+
};
|
|
191
|
+
} while (context.buffer.length > 0);
|
|
192
|
+
const runtime = createDependencyRuntimeContext();
|
|
193
|
+
const completeExec = {
|
|
194
|
+
...exec,
|
|
195
|
+
phase: "complete",
|
|
196
|
+
dependencyRuntime: runtime,
|
|
197
|
+
dependencyRegistry: runtime.registry,
|
|
198
|
+
trace: context.exec?.trace ?? context.trace ?? exec.trace
|
|
199
|
+
};
|
|
200
|
+
const endResult = await parser.complete(context.state, completeExec);
|
|
201
|
+
return endResult.success ? {
|
|
202
|
+
success: true,
|
|
203
|
+
value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
|
|
204
|
+
...endResult.deferred ? { deferred: true } : {},
|
|
205
|
+
...endResult.deferredKeys ? { deferredKeys: endResult.deferredKeys } : {}
|
|
206
|
+
} : {
|
|
207
|
+
success: false,
|
|
208
|
+
error: endResult.error
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Parses an array of command-line arguments using the provided combined parser.
|
|
213
|
+
* This function processes the input arguments, applying the parser to each
|
|
214
|
+
* argument until all arguments are consumed or an error occurs.
|
|
215
|
+
*
|
|
216
|
+
* The return type depends on the parser's mode:
|
|
217
|
+
* - Sync parsers return `Result<T>` directly.
|
|
218
|
+
* - Async parsers return `Promise<Result<T>>`.
|
|
219
|
+
*
|
|
220
|
+
* For explicit control, use {@link parseSync} or {@link parseAsync}.
|
|
221
|
+
*
|
|
222
|
+
* @template M The execution mode of the parser.
|
|
223
|
+
* @template T The type of the value produced by the parser.
|
|
224
|
+
* @param parser The combined {@link Parser} to use for parsing the input
|
|
225
|
+
* arguments.
|
|
226
|
+
* @param args The array of command-line arguments to parse. Usually this is
|
|
227
|
+
* `process.argv.slice(2)` in Node.js or `Deno.args` in Deno.
|
|
228
|
+
* @param options Optional {@link ParseOptions} for customizing parsing behavior.
|
|
229
|
+
* @returns A {@link Result} object (for sync) or Promise thereof (for async)
|
|
230
|
+
* indicating whether the parsing was successful or not.
|
|
231
|
+
* @throws {TypeError} When a synchronous dependency source extractor returns a
|
|
232
|
+
* thenable during completion-time dependency seeding.
|
|
233
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
234
|
+
*/
|
|
235
|
+
function parse(parser, args, options) {
|
|
236
|
+
return dispatchByMode(parser.mode, () => parseSync(parser, args, options), () => parseAsync(parser, args, options));
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Generates command-line suggestions based on current parsing state.
|
|
240
|
+
* This function processes the input arguments up to the last argument,
|
|
241
|
+
* then calls the parser's suggest method with the remaining prefix.
|
|
242
|
+
*
|
|
243
|
+
* This function only accepts synchronous parsers. For asynchronous parsers,
|
|
244
|
+
* use {@link suggestAsync}.
|
|
245
|
+
*
|
|
246
|
+
* @template T The type of the value produced by the parser.
|
|
247
|
+
* @param parser The {@link Parser} to use for generating suggestions.
|
|
248
|
+
* Must be a synchronous parser.
|
|
249
|
+
* @param args The array of command-line arguments including the partial
|
|
250
|
+
* argument to complete. The last element is treated as
|
|
251
|
+
* the prefix for suggestions.
|
|
252
|
+
* @param options Optional {@link ParseOptions} for customizing parsing behavior.
|
|
253
|
+
* @returns An array of {@link Suggestion} objects containing completion
|
|
254
|
+
* candidates.
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* const parser = object({
|
|
258
|
+
* verbose: option("-v", "--verbose"),
|
|
259
|
+
* format: option("-f", "--format", choice(["json", "yaml"]))
|
|
260
|
+
* });
|
|
261
|
+
*
|
|
262
|
+
* // Get suggestions for options starting with "--"
|
|
263
|
+
* const suggestions = suggestSync(parser, ["--"]);
|
|
264
|
+
* // Returns: [{ text: "--verbose" }, { text: "--format" }]
|
|
265
|
+
*
|
|
266
|
+
* // Get suggestions after parsing some arguments
|
|
267
|
+
* const suggestions2 = suggestSync(parser, ["-v", "--format="]);
|
|
268
|
+
* // Returns: [{ text: "--format=json" }, { text: "--format=yaml" }]
|
|
269
|
+
* ```
|
|
270
|
+
* @throws {TypeError} When a synchronous dependency source extractor returns a
|
|
271
|
+
* thenable during suggestion seeding.
|
|
272
|
+
* @since 0.6.0
|
|
273
|
+
* @since 0.9.0 Renamed from the original `suggest` function.
|
|
274
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
275
|
+
*/
|
|
276
|
+
function suggestSync(parser, args, options) {
|
|
277
|
+
const allButLast = args.slice(0, -1);
|
|
278
|
+
const prefix = args[args.length - 1];
|
|
279
|
+
const initialState = injectAnnotationsIntoState(parser.initialState, options);
|
|
280
|
+
let context = createParserContext({
|
|
281
|
+
buffer: allButLast,
|
|
282
|
+
state: initialState,
|
|
283
|
+
optionsTerminated: false
|
|
284
|
+
}, {
|
|
285
|
+
usage: parser.usage,
|
|
286
|
+
phase: "suggest",
|
|
287
|
+
path: [],
|
|
288
|
+
trace: createInputTrace()
|
|
289
|
+
});
|
|
290
|
+
while (context.buffer.length > 0) {
|
|
291
|
+
const result = parser.parse(context);
|
|
292
|
+
if (!result.success) return Array.from(parser.suggest(withSuggestRuntime(parser, context), prefix));
|
|
293
|
+
const previousBuffer = context.buffer;
|
|
294
|
+
context = result.next;
|
|
295
|
+
if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
|
|
296
|
+
}
|
|
297
|
+
return Array.from(parser.suggest(withSuggestRuntime(parser, context), prefix));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Creates a dependency runtime from the current parser state and returns
|
|
301
|
+
* a context with the populated registry. Used by top-level suggest
|
|
302
|
+
* functions to mirror the construct-owned model where suggest() receives
|
|
303
|
+
* a context with a dependency registry.
|
|
304
|
+
* @internal
|
|
305
|
+
*/
|
|
306
|
+
function withSuggestRuntime(parser, context) {
|
|
307
|
+
const runtime = createDependencyRuntimeContext();
|
|
308
|
+
const nodes = getParserSuggestRuntimeNodes(parser, context.state, context.exec?.path ?? []);
|
|
309
|
+
if (nodes.length > 0) collectExplicitSourceValues(nodes, runtime);
|
|
310
|
+
return {
|
|
311
|
+
...context,
|
|
312
|
+
dependencyRegistry: runtime.registry,
|
|
313
|
+
exec: context.exec ? {
|
|
314
|
+
...context.exec,
|
|
315
|
+
dependencyRuntime: runtime,
|
|
316
|
+
dependencyRegistry: runtime.registry
|
|
317
|
+
} : void 0
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
async function withSuggestRuntimeAsync(parser, context) {
|
|
321
|
+
const runtime = createDependencyRuntimeContext();
|
|
322
|
+
const nodes = getParserSuggestRuntimeNodes(parser, context.state, context.exec?.path ?? []);
|
|
323
|
+
if (nodes.length > 0) await collectExplicitSourceValuesAsync(nodes, runtime);
|
|
324
|
+
return {
|
|
325
|
+
...context,
|
|
326
|
+
dependencyRegistry: runtime.registry,
|
|
327
|
+
exec: context.exec ? {
|
|
328
|
+
...context.exec,
|
|
329
|
+
dependencyRuntime: runtime,
|
|
330
|
+
dependencyRegistry: runtime.registry
|
|
331
|
+
} : void 0
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Returns suggest-time runtime nodes for a parser, falling back to the
|
|
336
|
+
* parser's own source metadata when it does not expose a custom hook.
|
|
337
|
+
*
|
|
338
|
+
* @param parser The parser whose suggest-time runtime nodes should be resolved.
|
|
339
|
+
* @param state The current parser state.
|
|
340
|
+
* @param path The path to this parser within the parse tree.
|
|
341
|
+
* @returns The runtime nodes used to seed suggest-time dependency resolution.
|
|
342
|
+
* @internal
|
|
343
|
+
*/
|
|
344
|
+
function getParserSuggestRuntimeNodes(parser, state, path) {
|
|
345
|
+
if (typeof parser.getSuggestRuntimeNodes === "function") return parser.getSuggestRuntimeNodes(state, path);
|
|
346
|
+
if (parser.dependencyMetadata?.source == null) return [];
|
|
347
|
+
return [{
|
|
348
|
+
path,
|
|
349
|
+
parser,
|
|
350
|
+
state
|
|
351
|
+
}];
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Returns wrapper-aware suggest-time runtime nodes for parsers that delegate
|
|
355
|
+
* to an inner parser while also exposing their own source metadata.
|
|
356
|
+
*
|
|
357
|
+
* The inner parser's nodes are always preserved so nested wrappers and
|
|
358
|
+
* constructs can continue to seed the dependency runtime recursively. When
|
|
359
|
+
* the outer parser itself owns source metadata, its `(path, parser, state)`
|
|
360
|
+
* node is appended so outer precedence rules still apply.
|
|
361
|
+
*
|
|
362
|
+
* @internal
|
|
363
|
+
*/
|
|
364
|
+
function getDelegatingSuggestRuntimeNodes(innerParser, outerParser, state, path, innerState, outerPosition = "append") {
|
|
365
|
+
const innerNodes = getParserSuggestRuntimeNodes(innerParser, innerState, path);
|
|
366
|
+
if (outerParser.dependencyMetadata?.source == null) return innerNodes;
|
|
367
|
+
const outerNode = {
|
|
368
|
+
path,
|
|
369
|
+
parser: outerParser,
|
|
370
|
+
state
|
|
371
|
+
};
|
|
372
|
+
return outerPosition === "prepend" ? [outerNode, ...innerNodes] : [...innerNodes, outerNode];
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Composes source metadata for a wrapper parser while preserving any derived
|
|
376
|
+
* or transform capabilities from the inner parser unchanged.
|
|
377
|
+
*
|
|
378
|
+
* @internal
|
|
379
|
+
*/
|
|
380
|
+
function composeWrappedSourceMetadata(dependencyMetadata, wrapSource) {
|
|
381
|
+
if (dependencyMetadata?.source == null) return dependencyMetadata;
|
|
382
|
+
return {
|
|
383
|
+
...dependencyMetadata,
|
|
384
|
+
source: wrapSource(dependencyMetadata.source)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Marks a parser as inheriting parent-state annotations through wrapper-state
|
|
389
|
+
* reconstruction.
|
|
390
|
+
*
|
|
391
|
+
* @internal
|
|
392
|
+
*/
|
|
393
|
+
function defineInheritedAnnotationParser(parser) {
|
|
394
|
+
Object.defineProperty(parser, inheritParentAnnotationsKey, {
|
|
395
|
+
value: true,
|
|
396
|
+
configurable: true,
|
|
397
|
+
enumerable: false
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Marks a wrapper parser as requiring a real source-binding state before
|
|
402
|
+
* annotation-only primitive wrappers should trigger completion.
|
|
403
|
+
*
|
|
404
|
+
* @internal
|
|
405
|
+
*/
|
|
406
|
+
function defineSourceBindingOnlyAnnotationCompletionParser(parser) {
|
|
407
|
+
Object.defineProperty(parser, annotationWrapperRequiresSourceBindingKey, {
|
|
408
|
+
value: true,
|
|
409
|
+
configurable: true,
|
|
410
|
+
enumerable: false
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Generates command-line suggestions based on current parsing state.
|
|
415
|
+
* This function processes the input arguments up to the last argument,
|
|
416
|
+
* then calls the parser's suggest method with the remaining prefix.
|
|
417
|
+
*
|
|
418
|
+
* This function accepts any parser (sync or async) and always returns a Promise.
|
|
419
|
+
* For synchronous suggestion generation with sync parsers, use
|
|
420
|
+
* {@link suggestSync} instead.
|
|
421
|
+
*
|
|
422
|
+
* @template T The type of the value produced by the parser.
|
|
423
|
+
* @param parser The {@link Parser} to use for generating suggestions.
|
|
424
|
+
* @param args The array of command-line arguments including the partial
|
|
425
|
+
* argument to complete. The last element is treated as
|
|
426
|
+
* the prefix for suggestions.
|
|
427
|
+
* @param options Optional {@link ParseOptions} for customizing parsing behavior.
|
|
428
|
+
* @returns A Promise that resolves to an array of {@link Suggestion} objects
|
|
429
|
+
* containing completion candidates.
|
|
430
|
+
* @since 0.9.0
|
|
431
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
432
|
+
*/
|
|
433
|
+
async function suggestAsync(parser, args, options) {
|
|
434
|
+
const allButLast = args.slice(0, -1);
|
|
435
|
+
const prefix = args[args.length - 1];
|
|
436
|
+
const initialState = injectAnnotationsIntoState(parser.initialState, options);
|
|
437
|
+
let context = createParserContext({
|
|
438
|
+
buffer: allButLast,
|
|
439
|
+
state: initialState,
|
|
440
|
+
optionsTerminated: false
|
|
441
|
+
}, {
|
|
442
|
+
usage: parser.usage,
|
|
443
|
+
phase: "suggest",
|
|
444
|
+
path: [],
|
|
445
|
+
trace: createInputTrace()
|
|
446
|
+
});
|
|
447
|
+
while (context.buffer.length > 0) {
|
|
448
|
+
const result = await parser.parse(context);
|
|
449
|
+
if (!result.success) {
|
|
450
|
+
const ctx$1 = await withSuggestRuntimeAsync(parser, context);
|
|
451
|
+
const suggestions$1 = [];
|
|
452
|
+
for await (const suggestion of parser.suggest(ctx$1, prefix)) suggestions$1.push(suggestion);
|
|
453
|
+
return suggestions$1;
|
|
454
|
+
}
|
|
455
|
+
const previousBuffer = context.buffer;
|
|
456
|
+
context = result.next;
|
|
457
|
+
if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
|
|
458
|
+
}
|
|
459
|
+
const ctx = await withSuggestRuntimeAsync(parser, context);
|
|
460
|
+
const suggestions = [];
|
|
461
|
+
for await (const suggestion of parser.suggest(ctx, prefix)) suggestions.push(suggestion);
|
|
462
|
+
return suggestions;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Generates command-line suggestions based on current parsing state.
|
|
466
|
+
* This function processes the input arguments up to the last argument,
|
|
467
|
+
* then calls the parser's suggest method with the remaining prefix.
|
|
468
|
+
*
|
|
469
|
+
* The return type depends on the parser's mode:
|
|
470
|
+
* - Sync parsers return `readonly Suggestion[]` directly.
|
|
471
|
+
* - Async parsers return `Promise<readonly Suggestion[]>`.
|
|
472
|
+
*
|
|
473
|
+
* For explicit control, use {@link suggestSync} or {@link suggestAsync}.
|
|
474
|
+
*
|
|
475
|
+
* @template M The execution mode of the parser.
|
|
476
|
+
* @template T The type of the value produced by the parser.
|
|
477
|
+
* @param parser The {@link Parser} to use for generating suggestions.
|
|
478
|
+
* @param args The array of command-line arguments including the partial
|
|
479
|
+
* argument to complete. The last element is treated as
|
|
480
|
+
* the prefix for suggestions.
|
|
481
|
+
* @param options Optional {@link ParseOptions} for customizing parsing behavior.
|
|
482
|
+
* @returns An array of {@link Suggestion} objects (for sync) or Promise thereof
|
|
483
|
+
* (for async) containing completion candidates.
|
|
484
|
+
* @throws {TypeError} When a synchronous dependency source extractor returns a
|
|
485
|
+
* thenable during suggestion seeding.
|
|
486
|
+
* @since 0.6.0
|
|
487
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
488
|
+
*/
|
|
489
|
+
function suggest(parser, args, options) {
|
|
490
|
+
return dispatchByMode(parser.mode, () => suggestSync(parser, args, options), () => suggestAsync(parser, args, options));
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Recursively searches for a command within nested exclusive usage terms.
|
|
494
|
+
* When the command is found, returns the expanded usage terms for that command.
|
|
495
|
+
*
|
|
496
|
+
* @param term The usage term to search in
|
|
497
|
+
* @param commandName The command name to find
|
|
498
|
+
* @returns The expanded usage terms if found, null otherwise
|
|
499
|
+
*/
|
|
500
|
+
function findCommandInExclusive(term, commandName) {
|
|
501
|
+
if (term.type !== "exclusive") return null;
|
|
502
|
+
for (const termGroup of term.terms) {
|
|
503
|
+
const firstTerm = termGroup[0];
|
|
504
|
+
if (firstTerm?.type === "command" && firstTerm.name === commandName) return termGroup;
|
|
505
|
+
if (firstTerm?.type === "exclusive") {
|
|
506
|
+
const found = findCommandInExclusive(firstTerm, commandName);
|
|
507
|
+
if (found) return [...found, ...termGroup.slice(1)];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Generates a documentation page for a synchronous parser.
|
|
514
|
+
*
|
|
515
|
+
* This is the sync-specific version of {@link getDocPage}. It only accepts
|
|
516
|
+
* sync parsers and returns the documentation page directly (not wrapped
|
|
517
|
+
* in a Promise).
|
|
518
|
+
*
|
|
519
|
+
* @param parser The sync parser to generate documentation for.
|
|
520
|
+
* @param argsOrOptions Optional array of command-line arguments for context,
|
|
521
|
+
* or a {@link ParseOptions} object for annotations. When a
|
|
522
|
+
* `ParseOptions` is passed here, the `options` parameter is ignored.
|
|
523
|
+
* @param options Optional {@link ParseOptions} for customizing parsing
|
|
524
|
+
* behavior. Only used when `argsOrOptions` is an array or omitted.
|
|
525
|
+
* @returns A {@link DocPage} or `undefined`.
|
|
526
|
+
* @since 0.9.0
|
|
527
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
528
|
+
* @since 1.0.0 The second parameter now also accepts a `ParseOptions` object
|
|
529
|
+
* directly.
|
|
530
|
+
*/
|
|
531
|
+
function getDocPageSync(parser, argsOrOptions, options) {
|
|
532
|
+
if (Array.isArray(argsOrOptions)) return getDocPageSyncImpl(parser, argsOrOptions, options);
|
|
533
|
+
return getDocPageSyncImpl(parser, [], argsOrOptions ?? options);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Generates a documentation page for any parser, returning a Promise.
|
|
537
|
+
*
|
|
538
|
+
* This function accepts parsers of any mode (sync or async) and always
|
|
539
|
+
* returns a Promise. Use this when working with parsers that may contain
|
|
540
|
+
* async value parsers.
|
|
541
|
+
*
|
|
542
|
+
* @param parser The parser to generate documentation for.
|
|
543
|
+
* @param argsOrOptions Optional array of command-line arguments for context,
|
|
544
|
+
* or a {@link ParseOptions} object for annotations. When a
|
|
545
|
+
* `ParseOptions` is passed here, the `options` parameter is ignored.
|
|
546
|
+
* @param options Optional {@link ParseOptions} for customizing parsing
|
|
547
|
+
* behavior. Only used when `argsOrOptions` is an array or omitted.
|
|
548
|
+
* @returns A Promise of {@link DocPage} or `undefined`.
|
|
549
|
+
* @since 0.9.0
|
|
550
|
+
* @since 0.10.0 Added optional `options` parameter for annotations support.
|
|
551
|
+
* @since 1.0.0 The second parameter now also accepts a `ParseOptions` object
|
|
552
|
+
* directly.
|
|
553
|
+
*/
|
|
554
|
+
function getDocPageAsync(parser, argsOrOptions, options) {
|
|
555
|
+
const args = Array.isArray(argsOrOptions) ? argsOrOptions : [];
|
|
556
|
+
const opts = Array.isArray(argsOrOptions) ? options : argsOrOptions ?? options;
|
|
557
|
+
if (parser.mode === "sync") return Promise.resolve(getDocPageSyncImpl(parser, args, opts));
|
|
558
|
+
return getDocPageAsyncImpl(parser, args, opts);
|
|
559
|
+
}
|
|
560
|
+
function getDocPage(parser, argsOrOptions, options) {
|
|
561
|
+
const args = Array.isArray(argsOrOptions) ? argsOrOptions : [];
|
|
562
|
+
const opts = Array.isArray(argsOrOptions) ? options : argsOrOptions ?? options;
|
|
563
|
+
if (parser.mode === "sync") return getDocPageSyncImpl(parser, args, opts);
|
|
564
|
+
return getDocPageAsyncImpl(parser, args, opts);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Internal sync implementation of getDocPage.
|
|
568
|
+
*/
|
|
569
|
+
function getDocPageSyncImpl(parser, args, options) {
|
|
570
|
+
const initialState = injectAnnotationsIntoState(parser.initialState, options);
|
|
571
|
+
const exec = {
|
|
572
|
+
usage: parser.usage,
|
|
573
|
+
phase: "parse",
|
|
574
|
+
path: [],
|
|
575
|
+
trace: createInputTrace()
|
|
576
|
+
};
|
|
577
|
+
let context = createParserContext({
|
|
578
|
+
buffer: args,
|
|
579
|
+
state: initialState,
|
|
580
|
+
optionsTerminated: false
|
|
581
|
+
}, exec);
|
|
582
|
+
while (context.buffer.length > 0) {
|
|
583
|
+
const result = parser.parse(context);
|
|
584
|
+
if (!result.success) break;
|
|
585
|
+
const previousBuffer = context.buffer;
|
|
586
|
+
context = result.next;
|
|
587
|
+
if (isBufferUnchanged(previousBuffer, context.buffer)) break;
|
|
588
|
+
}
|
|
589
|
+
return buildDocPage(parser, context, args);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Internal async implementation of getDocPage.
|
|
593
|
+
*/
|
|
594
|
+
async function getDocPageAsyncImpl(parser, args, options) {
|
|
595
|
+
const initialState = injectAnnotationsIntoState(parser.initialState, options);
|
|
596
|
+
const exec = {
|
|
597
|
+
usage: parser.usage,
|
|
598
|
+
phase: "parse",
|
|
599
|
+
path: [],
|
|
600
|
+
trace: createInputTrace()
|
|
601
|
+
};
|
|
602
|
+
let context = createParserContext({
|
|
603
|
+
buffer: args,
|
|
604
|
+
state: initialState,
|
|
605
|
+
optionsTerminated: false
|
|
606
|
+
}, exec);
|
|
607
|
+
while (context.buffer.length > 0) {
|
|
608
|
+
const result = await parser.parse(context);
|
|
609
|
+
if (!result.success) break;
|
|
610
|
+
const previousBuffer = context.buffer;
|
|
611
|
+
context = result.next;
|
|
612
|
+
if (isBufferUnchanged(previousBuffer, context.buffer)) break;
|
|
613
|
+
}
|
|
614
|
+
return buildDocPage(parser, context, args);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Builds a DocPage from the parser and context.
|
|
618
|
+
* Shared by both sync and async implementations.
|
|
619
|
+
*/
|
|
620
|
+
function buildDocPage(parser, context, args) {
|
|
621
|
+
let effectiveArgs = args;
|
|
622
|
+
let { brief, description, fragments, footer } = parser.getDocFragments({
|
|
623
|
+
kind: "available",
|
|
624
|
+
state: context.state
|
|
625
|
+
}, void 0);
|
|
626
|
+
if (args.length === 0 && Reflect.get(parser, Symbol.for("@optique/core/commandParser")) === true && fragments.length === 1 && fragments[0].type === "entry" && fragments[0].term.type === "command") {
|
|
627
|
+
const cmdName = fragments[0].term.name;
|
|
628
|
+
const matchedState = inheritAnnotations(context.state, ["matched", cmdName]);
|
|
629
|
+
const matched = parser.getDocFragments({
|
|
630
|
+
kind: "available",
|
|
631
|
+
state: matchedState
|
|
632
|
+
}, void 0);
|
|
633
|
+
({brief, description, fragments, footer} = matched);
|
|
634
|
+
effectiveArgs = [cmdName];
|
|
635
|
+
}
|
|
636
|
+
const buildingSections = [];
|
|
637
|
+
let untitledSection = null;
|
|
638
|
+
const titledSectionMap = /* @__PURE__ */ new Map();
|
|
639
|
+
for (const fragment of fragments) if (fragment.type === "entry") {
|
|
640
|
+
if (isDocEntryHidden(fragment)) continue;
|
|
641
|
+
if (untitledSection == null) {
|
|
642
|
+
untitledSection = { entries: [] };
|
|
643
|
+
buildingSections.push(untitledSection);
|
|
644
|
+
}
|
|
645
|
+
untitledSection.entries.push(cloneDocEntry(fragment));
|
|
646
|
+
} else if (fragment.type === "section") {
|
|
647
|
+
const visible = fragment.entries.filter((e) => !isDocEntryHidden(e));
|
|
648
|
+
if (visible.length === 0) continue;
|
|
649
|
+
if (fragment.title == null) {
|
|
650
|
+
if (untitledSection == null) {
|
|
651
|
+
untitledSection = { entries: [] };
|
|
652
|
+
buildingSections.push(untitledSection);
|
|
653
|
+
}
|
|
654
|
+
untitledSection.entries.push(...visible.map(cloneDocEntry));
|
|
655
|
+
} else {
|
|
656
|
+
let section = titledSectionMap.get(fragment.title);
|
|
657
|
+
if (section == null) {
|
|
658
|
+
section = {
|
|
659
|
+
title: fragment.title,
|
|
660
|
+
entries: []
|
|
661
|
+
};
|
|
662
|
+
titledSectionMap.set(fragment.title, section);
|
|
663
|
+
buildingSections.push(section);
|
|
664
|
+
}
|
|
665
|
+
section.entries.push(...visible.map(cloneDocEntry));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
const sections = buildingSections;
|
|
669
|
+
const usage = [...normalizeUsage(parser.usage)];
|
|
670
|
+
const maybeApplyCommandUsageLine = (term, arg, isLastArg, usageIndex) => {
|
|
671
|
+
if (term?.type !== "command" || term.name !== arg || !isLastArg || term.usageLine == null) return;
|
|
672
|
+
const defaultUsageLine = cloneUsage(usage.slice(usageIndex + 1));
|
|
673
|
+
const customUsageLine = typeof term.usageLine === "function" ? term.usageLine(defaultUsageLine) : term.usageLine;
|
|
674
|
+
const normalizedCustomUsageLine = normalizeUsage(customUsageLine);
|
|
675
|
+
usage.splice(usageIndex + 1, usage.length - (usageIndex + 1), ...normalizedCustomUsageLine);
|
|
676
|
+
};
|
|
677
|
+
let i = 0;
|
|
678
|
+
for (let argIndex = 0; argIndex < effectiveArgs.length; argIndex++) {
|
|
679
|
+
const arg = effectiveArgs[argIndex];
|
|
680
|
+
if (i >= usage.length) break;
|
|
681
|
+
let term = usage[i];
|
|
682
|
+
if (term.type === "exclusive") {
|
|
683
|
+
const found = findCommandInExclusive(term, arg);
|
|
684
|
+
if (found) {
|
|
685
|
+
usage.splice(i, 1, ...found);
|
|
686
|
+
term = usage[i];
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
maybeApplyCommandUsageLine(term, arg, argIndex === effectiveArgs.length - 1, i);
|
|
690
|
+
i++;
|
|
691
|
+
}
|
|
692
|
+
if (effectiveArgs.length === 0 && usage.length > 0) {
|
|
693
|
+
const first = usage[0];
|
|
694
|
+
if (first.type === "command" && first.usageLine != null) {
|
|
695
|
+
const defaultUsageLine = cloneUsage(usage.slice(1));
|
|
696
|
+
const customUsageLine = typeof first.usageLine === "function" ? first.usageLine(defaultUsageLine) : first.usageLine;
|
|
697
|
+
const normalizedCustomUsageLine = normalizeUsage(customUsageLine);
|
|
698
|
+
usage.splice(1, usage.length - 1, ...normalizedCustomUsageLine);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
usage,
|
|
703
|
+
sections,
|
|
704
|
+
...brief != null && { brief: cloneMessage(brief) },
|
|
705
|
+
...description != null && { description: cloneMessage(description) },
|
|
706
|
+
...footer != null && { footer: cloneMessage(footer) }
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
//#endregion
|
|
711
|
+
export { annotationWrapperRequiresSourceBindingKey, composeWrappedSourceMetadata, createParserContext, defineInheritedAnnotationParser, defineSourceBindingOnlyAnnotationCompletionParser, getDelegatingSuggestRuntimeNodes, getDocPage, getDocPageAsync, getDocPageSync, getParserSuggestRuntimeNodes, inheritParentAnnotationsKey, parse, parseAsync, parseSync, suggest, suggestAsync, suggestSync, unmatchedNonCliDependencySourceStateMarker };
|