@optique/core 1.0.0-dev.1467 → 1.0.0-dev.1470

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 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` contains a CR or LF character, if
186
- * any non-empty section's title is empty, whitespace-only, or contains a CR
187
- * or LF character, or if `maxWidth` is not a finite integer.
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
- if (/[\r\n]/.test(programName)) throw new TypeError("Program name must not contain newlines.");
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}.`);
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` contains a CR or LF character, if
315
- * any non-empty section's title is empty, whitespace-only, or contains a CR
316
- * or LF character, or if `maxWidth` is not a finite integer.
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` contains a CR or LF character, if
315
- * any non-empty section's title is empty, whitespace-only, or contains a CR
316
- * or LF character, or if `maxWidth` is not a finite integer.
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,4 +1,5 @@
1
1
  import { cloneMessage, formatMessage, text } from "./message.js";
2
+ import { validateProgramName } from "./validate.js";
2
3
  import { cloneUsageTerm, formatUsage, formatUsageTerm, isDocHidden } from "./usage.js";
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` contains a CR or LF character, if
186
- * any non-empty section's title is empty, whitespace-only, or contains a CR
187
- * or LF character, or if `maxWidth` is not a finite integer.
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
- if (/[\r\n]/.test(programName)) throw new TypeError("Program name must not contain newlines.");
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}.`);
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";
@@ -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
@@ -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);
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);
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(/[\x00-\x1f\x7f]/g, (ch) => {
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 (/[\x00-\x1f\x7f]/.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
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 (/[\x00-\x1f\x7f]/.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
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(/[\x00-\x1f\x7f]/g, (ch) => {
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 (/[\x00-\x1f\x7f]/.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
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 (/[\x00-\x1f\x7f]/.test(name)) throw new TypeError(`${label} name must not contain control characters: "${escapeControlChars(name)}".`);
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1467+daf159c7",
3
+ "version": "1.0.0-dev.1470+e7499369",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",