@optique/core 1.0.0-dev.1467 → 1.0.0-dev.1484
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/doc.cjs +55 -5
- package/dist/doc.d.cts +4 -3
- package/dist/doc.d.ts +4 -3
- package/dist/doc.js +56 -6
- package/dist/facade.cjs +1 -1
- package/dist/facade.js +1 -1
- package/dist/primitives.cjs +1 -1
- package/dist/primitives.js +1 -1
- package/dist/usage.cjs +20 -4
- package/dist/usage.d.cts +2 -0
- package/dist/usage.d.ts +2 -0
- package/dist/usage.js +21 -4
- package/dist/validate.cjs +28 -5
- package/dist/validate.js +27 -5
- package/package.json +1 -1
package/dist/doc.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_message = require('./message.cjs');
|
|
2
|
+
const require_validate = require('./validate.cjs');
|
|
2
3
|
const require_usage = require('./usage.cjs');
|
|
3
4
|
|
|
4
5
|
//#region src/doc.ts
|
|
@@ -182,9 +183,10 @@ function defaultSectionOrder(a, b) {
|
|
|
182
183
|
* @param page The documentation page to format
|
|
183
184
|
* @param options Formatting options to customize the output
|
|
184
185
|
* @returns A formatted string representation of the documentation page
|
|
185
|
-
* @throws {TypeError} If `programName`
|
|
186
|
-
*
|
|
187
|
-
*
|
|
186
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
187
|
+
* whitespace-only, or contains control characters, if any non-empty
|
|
188
|
+
* section's title is empty, whitespace-only, or contains a CR or LF
|
|
189
|
+
* character, or if `maxWidth` is not a finite integer.
|
|
188
190
|
* @throws {RangeError} If any entry needs a description column and `maxWidth`
|
|
189
191
|
* is too small to fit the minimum layout (less than `termIndent + 4`), or if
|
|
190
192
|
* `showChoices.maxItems` is less than `1`.
|
|
@@ -208,7 +210,7 @@ function defaultSectionOrder(a, b) {
|
|
|
208
210
|
* ```
|
|
209
211
|
*/
|
|
210
212
|
function formatDocPage(programName, page, options = {}) {
|
|
211
|
-
|
|
213
|
+
require_validate.validateProgramName(programName);
|
|
212
214
|
const termIndent = options.termIndent ?? 2;
|
|
213
215
|
const termWidth = options.termWidth ?? 26;
|
|
214
216
|
if (options.maxWidth != null && (!Number.isFinite(options.maxWidth) || !Number.isInteger(options.maxWidth))) throw new TypeError(`maxWidth must be a finite integer, got ${options.maxWidth}.`);
|
|
@@ -246,7 +248,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
246
248
|
const splitEntryMin = termIndent + 2 + Math.max(2, 2 * minDescWidth - 1);
|
|
247
249
|
const fixedEntryMin = termIndent + 2 + termWidth + minDescWidth;
|
|
248
250
|
const entryMin = needsDescColumn ? Math.min(splitEntryMin, fixedEntryMin) : hasEntries ? termIndent + 1 : 1;
|
|
249
|
-
const usageMin = page.usage != null ?
|
|
251
|
+
const usageMin = page.usage != null ? 7 + Math.max(programName.length, Math.min(maxVisibleAtomicWidth(page.usage), programName.length + 7)) : 1;
|
|
250
252
|
let sectionMin = 1;
|
|
251
253
|
if (page.examples != null) sectionMin = Math.max(sectionMin, 9);
|
|
252
254
|
if (page.author != null) sectionMin = Math.max(sectionMin, 7);
|
|
@@ -449,6 +451,54 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
449
451
|
function indentLines(text$1, indent) {
|
|
450
452
|
return text$1.split("\n").join("\n" + " ".repeat(indent));
|
|
451
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Returns the width of the widest non-breakable segment among visible
|
|
456
|
+
* (non-usage-hidden) terms in a usage tree. Hidden terms are excluded
|
|
457
|
+
* because they are filtered out before rendering, so they do not
|
|
458
|
+
* contribute to the rendered width.
|
|
459
|
+
*/
|
|
460
|
+
function maxVisibleAtomicWidth(usage) {
|
|
461
|
+
let max = 0;
|
|
462
|
+
for (const term of usage) switch (term.type) {
|
|
463
|
+
case "argument":
|
|
464
|
+
if (!require_usage.isUsageHidden(term.hidden)) max = Math.max(max, term.metavar.length);
|
|
465
|
+
break;
|
|
466
|
+
case "option":
|
|
467
|
+
if (!require_usage.isUsageHidden(term.hidden) && term.names.length > 0) {
|
|
468
|
+
for (const name of term.names) max = Math.max(max, name.length);
|
|
469
|
+
if (term.metavar != null) max = Math.max(max, term.metavar.length);
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
472
|
+
case "command":
|
|
473
|
+
if (!require_usage.isUsageHidden(term.hidden)) max = Math.max(max, term.name.length);
|
|
474
|
+
break;
|
|
475
|
+
case "passthrough":
|
|
476
|
+
if (!require_usage.isUsageHidden(term.hidden)) max = Math.max(max, 5);
|
|
477
|
+
break;
|
|
478
|
+
case "optional":
|
|
479
|
+
max = Math.max(max, maxVisibleAtomicWidth(term.terms));
|
|
480
|
+
break;
|
|
481
|
+
case "multiple": {
|
|
482
|
+
const innerMax = maxVisibleAtomicWidth(term.terms);
|
|
483
|
+
if (innerMax > 0) max = Math.max(max, 3, innerMax);
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
case "exclusive":
|
|
487
|
+
for (const branch of term.terms) {
|
|
488
|
+
const first = branch[0];
|
|
489
|
+
if (first?.type === "command" && require_usage.isUsageHidden(first.hidden)) continue;
|
|
490
|
+
max = Math.max(max, maxVisibleAtomicWidth(branch));
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
case "literal":
|
|
494
|
+
if (term.value !== "") max = Math.max(max, term.value.length);
|
|
495
|
+
break;
|
|
496
|
+
case "ellipsis":
|
|
497
|
+
max = Math.max(max, 3);
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
return max;
|
|
501
|
+
}
|
|
452
502
|
const ansiEscapeCodeRegex = /\x1B\[[0-9;]*[a-zA-Z]/g;
|
|
453
503
|
function ansiAwareRightPad(text$1, length, char = " ") {
|
|
454
504
|
const strippedText = text$1.replace(ansiEscapeCodeRegex, "");
|
package/dist/doc.d.cts
CHANGED
|
@@ -311,9 +311,10 @@ interface DocPageFormatOptions {
|
|
|
311
311
|
* @param page The documentation page to format
|
|
312
312
|
* @param options Formatting options to customize the output
|
|
313
313
|
* @returns A formatted string representation of the documentation page
|
|
314
|
-
* @throws {TypeError} If `programName`
|
|
315
|
-
*
|
|
316
|
-
*
|
|
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 empty, whitespace-only, or contains a CR or LF
|
|
317
|
+
* character, or if `maxWidth` is not a finite integer.
|
|
317
318
|
* @throws {RangeError} If any entry needs a description column and `maxWidth`
|
|
318
319
|
* is too small to fit the minimum layout (less than `termIndent + 4`), or if
|
|
319
320
|
* `showChoices.maxItems` is less than `1`.
|
package/dist/doc.d.ts
CHANGED
|
@@ -311,9 +311,10 @@ interface DocPageFormatOptions {
|
|
|
311
311
|
* @param page The documentation page to format
|
|
312
312
|
* @param options Formatting options to customize the output
|
|
313
313
|
* @returns A formatted string representation of the documentation page
|
|
314
|
-
* @throws {TypeError} If `programName`
|
|
315
|
-
*
|
|
316
|
-
*
|
|
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 empty, whitespace-only, or contains a CR or LF
|
|
317
|
+
* character, or if `maxWidth` is not a finite integer.
|
|
317
318
|
* @throws {RangeError} If any entry needs a description column and `maxWidth`
|
|
318
319
|
* is too small to fit the minimum layout (less than `termIndent + 4`), or if
|
|
319
320
|
* `showChoices.maxItems` is less than `1`.
|
package/dist/doc.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneMessage, formatMessage, text } from "./message.js";
|
|
2
|
-
import {
|
|
2
|
+
import { validateProgramName } from "./validate.js";
|
|
3
|
+
import { cloneUsageTerm, formatUsage, formatUsageTerm, isDocHidden, isUsageHidden } from "./usage.js";
|
|
3
4
|
|
|
4
5
|
//#region src/doc.ts
|
|
5
6
|
/**
|
|
@@ -182,9 +183,10 @@ function defaultSectionOrder(a, b) {
|
|
|
182
183
|
* @param page The documentation page to format
|
|
183
184
|
* @param options Formatting options to customize the output
|
|
184
185
|
* @returns A formatted string representation of the documentation page
|
|
185
|
-
* @throws {TypeError} If `programName`
|
|
186
|
-
*
|
|
187
|
-
*
|
|
186
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
187
|
+
* whitespace-only, or contains control characters, if any non-empty
|
|
188
|
+
* section's title is empty, whitespace-only, or contains a CR or LF
|
|
189
|
+
* character, or if `maxWidth` is not a finite integer.
|
|
188
190
|
* @throws {RangeError} If any entry needs a description column and `maxWidth`
|
|
189
191
|
* is too small to fit the minimum layout (less than `termIndent + 4`), or if
|
|
190
192
|
* `showChoices.maxItems` is less than `1`.
|
|
@@ -208,7 +210,7 @@ function defaultSectionOrder(a, b) {
|
|
|
208
210
|
* ```
|
|
209
211
|
*/
|
|
210
212
|
function formatDocPage(programName, page, options = {}) {
|
|
211
|
-
|
|
213
|
+
validateProgramName(programName);
|
|
212
214
|
const termIndent = options.termIndent ?? 2;
|
|
213
215
|
const termWidth = options.termWidth ?? 26;
|
|
214
216
|
if (options.maxWidth != null && (!Number.isFinite(options.maxWidth) || !Number.isInteger(options.maxWidth))) throw new TypeError(`maxWidth must be a finite integer, got ${options.maxWidth}.`);
|
|
@@ -246,7 +248,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
246
248
|
const splitEntryMin = termIndent + 2 + Math.max(2, 2 * minDescWidth - 1);
|
|
247
249
|
const fixedEntryMin = termIndent + 2 + termWidth + minDescWidth;
|
|
248
250
|
const entryMin = needsDescColumn ? Math.min(splitEntryMin, fixedEntryMin) : hasEntries ? termIndent + 1 : 1;
|
|
249
|
-
const usageMin = page.usage != null ?
|
|
251
|
+
const usageMin = page.usage != null ? 7 + Math.max(programName.length, Math.min(maxVisibleAtomicWidth(page.usage), programName.length + 7)) : 1;
|
|
250
252
|
let sectionMin = 1;
|
|
251
253
|
if (page.examples != null) sectionMin = Math.max(sectionMin, 9);
|
|
252
254
|
if (page.author != null) sectionMin = Math.max(sectionMin, 7);
|
|
@@ -449,6 +451,54 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
449
451
|
function indentLines(text$1, indent) {
|
|
450
452
|
return text$1.split("\n").join("\n" + " ".repeat(indent));
|
|
451
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Returns the width of the widest non-breakable segment among visible
|
|
456
|
+
* (non-usage-hidden) terms in a usage tree. Hidden terms are excluded
|
|
457
|
+
* because they are filtered out before rendering, so they do not
|
|
458
|
+
* contribute to the rendered width.
|
|
459
|
+
*/
|
|
460
|
+
function maxVisibleAtomicWidth(usage) {
|
|
461
|
+
let max = 0;
|
|
462
|
+
for (const term of usage) switch (term.type) {
|
|
463
|
+
case "argument":
|
|
464
|
+
if (!isUsageHidden(term.hidden)) max = Math.max(max, term.metavar.length);
|
|
465
|
+
break;
|
|
466
|
+
case "option":
|
|
467
|
+
if (!isUsageHidden(term.hidden) && term.names.length > 0) {
|
|
468
|
+
for (const name of term.names) max = Math.max(max, name.length);
|
|
469
|
+
if (term.metavar != null) max = Math.max(max, term.metavar.length);
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
472
|
+
case "command":
|
|
473
|
+
if (!isUsageHidden(term.hidden)) max = Math.max(max, term.name.length);
|
|
474
|
+
break;
|
|
475
|
+
case "passthrough":
|
|
476
|
+
if (!isUsageHidden(term.hidden)) max = Math.max(max, 5);
|
|
477
|
+
break;
|
|
478
|
+
case "optional":
|
|
479
|
+
max = Math.max(max, maxVisibleAtomicWidth(term.terms));
|
|
480
|
+
break;
|
|
481
|
+
case "multiple": {
|
|
482
|
+
const innerMax = maxVisibleAtomicWidth(term.terms);
|
|
483
|
+
if (innerMax > 0) max = Math.max(max, 3, innerMax);
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
case "exclusive":
|
|
487
|
+
for (const branch of term.terms) {
|
|
488
|
+
const first = branch[0];
|
|
489
|
+
if (first?.type === "command" && isUsageHidden(first.hidden)) continue;
|
|
490
|
+
max = Math.max(max, maxVisibleAtomicWidth(branch));
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
case "literal":
|
|
494
|
+
if (term.value !== "") max = Math.max(max, term.value.length);
|
|
495
|
+
break;
|
|
496
|
+
case "ellipsis":
|
|
497
|
+
max = Math.max(max, 3);
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
return max;
|
|
501
|
+
}
|
|
452
502
|
const ansiEscapeCodeRegex = /\x1B\[[0-9;]*[a-zA-Z]/g;
|
|
453
503
|
function ansiAwareRightPad(text$1, length, char = " ") {
|
|
454
504
|
const strippedText = text$1.replace(ansiEscapeCodeRegex, "");
|
package/dist/facade.cjs
CHANGED
|
@@ -2,12 +2,12 @@ const require_annotations = require('./annotations.cjs');
|
|
|
2
2
|
const require_message = require('./message.cjs');
|
|
3
3
|
const require_completion = require('./completion.cjs');
|
|
4
4
|
const require_mode_dispatch = require('./mode-dispatch.cjs');
|
|
5
|
+
const require_validate = require('./validate.cjs');
|
|
5
6
|
const require_usage = require('./usage.cjs');
|
|
6
7
|
const require_doc = require('./doc.cjs');
|
|
7
8
|
const require_constructs = require('./constructs.cjs');
|
|
8
9
|
const require_context = require('./context.cjs');
|
|
9
10
|
const require_modifiers = require('./modifiers.cjs');
|
|
10
|
-
const require_validate = require('./validate.cjs');
|
|
11
11
|
const require_valueparser = require('./valueparser.cjs');
|
|
12
12
|
const require_primitives = require('./primitives.cjs');
|
|
13
13
|
const require_parser = require('./parser.cjs');
|
package/dist/facade.js
CHANGED
|
@@ -2,12 +2,12 @@ import { injectAnnotations } from "./annotations.js";
|
|
|
2
2
|
import { commandLine, formatMessage, lineBreak, message, optionName, text, value } from "./message.js";
|
|
3
3
|
import { bash, fish, nu, pwsh, zsh } from "./completion.js";
|
|
4
4
|
import { dispatchByMode } from "./mode-dispatch.js";
|
|
5
|
+
import { validateCommandNames, validateOptionNames } from "./validate.js";
|
|
5
6
|
import { formatUsage } from "./usage.js";
|
|
6
7
|
import { formatDocPage } from "./doc.js";
|
|
7
8
|
import { group, longestMatch, object } from "./constructs.js";
|
|
8
9
|
import { isPlaceholderValue } from "./context.js";
|
|
9
10
|
import { multiple, optional, withDefault } from "./modifiers.js";
|
|
10
|
-
import { validateCommandNames, validateOptionNames } from "./validate.js";
|
|
11
11
|
import { string } from "./valueparser.js";
|
|
12
12
|
import { argument, command, constant, flag, option } from "./primitives.js";
|
|
13
13
|
import { getDocPage, parseAsync, parseSync, suggest, suggestAsync } from "./parser.js";
|
package/dist/primitives.cjs
CHANGED
|
@@ -2,10 +2,10 @@ const require_annotations = require('./annotations.cjs');
|
|
|
2
2
|
const require_message = require('./message.cjs');
|
|
3
3
|
const require_dependency = require('./dependency.cjs');
|
|
4
4
|
const require_mode_dispatch = require('./mode-dispatch.cjs');
|
|
5
|
+
const require_validate = require('./validate.cjs');
|
|
5
6
|
const require_usage = require('./usage.cjs');
|
|
6
7
|
const require_suggestion = require('./suggestion.cjs');
|
|
7
8
|
const require_usage_internals = require('./usage-internals.cjs');
|
|
8
|
-
const require_validate = require('./validate.cjs');
|
|
9
9
|
const require_valueparser = require('./valueparser.cjs');
|
|
10
10
|
|
|
11
11
|
//#region src/primitives.ts
|
package/dist/primitives.js
CHANGED
|
@@ -2,10 +2,10 @@ import { annotateFreshArray, getAnnotations } from "./annotations.js";
|
|
|
2
2
|
import { message, metavar, optionName, optionNames, text, valueSet } from "./message.js";
|
|
3
3
|
import { createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, dependencyId, getDefaultValuesFunction, getDependencyIds, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isPendingDependencySourceState, suggestWithDependency } from "./dependency.js";
|
|
4
4
|
import { dispatchIterableByMode } from "./mode-dispatch.js";
|
|
5
|
+
import { validateOptionNames } from "./validate.js";
|
|
5
6
|
import { extractOptionNames, isDocHidden, isSuggestionHidden } from "./usage.js";
|
|
6
7
|
import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, findSimilar } from "./suggestion.js";
|
|
7
8
|
import { extractLeadingCommandNames } from "./usage-internals.js";
|
|
8
|
-
import { validateOptionNames } from "./validate.js";
|
|
9
9
|
import { isValueParser } from "./valueparser.js";
|
|
10
10
|
|
|
11
11
|
//#region src/primitives.ts
|
package/dist/usage.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_validate = require('./validate.cjs');
|
|
1
2
|
|
|
2
3
|
//#region src/usage.ts
|
|
3
4
|
/**
|
|
@@ -147,8 +148,11 @@ function extractArgumentMetavars(usage) {
|
|
|
147
148
|
* @param options Optional formatting options to customize the output.
|
|
148
149
|
* See {@link UsageFormatOptions} for available options.
|
|
149
150
|
* @returns A formatted string representation of the usage description.
|
|
151
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
152
|
+
* whitespace-only, or contains control characters.
|
|
150
153
|
*/
|
|
151
154
|
function formatUsage(programName, usage, options = {}) {
|
|
155
|
+
require_validate.validateProgramName(programName);
|
|
152
156
|
usage = normalizeUsage(filterUsageForDisplay(usage));
|
|
153
157
|
if (options.expandCommands) {
|
|
154
158
|
const lastTerm = usage.at(-1);
|
|
@@ -160,13 +164,24 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
160
164
|
if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
|
|
161
165
|
lines.push(formatUsage(programName, command, options));
|
|
162
166
|
}
|
|
163
|
-
return lines.join("\n");
|
|
167
|
+
if (lines.length > 0) return lines.join("\n");
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
|
-
let output = options.colors ? `\x1b[1m${programName}\x1b[0m
|
|
167
|
-
let lineWidth = programName.length
|
|
170
|
+
let output = options.colors ? `\x1b[1m${programName}\x1b[0m` : programName;
|
|
171
|
+
let lineWidth = programName.length;
|
|
172
|
+
let first = true;
|
|
168
173
|
for (const { text, width } of formatUsageTerms(usage, options)) {
|
|
169
|
-
if (
|
|
174
|
+
if (first) {
|
|
175
|
+
first = false;
|
|
176
|
+
if (options.maxWidth != null && lineWidth + 1 + width > options.maxWidth) {
|
|
177
|
+
output += "\n";
|
|
178
|
+
lineWidth = 0;
|
|
179
|
+
} else {
|
|
180
|
+
output += " ";
|
|
181
|
+
lineWidth += 1;
|
|
182
|
+
}
|
|
183
|
+
} else if (options.maxWidth != null && lineWidth + width > options.maxWidth) {
|
|
184
|
+
if (output.endsWith(" ")) output = output.slice(0, -1);
|
|
170
185
|
output += "\n";
|
|
171
186
|
lineWidth = 0;
|
|
172
187
|
if (text === " ") continue;
|
|
@@ -404,6 +419,7 @@ function formatUsageTerm(term, options = {}) {
|
|
|
404
419
|
let output = "";
|
|
405
420
|
for (const { text, width } of formatUsageTermInternal(visibleTerms[0], options)) {
|
|
406
421
|
if (options.maxWidth != null && lineWidth + width > options.maxWidth) {
|
|
422
|
+
if (output.endsWith(" ")) output = output.slice(0, -1);
|
|
407
423
|
output += "\n";
|
|
408
424
|
lineWidth = 0;
|
|
409
425
|
if (text === " ") continue;
|
package/dist/usage.d.cts
CHANGED
|
@@ -325,6 +325,8 @@ interface UsageFormatOptions {
|
|
|
325
325
|
* @param options Optional formatting options to customize the output.
|
|
326
326
|
* See {@link UsageFormatOptions} for available options.
|
|
327
327
|
* @returns A formatted string representation of the usage description.
|
|
328
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
329
|
+
* whitespace-only, or contains control characters.
|
|
328
330
|
*/
|
|
329
331
|
declare function formatUsage(programName: string, usage: Usage, options?: UsageFormatOptions): string;
|
|
330
332
|
/**
|
package/dist/usage.d.ts
CHANGED
|
@@ -325,6 +325,8 @@ interface UsageFormatOptions {
|
|
|
325
325
|
* @param options Optional formatting options to customize the output.
|
|
326
326
|
* See {@link UsageFormatOptions} for available options.
|
|
327
327
|
* @returns A formatted string representation of the usage description.
|
|
328
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
329
|
+
* whitespace-only, or contains control characters.
|
|
328
330
|
*/
|
|
329
331
|
declare function formatUsage(programName: string, usage: Usage, options?: UsageFormatOptions): string;
|
|
330
332
|
/**
|
package/dist/usage.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { validateProgramName } from "./validate.js";
|
|
2
|
+
|
|
1
3
|
//#region src/usage.ts
|
|
2
4
|
/**
|
|
3
5
|
* Returns whether the term should be hidden from usage output.
|
|
@@ -146,8 +148,11 @@ function extractArgumentMetavars(usage) {
|
|
|
146
148
|
* @param options Optional formatting options to customize the output.
|
|
147
149
|
* See {@link UsageFormatOptions} for available options.
|
|
148
150
|
* @returns A formatted string representation of the usage description.
|
|
151
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
152
|
+
* whitespace-only, or contains control characters.
|
|
149
153
|
*/
|
|
150
154
|
function formatUsage(programName, usage, options = {}) {
|
|
155
|
+
validateProgramName(programName);
|
|
151
156
|
usage = normalizeUsage(filterUsageForDisplay(usage));
|
|
152
157
|
if (options.expandCommands) {
|
|
153
158
|
const lastTerm = usage.at(-1);
|
|
@@ -159,13 +164,24 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
159
164
|
if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
|
|
160
165
|
lines.push(formatUsage(programName, command, options));
|
|
161
166
|
}
|
|
162
|
-
return lines.join("\n");
|
|
167
|
+
if (lines.length > 0) return lines.join("\n");
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
|
-
let output = options.colors ? `\x1b[1m${programName}\x1b[0m
|
|
166
|
-
let lineWidth = programName.length
|
|
170
|
+
let output = options.colors ? `\x1b[1m${programName}\x1b[0m` : programName;
|
|
171
|
+
let lineWidth = programName.length;
|
|
172
|
+
let first = true;
|
|
167
173
|
for (const { text, width } of formatUsageTerms(usage, options)) {
|
|
168
|
-
if (
|
|
174
|
+
if (first) {
|
|
175
|
+
first = false;
|
|
176
|
+
if (options.maxWidth != null && lineWidth + 1 + width > options.maxWidth) {
|
|
177
|
+
output += "\n";
|
|
178
|
+
lineWidth = 0;
|
|
179
|
+
} else {
|
|
180
|
+
output += " ";
|
|
181
|
+
lineWidth += 1;
|
|
182
|
+
}
|
|
183
|
+
} else if (options.maxWidth != null && lineWidth + width > options.maxWidth) {
|
|
184
|
+
if (output.endsWith(" ")) output = output.slice(0, -1);
|
|
169
185
|
output += "\n";
|
|
170
186
|
lineWidth = 0;
|
|
171
187
|
if (text === " ") continue;
|
|
@@ -403,6 +419,7 @@ function formatUsageTerm(term, options = {}) {
|
|
|
403
419
|
let output = "";
|
|
404
420
|
for (const { text, width } of formatUsageTermInternal(visibleTerms[0], options)) {
|
|
405
421
|
if (options.maxWidth != null && lineWidth + width > options.maxWidth) {
|
|
422
|
+
if (output.endsWith(" ")) output = output.slice(0, -1);
|
|
406
423
|
output += "\n";
|
|
407
424
|
lineWidth = 0;
|
|
408
425
|
if (text === " ") continue;
|
package/dist/validate.cjs
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
//#region src/validate.ts
|
|
3
3
|
/**
|
|
4
|
+
* Matches Unicode control characters: C0 (U+0000–U+001F), DEL (U+007F),
|
|
5
|
+
* C1 (U+0080–U+009F), and line separators (U+2028, U+2029).
|
|
6
|
+
*/
|
|
7
|
+
const CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f\u2028\u2029]/;
|
|
8
|
+
const CONTROL_CHAR_RE_GLOBAL = new RegExp(CONTROL_CHAR_RE.source, "g");
|
|
9
|
+
/**
|
|
4
10
|
* Escapes control characters in a string for readable error messages.
|
|
5
11
|
*
|
|
6
12
|
* @param value The string to escape.
|
|
@@ -8,13 +14,13 @@
|
|
|
8
14
|
* sequences.
|
|
9
15
|
*/
|
|
10
16
|
function escapeControlChars(value) {
|
|
11
|
-
return value.replace(
|
|
17
|
+
return value.replace(CONTROL_CHAR_RE_GLOBAL, (ch) => {
|
|
12
18
|
const code = ch.charCodeAt(0);
|
|
13
19
|
switch (code) {
|
|
14
20
|
case 9: return "\\t";
|
|
15
21
|
case 10: return "\\n";
|
|
16
22
|
case 13: return "\\r";
|
|
17
|
-
default: return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
23
|
+
default: return code > 255 ? `\\u${code.toString(16).padStart(4, "0")}` : `\\x${code.toString(16).padStart(2, "0")}`;
|
|
18
24
|
}
|
|
19
25
|
});
|
|
20
26
|
}
|
|
@@ -32,7 +38,7 @@ function validateOptionNames(names, label) {
|
|
|
32
38
|
for (const name of names) {
|
|
33
39
|
if (name === "") throw new TypeError(`${label} name must not be empty.`);
|
|
34
40
|
if (/^\s+$/.test(name)) throw new TypeError(`${label} name must not be whitespace-only: "${escapeControlChars(name)}".`);
|
|
35
|
-
if (
|
|
41
|
+
if (CONTROL_CHAR_RE.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
|
|
36
42
|
if (/\s/.test(name)) throw new TypeError(`${label} name must not contain whitespace: "${escapeControlChars(name)}".`);
|
|
37
43
|
if (!/^(--|[-/+])/.test(name)) throw new TypeError(`${label} name must start with "--", "-", "/", or "+": "${name}".`);
|
|
38
44
|
if (name === "--") throw new TypeError(`${label} name must not be the options terminator "--".`);
|
|
@@ -52,11 +58,28 @@ function validateCommandNames(names, label) {
|
|
|
52
58
|
for (const name of names) {
|
|
53
59
|
if (name === "") throw new TypeError(`${label} name must not be empty.`);
|
|
54
60
|
if (/^\s+$/.test(name)) throw new TypeError(`${label} name must not be whitespace-only: "${escapeControlChars(name)}".`);
|
|
55
|
-
if (
|
|
61
|
+
if (CONTROL_CHAR_RE.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
|
|
56
62
|
if (/\s/.test(name)) throw new TypeError(`${label} name must not contain whitespace: "${escapeControlChars(name)}".`);
|
|
57
63
|
}
|
|
58
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Validates a program name at runtime.
|
|
67
|
+
*
|
|
68
|
+
* Program names may contain spaces (e.g., file paths), but must not be empty,
|
|
69
|
+
* whitespace-only, or contain control characters.
|
|
70
|
+
*
|
|
71
|
+
* @param programName The program name to validate.
|
|
72
|
+
* @throws {TypeError} If the value is not a string, is empty,
|
|
73
|
+
* whitespace-only, or contains control characters.
|
|
74
|
+
*/
|
|
75
|
+
function validateProgramName(programName) {
|
|
76
|
+
if (typeof programName !== "string") throw new TypeError("Program name must be a string.");
|
|
77
|
+
if (programName === "") throw new TypeError("Program name must not be empty.");
|
|
78
|
+
if (/^\s+$/.test(programName)) throw new TypeError(`Program name must not be whitespace-only: "${escapeControlChars(programName)}".`);
|
|
79
|
+
if (CONTROL_CHAR_RE.test(programName)) throw new TypeError(`Program name must not contain control characters: "${escapeControlChars(programName)}".`);
|
|
80
|
+
}
|
|
59
81
|
|
|
60
82
|
//#endregion
|
|
61
83
|
exports.validateCommandNames = validateCommandNames;
|
|
62
|
-
exports.validateOptionNames = validateOptionNames;
|
|
84
|
+
exports.validateOptionNames = validateOptionNames;
|
|
85
|
+
exports.validateProgramName = validateProgramName;
|
package/dist/validate.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
//#region src/validate.ts
|
|
2
2
|
/**
|
|
3
|
+
* Matches Unicode control characters: C0 (U+0000–U+001F), DEL (U+007F),
|
|
4
|
+
* C1 (U+0080–U+009F), and line separators (U+2028, U+2029).
|
|
5
|
+
*/
|
|
6
|
+
const CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f\u2028\u2029]/;
|
|
7
|
+
const CONTROL_CHAR_RE_GLOBAL = new RegExp(CONTROL_CHAR_RE.source, "g");
|
|
8
|
+
/**
|
|
3
9
|
* Escapes control characters in a string for readable error messages.
|
|
4
10
|
*
|
|
5
11
|
* @param value The string to escape.
|
|
@@ -7,13 +13,13 @@
|
|
|
7
13
|
* sequences.
|
|
8
14
|
*/
|
|
9
15
|
function escapeControlChars(value) {
|
|
10
|
-
return value.replace(
|
|
16
|
+
return value.replace(CONTROL_CHAR_RE_GLOBAL, (ch) => {
|
|
11
17
|
const code = ch.charCodeAt(0);
|
|
12
18
|
switch (code) {
|
|
13
19
|
case 9: return "\\t";
|
|
14
20
|
case 10: return "\\n";
|
|
15
21
|
case 13: return "\\r";
|
|
16
|
-
default: return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
22
|
+
default: return code > 255 ? `\\u${code.toString(16).padStart(4, "0")}` : `\\x${code.toString(16).padStart(2, "0")}`;
|
|
17
23
|
}
|
|
18
24
|
});
|
|
19
25
|
}
|
|
@@ -31,7 +37,7 @@ function validateOptionNames(names, label) {
|
|
|
31
37
|
for (const name of names) {
|
|
32
38
|
if (name === "") throw new TypeError(`${label} name must not be empty.`);
|
|
33
39
|
if (/^\s+$/.test(name)) throw new TypeError(`${label} name must not be whitespace-only: "${escapeControlChars(name)}".`);
|
|
34
|
-
if (
|
|
40
|
+
if (CONTROL_CHAR_RE.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
|
|
35
41
|
if (/\s/.test(name)) throw new TypeError(`${label} name must not contain whitespace: "${escapeControlChars(name)}".`);
|
|
36
42
|
if (!/^(--|[-/+])/.test(name)) throw new TypeError(`${label} name must start with "--", "-", "/", or "+": "${name}".`);
|
|
37
43
|
if (name === "--") throw new TypeError(`${label} name must not be the options terminator "--".`);
|
|
@@ -51,10 +57,26 @@ function validateCommandNames(names, label) {
|
|
|
51
57
|
for (const name of names) {
|
|
52
58
|
if (name === "") throw new TypeError(`${label} name must not be empty.`);
|
|
53
59
|
if (/^\s+$/.test(name)) throw new TypeError(`${label} name must not be whitespace-only: "${escapeControlChars(name)}".`);
|
|
54
|
-
if (
|
|
60
|
+
if (CONTROL_CHAR_RE.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
|
|
55
61
|
if (/\s/.test(name)) throw new TypeError(`${label} name must not contain whitespace: "${escapeControlChars(name)}".`);
|
|
56
62
|
}
|
|
57
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Validates a program name at runtime.
|
|
66
|
+
*
|
|
67
|
+
* Program names may contain spaces (e.g., file paths), but must not be empty,
|
|
68
|
+
* whitespace-only, or contain control characters.
|
|
69
|
+
*
|
|
70
|
+
* @param programName The program name to validate.
|
|
71
|
+
* @throws {TypeError} If the value is not a string, is empty,
|
|
72
|
+
* whitespace-only, or contains control characters.
|
|
73
|
+
*/
|
|
74
|
+
function validateProgramName(programName) {
|
|
75
|
+
if (typeof programName !== "string") throw new TypeError("Program name must be a string.");
|
|
76
|
+
if (programName === "") throw new TypeError("Program name must not be empty.");
|
|
77
|
+
if (/^\s+$/.test(programName)) throw new TypeError(`Program name must not be whitespace-only: "${escapeControlChars(programName)}".`);
|
|
78
|
+
if (CONTROL_CHAR_RE.test(programName)) throw new TypeError(`Program name must not contain control characters: "${escapeControlChars(programName)}".`);
|
|
79
|
+
}
|
|
58
80
|
|
|
59
81
|
//#endregion
|
|
60
|
-
export { validateCommandNames, validateOptionNames };
|
|
82
|
+
export { validateCommandNames, validateOptionNames, validateProgramName };
|