@optique/core 1.4.0-dev.2899 → 1.4.0-dev.2935

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.
@@ -9,6 +9,7 @@ const require_internal_dependency = require('./internal/dependency.cjs');
9
9
  const require_dependency_runtime = require('./dependency-runtime.cjs');
10
10
  const require_internal_parser = require('./internal/parser.cjs');
11
11
  const require_annotation_state = require('./annotation-state.cjs');
12
+ const require_internal_doc_metadata = require('./internal/doc-metadata.cjs');
12
13
  const require_empty_input = require('./internal/empty-input.cjs');
13
14
  const require_execution_context = require('./execution-context.cjs');
14
15
  const require_usage_internals = require('./usage-internals.cjs');
@@ -2012,7 +2013,10 @@ function or(...args) {
2012
2013
  defineExclusiveSchedulingNodes(singleResult, parsers);
2013
2014
  require_internal_parser.defineInheritedAnnotationParser(singleResult);
2014
2015
  require_passthrough.defineReachableChildren(singleResult, parsers, (state) => selectExclusiveChildren(parsers, state, true));
2015
- return require_modifiers.fluent(require_empty_input.withEmptyInputFacts(require_short_option.scopeParser(singleResult, require_short_option.combinedOptionScope(parsers, parsers.map((_, index) => index))), emptyInputFacts));
2016
+ return require_internal_doc_metadata.captureDocParser(require_modifiers.fluent(require_empty_input.withEmptyInputFacts(require_short_option.scopeParser(singleResult, require_short_option.combinedOptionScope(parsers, parsers.map((_, index) => index))), emptyInputFacts)), {
2017
+ kind: "or",
2018
+ children: parsers
2019
+ });
2016
2020
  }
