@noresources/command 0.0.0-stage → 0.1.1
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/README.md +76 -2
- package/package.json +44 -4
- package/src/Builders.mjs +368 -0
- package/src/Command.mjs +159 -0
- package/src/CommandHandler.mjs +14 -0
- package/src/Documentation.mjs +63 -0
- package/src/Input.mjs +42 -0
- package/src/Options.mjs +515 -0
- package/src/Parameters.mjs +350 -0
- package/src/Positionals.mjs +493 -0
- package/src/commander/adapters.mjs +343 -0
- package/src/commander/index.mjs +26 -0
- package/src/commander/package.json +1 -0
- package/src/lib/options/Verbosity.mjs +55 -0
- package/src/lib/options/index.mjs +15 -0
- package/src/utilities.mjs +40 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Options and command-line argument type descriptors.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createDocumentation
|
|
9
|
+
} from './Documentation.mjs';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
implode,
|
|
13
|
+
merge
|
|
14
|
+
} from '@noresources/core';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @class CoercionError
|
|
18
|
+
* @description A specific error class used to signal that the raw input value
|
|
19
|
+
* received during parameter parsing could not be successfully transformed (coerced)
|
|
20
|
+
* into the required data type (e.g., attempting to parse 'abc' as a number).
|
|
21
|
+
* This indicates a fundamental data type mismatch.
|
|
22
|
+
* @extends {Error}
|
|
23
|
+
* @example
|
|
24
|
+
* throw new CoercionError("Invalid format for required integer.");
|
|
25
|
+
*/
|
|
26
|
+
export class CoercionError extends Error {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @class ValidationError
|
|
30
|
+
*
|
|
31
|
+
* @description A specific error class thrown when an argument value,
|
|
32
|
+
* *after* successfully completing type coercion, still fails to meet predefined
|
|
33
|
+
* structural or logical constraints.
|
|
34
|
+
* Examples include string length violations, out-of-range numeric values,
|
|
35
|
+
* or membership in an invalid enumeration set.
|
|
36
|
+
* @extends {Error}
|
|
37
|
+
* @example
|
|
38
|
+
* throw new ValidationError("Input must be a positive number.");
|
|
39
|
+
*/
|
|
40
|
+
export class ValidationError extends Error {}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @class Parameter
|
|
44
|
+
* @description The abstract base class for defining structured command-line argument descriptors.
|
|
45
|
+
* It serves as the core mechanism for encapsulating argument metadata, including
|
|
46
|
+
* the expected type, custom coercion functions, and validation rules.
|
|
47
|
+
* Subclasses are expected to extend this class and specialize the behavior.
|
|
48
|
+
*
|
|
49
|
+
* @property {string} type - The canonical identifier string representing the expected data type (e.g., 'string', 'number', 'integer').
|
|
50
|
+
* @property {function} [coercer] - An optional function that transforms the raw input value into the intended type. It must accept one argument (the raw value).
|
|
51
|
+
* @property {function} [validator] - An optional function executed after coercion to validate the resulting value against constraints. It must accept one argument (the coerced value).
|
|
52
|
+
* @property {object} [documentation] - A structured object containing generated metadata derived from the parameter's configuration.
|
|
53
|
+
* @abstract
|
|
54
|
+
*/
|
|
55
|
+
export class Parameter {
|
|
56
|
+
/**
|
|
57
|
+
* Creates an argument descriptor instance.
|
|
58
|
+
* @param {string} type - The primary type identifier for the argument (e.g., 'string', 'boolean'). This defines the parameter's contract.
|
|
59
|
+
* @param {Object<string, any>} [descriptor={}] - An optional configuration object containing type-specific attributes (e.g., `minLength`, `minValue`) or custom methods (`coercer`, `validator`).
|
|
60
|
+
* @throws {Error} If essential type information is missing, though internal logic should prevent this.
|
|
61
|
+
*/
|
|
62
|
+
constructor (type, descriptor) {
|
|
63
|
+
this.type = type || '*';
|
|
64
|
+
this._documentation = undefined;
|
|
65
|
+
if (typeof descriptor === 'object' && descriptor !== null) {
|
|
66
|
+
for (const [name, value] of Object.entries(descriptor)) {
|
|
67
|
+
/**
|
|
68
|
+
* @todo test if name is a setter instead of checking against "undefined"
|
|
69
|
+
*/
|
|
70
|
+
if (!(name in this) || (typeof this[name] === 'undefined')) {
|
|
71
|
+
this[name] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Gets the parameter {Documentation} object.
|
|
79
|
+
*
|
|
80
|
+
* @returns {Documentation}
|
|
81
|
+
*/
|
|
82
|
+
get documentation () {
|
|
83
|
+
return this._documentation;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Sets the parameter documentation.
|
|
89
|
+
*
|
|
90
|
+
* The input value is normalized to a {Documentation} object.
|
|
91
|
+
*
|
|
92
|
+
* @param {Documentation|string|undefined value - User-defined documentation.
|
|
93
|
+
* If undefined, the documentation object is cleared.
|
|
94
|
+
*/
|
|
95
|
+
set documentation (value) {
|
|
96
|
+
if (!value) {
|
|
97
|
+
this._documentation = undefined;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this._documentation = createDocumentation(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Attempts to convert the raw input value into a data type appropriate for this parameter's contract.
|
|
106
|
+
* This function encapsulates the type coercion logic.
|
|
107
|
+
* @param {any} value - The raw, unparsed value received from the input source (e.g., command line).
|
|
108
|
+
* @returns {any} The value, successfully transformed and coerced into the target type.
|
|
109
|
+
* @throws {CoercionError} Thrown if the internal coercion mechanism fails to successfully convert the input value.
|
|
110
|
+
*/
|
|
111
|
+
coerce (value) {
|
|
112
|
+
if (typeof this.coercer === 'function') {
|
|
113
|
+
// Use apply to ensure 'this' context inside the coercer remains correct.
|
|
114
|
+
return this.coercer.apply (this, [value]);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validates the value against all defined constraints (e.g., minLength, maxLength, custom validators).
|
|
121
|
+
* This method is designed to be called ONLY after the `coerce()` method has successfully run.
|
|
122
|
+
* @param {any} value - The value that has already been coerced by the `coerce()` method.
|
|
123
|
+
* @throws {ValidationError} Thrown if the value violates any established constraints defined in the descriptor.
|
|
124
|
+
*/
|
|
125
|
+
validate (value) {
|
|
126
|
+
if (typeof this.validator === 'function') {
|
|
127
|
+
// Use apply to ensure 'this' context inside the validator remains correct.
|
|
128
|
+
this.validator.apply (this, [value]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @class EnumerationParameter
|
|
135
|
+
*
|
|
136
|
+
* @extends {Parameter}
|
|
137
|
+
*
|
|
138
|
+
* @description A specialized argument descriptor used when the acceptable values
|
|
139
|
+
* are restricted to a fixed list. This implementation adds logic to enforce
|
|
140
|
+
* membership validation, checking if the coerced value is present within the defined `values` array.
|
|
141
|
+
* @property {Array<any>} values - The fixed set of acceptable values for this parameter.
|
|
142
|
+
*/
|
|
143
|
+
export class EnumerationParameter extends Parameter {
|
|
144
|
+
/**
|
|
145
|
+
* Creates an EnumerationParameter instance.
|
|
146
|
+
*
|
|
147
|
+
* @param {Object} options - The configuration object.
|
|
148
|
+
* @param {Array<any>} options.values - The strict list of permissible values.
|
|
149
|
+
*/
|
|
150
|
+
constructor (options) {
|
|
151
|
+
super ('enumeration', merge ({
|
|
152
|
+
'values': []
|
|
153
|
+
}, options));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Overrides the base validation to perform set membership checking.
|
|
158
|
+
* @param {any} value - The coerced value.
|
|
159
|
+
* @throws {ValidationError} Thrown if the value is not found within the defined `values` list.
|
|
160
|
+
*/
|
|
161
|
+
validate (value) {
|
|
162
|
+
super.validate (value);
|
|
163
|
+
if (!this.values.includes (value)) {
|
|
164
|
+
const expected = implode (this.values, {
|
|
165
|
+
'between': ', ',
|
|
166
|
+
'betweenLast': ' or '
|
|
167
|
+
});
|
|
168
|
+
throw new ValidationError('Value must be one of ' + expected);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @class StringParameter
|
|
175
|
+
* @extends {Parameter}
|
|
176
|
+
* @description A highly specialized argument type descriptor dedicated to string inputs.
|
|
177
|
+
* It enforces string-specific constraints, primarily minimum and maximum character lengths.
|
|
178
|
+
* This class ensures that all input intended to be a string is rigorously validated
|
|
179
|
+
* for length and type consistency.
|
|
180
|
+
*
|
|
181
|
+
* @property {number} [minLength] - The minimum required length (inclusive). The string must have at least this many characters.
|
|
182
|
+
* @property {number} [maxLength] - The maximum allowed length (exclusive of the upper bound). The string length must be less than this value.
|
|
183
|
+
*/
|
|
184
|
+
export class StringParameter extends Parameter {
|
|
185
|
+
/**
|
|
186
|
+
* Creates a StringParameter descriptor.
|
|
187
|
+
* @param {Object} options - Configuration options containing string constraints.
|
|
188
|
+
* @param {number} [options.minLength] - The minimum acceptable length (default is 0).
|
|
189
|
+
* @param {number} [options.maxLength] - The maximum acceptable length (exclusive upper bound).
|
|
190
|
+
*/
|
|
191
|
+
constructor (options) {
|
|
192
|
+
super('string', merge ({
|
|
193
|
+
'minLength': 0
|
|
194
|
+
}, options));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Overrides the base coercion mechanism to guarantee that the final value is a string.
|
|
199
|
+
* This is critical for enforcing string-based constraints.
|
|
200
|
+
* @param {any} value - The raw input value received.
|
|
201
|
+
* @returns {string} The value, guaranteed to be a string representation of the input.
|
|
202
|
+
*/
|
|
203
|
+
coerce (value) {
|
|
204
|
+
return super.coerce(value).toString();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Executes advanced validation, checking the length of the string
|
|
209
|
+
* against the `minLength` and `maxLength` properties.
|
|
210
|
+
* @param {string} value - The coerced string value.
|
|
211
|
+
* @throws {ValidationError} Thrown if the string's length falls outside the configured boundaries.
|
|
212
|
+
*/
|
|
213
|
+
validate (value) {
|
|
214
|
+
super.validate(value);
|
|
215
|
+
|
|
216
|
+
if (typeof this.minLength === 'number') {
|
|
217
|
+
if (value.length < this.minLength) {
|
|
218
|
+
throw new ValidationError (`String length must be at least ${this.minLength}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (typeof this.maxLength === 'number') {
|
|
223
|
+
if (value.length >= this.maxLength) {
|
|
224
|
+
throw new ValidationError (`Value length cannot exceed ${this.maxLength}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @class NumberParameter
|
|
232
|
+
* @extends {Parameter}
|
|
233
|
+
* @description A type descriptor specialized for numeric data types (float).
|
|
234
|
+
* It handles the parsing of raw inputs (which might be strings) into IEEE 754 floating-point numbers
|
|
235
|
+
* and validates the resulting number against explicit `minValue` and `maxValue` constraints.
|
|
236
|
+
* @param {Object} options - Configuration options containing numerical constraints.
|
|
237
|
+
* @param {string} [subtype='number'] - A specific numerical type ('number' for float, 'integer' for integer).
|
|
238
|
+
*/
|
|
239
|
+
export class NumberParameter extends Parameter {
|
|
240
|
+
/**
|
|
241
|
+
* Creates a NumberParameter instance.
|
|
242
|
+
* @param {Object} options - Options including constraints like `minValue` or `maxValue`.
|
|
243
|
+
* @param {string} subtype - The specific sub-type ('number' or 'integer').
|
|
244
|
+
*/
|
|
245
|
+
constructor (options, subtype) {
|
|
246
|
+
super (subtype || 'number', merge ({
|
|
247
|
+
'validator': Number.isFinite,
|
|
248
|
+
'minValue': undefined,
|
|
249
|
+
'maxValue': undefined
|
|
250
|
+
}, options));
|
|
251
|
+
this._parserFunction = parseFloat;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Overrides base coercion to perform floating-point parsing.
|
|
256
|
+
* @param {any} value - The raw input value.
|
|
257
|
+
* @returns {number} The successfully parsed number.
|
|
258
|
+
* @throws {CoercionError} If the parsing attempt results in NaN.
|
|
259
|
+
*/
|
|
260
|
+
coerce (value) {
|
|
261
|
+
const number = this._parserFunction(super.coerce(value));
|
|
262
|
+
if (isNaN(number)) {
|
|
263
|
+
throw new CoercionError('Could not coerce ' + (typeof value) + ' to number');
|
|
264
|
+
}
|
|
265
|
+
return number;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Validates the numeric value against the configured minimum and maximum bounds.
|
|
270
|
+
* This must be called after successful coercion.
|
|
271
|
+
* @param {number} value - The coerced numeric value.
|
|
272
|
+
* @throws {ValidationError} Thrown if the value is outside the [minValue, maxValue] range.
|
|
273
|
+
*/
|
|
274
|
+
validate (value) {
|
|
275
|
+
super.validate(value);
|
|
276
|
+
if (typeof this.minValue === 'number') {
|
|
277
|
+
if (value < this.minValue) {
|
|
278
|
+
throw new ValidationError ('Value must be at least ' + this.minValue);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (typeof this.maxValue === 'number') {
|
|
282
|
+
if (value > this.maxValue) {
|
|
283
|
+
throw new ValidationError ('Value must be at most ' + this.maxValue);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @class IntegerParameter
|
|
291
|
+
* @extends {NumberParameter}
|
|
292
|
+
* @description A highly specialized numeric type descriptor that enforces strict integer constraints.
|
|
293
|
+
* It inherits all floating-point range checking from {@link NumberParameter} and overrides
|
|
294
|
+
* the parsing function to use `parseInt`, ensuring that fractional parts are discarded
|
|
295
|
+
* during the initial type transformation.
|
|
296
|
+
*/
|
|
297
|
+
export class IntegerParameter extends NumberParameter {
|
|
298
|
+
/**
|
|
299
|
+
* Creates an IntegerParameter instance.
|
|
300
|
+
* @param {Object} options - Configuration options, inheriting from NumberParameter.
|
|
301
|
+
*/
|
|
302
|
+
constructor(options) {
|
|
303
|
+
super(merge({
|
|
304
|
+
'validator': Number.isInteger
|
|
305
|
+
}, options), 'integer');
|
|
306
|
+
this._parserFunction = parseInt;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @function createParameter
|
|
312
|
+
* @description Creates and initializes a type-specific parameter descriptor instance.
|
|
313
|
+
* This factory function abstracts away the instantiation logic, intelligently
|
|
314
|
+
* determining and returning the correct implementation (e.g., {@link Parameter},
|
|
315
|
+
* {@link StringParameter}) based on the provided type specification.
|
|
316
|
+
*
|
|
317
|
+
* @param {string | {type: string}} type - The parameter type definition. This can be a simple type string (e.g., 'string') or an object containing a type property (e.g., `{type: 'string'}`).
|
|
318
|
+
* @param {Object} [options={}] - An optional configuration object containing all type-specific constraints (e.g., `minLength`, `minValue`).
|
|
319
|
+
* @returns {Parameter | StringParameter} A fully initialized descriptor instance suitable for argument validation.
|
|
320
|
+
* @throws {Error} Thrown if the specified type is unknown or unhandled by the current module.
|
|
321
|
+
* @global
|
|
322
|
+
*/
|
|
323
|
+
export function createParameter () {
|
|
324
|
+
let type = arguments[0];
|
|
325
|
+
let options = arguments[1];
|
|
326
|
+
|
|
327
|
+
if (typeof type === 'object') {
|
|
328
|
+
options = type;
|
|
329
|
+
type = type.type;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
switch (type) {
|
|
333
|
+
case 'enumeration': return new EnumerationParameter(options);
|
|
334
|
+
case 'string': return new StringParameter(options);
|
|
335
|
+
case 'integer': return new IntegerParameter(options);
|
|
336
|
+
case 'float':
|
|
337
|
+
case 'number':
|
|
338
|
+
return new NumberParameter(options);
|
|
339
|
+
}
|
|
340
|
+
return new Parameter(type, options);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export default {
|
|
344
|
+
createParameter,
|
|
345
|
+
Parameter,
|
|
346
|
+
EnumerationParameter,
|
|
347
|
+
StringParameter,
|
|
348
|
+
NumberParameter,
|
|
349
|
+
IntegerParameter
|
|
350
|
+
};
|