@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,44 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/displaywidth.ts
|
|
3
|
+
const ansiRegex = /\x1B(?:\[[0-9;:]*[@-~]|\][^\x1B\x07]*(?:\x1B\\|\x07))/g;
|
|
4
|
+
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
5
|
+
/**
|
|
6
|
+
* Computes the terminal display width of a string, accounting for
|
|
7
|
+
* East Asian wide characters, combining marks, emoji, and ANSI escapes.
|
|
8
|
+
*
|
|
9
|
+
* @param text The string to measure.
|
|
10
|
+
* @returns The number of terminal columns the string occupies.
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
function getDisplayWidth(text) {
|
|
14
|
+
const stripped = text.replace(ansiRegex, "");
|
|
15
|
+
let width = 0;
|
|
16
|
+
for (const { segment } of segmenter.segment(stripped)) width += graphemeWidth(segment);
|
|
17
|
+
return width;
|
|
18
|
+
}
|
|
19
|
+
const zeroWidthRegex = /^[\p{Cf}\p{Mn}\p{Me}]+$/u;
|
|
20
|
+
const emojiPresentationRegex = /\p{Emoji_Presentation}/u;
|
|
21
|
+
const emojiWithVS16Regex = /\p{Emoji}\uFE0F/u;
|
|
22
|
+
const keycapEmojiRegex = /^[#*0-9]\uFE0F?\u20E3$/u;
|
|
23
|
+
const textPresentationRegex = /\uFE0E/u;
|
|
24
|
+
const regionalIndicatorRegex = /[\u{1F1E6}-\u{1F1FF}]/u;
|
|
25
|
+
function graphemeWidth(grapheme) {
|
|
26
|
+
const cp = grapheme.codePointAt(0);
|
|
27
|
+
if (cp == null) return 0;
|
|
28
|
+
if (cp === 9) return 1;
|
|
29
|
+
if (cp < 32 || cp >= 127 && cp < 160) return 0;
|
|
30
|
+
if (zeroWidthRegex.test(grapheme)) return 0;
|
|
31
|
+
if (keycapEmojiRegex.test(grapheme) || !textPresentationRegex.test(grapheme) && (emojiPresentationRegex.test(grapheme) || emojiWithVS16Regex.test(grapheme) || regionalIndicatorRegex.test(grapheme))) return 2;
|
|
32
|
+
let width = isEastAsianWide(cp) ? 2 : 1;
|
|
33
|
+
for (let i = cp > 65535 ? 2 : 1; i < grapheme.length; i++) {
|
|
34
|
+
const c = grapheme.charCodeAt(i);
|
|
35
|
+
if (c === 65438 || c === 65439) width += 1;
|
|
36
|
+
}
|
|
37
|
+
return width;
|
|
38
|
+
}
|
|
39
|
+
function isEastAsianWide(cp) {
|
|
40
|
+
return cp >= 4352 && cp <= 4447 || cp >= 9001 && cp <= 9002 || cp >= 9776 && cp <= 9783 || cp >= 9866 && cp <= 9871 || cp >= 11904 && cp <= 12255 || cp >= 12272 && cp <= 12350 || cp >= 12353 && cp <= 40959 || cp >= 40960 && cp <= 42191 || cp >= 43360 && cp <= 43391 || cp >= 44032 && cp <= 55215 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 94176 && cp <= 101759 || cp >= 110576 && cp <= 111359 || cp >= 127488 && cp <= 127490 || cp >= 127504 && cp <= 127547 || cp >= 127552 && cp <= 127560 || cp >= 127568 && cp <= 127569 || cp >= 127584 && cp <= 127589 || cp >= 131072 && cp <= 196605 || cp >= 196608 && cp <= 262141;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
exports.getDisplayWidth = getDisplayWidth;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/displaywidth.ts
|
|
2
|
+
const ansiRegex = /\x1B(?:\[[0-9;:]*[@-~]|\][^\x1B\x07]*(?:\x1B\\|\x07))/g;
|
|
3
|
+
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
4
|
+
/**
|
|
5
|
+
* Computes the terminal display width of a string, accounting for
|
|
6
|
+
* East Asian wide characters, combining marks, emoji, and ANSI escapes.
|
|
7
|
+
*
|
|
8
|
+
* @param text The string to measure.
|
|
9
|
+
* @returns The number of terminal columns the string occupies.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
function getDisplayWidth(text) {
|
|
13
|
+
const stripped = text.replace(ansiRegex, "");
|
|
14
|
+
let width = 0;
|
|
15
|
+
for (const { segment } of segmenter.segment(stripped)) width += graphemeWidth(segment);
|
|
16
|
+
return width;
|
|
17
|
+
}
|
|
18
|
+
const zeroWidthRegex = /^[\p{Cf}\p{Mn}\p{Me}]+$/u;
|
|
19
|
+
const emojiPresentationRegex = /\p{Emoji_Presentation}/u;
|
|
20
|
+
const emojiWithVS16Regex = /\p{Emoji}\uFE0F/u;
|
|
21
|
+
const keycapEmojiRegex = /^[#*0-9]\uFE0F?\u20E3$/u;
|
|
22
|
+
const textPresentationRegex = /\uFE0E/u;
|
|
23
|
+
const regionalIndicatorRegex = /[\u{1F1E6}-\u{1F1FF}]/u;
|
|
24
|
+
function graphemeWidth(grapheme) {
|
|
25
|
+
const cp = grapheme.codePointAt(0);
|
|
26
|
+
if (cp == null) return 0;
|
|
27
|
+
if (cp === 9) return 1;
|
|
28
|
+
if (cp < 32 || cp >= 127 && cp < 160) return 0;
|
|
29
|
+
if (zeroWidthRegex.test(grapheme)) return 0;
|
|
30
|
+
if (keycapEmojiRegex.test(grapheme) || !textPresentationRegex.test(grapheme) && (emojiPresentationRegex.test(grapheme) || emojiWithVS16Regex.test(grapheme) || regionalIndicatorRegex.test(grapheme))) return 2;
|
|
31
|
+
let width = isEastAsianWide(cp) ? 2 : 1;
|
|
32
|
+
for (let i = cp > 65535 ? 2 : 1; i < grapheme.length; i++) {
|
|
33
|
+
const c = grapheme.charCodeAt(i);
|
|
34
|
+
if (c === 65438 || c === 65439) width += 1;
|
|
35
|
+
}
|
|
36
|
+
return width;
|
|
37
|
+
}
|
|
38
|
+
function isEastAsianWide(cp) {
|
|
39
|
+
return cp >= 4352 && cp <= 4447 || cp >= 9001 && cp <= 9002 || cp >= 9776 && cp <= 9783 || cp >= 9866 && cp <= 9871 || cp >= 11904 && cp <= 12255 || cp >= 12272 && cp <= 12350 || cp >= 12353 && cp <= 40959 || cp >= 40960 && cp <= 42191 || cp >= 43360 && cp <= 43391 || cp >= 44032 && cp <= 55215 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 94176 && cp <= 101759 || cp >= 110576 && cp <= 111359 || cp >= 127488 && cp <= 127490 || cp >= 127504 && cp <= 127547 || cp >= 127552 && cp <= 127560 || cp >= 127568 && cp <= 127569 || cp >= 127584 && cp <= 127589 || cp >= 131072 && cp <= 196605 || cp >= 196608 && cp <= 262141;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
export { getDisplayWidth };
|
package/dist/doc.cjs
CHANGED
|
@@ -1,8 +1,145 @@
|
|
|
1
|
+
const require_displaywidth = require('./displaywidth.cjs');
|
|
1
2
|
const require_message = require('./message.cjs');
|
|
3
|
+
const require_validate = require('./validate.cjs');
|
|
2
4
|
const require_usage = require('./usage.cjs');
|
|
3
5
|
|
|
4
6
|
//#region src/doc.ts
|
|
5
7
|
/**
|
|
8
|
+
* Returns whether a doc entry's term is hidden from documentation.
|
|
9
|
+
* Only term types with a `hidden` field (argument, option, command,
|
|
10
|
+
* passthrough) are checked; other types always return `false`.
|
|
11
|
+
*
|
|
12
|
+
* @param entry The doc entry to check.
|
|
13
|
+
* @returns `true` if the entry should be hidden from documentation.
|
|
14
|
+
* @since 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
function isDocEntryHidden(entry) {
|
|
17
|
+
const term = entry.term;
|
|
18
|
+
if (term.type === "argument" || term.type === "option" || term.type === "command" || term.type === "passthrough") return require_usage.isDocHidden(term.hidden);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function getDocEntryKey(entry) {
|
|
22
|
+
const term = entry.term;
|
|
23
|
+
switch (term.type) {
|
|
24
|
+
case "command": return `command:${term.name}`;
|
|
25
|
+
case "option": return `option:${[...term.names].sort().join(",")}:${term.metavar ?? ""}`;
|
|
26
|
+
case "argument": return `argument:${term.metavar}`;
|
|
27
|
+
default: return JSON.stringify(term);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Removes duplicate {@link DocEntry} values that share the same surface
|
|
32
|
+
* syntax (same term type and identifying names). Doc-hidden entries are
|
|
33
|
+
* filtered out first so they cannot influence the ordering of visible
|
|
34
|
+
* entries. Among the remaining visible entries, the first occurrence is
|
|
35
|
+
* kept and later duplicates are discarded.
|
|
36
|
+
*
|
|
37
|
+
* Positional argument entries are never deduplicated because they are
|
|
38
|
+
* distinguished by position, not by metavar, and {@link DocEntry} does
|
|
39
|
+
* not carry position information.
|
|
40
|
+
*
|
|
41
|
+
* @param entries The entries to deduplicate.
|
|
42
|
+
* @returns A new array with hidden entries removed and duplicates
|
|
43
|
+
* collapsed, preserving insertion order of visible entries.
|
|
44
|
+
* @since 1.0.0
|
|
45
|
+
*/
|
|
46
|
+
function deduplicateDocEntries(entries) {
|
|
47
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48
|
+
const result = [];
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (isDocEntryHidden(entry)) continue;
|
|
51
|
+
if (entry.term.type === "argument") {
|
|
52
|
+
result.push(entry);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const key = getDocEntryKey(entry);
|
|
56
|
+
if (!seen.has(key)) {
|
|
57
|
+
seen.add(key);
|
|
58
|
+
result.push(entry);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Removes duplicate entries from a list of {@link DocFragment} values.
|
|
65
|
+
* Entry-type fragments are deduplicated by their surface syntax key.
|
|
66
|
+
* Section-type fragments have their entries deduplicated internally.
|
|
67
|
+
*
|
|
68
|
+
* @param fragments The fragments to deduplicate.
|
|
69
|
+
* @returns A new array with duplicate entries removed.
|
|
70
|
+
* @since 1.0.0
|
|
71
|
+
*/
|
|
72
|
+
function deduplicateDocFragments(fragments) {
|
|
73
|
+
const untitledSeen = /* @__PURE__ */ new Set();
|
|
74
|
+
const titledSectionMap = /* @__PURE__ */ new Map();
|
|
75
|
+
const titledSectionPositioned = /* @__PURE__ */ new Set();
|
|
76
|
+
const slots = [];
|
|
77
|
+
for (const fragment of fragments) if (fragment.type === "entry") {
|
|
78
|
+
if (isDocEntryHidden(fragment)) continue;
|
|
79
|
+
if (fragment.term.type === "argument") slots.push(fragment);
|
|
80
|
+
else {
|
|
81
|
+
const key = getDocEntryKey(fragment);
|
|
82
|
+
if (!untitledSeen.has(key)) {
|
|
83
|
+
untitledSeen.add(key);
|
|
84
|
+
slots.push(fragment);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else if (fragment.title == null) {
|
|
88
|
+
const dedupedEntries = [];
|
|
89
|
+
for (const entry of fragment.entries) {
|
|
90
|
+
if (isDocEntryHidden(entry)) continue;
|
|
91
|
+
if (entry.term.type === "argument") {
|
|
92
|
+
dedupedEntries.push(entry);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const key = getDocEntryKey(entry);
|
|
96
|
+
if (!untitledSeen.has(key)) {
|
|
97
|
+
untitledSeen.add(key);
|
|
98
|
+
dedupedEntries.push(entry);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (dedupedEntries.length > 0) slots.push({
|
|
102
|
+
...fragment,
|
|
103
|
+
type: "section",
|
|
104
|
+
entries: dedupedEntries
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
if (!titledSectionMap.has(fragment.title)) titledSectionMap.set(fragment.title, []);
|
|
108
|
+
if (!titledSectionPositioned.has(fragment.title) && fragment.entries.some((e) => !isDocEntryHidden(e))) {
|
|
109
|
+
titledSectionPositioned.add(fragment.title);
|
|
110
|
+
slots.push(fragment.title);
|
|
111
|
+
}
|
|
112
|
+
titledSectionMap.get(fragment.title).push(...fragment.entries);
|
|
113
|
+
}
|
|
114
|
+
const result = [];
|
|
115
|
+
for (const slot of slots) if (typeof slot === "string") {
|
|
116
|
+
const entries = deduplicateDocEntries(titledSectionMap.get(slot));
|
|
117
|
+
if (entries.length > 0) result.push({
|
|
118
|
+
type: "section",
|
|
119
|
+
title: slot,
|
|
120
|
+
entries
|
|
121
|
+
});
|
|
122
|
+
} else result.push(slot);
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Creates a deep clone of a {@link DocEntry}. The `term` is cloned via
|
|
127
|
+
* {@link cloneUsageTerm}, and `description`, `default`, and `choices`
|
|
128
|
+
* messages are cloned via {@link cloneMessage}.
|
|
129
|
+
*
|
|
130
|
+
* @param entry The documentation entry to clone.
|
|
131
|
+
* @returns A structurally equal but referentially distinct copy.
|
|
132
|
+
* @since 1.0.0
|
|
133
|
+
*/
|
|
134
|
+
function cloneDocEntry(entry) {
|
|
135
|
+
return {
|
|
136
|
+
term: require_usage.cloneUsageTerm(entry.term),
|
|
137
|
+
...entry.description != null && { description: require_message.cloneMessage(entry.description) },
|
|
138
|
+
...entry.default != null && { default: require_message.cloneMessage(entry.default) },
|
|
139
|
+
...entry.choices != null && { choices: require_message.cloneMessage(entry.choices) }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
6
143
|
* Classifies a {@link DocSection} by its content type for use in the
|
|
7
144
|
* default smart sort.
|
|
8
145
|
*
|
|
@@ -47,6 +184,13 @@ function defaultSectionOrder(a, b) {
|
|
|
47
184
|
* @param page The documentation page to format
|
|
48
185
|
* @param options Formatting options to customize the output
|
|
49
186
|
* @returns A formatted string representation of the documentation page
|
|
187
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
188
|
+
* whitespace-only, or contains control characters, if any non-empty
|
|
189
|
+
* section's title is not a string, is empty, whitespace-only, or contains
|
|
190
|
+
* control characters, or if `maxWidth` is not a finite integer.
|
|
191
|
+
* @throws {RangeError} If any entry needs a description column and `maxWidth`
|
|
192
|
+
* is too small to fit the minimum layout (less than `termIndent + 4`), or if
|
|
193
|
+
* `showChoices.maxItems` is less than `1`.
|
|
50
194
|
*
|
|
51
195
|
* @example
|
|
52
196
|
* ```typescript
|
|
@@ -67,10 +211,70 @@ function defaultSectionOrder(a, b) {
|
|
|
67
211
|
* ```
|
|
68
212
|
*/
|
|
69
213
|
function formatDocPage(programName, page, options = {}) {
|
|
214
|
+
require_validate.validateProgramName(programName);
|
|
70
215
|
const termIndent = options.termIndent ?? 2;
|
|
71
216
|
const termWidth = options.termWidth ?? 26;
|
|
217
|
+
if (options.maxWidth != null && (!Number.isFinite(options.maxWidth) || !Number.isInteger(options.maxWidth))) throw new TypeError(`maxWidth must be a finite integer, got ${options.maxWidth}.`);
|
|
218
|
+
const filteredSections = page.sections.map((s) => ({
|
|
219
|
+
...s,
|
|
220
|
+
entries: s.entries.filter((e) => {
|
|
221
|
+
const rendered = require_usage.formatUsageTerm(e.term, { context: "doc" });
|
|
222
|
+
return rendered.trim() !== "";
|
|
223
|
+
})
|
|
224
|
+
}));
|
|
225
|
+
page = {
|
|
226
|
+
...page,
|
|
227
|
+
sections: filteredSections
|
|
228
|
+
};
|
|
229
|
+
if (typeof options.showChoices === "object" && options.showChoices.maxItems != null) {
|
|
230
|
+
const maxItems = options.showChoices.maxItems;
|
|
231
|
+
if (maxItems < 1) throw new RangeError(`showChoices.maxItems must be at least 1, but got ${maxItems}.`);
|
|
232
|
+
}
|
|
233
|
+
const hasContent = (msg) => Array.isArray(msg) && msg.length > 0;
|
|
234
|
+
if (options.maxWidth != null) {
|
|
235
|
+
const hasEntries = page.sections.some((s) => s.entries.length > 0);
|
|
236
|
+
const needsDescColumn = hasEntries && page.sections.some((s) => s.entries.some((e) => hasContent(e.description) || options.showDefault && hasContent(e.default) || options.showChoices && hasContent(e.choices)));
|
|
237
|
+
let minDescWidth = 1;
|
|
238
|
+
if (needsDescColumn) {
|
|
239
|
+
if (options.showDefault && page.sections.some((s) => s.entries.some((e) => hasContent(e.default)))) {
|
|
240
|
+
const prefix = typeof options.showDefault === "object" ? options.showDefault.prefix ?? " [" : " [";
|
|
241
|
+
minDescWidth = Math.max(minDescWidth, require_displaywidth.getDisplayWidth(prefix));
|
|
242
|
+
}
|
|
243
|
+
if (options.showChoices && page.sections.some((s) => s.entries.some((e) => hasContent(e.choices)))) {
|
|
244
|
+
const prefix = typeof options.showChoices === "object" ? options.showChoices.prefix ?? " (" : " (";
|
|
245
|
+
const label = typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ";
|
|
246
|
+
minDescWidth = Math.max(minDescWidth, require_displaywidth.getDisplayWidth(prefix) + require_displaywidth.getDisplayWidth(label));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const splitEntryMin = termIndent + 2 + Math.max(2, 2 * minDescWidth - 1);
|
|
250
|
+
const fixedEntryMin = termIndent + 2 + termWidth + minDescWidth;
|
|
251
|
+
const entryMin = needsDescColumn ? Math.min(splitEntryMin, fixedEntryMin) : hasEntries ? termIndent + 1 : 1;
|
|
252
|
+
const programNameWidth = require_displaywidth.getDisplayWidth(programName);
|
|
253
|
+
const usageMin = page.usage != null ? 7 + Math.max(programNameWidth, Math.min(maxVisibleAtomicWidth(page.usage), programNameWidth + 7)) : 1;
|
|
254
|
+
let sectionMin = 1;
|
|
255
|
+
if (hasContent(page.examples)) sectionMin = Math.max(sectionMin, 9);
|
|
256
|
+
if (hasContent(page.author)) sectionMin = Math.max(sectionMin, 7);
|
|
257
|
+
if (hasContent(page.bugs)) sectionMin = Math.max(sectionMin, 5);
|
|
258
|
+
const minWidth = Math.max(entryMin, usageMin, sectionMin);
|
|
259
|
+
if (options.maxWidth < minWidth) throw new RangeError(`maxWidth must be at least ${minWidth}, got ${options.maxWidth}.`);
|
|
260
|
+
if (needsDescColumn && minDescWidth > 1) {
|
|
261
|
+
const avail = options.maxWidth - termIndent - 2;
|
|
262
|
+
const effTW = avail >= termWidth + 1 ? termWidth : Math.max(1, Math.floor(avail / 2));
|
|
263
|
+
const descW = avail - effTW;
|
|
264
|
+
if (descW < minDescWidth) {
|
|
265
|
+
const needed = termIndent + termWidth + 2 + minDescWidth;
|
|
266
|
+
throw new RangeError(`maxWidth must be at least ${needed}, got ${options.maxWidth}.`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
let effectiveTermWidth;
|
|
271
|
+
if (options.maxWidth == null) effectiveTermWidth = termWidth;
|
|
272
|
+
else {
|
|
273
|
+
const availableForColumns = options.maxWidth - termIndent - 2;
|
|
274
|
+
effectiveTermWidth = availableForColumns >= termWidth + 1 ? termWidth : Math.max(1, Math.floor(availableForColumns / 2));
|
|
275
|
+
}
|
|
72
276
|
let output = "";
|
|
73
|
-
if (page.brief
|
|
277
|
+
if (hasContent(page.brief)) {
|
|
74
278
|
output += require_message.formatMessage(page.brief, {
|
|
75
279
|
colors: options.colors,
|
|
76
280
|
maxWidth: options.maxWidth,
|
|
@@ -88,7 +292,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
88
292
|
}), 7);
|
|
89
293
|
output += "\n";
|
|
90
294
|
}
|
|
91
|
-
if (page.description
|
|
295
|
+
if (hasContent(page.description)) {
|
|
92
296
|
output += "\n";
|
|
93
297
|
output += require_message.formatMessage(page.description, {
|
|
94
298
|
colors: options.colors,
|
|
@@ -110,6 +314,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
110
314
|
if (section.entries.length < 1) continue;
|
|
111
315
|
output += "\n";
|
|
112
316
|
if (section.title != null) {
|
|
317
|
+
require_validate.validateLabel(section.title);
|
|
113
318
|
const sectionLabel = options.colors ? `\x1b[1;2m${section.title}:\x1b[0m\n` : `${section.title}:\n`;
|
|
114
319
|
output += sectionLabel;
|
|
115
320
|
}
|
|
@@ -117,11 +322,12 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
117
322
|
const term = require_usage.formatUsageTerm(entry.term, {
|
|
118
323
|
colors: options.colors,
|
|
119
324
|
optionsSeparator: ", ",
|
|
325
|
+
context: "doc",
|
|
120
326
|
maxWidth: options.maxWidth == null ? void 0 : options.maxWidth - termIndent
|
|
121
327
|
});
|
|
122
|
-
const descColumnWidth = options.maxWidth == null ? void 0 : options.maxWidth - termIndent -
|
|
328
|
+
const descColumnWidth = options.maxWidth == null ? void 0 : options.maxWidth - termIndent - effectiveTermWidth - 2;
|
|
123
329
|
const termVisibleWidth = lastLineVisibleLength(term);
|
|
124
|
-
const extraTermOffset = descColumnWidth != null ? Math.max(0, termVisibleWidth -
|
|
330
|
+
const extraTermOffset = descColumnWidth != null ? Math.max(0, termVisibleWidth - effectiveTermWidth) : 0;
|
|
125
331
|
const currentExtraOffset = () => description.includes("\n") ? 0 : extraTermOffset;
|
|
126
332
|
const descFormatOptions = {
|
|
127
333
|
colors: options.colors,
|
|
@@ -130,22 +336,24 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
130
336
|
startWidth: extraTermOffset > 0 ? extraTermOffset : void 0
|
|
131
337
|
};
|
|
132
338
|
let description = entry.description == null ? "" : require_message.formatMessage(entry.description, descFormatOptions);
|
|
133
|
-
if (options.showDefault && entry.default
|
|
339
|
+
if (options.showDefault && hasContent(entry.default)) {
|
|
134
340
|
const prefix = typeof options.showDefault === "object" ? options.showDefault.prefix ?? " [" : " [";
|
|
135
341
|
const suffix = typeof options.showDefault === "object" ? options.showDefault.suffix ?? "]" : "]";
|
|
342
|
+
const prefixWidth = require_displaywidth.getDisplayWidth(prefix);
|
|
343
|
+
const suffixWidth = require_displaywidth.getDisplayWidth(suffix);
|
|
136
344
|
let defaultStartWidth;
|
|
137
345
|
if (descColumnWidth != null) {
|
|
138
346
|
const lastW = lastLineVisibleLength(description);
|
|
139
347
|
const effectiveLastW = lastW + currentExtraOffset();
|
|
140
|
-
if (effectiveLastW +
|
|
348
|
+
if (effectiveLastW + prefixWidth >= descColumnWidth) {
|
|
141
349
|
description += "\n";
|
|
142
|
-
defaultStartWidth =
|
|
143
|
-
} else defaultStartWidth = effectiveLastW +
|
|
350
|
+
defaultStartWidth = prefixWidth;
|
|
351
|
+
} else defaultStartWidth = effectiveLastW + prefixWidth;
|
|
144
352
|
}
|
|
145
353
|
const defaultFormatOptions = {
|
|
146
354
|
colors: options.colors ? { resetSuffix: "\x1B[2m" } : false,
|
|
147
355
|
quotes: !options.colors,
|
|
148
|
-
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth -
|
|
356
|
+
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - suffixWidth,
|
|
149
357
|
startWidth: defaultStartWidth
|
|
150
358
|
};
|
|
151
359
|
const defaultContent = require_message.formatMessage(entry.default, defaultFormatOptions);
|
|
@@ -153,7 +361,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
153
361
|
const formattedDefault = options.colors ? `\x1b[2m${defaultText}\x1b[0m` : defaultText;
|
|
154
362
|
description += formattedDefault;
|
|
155
363
|
}
|
|
156
|
-
if (options.showChoices && entry.choices
|
|
364
|
+
if (options.showChoices && hasContent(entry.choices)) {
|
|
157
365
|
const prefix = typeof options.showChoices === "object" ? options.showChoices.prefix ?? " (" : " (";
|
|
158
366
|
const suffix = typeof options.showChoices === "object" ? options.showChoices.suffix ?? ")" : ")";
|
|
159
367
|
const label = typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ";
|
|
@@ -174,11 +382,14 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
174
382
|
}
|
|
175
383
|
if (truncated) truncatedTerms = [...terms.slice(0, cutIndex), require_message.text(", ...")];
|
|
176
384
|
}
|
|
385
|
+
const choicesPrefixWidth = require_displaywidth.getDisplayWidth(prefix);
|
|
386
|
+
const choicesSuffixWidth = require_displaywidth.getDisplayWidth(suffix);
|
|
387
|
+
const choicesLabelWidth = require_displaywidth.getDisplayWidth(label);
|
|
177
388
|
let choicesStartWidth;
|
|
178
389
|
if (descColumnWidth != null) {
|
|
179
390
|
const lastW = lastLineVisibleLength(description);
|
|
180
391
|
const effectiveLastW = lastW + currentExtraOffset();
|
|
181
|
-
const prefixLabelLen =
|
|
392
|
+
const prefixLabelLen = choicesPrefixWidth + choicesLabelWidth;
|
|
182
393
|
if (effectiveLastW + prefixLabelLen >= descColumnWidth) {
|
|
183
394
|
description += "\n";
|
|
184
395
|
choicesStartWidth = prefixLabelLen;
|
|
@@ -187,7 +398,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
187
398
|
const choicesFormatOptions = {
|
|
188
399
|
colors: options.colors ? { resetSuffix: "\x1B[2m" } : false,
|
|
189
400
|
quotes: false,
|
|
190
|
-
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth -
|
|
401
|
+
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - choicesSuffixWidth,
|
|
191
402
|
startWidth: choicesStartWidth
|
|
192
403
|
};
|
|
193
404
|
const choicesDisplay = require_message.formatMessage(truncatedTerms, choicesFormatOptions);
|
|
@@ -195,10 +406,10 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
195
406
|
const formattedChoices = options.colors ? `\x1b[2m${choicesText}\x1b[0m` : choicesText;
|
|
196
407
|
description += formattedChoices;
|
|
197
408
|
}
|
|
198
|
-
output += `${" ".repeat(termIndent)}${ansiAwareRightPad(term,
|
|
409
|
+
output += `${" ".repeat(termIndent)}${ansiAwareRightPad(term, effectiveTermWidth)}${description === "" ? "" : ` ${indentLines(description, termIndent + effectiveTermWidth + 2)}`}\n`;
|
|
199
410
|
}
|
|
200
411
|
}
|
|
201
|
-
if (page.examples
|
|
412
|
+
if (hasContent(page.examples)) {
|
|
202
413
|
output += "\n";
|
|
203
414
|
const examplesLabel = options.colors ? "\x1B[1;2mExamples:\x1B[0m\n" : "Examples:\n";
|
|
204
415
|
output += examplesLabel;
|
|
@@ -210,7 +421,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
210
421
|
output += " " + indentLines(examplesContent, 2);
|
|
211
422
|
output += "\n";
|
|
212
423
|
}
|
|
213
|
-
if (page.author
|
|
424
|
+
if (hasContent(page.author)) {
|
|
214
425
|
output += "\n";
|
|
215
426
|
const authorLabel = options.colors ? "\x1B[1;2mAuthor:\x1B[0m\n" : "Author:\n";
|
|
216
427
|
output += authorLabel;
|
|
@@ -222,7 +433,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
222
433
|
output += " " + indentLines(authorContent, 2);
|
|
223
434
|
output += "\n";
|
|
224
435
|
}
|
|
225
|
-
if (page.bugs
|
|
436
|
+
if (hasContent(page.bugs)) {
|
|
226
437
|
output += "\n";
|
|
227
438
|
const bugsLabel = options.colors ? "\x1B[1;2mBugs:\x1B[0m\n" : "Bugs:\n";
|
|
228
439
|
output += bugsLabel;
|
|
@@ -234,7 +445,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
234
445
|
output += " " + indentLines(bugsContent, 2);
|
|
235
446
|
output += "\n";
|
|
236
447
|
}
|
|
237
|
-
if (page.footer
|
|
448
|
+
if (hasContent(page.footer)) {
|
|
238
449
|
output += "\n";
|
|
239
450
|
output += require_message.formatMessage(page.footer, {
|
|
240
451
|
colors: options.colors,
|
|
@@ -247,17 +458,68 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
247
458
|
function indentLines(text$1, indent) {
|
|
248
459
|
return text$1.split("\n").join("\n" + " ".repeat(indent));
|
|
249
460
|
}
|
|
250
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Returns the width of the widest non-breakable segment among visible
|
|
463
|
+
* (non-usage-hidden) terms in a usage tree. Hidden terms are excluded
|
|
464
|
+
* because they are filtered out before rendering, so they do not
|
|
465
|
+
* contribute to the rendered width.
|
|
466
|
+
*/
|
|
467
|
+
function maxVisibleAtomicWidth(usage) {
|
|
468
|
+
let max = 0;
|
|
469
|
+
for (const term of usage) switch (term.type) {
|
|
470
|
+
case "argument":
|
|
471
|
+
if (!require_usage.isUsageHidden(term.hidden)) max = Math.max(max, require_displaywidth.getDisplayWidth(term.metavar));
|
|
472
|
+
break;
|
|
473
|
+
case "option":
|
|
474
|
+
if (!require_usage.isUsageHidden(term.hidden) && term.names.length > 0) {
|
|
475
|
+
for (const name of term.names) max = Math.max(max, require_displaywidth.getDisplayWidth(name));
|
|
476
|
+
if (term.metavar != null) max = Math.max(max, require_displaywidth.getDisplayWidth(term.metavar));
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
case "command":
|
|
480
|
+
if (!require_usage.isUsageHidden(term.hidden)) max = Math.max(max, require_displaywidth.getDisplayWidth(term.name));
|
|
481
|
+
break;
|
|
482
|
+
case "passthrough":
|
|
483
|
+
if (!require_usage.isUsageHidden(term.hidden)) max = Math.max(max, 5);
|
|
484
|
+
break;
|
|
485
|
+
case "optional":
|
|
486
|
+
max = Math.max(max, maxVisibleAtomicWidth(term.terms));
|
|
487
|
+
break;
|
|
488
|
+
case "multiple": {
|
|
489
|
+
const innerMax = maxVisibleAtomicWidth(term.terms);
|
|
490
|
+
if (innerMax > 0) max = Math.max(max, 3, innerMax);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
case "exclusive":
|
|
494
|
+
for (const branch of term.terms) {
|
|
495
|
+
const first = branch[0];
|
|
496
|
+
if (first?.type === "command" && require_usage.isUsageHidden(first.hidden)) continue;
|
|
497
|
+
max = Math.max(max, maxVisibleAtomicWidth(branch));
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
case "literal":
|
|
501
|
+
if (term.value !== "") max = Math.max(max, require_displaywidth.getDisplayWidth(term.value));
|
|
502
|
+
break;
|
|
503
|
+
case "ellipsis":
|
|
504
|
+
max = Math.max(max, 3);
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
return max;
|
|
508
|
+
}
|
|
251
509
|
function ansiAwareRightPad(text$1, length, char = " ") {
|
|
252
|
-
const
|
|
253
|
-
if (
|
|
254
|
-
return text$1 + char.repeat(length -
|
|
510
|
+
const visibleWidth = lastLineVisibleLength(text$1);
|
|
511
|
+
if (visibleWidth >= length) return text$1;
|
|
512
|
+
return text$1 + char.repeat(length - visibleWidth);
|
|
255
513
|
}
|
|
256
514
|
function lastLineVisibleLength(text$1) {
|
|
257
515
|
const lastNewline = text$1.lastIndexOf("\n");
|
|
258
516
|
const lastLine = lastNewline === -1 ? text$1 : text$1.slice(lastNewline + 1);
|
|
259
|
-
return
|
|
517
|
+
return require_displaywidth.getDisplayWidth(lastLine);
|
|
260
518
|
}
|
|
261
519
|
|
|
262
520
|
//#endregion
|
|
263
|
-
exports.
|
|
521
|
+
exports.cloneDocEntry = cloneDocEntry;
|
|
522
|
+
exports.deduplicateDocEntries = deduplicateDocEntries;
|
|
523
|
+
exports.deduplicateDocFragments = deduplicateDocFragments;
|
|
524
|
+
exports.formatDocPage = formatDocPage;
|
|
525
|
+
exports.isDocEntryHidden = isDocEntryHidden;
|
package/dist/doc.d.cts
CHANGED
|
@@ -103,6 +103,53 @@ interface DocFragments {
|
|
|
103
103
|
*/
|
|
104
104
|
readonly footer?: Message;
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Returns whether a doc entry's term is hidden from documentation.
|
|
108
|
+
* Only term types with a `hidden` field (argument, option, command,
|
|
109
|
+
* passthrough) are checked; other types always return `false`.
|
|
110
|
+
*
|
|
111
|
+
* @param entry The doc entry to check.
|
|
112
|
+
* @returns `true` if the entry should be hidden from documentation.
|
|
113
|
+
* @since 1.0.0
|
|
114
|
+
*/
|
|
115
|
+
declare function isDocEntryHidden(entry: DocEntry): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Removes duplicate {@link DocEntry} values that share the same surface
|
|
118
|
+
* syntax (same term type and identifying names). Doc-hidden entries are
|
|
119
|
+
* filtered out first so they cannot influence the ordering of visible
|
|
120
|
+
* entries. Among the remaining visible entries, the first occurrence is
|
|
121
|
+
* kept and later duplicates are discarded.
|
|
122
|
+
*
|
|
123
|
+
* Positional argument entries are never deduplicated because they are
|
|
124
|
+
* distinguished by position, not by metavar, and {@link DocEntry} does
|
|
125
|
+
* not carry position information.
|
|
126
|
+
*
|
|
127
|
+
* @param entries The entries to deduplicate.
|
|
128
|
+
* @returns A new array with hidden entries removed and duplicates
|
|
129
|
+
* collapsed, preserving insertion order of visible entries.
|
|
130
|
+
* @since 1.0.0
|
|
131
|
+
*/
|
|
132
|
+
declare function deduplicateDocEntries(entries: readonly DocEntry[]): DocEntry[];
|
|
133
|
+
/**
|
|
134
|
+
* Removes duplicate entries from a list of {@link DocFragment} values.
|
|
135
|
+
* Entry-type fragments are deduplicated by their surface syntax key.
|
|
136
|
+
* Section-type fragments have their entries deduplicated internally.
|
|
137
|
+
*
|
|
138
|
+
* @param fragments The fragments to deduplicate.
|
|
139
|
+
* @returns A new array with duplicate entries removed.
|
|
140
|
+
* @since 1.0.0
|
|
141
|
+
*/
|
|
142
|
+
declare function deduplicateDocFragments(fragments: readonly DocFragment[]): DocFragment[];
|
|
143
|
+
/**
|
|
144
|
+
* Creates a deep clone of a {@link DocEntry}. The `term` is cloned via
|
|
145
|
+
* {@link cloneUsageTerm}, and `description`, `default`, and `choices`
|
|
146
|
+
* messages are cloned via {@link cloneMessage}.
|
|
147
|
+
*
|
|
148
|
+
* @param entry The documentation entry to clone.
|
|
149
|
+
* @returns A structurally equal but referentially distinct copy.
|
|
150
|
+
* @since 1.0.0
|
|
151
|
+
*/
|
|
152
|
+
declare function cloneDocEntry(entry: DocEntry): DocEntry;
|
|
106
153
|
/**
|
|
107
154
|
* Configuration for customizing default value display formatting.
|
|
108
155
|
*
|
|
@@ -148,9 +195,10 @@ interface ShowChoicesOptions {
|
|
|
148
195
|
readonly label?: string;
|
|
149
196
|
/**
|
|
150
197
|
* Maximum number of choice values to display before truncating with
|
|
151
|
-
* `...`. Set to `Infinity` to show all choices.
|
|
198
|
+
* `...`. Must be at least `1`. Set to `Infinity` to show all choices.
|
|
152
199
|
*
|
|
153
200
|
* @default `8`
|
|
201
|
+
* @throws {RangeError} If the value is less than `1`.
|
|
154
202
|
*/
|
|
155
203
|
readonly maxItems?: number;
|
|
156
204
|
}
|
|
@@ -263,6 +311,13 @@ interface DocPageFormatOptions {
|
|
|
263
311
|
* @param page The documentation page to format
|
|
264
312
|
* @param options Formatting options to customize the output
|
|
265
313
|
* @returns A formatted string representation of the documentation page
|
|
314
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
315
|
+
* whitespace-only, or contains control characters, if any non-empty
|
|
316
|
+
* section's title is not a string, is empty, whitespace-only, or contains
|
|
317
|
+
* control characters, or if `maxWidth` is not a finite integer.
|
|
318
|
+
* @throws {RangeError} If any entry needs a description column and `maxWidth`
|
|
319
|
+
* is too small to fit the minimum layout (less than `termIndent + 4`), or if
|
|
320
|
+
* `showChoices.maxItems` is less than `1`.
|
|
266
321
|
*
|
|
267
322
|
* @example
|
|
268
323
|
* ```typescript
|
|
@@ -284,4 +339,4 @@ interface DocPageFormatOptions {
|
|
|
284
339
|
*/
|
|
285
340
|
declare function formatDocPage(programName: string, page: DocPage, options?: DocPageFormatOptions): string;
|
|
286
341
|
//#endregion
|
|
287
|
-
export { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowChoicesOptions, ShowDefaultOptions, formatDocPage };
|
|
342
|
+
export { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowChoicesOptions, ShowDefaultOptions, cloneDocEntry, deduplicateDocEntries, deduplicateDocFragments, formatDocPage, isDocEntryHidden };
|