@optique/core 1.0.0-dev.1580 → 1.0.0-dev.1583

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/parser.d.ts CHANGED
@@ -164,13 +164,17 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
164
164
  * message.
165
165
  * @param state The current state of the parser, which may contain accumulated
166
166
  * data or context needed to produce the final value.
167
+ * @param exec Optional shared execution context. When provided, gives the
168
+ * parser access to cross-cutting runtime data such as the current
169
+ * execution phase and dependency registry.
167
170
  * @returns A result object indicating success or failure of
168
171
  * the transformation. If successful, it should contain
169
172
  * the parsed value of type {@link TValue}. If not applicable,
170
173
  * it should return an error message.
171
174
  * In async mode, returns a Promise that resolves to the result.
175
+ * @since 1.0.0 Added optional `exec` parameter.
172
176
  */
173
- complete(state: TState): ModeValue<M, ValueParserResult<TValue>>;
177
+ complete(state: TState, exec?: ExecutionContext): ModeValue<M, ValueParserResult<TValue>>;
174
178
  /**
175
179
  * Generates next-step suggestions based on the current context
176
180
  * and an optional prefix. This can be used to provide shell completion
@@ -221,10 +225,12 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
221
225
  * source) has resolved.
222
226
  *
223
227
  * @param state The current parser state.
228
+ * @param exec Optional shared execution context.
224
229
  * @returns `true` if completion should be deferred.
225
230
  * @since 1.0.0
231
+ * @since 1.0.0 Added optional `exec` parameter.
226
232
  */
227
- shouldDeferCompletion?(state: TState): boolean;
233
+ shouldDeferCompletion?(state: TState, exec?: ExecutionContext): boolean;
228
234
  /**
229
235
  * Normalizes a parsed value according to the underlying value parser's
230
236
  * configuration. When present, {@link withDefault} calls this method
@@ -246,11 +252,82 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
246
252
  */
247
253
  normalizeValue?(value: TValue): TValue;
248
254
  }
255
+ /**
256
+ * Parser-local frame data containing the input buffer and parser state.
257
+ * This represents the per-parser progress during parsing, separated from
258
+ * cross-cutting execution context.
259
+ * @template TState The type of the state used during parsing.
260
+ * @since 1.0.0
261
+ */
262
+ interface ParseFrame<TState> {
263
+ /**
264
+ * The array of input strings that the parser is currently processing.
265
+ */
266
+ readonly buffer: readonly string[];
267
+ /**
268
+ * The current state of the parser, which is used to track
269
+ * the progress of parsing and any accumulated data.
270
+ */
271
+ readonly state: TState;
272
+ /**
273
+ * A flag indicating whether no more options should be parsed and instead
274
+ * the remaining input should be treated as positional arguments.
275
+ */
276
+ readonly optionsTerminated: boolean;
277
+ }
278
+ /**
279
+ * The phase of the execution pipeline.
280
+ * @since 1.0.0
281
+ */
282
+ type ExecutionPhase = "parse" | "precomplete" | "resolve" | "complete" | "suggest";
283
+ /**
284
+ * Shared execution context carrying cross-cutting runtime data.
285
+ * This includes information that is shared across all parsers in a parse
286
+ * tree, such as usage information, dependency registries, and the current
287
+ * execution phase.
288
+ * @since 1.0.0
289
+ */
290
+ interface ExecutionContext {
291
+ /**
292
+ * Usage information for the entire parser tree.
293
+ */
294
+ readonly usage: Usage;
295
+ /**
296
+ * The current phase of the execution pipeline.
297
+ */
298
+ readonly phase: ExecutionPhase;
299
+ /**
300
+ * The path from the root to the current parser in the parse tree.
301
+ * Used by constructs to track the current position during dependency
302
+ * resolution and completion.
303
+ */
304
+ readonly path: readonly PropertyKey[];
305
+ /**
306
+ * A registry containing resolved dependency values from DependencySource
307
+ * parsers.
308
+ * @since 0.10.0
309
+ */
310
+ readonly dependencyRegistry?: DependencyRegistryLike;
311
+ }
249
312
  /**
250
313
  * The context of the parser, which includes the input buffer and the state.
314
+ *
315
+ * `ParserContext` provides structured access to shared execution context
316
+ * via {@link exec}, and flat access to all fields for backward
317
+ * compatibility.
318
+ *
251
319
  * @template TState The type of the state used during parsing.
252
320
  */
