@optique/core 1.0.0-dev.1514 → 1.0.0-dev.1523
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/constructs.cjs +5 -0
- package/dist/constructs.d.cts +12 -0
- package/dist/constructs.d.ts +12 -0
- package/dist/constructs.js +5 -0
- package/dist/doc.cjs +3 -3
- package/dist/doc.d.cts +2 -2
- package/dist/doc.d.ts +2 -2
- package/dist/doc.js +4 -4
- package/dist/facade.cjs +39 -0
- package/dist/facade.js +41 -2
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/usage.cjs +171 -6
- package/dist/usage.d.cts +75 -5
- package/dist/usage.d.ts +75 -5
- package/dist/usage.js +169 -7
- package/dist/validate.cjs +89 -0
- package/dist/validate.js +88 -1
- package/package.json +1 -1
package/dist/validate.cjs
CHANGED
|
@@ -63,6 +63,75 @@ function validateCommandNames(names, label) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
+
* Validates that there are no name collisions among meta features
|
|
67
|
+
* (help, version, completion) and between meta features and user parsers.
|
|
68
|
+
*
|
|
69
|
+
* The collision check is *position-aware*:
|
|
70
|
+
*
|
|
71
|
+
* - Meta **command** entries match at `args[0]` only, so they are checked
|
|
72
|
+
* against *leading* user names (those reachable before any positional gate).
|
|
73
|
+
* - Meta **option** entries use lenient scanners that match anywhere in
|
|
74
|
+
* `argv`, so they are checked against *all* user names at every depth,
|
|
75
|
+
* including literal values from conditional discriminators.
|
|
76
|
+
*
|
|
77
|
+
* Meta-vs-meta collisions are always checked in a unified namespace,
|
|
78
|
+
* because a meta command named `"--help"` and a meta option named
|
|
79
|
+
* `"--help"` both compete for the same token.
|
|
80
|
+
*
|
|
81
|
+
* @param userNames User parser names extracted at different scopes.
|
|
82
|
+
* @param metaEntries Active meta feature entries annotated with their kind.
|
|
83
|
+
* @throws {TypeError} If any collision or duplicate is detected.
|
|
84
|
+
* @since 1.0.0
|
|
85
|
+
*/
|
|
86
|
+
function validateMetaNameCollisions(userNames, metaEntries) {
|
|
87
|
+
for (const [, label, names] of metaEntries) {
|
|
88
|
+
const seen = /* @__PURE__ */ new Set();
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
if (seen.has(name)) throw new TypeError(`${capitalize(label)} has a duplicate name: "${name}"`);
|
|
91
|
+
seen.add(name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const nameToLabel = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const [, label, names] of metaEntries) for (const name of names) {
|
|
96
|
+
const existingLabel = nameToLabel.get(name);
|
|
97
|
+
if (existingLabel != null) throw new TypeError(`Name "${name}" is used by both ${existingLabel} and ${label}.`);
|
|
98
|
+
nameToLabel.set(name, label);
|
|
99
|
+
}
|
|
100
|
+
for (let i = 0; i < metaEntries.length; i++) {
|
|
101
|
+
const [, label, names, prefixMatch] = metaEntries[i];
|
|
102
|
+
if (!prefixMatch) continue;
|
|
103
|
+
for (const name of names) {
|
|
104
|
+
const prefix = name + "=";
|
|
105
|
+
for (let j = 0; j < metaEntries.length; j++) {
|
|
106
|
+
const [, otherLabel, otherNames] = metaEntries[j];
|
|
107
|
+
for (const otherName of otherNames) {
|
|
108
|
+
if (i === j && otherName === name) continue;
|
|
109
|
+
if (!otherName.startsWith(prefix)) continue;
|
|
110
|
+
throw new TypeError("The prefix form of name \"" + name + "\" in " + label + " shadows \"" + otherName + "\" in " + otherLabel + ".");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const [kind, label, names, prefixMatch] of metaEntries) {
|
|
116
|
+
const optionNames = kind === "command" ? userNames.leadingOptions : userNames.allOptions;
|
|
117
|
+
const commandNames = kind === "command" ? userNames.leadingCommands : userNames.allCommands;
|
|
118
|
+
for (const name of names) {
|
|
119
|
+
if (optionNames.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
|
|
120
|
+
if (commandNames.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
|
|
121
|
+
if (kind === "option" && userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
|
|
122
|
+
if (prefixMatch) {
|
|
123
|
+
const prefix = name + "=";
|
|
124
|
+
for (const userName of optionNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined option "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
|
|
125
|
+
for (const userName of commandNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined command "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
|
|
126
|
+
for (const literal of userNames.allLiterals) if (literal.startsWith(prefix)) throw new TypeError(`Literal value "${literal}" conflicts with the built-in ${label} (prefix "${prefix}").`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function capitalize(s) {
|
|
132
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
66
135
|
* Validates a program name at runtime.
|
|
67
136
|
*
|
|
68
137
|
* Program names may contain spaces (e.g., file paths), but must not be empty,
|
|
@@ -78,8 +147,28 @@ function validateProgramName(programName) {
|
|
|
78
147
|
if (/^\s+$/.test(programName)) throw new TypeError(`Program name must not be whitespace-only: "${escapeControlChars(programName)}".`);
|
|
79
148
|
if (CONTROL_CHAR_RE.test(programName)) throw new TypeError(`Program name must not contain control characters: "${escapeControlChars(programName)}".`);
|
|
80
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Validates a label at runtime.
|
|
152
|
+
*
|
|
153
|
+
* Labels are used as section titles in documentation output. They may contain
|
|
154
|
+
* spaces (e.g., "Connection options"), but must not be empty, whitespace-only,
|
|
155
|
+
* or contain control characters.
|
|
156
|
+
*
|
|
157
|
+
* @param label The label to validate.
|
|
158
|
+
* @throws {TypeError} If the label is not a string, is empty,
|
|
159
|
+
* whitespace-only, or contains control characters.
|
|
160
|
+
* @since 1.0.0
|
|
161
|
+
*/
|
|
162
|
+
function validateLabel(label) {
|
|
163
|
+
if (typeof label !== "string") throw new TypeError("Label must be a string.");
|
|
164
|
+
if (label === "") throw new TypeError("Label must not be empty.");
|
|
165
|
+
if (/^\s+$/.test(label)) throw new TypeError(`Label must not be whitespace-only: "${escapeControlChars(label)}".`);
|
|
166
|
+
if (CONTROL_CHAR_RE.test(label)) throw new TypeError(`Label must not contain control characters: "${escapeControlChars(label)}".`);
|
|
167
|
+
}
|
|
81
168
|
|
|
82
169
|
//#endregion
|
|
83
170
|
exports.validateCommandNames = validateCommandNames;
|
|
171
|
+
exports.validateLabel = validateLabel;
|
|
172
|
+
exports.validateMetaNameCollisions = validateMetaNameCollisions;
|
|
84
173
|
exports.validateOptionNames = validateOptionNames;
|
|
85
174
|
exports.validateProgramName = validateProgramName;
|
package/dist/validate.js
CHANGED
|
@@ -62,6 +62,75 @@ function validateCommandNames(names, label) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
+
* Validates that there are no name collisions among meta features
|
|
66
|
+
* (help, version, completion) and between meta features and user parsers.
|
|
67
|
+
*
|
|
68
|
+
* The collision check is *position-aware*:
|
|
69
|
+
*
|
|
70
|
+
* - Meta **command** entries match at `args[0]` only, so they are checked
|
|
71
|
+
* against *leading* user names (those reachable before any positional gate).
|
|
72
|
+
* - Meta **option** entries use lenient scanners that match anywhere in
|
|
73
|
+
* `argv`, so they are checked against *all* user names at every depth,
|
|
74
|
+
* including literal values from conditional discriminators.
|
|
75
|
+
*
|
|
76
|
+
* Meta-vs-meta collisions are always checked in a unified namespace,
|
|
77
|
+
* because a meta command named `"--help"` and a meta option named
|
|
78
|
+
* `"--help"` both compete for the same token.
|
|
79
|
+
*
|
|
80
|
+
* @param userNames User parser names extracted at different scopes.
|
|
81
|
+
* @param metaEntries Active meta feature entries annotated with their kind.
|
|
82
|
+
* @throws {TypeError} If any collision or duplicate is detected.
|
|
83
|
+
* @since 1.0.0
|
|
84
|
+
*/
|
|
85
|
+
function validateMetaNameCollisions(userNames, metaEntries) {
|
|
86
|
+
for (const [, label, names] of metaEntries) {
|
|
87
|
+
const seen = /* @__PURE__ */ new Set();
|
|
88
|
+
for (const name of names) {
|
|
89
|
+
if (seen.has(name)) throw new TypeError(`${capitalize(label)} has a duplicate name: "${name}"`);
|
|
90
|
+
seen.add(name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const nameToLabel = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const [, label, names] of metaEntries) for (const name of names) {
|
|
95
|
+
const existingLabel = nameToLabel.get(name);
|
|
96
|
+
if (existingLabel != null) throw new TypeError(`Name "${name}" is used by both ${existingLabel} and ${label}.`);
|
|
97
|
+
nameToLabel.set(name, label);
|
|
98
|
+
}
|
|
99
|
+
for (let i = 0; i < metaEntries.length; i++) {
|
|
100
|
+
const [, label, names, prefixMatch] = metaEntries[i];
|
|
101
|
+
if (!prefixMatch) continue;
|
|
102
|
+
for (const name of names) {
|
|
103
|
+
const prefix = name + "=";
|
|
104
|
+
for (let j = 0; j < metaEntries.length; j++) {
|
|
105
|
+
const [, otherLabel, otherNames] = metaEntries[j];
|
|
106
|
+
for (const otherName of otherNames) {
|
|
107
|
+
if (i === j && otherName === name) continue;
|
|
108
|
+
if (!otherName.startsWith(prefix)) continue;
|
|
109
|
+
throw new TypeError("The prefix form of name \"" + name + "\" in " + label + " shadows \"" + otherName + "\" in " + otherLabel + ".");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const [kind, label, names, prefixMatch] of metaEntries) {
|
|
115
|
+
const optionNames = kind === "command" ? userNames.leadingOptions : userNames.allOptions;
|
|
116
|
+
const commandNames = kind === "command" ? userNames.leadingCommands : userNames.allCommands;
|
|
117
|
+
for (const name of names) {
|
|
118
|
+
if (optionNames.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
|
|
119
|
+
if (commandNames.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
|
|
120
|
+
if (kind === "option" && userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
|
|
121
|
+
if (prefixMatch) {
|
|
122
|
+
const prefix = name + "=";
|
|
123
|
+
for (const userName of optionNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined option "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
|
|
124
|
+
for (const userName of commandNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined command "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
|
|
125
|
+
for (const literal of userNames.allLiterals) if (literal.startsWith(prefix)) throw new TypeError(`Literal value "${literal}" conflicts with the built-in ${label} (prefix "${prefix}").`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function capitalize(s) {
|
|
131
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
65
134
|
* Validates a program name at runtime.
|
|
66
135
|
*
|
|
67
136
|
* Program names may contain spaces (e.g., file paths), but must not be empty,
|
|
@@ -77,6 +146,24 @@ function validateProgramName(programName) {
|
|
|
77
146
|
if (/^\s+$/.test(programName)) throw new TypeError(`Program name must not be whitespace-only: "${escapeControlChars(programName)}".`);
|
|
78
147
|
if (CONTROL_CHAR_RE.test(programName)) throw new TypeError(`Program name must not contain control characters: "${escapeControlChars(programName)}".`);
|
|
79
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Validates a label at runtime.
|
|
151
|
+
*
|
|
152
|
+
* Labels are used as section titles in documentation output. They may contain
|
|
153
|
+
* spaces (e.g., "Connection options"), but must not be empty, whitespace-only,
|
|
154
|
+
* or contain control characters.
|
|
155
|
+
*
|
|
156
|
+
* @param label The label to validate.
|
|
157
|
+
* @throws {TypeError} If the label is not a string, is empty,
|
|
158
|
+
* whitespace-only, or contains control characters.
|
|
159
|
+
* @since 1.0.0
|
|
160
|
+
*/
|
|
161
|
+
function validateLabel(label) {
|
|
162
|
+
if (typeof label !== "string") throw new TypeError("Label must be a string.");
|
|
163
|
+
if (label === "") throw new TypeError("Label must not be empty.");
|
|
164
|
+
if (/^\s+$/.test(label)) throw new TypeError(`Label must not be whitespace-only: "${escapeControlChars(label)}".`);
|
|
165
|
+
if (CONTROL_CHAR_RE.test(label)) throw new TypeError(`Label must not contain control characters: "${escapeControlChars(label)}".`);
|
|
166
|
+
}
|
|
80
167
|
|
|
81
168
|
//#endregion
|
|
82
|
-
export { validateCommandNames, validateOptionNames, validateProgramName };
|
|
169
|
+
export { validateCommandNames, validateLabel, validateMetaNameCollisions, validateOptionNames, validateProgramName };
|