2017
2021
  /**
2018
2022
  * @since 0.5.0
@@ -3320,10 +3324,15 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
3320
3324
  state: require_annotation_state.getWrappedChildParseState(state, getFieldState(field, parser), parser)
3321
3325
  }));
3322
3326
  });
3323
- return require_modifiers.fluent(require_empty_input.withEmptyInputFacts(require_short_option.scopeParser(objectParser, require_short_option.combinedOptionScope(parserPairs.map(([, p]) => p), parserPairs.map(([field]) => field)), void 0, true), combinedMode === "sync" ? require_empty_input.objectFacts(parserPairs.map(([, p]) => ({
3327
+ return require_internal_doc_metadata.captureDocParser(require_modifiers.fluent(require_empty_input.withEmptyInputFacts(require_short_option.scopeParser(objectParser, require_short_option.combinedOptionScope(parserPairs.map(([, p]) => p), parserPairs.map(([field]) => field)), void 0, true), combinedMode === "sync" ? require_empty_input.objectFacts(parserPairs.map(([, p]) => ({
3324
3328
  facts: require_empty_input.getEmptyInputFacts(p),
3325
3329
  matchesTokens: p.leadingNames.size > 0 || p.acceptingAnyToken
3326
- })), parserPairs.map(([field]) => field)) : {}));
3330
+ })), parserPairs.map(([field]) => field)) : {})), {
3331
+ kind: "object",
3332
+ children: parserPairs.map((pair) => pair[1]),
3333
+ label,
3334
+ hidden: options.hidden
3335
+ });
3327
3336
  }
3328
3337
  function isParserLike(value) {
3329
3338
  return value != null && (typeof value === "object" || typeof value === "function") && "parse" in value && "$valueType" in value && "$stateType" in value;
@@ -4748,9 +4757,13 @@ function seq(...rawArgs) {
4748
4757
  enumerable: false
4749
4758
  });
4750
4759
  require_internal_parser.defineInheritedAnnotationParser(seqParser);
4751
- return require_modifiers.fluent(require_short_option.scopeParser(seqParser, seqScope.source, (context) => {
4760
+ return require_internal_doc_metadata.captureDocParser(require_modifiers.fluent(require_short_option.scopeParser(seqParser, seqScope.source, (context) => {
4752
4761
  seqScope.select(context, reachableSeqScope(context));
4753
- }, true));
4762
+ }, true)), {
4763
+ kind: "seq",
4764
+ children: parsers,
4765
+ label
4766
+ });
4754
4767
  }
4755
4768
  function merge(...args) {
4756
4769
  const label = typeof args[0] === "string" ? args[0] : void 0;
@@ -9,6 +9,7 @@ import { createDependencySourceState, dependencyId, isDependencySourceState, isP
9
9
  import { buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyRuntimeContext, effectfulSchedulingNodesKey, exclusiveSourceScopeKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, forkRuntimeForCommittedSubtree, getDefaultDependencySnapshot, guaranteedStaticSourceIdsKey, hasInactiveCompletion, isMatchedState, recordSourceScope, releaseUncollectedSourceValues, replaceCachedBarrierFailure, resolveDerivedSourceValues, resolveDerivedSourceValuesAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey } from "./dependency-runtime.js";
10
10
  import { defineInheritedAnnotationParser, defineParseLanes, getOwnParseLanes, getParserSuggestRuntimeNodes, unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
11
11
  import { annotationViewTargets, getWrappedChildParseState, getWrappedChildState, reconcileObjectChildState, unwrapAnnotationView } from "./annotation-state.js";
12
+ import { captureDocParser } from "./internal/doc-metadata.js";
12
13
  import { acceptsEmptyInput, getEmptyInputFacts, longestMatchFacts, objectFacts, orFacts, tupleFacts, withEmptyInputFacts } from "./internal/empty-input.js";
13
14
  import { mergeChildExec, withChildContext, withChildExecPath } from "./execution-context.js";
14
15
  import { collectLeadingCandidates } from "./usage-internals.js";
@@ -2012,7 +2013,10 @@ function or(...args) {
2012
2013
  defineExclusiveSchedulingNodes(singleResult, parsers);
2013
2014
  defineInheritedAnnotationParser(singleResult);
2014
2015
  defineReachableChildren(singleResult, parsers, (state) => selectExclusiveChildren(parsers, state, true));
2015
- return fluent(withEmptyInputFacts(scopeParser(singleResult, combinedOptionScope(parsers, parsers.map((_, index) => index))), emptyInputFacts));
2016
+ return captureDocParser(fluent(withEmptyInputFacts(scopeParser(singleResult, combinedOptionScope(parsers, parsers.map((_, index) => index))), emptyInputFacts)), {
2017
+ kind: "or",
2018
+ children: parsers
2019
+ });
2016
2020
  }
2017
2021
  /**
2018
2022
  * @since 0.5.0
@@ -3320,10 +3324,15 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
3320
3324
  state: getWrappedChildParseState(state, getFieldState(field, parser), parser)
3321
3325
  }));
3322
3326
  });
3323
- return fluent(withEmptyInputFacts(scopeParser(objectParser, combinedOptionScope(parserPairs.map(([, p]) => p), parserPairs.map(([field]) => field)), void 0, true), combinedMode === "sync" ? objectFacts(parserPairs.map(([, p]) => ({
3327
+ return captureDocParser(fluent(withEmptyInputFacts(scopeParser(objectParser, combinedOptionScope(parserPairs.map(([, p]) => p), parserPairs.map(([field]) => field)), void 0, true), combinedMode === "sync" ? objectFacts(parserPairs.map(([, p]) => ({
3324
3328
  facts: getEmptyInputFacts(p),
3325
3329
  matchesTokens: p.leadingNames.size > 0 || p.acceptingAnyToken
3326
- })), parserPairs.map(([field]) => field)) : {}));
3330
+ })), parserPairs.map(([field]) => field)) : {})), {
3331
+ kind: "object",
3332
+ children: parserPairs.map((pair) => pair[1]),
3333
+ label,
3334
+ hidden: options.hidden
3335
+ });
3327
3336
  }
3328
3337
  function isParserLike(value) {
3329
3338
  return value != null && (typeof value === "object" || typeof value === "function") && "parse" in value && "$valueType" in value && "$stateType" in value;
@@ -4748,9 +4757,13 @@ function seq(...rawArgs) {
4748
4757
  enumerable: false
4749
4758
  });
4750
4759
  defineInheritedAnnotationParser(seqParser);
4751
- return fluent(scopeParser(seqParser, seqScope.source, (context) => {
4760
+ return captureDocParser(fluent(scopeParser(seqParser, seqScope.source, (context) => {
4752
4761
  seqScope.select(context, reachableSeqScope(context));
4753
- }, true));
4762
+ }, true)), {
4763
+ kind: "seq",
4764
+ children: parsers,
4765
+ label
4766
+ });
4754
4767
  }
4755
4768
  function merge(...args) {
4756
4769
  const label = typeof args[0] === "string" ? args[0] : void 0;
@@ -0,0 +1,480 @@
1
+ const require_validate = require('./validate.cjs');
2
+ const require_usage = require('./usage.cjs');
3
+ const require_message = require('./message.cjs');
4
+ const require_doc = require('./doc.cjs');
5
+ const require_internal_doc_metadata = require('./internal/doc-metadata.cjs');
6
+
7
+ //#region src/doc-pages.ts
8
+ /** Strict collection failed with explicit partial result data. @since 1.4.0 */
9
+ var DocPageCollectionError = class extends TypeError {
10
+ /** Collected pages and every unresolved visible scope. */
11
+ result;
12
+ /**
13
+ * Constructs an error from a partial collection.
14
+ * @param result Collected pages and gaps.
15
+ */
16
+ constructor(result) {
17
+ super("Documentation collection has unresolved scopes.");
18
+ this.name = "DocPageCollectionError";
19
+ this.result = result;
20
+ }
21
+ };
22
+ const emptyProjection = {
23
+ usage: [],
24
+ fragments: []
25
+ };
26
+ function key(path) {
27
+ if (path.length > 0) require_validate.validateCommandNames(path, "Documentation path");
28
+ if (path.some((token) => !token.isWellFormed())) throw new TypeError("Documentation paths must contain well-formed Unicode tokens.");
29
+ return JSON.stringify(path);
30
+ }
31
+ function prefix(parent, child) {
32
+ return parent.length <= child.length && parent.every((token, index) => token === child[index]);
33
+ }
34
+ function hasPath(parser, path) {
35
+ const node = require_internal_doc_metadata.getDocNode(parser);
36
+ if (node == null || path.length === 0 || require_usage.isDocHidden(node.hidden) || ambiguousSequence(node)) return false;
37
+ if (node.kind === "command") return !require_usage.isDocHidden(node.hidden) && node.name === path[0] && (path.length === 1 || hasPath(node.children[0], path.slice(1)));
38
+ return node.children?.some((child) => hasPath(child, path)) ?? false;
39
+ }
40
+ function hasCommands(parser) {
41
+ const node = require_internal_doc_metadata.getDocNode(parser);
42
+ if (node == null || require_usage.isDocHidden(node.hidden)) return false;
43
+ return node.kind === "command" || node.children?.some(hasCommands) === true;
44
+ }
45
+ function ambiguousSequence(node) {
46
+ return node.kind === "seq" && (node.children?.filter(hasCommands).length ?? 0) > 1;
47
+ }
48
+ function hideUsage(usage, pathTerms) {
49
+ return usage.map((term) => {
50
+ if (pathTerms.includes(term)) return term;
51
+ if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") return {
52
+ ...term,
53
+ terms: hideUsage(term.terms, pathTerms)
54
+ };
55
+ if (term.type === "exclusive") return {
56
+ ...term,
57
+ terms: term.terms.map((terms) => hideUsage(terms, pathTerms))
58
+ };
59
+ if (term.type === "option" || term.type === "argument" || term.type === "command" || term.type === "passthrough") return {
60
+ ...term,
61
+ hidden: require_usage.mergeHidden(term.hidden, "usage")
62
+ };
63
+ return term;
64
+ });
65
+ }
66
+ function leafTerm(usage) {
67
+ for (const term of usage) if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") {
68
+ const found = leafTerm(term.terms);
69
+ if (found != null) return found;
70
+ } else return term;
71
+ return void 0;
72
+ }
73
+ function staticDefault(parser, value) {
74
+ const node = require_internal_doc_metadata.getDocNode(parser);
75
+ if (node == null) return void 0;
76
+ if (node.kind !== "leaf") {
77
+ const preservesValue = node.kind === "command" || node.kind === "or" || node.kind === "optional" || node.kind === "default";
78
+ return preservesValue && node.children?.length === 1 ? staticDefault(node.children[0], value) : void 0;
79
+ }
80
+ if (node.boolean && typeof value === "boolean") return [{
81
+ type: "text",
82
+ text: String(value)
83
+ }];
84
+ const metadata = node.valueParser == null ? void 0 : require_internal_doc_metadata.getDocValue(node.valueParser);
85
+ if (metadata == null || metadata.canFormatDefault === false || typeof value !== metadata.kind) return void 0;
86
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") return void 0;
87
+ return [{
88
+ type: "text",
89
+ text: metadata.preserveNegativeZero && Object.is(value, -0) ? "-0" : String(value)
90
+ }];
91
+ }
92
+ function fragmentsEntries(fragments) {
93
+ return fragments.flatMap((fragment) => fragment.type === "entry" ? [fragment] : fragment.entries);
94
+ }
95
+ function defaultFragments(fragments, value) {
96
+ return fragments.map((fragment) => fragment.type === "entry" ? {
97
+ ...fragment,
98
+ default: value
99
+ } : {
100
+ ...fragment,
101
+ entries: fragment.entries.map((entry) => ({
102
+ ...entry,
103
+ default: value
104
+ }))
105
+ });
106
+ }
107
+ function sections(fragments) {
108
+ const result = [];
109
+ const named = /* @__PURE__ */ new Map();
110
+ for (const fragment of require_doc.deduplicateDocFragments(fragments)) {
111
+ const title = fragment.type === "entry" ? void 0 : fragment.title;
112
+ let entries = named.get(title);
113
+ if (entries == null) {
114
+ entries = [];
115
+ named.set(title, entries);
116
+ result.push({
117
+ title,
118
+ entries
119
+ });
120
+ }
121
+ entries.push(...(fragment.type === "entry" ? [fragment] : fragment.entries).map(require_doc.cloneDocEntry));
122
+ }
123
+ return result;
124
+ }
125
+ function publicUsage(usage) {
126
+ return usage.map((term) => {
127
+ if (term.type === "command") return {
128
+ type: "command",
129
+ name: term.name,
130
+ ...term.hidden != null && { hidden: term.hidden }
131
+ };
132
+ if (term.type === "exclusive") return {
133
+ ...term,
134
+ terms: term.terms.map(publicUsage)
135
+ };
136
+ if (term.type === "optional" || term.type === "multiple" || term.type === "sequence") return {
137
+ ...term,
138
+ terms: publicUsage(term.terms)
139
+ };
140
+ return require_usage.cloneUsageTerm(term);
141
+ });
142
+ }
143
+ function docHidden(entry, entries) {
144
+ for (let length = 1; length <= entry.path.length; length++) if (require_usage.isDocHidden(entries.get(key(entry.path.slice(0, length)))?.hidden)) return true;
145
+ return false;
146
+ }
147
+ /**
148
+ * Collects documentation through core-owned constructor metadata, without
149
+ * parsing, defaults, normalization, validators, sources, suggestions, handlers,
150
+ * or custom parser/value-parser documentation/formatting methods.
151
+ *
152
+ * Supported constructors are command/or/object/seq, option/flag/argument,
153
+ * optional/multiple/withDefault; safe value metadata comes from string/integer/
154
+ * choice. Unregistered wrappers are opaque. Custom value parsers retain only
155
+ * enclosing primitive metadata captured at construction. Function defaults are
156
+ * omitted unless withDefault supplies a documentation message. Unsupported
157
+ * static default formatting is reported instead of evaluated.
158
+ *
159
+ * Imports and definition/page construction remain application-owned and are
160
+ * not sandboxed. Supply inert definitions separately from executable startup.
161
+ *
162
+ * @param parser The parser definition to document.
163
+ * @param options Strictness and author-supplied page/subtree replacements.
164
+ * @returns Canonical pages and explicit unresolved scopes for the user parser.
165
+ * @throws {DocPageCollectionError} If strict mode finds unresolved scopes.
166
+ * @throws {TypeError} If supplied paths/aliases are invalid or ambiguous,
167
+ * or supplied canonical paths are duplicated.
168
+ * @since 1.4.0
169
+ */
170
+ function collectDocPages(parser, options = {}) {
171
+ const discovered = new Map([["[]", { path: [] }]]);
172
+ const reports = /* @__PURE__ */ new Map();
173
+ const report = (path, scope, reason) => {
174
+ const gap = {
175
+ path: [...path],
176
+ scope,
177
+ reason
178
+ };
179
+ reports.set(JSON.stringify(gap), gap);
180
+ };
181
+ function enumerate(input, path) {
182
+ const node = require_internal_doc_metadata.getDocNode(input);
183
+ if (node == null) {
184
+ report(path, "subtree", "unregistered-parser");
185
+ return;
186
+ }
187
+ if (require_usage.isDocHidden(node.hidden)) return;
188
+ if (ambiguousSequence(node)) {
189
+ report(path, "subtree", "ambiguous-command-path");
190
+ return;
191
+ }
192
+ if (node.kind === "command") {
193
+ if (require_usage.isDocHidden(node.hidden)) return;
194
+ const childPath = [...path, node.name];
195
+ const pathKey = key(childPath);
196
+ if (discovered.has(pathKey)) report(childPath, "subtree", "ambiguous-command-path");
197
+ else discovered.set(pathKey, {
198
+ path: childPath,
199
+ node
200
+ });
201
+ enumerate(node.children[0], childPath);
202
+ } else for (const child of node.children ?? []) enumerate(child, path);
203
+ }
204
+ enumerate(parser, []);
205
+ function project(input, target, path, pagePath) {
206
+ const node = require_internal_doc_metadata.getDocNode(input);
207
+ if (node == null) {
208
+ report(path, "subtree", "unregistered-parser");
209
+ if (key(path) !== key(pagePath)) report(pagePath, "page", "unregistered-parser");
210
+ return {
211
+ ...emptyProjection,
212
+ unresolvedUsage: true
213
+ };
214
+ }
215
+ if (require_usage.isDocHidden(node.hidden)) return {
216
+ ...emptyProjection,
217
+ usage: node.usage
218
+ };
219
+ if (ambiguousSequence(node)) {
220
+ report(path, "subtree", "ambiguous-command-path");
221
+ if (key(path) !== key(pagePath)) report(pagePath, "page", "ambiguous-command-path");
222
+ return {
223
+ ...emptyProjection,
224
+ unresolvedUsage: true
225
+ };
226
+ }
227
+ if (node.kind === "leaf") {
228
+ const term = leafTerm(node.usage);
229
+ if (term == null || "hidden" in term && require_usage.isDocHidden(term.hidden)) return {
230
+ ...emptyProjection,
231
+ usage: node.usage
232
+ };
233
+ const metadata = node.valueParser == null ? void 0 : require_internal_doc_metadata.getDocValue(node.valueParser);
234
+ if (node.valueParser != null && metadata == null) report(pagePath, "page", "value-metadata-unavailable");
235
+ const choices = metadata?.choices == null ? void 0 : [{
236
+ type: "text",
237
+ text: metadata.choices.map((value) => metadata.preserveNegativeZero && Object.is(value, -0) ? "-0" : String(value)).join(", ")
238
+ }];
239
+ return {
240
+ usage: node.usage,
241
+ fragments: [{
242
+ type: "entry",
243
+ term,
244
+ description: node.description,
245
+ choices
246
+ }]
247
+ };
248
+ }
249
+ if (node.kind === "command") {
250
+ if (require_usage.isDocHidden(node.hidden)) return {
251
+ ...emptyProjection,
252
+ usage: node.usage
253
+ };
254
+ if (target.length === 0) return {
255
+ usage: node.usage,
256
+ fragments: [{
257
+ type: "entry",
258
+ term: {
259
+ type: "command",
260
+ name: node.name,
261
+ aliases: node.aliases,
262
+ hidden: node.hidden
263
+ },
264
+ description: node.brief ?? node.description,
265
+ showAliases: node.showAliases
266
+ }]
267
+ };
268
+ if (target[0] !== node.name) return emptyProjection;
269
+ const selected = project(node.children[0], target.slice(1), [...path, node.name], pagePath);
270
+ let usage$1 = selected.usage;
271
+ let unresolvedUsage = selected.unresolvedUsage;
272
+ if (target.length === 1 && node.usageLine != null) if (typeof node.usageLine === "function") {
273
+ report([...path, node.name], "page", "functional-usage");
274
+ unresolvedUsage = true;
275
+ } else {
276
+ usage$1 = node.usageLine;
277
+ unresolvedUsage = false;
278
+ }
279
+ const pathTerm = {
280
+ type: "command",
281
+ name: node.name
282
+ };
283
+ return {
284
+ ...selected,
285
+ usage: [pathTerm, ...usage$1],
286
+ pathTerms: [pathTerm, ...selected.pathTerms ?? []],
287
+ unresolvedUsage,
288
+ brief: selected.brief ?? node.brief,
289
+ description: selected.description ?? node.description,
290
+ footer: selected.footer ?? node.footer,
291
+ showUsage: selected.showUsage ?? node.showUsage
292
+ };
293
+ }
294
+ if (node.kind === "optional" || node.kind === "multiple" || node.kind === "default") {
295
+ const inner = project(node.children[0], target, path, pagePath);
296
+ if (node.kind === "default") {
297
+ const usage$1 = [{
298
+ type: "optional",
299
+ terms: inner.usage
300
+ }];
301
+ if (node.defaultMessage != null) return {
302
+ ...inner,
303
+ usage: usage$1,
304
+ fragments: defaultFragments(inner.fragments, node.defaultMessage)
305
+ };
306
+ if (typeof node.defaultValue !== "function" && fragmentsEntries(inner.fragments).length > 0) {
307
+ const formatted = staticDefault(node.children[0], node.defaultValue);
308
+ if (formatted != null) return {
309
+ ...inner,
310
+ usage: usage$1,
311
+ fragments: defaultFragments(inner.fragments, formatted)
312
+ };
313
+ report(pagePath, "page", "static-default-format-unavailable");
314
+ }
315
+ return {
316
+ ...inner,
317
+ usage: usage$1
318
+ };
319
+ }
320
+ return {
321
+ ...inner,
322
+ usage: [node.kind === "optional" ? {
323
+ type: "optional",
324
+ terms: inner.usage
325
+ } : {
326
+ ...node.usage.find((term) => term.type === "multiple"),
327
+ type: "multiple",
328
+ terms: inner.usage,
329
+ min: node.min ?? 0
330
+ }]
331
+ };
332
+ }
333
+ const children = node.children ?? [];
334
+ if (node.kind === "or") {
335
+ if (target.length > 0) {
336
+ const selected = children.filter((child) => hasPath(child, target));
337
+ if (selected.length !== 1) {
338
+ report(path, "subtree", "ambiguous-command-path");
339
+ if (key(path) !== key(pagePath)) report(pagePath, "page", "ambiguous-command-path");
340
+ return {
341
+ ...emptyProjection,
342
+ unresolvedUsage: true
343
+ };
344
+ }
345
+ return project(selected[0], target, path, pagePath);
346
+ }
347
+ const projected$1 = children.map((child) => project(child, [], path, pagePath));
348
+ return {
349
+ usage: node.usage,
350
+ fragments: projected$1.flatMap((child) => child.fragments),
351
+ unresolvedUsage: projected$1.some((child) => child.unresolvedUsage)
352
+ };
353
+ }
354
+ const projected = children.map((child) => project(child, hasPath(child, target) ? target : [], path, pagePath));
355
+ const fragments = projected.flatMap((child) => child.fragments);
356
+ const pathTerms = projected.flatMap((child) => child.pathTerms ?? []);
357
+ const usage = node.kind === "seq" ? [{
358
+ type: "sequence",
359
+ terms: projected.flatMap((child) => child.usage)
360
+ }] : projected.flatMap((child) => child.usage);
361
+ return {
362
+ usage: node.hidden === "usage" ? hideUsage(usage, pathTerms) : usage,
363
+ pathTerms,
364
+ fragments: node.label == null ? fragments : [...fragments.filter((fragment) => fragment.type === "section" && fragment.title != null), {
365
+ type: "section",
366
+ title: node.label,
367
+ entries: fragmentsEntries(fragments.filter((fragment) => fragment.type === "entry" || fragment.title == null))
368
+ }],
369
+ unresolvedUsage: projected.some((child) => child.unresolvedUsage),
370
+ brief: projected.find((child) => child.brief != null)?.brief,
371
+ description: projected.find((child) => child.description != null)?.description,
372
+ footer: projected.find((child) => child.footer != null)?.footer,
373
+ showUsage: projected.find((child) => child.showUsage != null)?.showUsage
374
+ };
375
+ }
376
+ const entries = /* @__PURE__ */ new Map();
377
+ for (const { path, node } of discovered.values()) {
378
+ const page = project(parser, path, [], path);
379
+ entries.set(key(path), {
380
+ path,
381
+ aliases: node?.aliases?.filter((alias) => !node.hiddenAliases?.includes(alias)),
382
+ hiddenAliases: node?.hiddenAliases?.slice(),
383
+ hidden: node?.hidden,
384
+ showAliases: node?.showAliases,
385
+ source: "automatic",
386
+ page: {
387
+ usage: page.unresolvedUsage ? void 0 : publicUsage(page.usage),
388
+ sections: sections(page.fragments),
389
+ brief: page.brief == null ? void 0 : require_message.cloneMessage(page.brief),
390
+ description: page.description == null ? void 0 : require_message.cloneMessage(page.description),
391
+ footer: page.footer == null ? void 0 : require_message.cloneMessage(page.footer),
392
+ showUsage: page.showUsage
393
+ }
394
+ });
395
+ }
396
+ const supplied = /* @__PURE__ */ new Map();
397
+ for (const entry of options.entries ?? []) {
398
+ const entryKey = key(entry.path);
399
+ if (supplied.has(entryKey)) throw new TypeError(`Duplicate supplied command path: ${entryKey}`);
400
+ if (entry.aliases?.length || entry.hiddenAliases?.length) require_validate.validateCommandNames([...entry.aliases ?? [], ...entry.hiddenAliases ?? []], "Documentation alias");
401
+ if (entry.path.length === 0 && (entry.aliases?.length || entry.hiddenAliases?.length || entry.hidden)) throw new TypeError("The root page cannot have aliases or hidden visibility.");
402
+ if (entry.coverage != null && entry.coverage !== "page" && entry.coverage !== "subtree") throw new TypeError("Supplied coverage must be page or subtree.");
403
+ supplied.set(entryKey, entry);
404
+ entries.set(entryKey, {
405
+ ...entries.get(entryKey),
406
+ ...entry,
407
+ path: [...entry.path],
408
+ source: "supplied",
409
+ coverage: entry.coverage ?? "page"
410
+ });
411
+ }
412
+ const visible = [...entries.values()].filter((entry) => !docHidden(entry, entries));
413
+ const unresolved = [...reports.values()].filter((gap) => {
414
+ const enclosing = entries.get(key(gap.path));
415
+ if (enclosing != null && docHidden(enclosing, entries)) return false;
416
+ if (gap.reason === "functional-usage") return supplied.get(key(gap.path))?.page.usage == null;
417
+ if (!supplied.has(key(gap.path))) return true;
418
+ if (gap.scope === "page") return false;
419
+ return ![...supplied.values()].some((entry) => entry.coverage === "subtree" && prefix(entry.path, gap.path));
420
+ });
421
+ const missing = /* @__PURE__ */ new Map();
422
+ const spellings = /* @__PURE__ */ new Map();
423
+ for (const entry of entries.values()) {
424
+ if (entry.path.length === 0) continue;
425
+ for (const spelling of [
426
+ entry.path.at(-1),
427
+ ...entry.aliases ?? [],
428
+ ...entry.hiddenAliases ?? []
429
+ ]) {
430
+ const spellingKey = key([...entry.path.slice(0, -1), spelling]);
431
+ const previous = spellings.get(spellingKey);
432
+ if (previous != null && previous !== key(entry.path)) throw new TypeError(`Ambiguous command spelling: ${spellingKey}`);
433
+ spellings.set(spellingKey, key(entry.path));
434
+ }
435
+ }
436
+ for (const entry of visible) {
437
+ for (let length = 0; length < entry.path.length; length++) {
438
+ const parent = entry.path.slice(0, length);
439
+ if (!entries.has(key(parent))) missing.set(key(parent), parent);
440
+ }
441
+ for (const item of entry.page.sections.flatMap((section) => section.entries)) {
442
+ if (item.term.type !== "command" || require_usage.isDocHidden(item.term.hidden)) continue;
443
+ const child = [...entry.path, item.term.name];
444
+ if (!entries.has(key(child))) missing.set(key(child), child);
445
+ }
446
+ }
447
+ for (const path of missing.values()) unresolved.push({
448
+ path,
449
+ scope: "page",
450
+ reason: "missing-command-page"
451
+ });
452
+ const filtered = visible.map((entry) => {
453
+ const pageSections = entry.page.sections.map((section) => ({
454
+ ...section,
455
+ entries: section.entries.filter((item) => {
456
+ if (item.term.type !== "command") return true;
457
+ const target = entries.get(key([...entry.path, item.term.name]));
458
+ return target == null || !docHidden(target, entries);
459
+ })
460
+ }));
461
+ return pageSections.some((section, index) => section.entries.length !== entry.page.sections[index].entries.length) ? {
462
+ ...entry,
463
+ page: {
464
+ ...entry.page,
465
+ sections: pageSections
466
+ }
467
+ } : entry;
468
+ });
469
+ const result = {
470
+ entries: filtered,
471
+ unresolved,
472
+ scope: "parser"
473
+ };
474
+ if ((options.strict ?? true) && unresolved.length > 0) throw new DocPageCollectionError(result);
475
+ return result;
476
+ }
477
+
478
+ //#endregion
479
+ exports.DocPageCollectionError = DocPageCollectionError;
480
+ exports.collectDocPages = collectDocPages;
@@ -0,0 +1,98 @@
1
+ import { HiddenVisibility } from "./usage.cjs";
2
+ import { DocPage } from "./doc.cjs";
3
+ import { Mode, Parser } from "./internal/parser.cjs";
4
+
5
+ //#region src/doc-pages.d.ts
6
+
7
+ /** A command page with an explicit canonical path. @since 1.4.0 */
8
+ interface CommandDocPage {
9
+ /** Canonical tokens, excluding program name and positional values. */
10
+ readonly path: readonly string[];
11
+ /** Constructed documentation, with command tokens already in usage. */
12
+ readonly page: DocPage;
13
+ /** Visible aliases of the final command token. */
14
+ readonly aliases?: readonly string[];
15
+ /** Accepted alternative names reserved for validation, never display. */
16
+ readonly hiddenAliases?: readonly string[];
17
+ /** Documentation visibility; hidden ancestors suppress descendants. */
18
+ readonly hidden?: HiddenVisibility;
19
+ /** Default alias display for this command. */
20
+ readonly showAliases?: boolean;
21
+ }
22
+ /** An author-supplied replacement or additional page. @since 1.4.0 */
23
+ interface SuppliedCommandDocPage extends CommandDocPage {
24
+ /**
25
+ * A page assertion covers this page only. A subtree assertion declares that
26
+ * the author supplied every visible descendant too; opaque descendants cannot
27
+ * be verified independently. Defaults to page.
28
+ */
29
+ readonly coverage?: "page" | "subtree";
30
+ }
31
+ /** A collected page and the origin of its documentation. @since 1.4.0 */
32
+ interface CollectedCommandDocPage extends CommandDocPage {
33
+ /** Whether metadata or the author provided this page. */
34
+ readonly source: "automatic" | "supplied";
35
+ /** Coverage asserted by the author, for supplied pages only. */
36
+ readonly coverage?: "page" | "subtree";
37
+ }
38
+ /** A visible scope whose documentation could not be collected. @since 1.4.0 */
39
+ interface DocPageCollectionGap {
40
+ /** Affected page or visible enclosing opaque scope; never positional values. */
41
+ readonly path: readonly string[];
42
+ /** Whether missing information affects this page or an opaque subtree. */
43
+ readonly scope: "page" | "subtree";
44
+ /** A stable diagnostic for the unsupported operation. */
45
+ readonly reason: "unregistered-parser" | "value-metadata-unavailable" | "static-default-format-unavailable" | "functional-usage" | "ambiguous-command-path" | "missing-command-page";
46
+ }
47
+ /** Result data for the supplied parser, without runner augmentation. @since 1.4.0 */
48
+ interface DocPageCollectionResult {
49
+ /** Root and supported commands in definition order, followed by additions. */
50
+ readonly entries: readonly CollectedCommandDocPage[];
51
+ /** Uncovered visible scopes; an empty list does not inspect author imports. */
52
+ readonly unresolved: readonly DocPageCollectionGap[];
53
+ /** Runner-added help/version/completion are outside this parser scope. */
54
+ readonly scope: "parser";
55
+ }
56
+ /** Options for metadata-only collection. @since 1.4.0 */
57
+ interface DocPageCollectionOptions {
58
+ /** Throw for uncovered scopes. Defaults to true. */
59
+ readonly strict?: boolean;
60
+ /** Replacements win at their canonical paths; duplicate supplied paths fail. */
61
+ readonly entries?: readonly SuppliedCommandDocPage[];
62
+ }
63
+ /** Strict collection failed with explicit partial result data. @since 1.4.0 */
64
+ declare class DocPageCollectionError extends TypeError {
65
+ /** Collected pages and every unresolved visible scope. */
66
+ readonly result: DocPageCollectionResult;
67
+ /**
68
+ * Constructs an error from a partial collection.
69
+ * @param result Collected pages and gaps.
70
+ */
71
+ constructor(result: DocPageCollectionResult);
72
+ }
73
+ /**
74
+ * Collects documentation through core-owned constructor metadata, without
75
+ * parsing, defaults, normalization, validators, sources, suggestions, handlers,
76
+ * or custom parser/value-parser documentation/formatting methods.
77
+ *
78
+ * Supported constructors are command/or/object/seq, option/flag/argument,
79
+ * optional/multiple/withDefault; safe value metadata comes from string/integer/
80
+ * choice. Unregistered wrappers are opaque. Custom value parsers retain only
81
+ * enclosing primitive metadata captured at construction. Function defaults are
82
+ * omitted unless withDefault supplies a documentation message. Unsupported
83
+ * static default formatting is reported instead of evaluated.
84
+ *
85
+ * Imports and definition/page construction remain application-owned and are
86
+ * not sandboxed. Supply inert definitions separately from executable startup.
87
+ *
88
+ * @param parser The parser definition to document.
89
+ * @param options Strictness and author-supplied page/subtree replacements.
90
+ * @returns Canonical pages and explicit unresolved scopes for the user parser.
91
+ * @throws {DocPageCollectionError} If strict mode finds unresolved scopes.
92
+ * @throws {TypeError} If supplied paths/aliases are invalid or ambiguous,
93
+ * or supplied canonical paths are duplicated.
94
+ * @since 1.4.0
95
+ */
96
+ declare function collectDocPages<M extends Mode, T, S>(parser: Parser<M, T, S>, options?: DocPageCollectionOptions): DocPageCollectionResult;
97
+ //#endregion
98
+ export { CollectedCommandDocPage, CommandDocPage, DocPageCollectionError, DocPageCollectionGap, DocPageCollectionOptions, DocPageCollectionResult, SuppliedCommandDocPage, collectDocPages };