253
321
  interface ParserContext<TState> {
322
+ /**
323
+ * Shared execution context (usage, phase, path, dependencyRegistry).
324
+ *
325
+ * Present when the context was created via {@link createParserContext}.
326
+ * Later runtime work will make this field required.
327
+ *
328
+ * @since 1.0.0
329
+ */
330
+ readonly exec?: ExecutionContext;
254
331
  /**
255
332
  * The array of input strings that the parser is currently processing.
256
333
  */
@@ -284,6 +361,19 @@ interface ParserContext<TState> {
284
361
  */
285
362
  readonly dependencyRegistry?: DependencyRegistryLike;
286
363
  }
364
+ /**
365
+ * Creates a {@link ParserContext} from a {@link ParseFrame} and an
366
+ * {@link ExecutionContext}. The returned object provides both structured
367
+ * access (`frame`, `exec`) and flat access (`buffer`, `state`, etc.)
368
+ * for backward compatibility.
369
+ *
370
+ * @template TState The type of the state used during parsing.
371
+ * @param frame Parser-local frame data.
372
+ * @param exec Shared execution context.
373
+ * @returns A {@link ParserContext} instance.
374
+ * @since 1.0.0
375
+ */
376
+ declare function createParserContext<TState>(frame: ParseFrame<TState>, exec: ExecutionContext): ParserContext<TState>;
287
377
  /**
288
378
  * Represents a suggestion for command-line completion or guidance.
289
379
  * @since 0.6.0
@@ -648,4 +738,4 @@ declare function getDocPage(parser: Parser<"sync", unknown, unknown>, argsOrOpti
648
738
  declare function getDocPage(parser: Parser<"async", unknown, unknown>, argsOrOptions?: readonly string[] | ParseOptions, options?: ParseOptions): Promise<DocPage | undefined>;
649
739
  declare function getDocPage<M extends Mode>(parser: Parser<M, unknown, unknown>, argsOrOptions?: readonly string[] | ParseOptions, options?: ParseOptions): ModeValue<M, DocPage | undefined>;
650
740
  //#endregion
651
- export { ArgumentErrorOptions, ArgumentOptions, CombineModes, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, GroupOptions, InferMode, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OptionState, OrErrorOptions, OrOptions, type ParseOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
741
+ export { ArgumentErrorOptions, ArgumentOptions, CombineModes, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, DuplicateOptionError, ExecutionContext, ExecutionPhase, FlagErrorOptions, FlagOptions, GroupOptions, InferMode, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OptionState, OrErrorOptions, OrOptions, ParseFrame, type ParseOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, createParserContext, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
package/dist/parser.js CHANGED
@@ -8,6 +8,28 @@ import { WithDefaultError, map, multiple, nonEmpty, optional, withDefault } from
8
8
  import { argument, command, constant, fail, flag, option, passThrough } from "./primitives.js";
9
9
 
10
10
  //#region src/parser.ts
11
+ /**
12
+ * Creates a {@link ParserContext} from a {@link ParseFrame} and an
13
+ * {@link ExecutionContext}. The returned object provides both structured
14
+ * access (`frame`, `exec`) and flat access (`buffer`, `state`, etc.)
15
+ * for backward compatibility.
16
+ *
17
+ * @template TState The type of the state used during parsing.
18
+ * @param frame Parser-local frame data.
19
+ * @param exec Shared execution context.
20
+ * @returns A {@link ParserContext} instance.
21
+ * @since 1.0.0
22
+ */
23
+ function createParserContext(frame, exec) {
24
+ return {
25
+ exec,
26
+ buffer: frame.buffer,
27
+ state: frame.state,
28
+ optionsTerminated: frame.optionsTerminated,
29
+ usage: exec.usage,
30
+ dependencyRegistry: exec.dependencyRegistry
31
+ };
32
+ }
11
33
  function injectAnnotationsIntoState(state, options) {
12
34
  const annotations = options?.annotations;
13
35
  if (annotations == null) return state;
@@ -38,12 +60,16 @@ function injectAnnotationsIntoState(state, options) {
38
60
  function parseSync(parser, args, options) {
39
61
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
40
62
  const shouldUnwrapAnnotatedValue = options?.annotations != null || isInjectedAnnotationWrapper(parser.initialState);
41
- let context = {
63
+ const exec = {
64
+ usage: parser.usage,
65
+ phase: "parse",
66
+ path: []
67
+ };
68
+ let context = createParserContext({
42
69
  buffer: args,
43
- optionsTerminated: false,
44
70
  state: initialState,
45
- usage: parser.usage
46
- };
71
+ optionsTerminated: false
72
+ }, exec);
47
73
  do {
48
74
  const result = parser.parse(context);
49
75
  if (!result.success) return {
@@ -57,7 +83,11 @@ function parseSync(parser, args, options) {
57
83
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
58
84
  };
59
85
  } while (context.buffer.length > 0);
60
- const endResult = parser.complete(context.state);
86
+ const completeExec = {
87
+ ...exec,
88
+ phase: "complete"
89
+ };
90
+ const endResult = parser.complete(context.state, completeExec);
61
91
  return endResult.success ? {
62
92
  success: true,
63
93
  value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -97,12 +127,16 @@ function isBufferUnchanged(previous, current) {
97
127
  async function parseAsync(parser, args, options) {
98
128
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
99
129
  const shouldUnwrapAnnotatedValue = options?.annotations != null || isInjectedAnnotationWrapper(parser.initialState);
100
- let context = {
130
+ const exec = {
131
+ usage: parser.usage,
132
+ phase: "parse",
133
+ path: []
134
+ };
135
+ let context = createParserContext({
101
136
  buffer: args,
102
- optionsTerminated: false,
103
137
  state: initialState,
104
- usage: parser.usage
105
- };
138
+ optionsTerminated: false
139
+ }, exec);
106
140
  do {
107
141
  const result = await parser.parse(context);
108
142
  if (!result.success) return {
@@ -116,7 +150,11 @@ async function parseAsync(parser, args, options) {
116
150
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
117
151
  };
118
152
  } while (context.buffer.length > 0);
119
- const endResult = await parser.complete(context.state);
153
+ const completeExec = {
154
+ ...exec,
155
+ phase: "complete"
156
+ };
157
+ const endResult = await parser.complete(context.state, completeExec);
120
158
  return endResult.success ? {
121
159
  success: true,
122
160
  value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -192,12 +230,15 @@ function suggestSync(parser, args, options) {
192
230
  const allButLast = args.slice(0, -1);
193
231
  const prefix = args[args.length - 1];
194
232
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
195
- let context = {
233
+ let context = createParserContext({
196
234
  buffer: allButLast,
197
- optionsTerminated: false,
198
235
  state: initialState,
199
- usage: parser.usage
200
- };
236
+ optionsTerminated: false
237
+ }, {
238
+ usage: parser.usage,
239
+ phase: "suggest",
240
+ path: []
241
+ });
201
242
  while (context.buffer.length > 0) {
202
243
  const result = parser.parse(context);
203
244
  if (!result.success) return Array.from(parser.suggest(context, prefix));
@@ -231,12 +272,15 @@ async function suggestAsync(parser, args, options) {
231
272
  const allButLast = args.slice(0, -1);
232
273
  const prefix = args[args.length - 1];
233
274
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
234
- let context = {
275
+ let context = createParserContext({
235
276
  buffer: allButLast,
236
- optionsTerminated: false,
237
277
  state: initialState,
238
- usage: parser.usage
239
- };
278
+ optionsTerminated: false
279
+ }, {
280
+ usage: parser.usage,
281
+ phase: "suggest",
282
+ path: []
283
+ });
240
284
  while (context.buffer.length > 0) {
241
285
  const result = await parser.parse(context);
242
286
  if (!result.success) {
@@ -486,4 +530,4 @@ function buildDocPage(parser, context, args) {
486
530
  }
487
531
 
488
532
  //#endregion
489
- export { DuplicateOptionError, WithDefaultError, argument, command, concat, conditional, constant, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
533
+ export { DuplicateOptionError, WithDefaultError, argument, command, concat, conditional, constant, createParserContext, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
@@ -53,7 +53,7 @@ function constant(value) {
53
53
  consumed: []
54
54
  };
55
55
  },
56
- complete(state) {
56
+ complete(state, _exec) {
57
57
  return {
58
58
  success: true,
59
59
  value: state
@@ -110,7 +110,7 @@ function fail() {
110
110
  error: require_message.message`No value provided.`
111
111
  };
112
112
  },
113
- complete(_state) {
113
+ complete(_state, _exec) {
114
114
  return {
115
115
  success: false,
116
116
  error: require_message.message`No value provided.`
@@ -500,7 +500,7 @@ function option(...args) {
500
500
  error: require_suggestion.createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
501
501
  };
502
502
  },
503
- complete(state) {
503
+ complete(state, _exec) {
504
504
  if (state == null) return valueParser == null ? {
505
505
  success: true,
506
506
  value: false
@@ -742,7 +742,7 @@ function flag(...args) {
742
742
  error: require_suggestion.createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
743
743
  };
744
744
  },
745
- complete(state) {
745
+ complete(state, _exec) {
746
746
  if (state == null) return {
747
747
  success: false,
748
748
  error: options.errors?.missing ? typeof options.errors.missing === "function" ? options.errors.missing(optionNames$1) : options.errors.missing : require_message.message`Required flag ${require_message.optionNames(optionNames$1)} is missing.`
@@ -881,7 +881,7 @@ function argument(valueParser, options = {}) {
881
881
  consumed: context.buffer.slice(0, i + 1)
882
882
  };
883
883
  },
884
- complete(state) {
884
+ complete(state, _exec) {
885
885
  if (state == null) return {
886
886
  success: false,
887
887
  error: options.errors?.endOfInput ?? require_message.message`Expected a ${require_message.metavar(valueParser.metavar)}, but too few arguments.`
@@ -1104,7 +1104,7 @@ function command(name, parser, options = {}) {
1104
1104
  error: options.errors?.invalidState ?? require_message.message`Invalid command state.`
1105
1105
  };
1106
1106
  },
1107
- complete(state) {
1107
+ complete(state, exec) {
1108
1108
  if (typeof state === "undefined") return {
1109
1109
  success: false,
1110
1110
  error: options.errors?.notFound ?? require_message.message`Command ${require_message.optionName(name)} was not matched.`
@@ -1117,13 +1117,13 @@ function command(name, parser, options = {}) {
1117
1117
  state: parser.initialState
1118
1118
  });
1119
1119
  if (isAsync) return parseResultOrPromise.then((parseResult$1) => {
1120
- if (parseResult$1.success) return parser.complete(parseResult$1.next.state);
1121
- return parser.complete(parser.initialState);
1120
+ if (parseResult$1.success) return parser.complete(parseResult$1.next.state, exec);
1121
+ return parser.complete(parser.initialState, exec);
1122
1122
  });
1123
1123
  const parseResult = parseResultOrPromise;
1124
- if (parseResult.success) return parser.complete(parseResult.next.state);
1125
- return parser.complete(parser.initialState);
1126
- } else if (state[0] === "parsing") return parser.complete(state[1]);
1124
+ if (parseResult.success) return parser.complete(parseResult.next.state, exec);
1125
+ return parser.complete(parser.initialState, exec);
1126
+ } else if (state[0] === "parsing") return parser.complete(state[1], exec);
1127
1127
  return {
1128
1128
  success: false,
1129
1129
  error: options.errors?.invalidState ?? require_message.message`Invalid command state during completion.`
@@ -1324,7 +1324,7 @@ function passThrough(options = {}) {
1324
1324
  error: require_message.message`Unknown passThrough format: ${format}.`
1325
1325
  };
1326
1326
  },
1327
- complete(state) {
1327
+ complete(state, _exec) {
1328
1328
  if (require_annotations.getAnnotations(state) == null) return {
1329
1329
  success: true,
1330
1330
  value: state
@@ -53,7 +53,7 @@ function constant(value) {
53
53
  consumed: []
54
54
  };
55
55
  },
56
- complete(state) {
56
+ complete(state, _exec) {
57
57
  return {
58
58
  success: true,
59
59
  value: state
@@ -110,7 +110,7 @@ function fail() {
110
110
  error: message`No value provided.`
111
111
  };
112
112
  },
113
- complete(_state) {
113
+ complete(_state, _exec) {
114
114
  return {
115
115
  success: false,
116
116
  error: message`No value provided.`
@@ -500,7 +500,7 @@ function option(...args) {
500
500
  error: createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
501
501
  };
502
502
  },
503
- complete(state) {
503
+ complete(state, _exec) {
504
504
  if (state == null) return valueParser == null ? {
505
505
  success: true,
506
506
  value: false
@@ -742,7 +742,7 @@ function flag(...args) {
742
742
  error: createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
743
743
  };
744
744
  },
745
- complete(state) {
745
+ complete(state, _exec) {
746
746
  if (state == null) return {
747
747
  success: false,
748
748
  error: options.errors?.missing ? typeof options.errors.missing === "function" ? options.errors.missing(optionNames$1) : options.errors.missing : message`Required flag ${optionNames(optionNames$1)} is missing.`
@@ -881,7 +881,7 @@ function argument(valueParser, options = {}) {
881
881
  consumed: context.buffer.slice(0, i + 1)
882
882
  };
883
883
  },
884
- complete(state) {
884
+ complete(state, _exec) {
885
885
  if (state == null) return {
886
886
  success: false,
887
887
  error: options.errors?.endOfInput ?? message`Expected a ${metavar(valueParser.metavar)}, but too few arguments.`
@@ -1104,7 +1104,7 @@ function command(name, parser, options = {}) {
1104
1104
  error: options.errors?.invalidState ?? message`Invalid command state.`
1105
1105
  };
1106
1106
  },
1107
- complete(state) {
1107
+ complete(state, exec) {
1108
1108
  if (typeof state === "undefined") return {
1109
1109
  success: false,
1110
1110
  error: options.errors?.notFound ?? message`Command ${optionName(name)} was not matched.`
@@ -1117,13 +1117,13 @@ function command(name, parser, options = {}) {
1117
1117
  state: parser.initialState
1118
1118
  });
1119
1119
  if (isAsync) return parseResultOrPromise.then((parseResult$1) => {
1120
- if (parseResult$1.success) return parser.complete(parseResult$1.next.state);
1121
- return parser.complete(parser.initialState);
1120
+ if (parseResult$1.success) return parser.complete(parseResult$1.next.state, exec);
1121
+ return parser.complete(parser.initialState, exec);
1122
1122
  });
1123
1123
  const parseResult = parseResultOrPromise;
1124
- if (parseResult.success) return parser.complete(parseResult.next.state);
1125
- return parser.complete(parser.initialState);
1126
- } else if (state[0] === "parsing") return parser.complete(state[1]);
1124
+ if (parseResult.success) return parser.complete(parseResult.next.state, exec);
1125
+ return parser.complete(parser.initialState, exec);
1126
+ } else if (state[0] === "parsing") return parser.complete(state[1], exec);
1127
1127
  return {
1128
1128
  success: false,
1129
1129
  error: options.errors?.invalidState ?? message`Invalid command state during completion.`
@@ -1324,7 +1324,7 @@ function passThrough(options = {}) {
1324
1324
  error: message`Unknown passThrough format: ${format}.`
1325
1325
  };
1326
1326
  },
1327
- complete(state) {
1327
+ complete(state, _exec) {
1328
1328
  if (getAnnotations(state) == null) return {
1329
1329
  success: true,
1330
1330
  value: state
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1580+2d932679",
3
+ "version": "1.0.0-dev.1583+82b74b4b",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",