@noresources/command 0.0.0-stage → 0.1.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.
@@ -0,0 +1,493 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file Defines core structures and logic for managing positional arguments
5
+ */
6
+
7
+ import {
8
+ transformParameterProperty
9
+ } from './utilities.mjs';
10
+
11
+ import {
12
+ Parameter
13
+ } from './Parameters.mjs';
14
+
15
+ import {
16
+ assertType,
17
+ assertTypes,
18
+ merge
19
+ } from '@noresources/core';
20
+
21
+ /**
22
+ * @constant {object} POSITIONAL_TYPE
23
+ * @description A static map containing string constants used globally to identify
24
+ * the semantic type of a positional argument definition, namely whether it
25
+ * represents a single fixed value or a sequence of values.
26
+ * @property {('VALUE'|'VARIADIC')} VALUE - Identifier used for defining a single, fixed-value positional argument.
27
+ * @property {('VALUE'|'VARIADIC')} VARIADIC - Identifier used for defining a variadic positional argument, which accepts zero or more inputs.
28
+ */
29
+ export const POSITIONAL_TYPE = {
30
+ 'VALUE': 'value',
31
+ 'VARIADIC': 'variadic'
32
+ };
33
+
34
+ /**
35
+ * @abstract
36
+ * @class Positional
37
+ * @description Base class providing the fundamental structure for any positional
38
+ * argument within the parsing system. It holds the metadata necessary to map
39
+ * the argument slot to a specific property in the resulting data object.
40
+ *
41
+ * @property {boolean} property - The string identifier corresponding to the property name in the output structure where the value will be stored.
42
+ * @property {number} [min=0] - The minimum number of values that must satisfy this argument's constraints.
43
+ * @property {number} [max=1] - The maximum number of values allowed for this argument (varies by concrete implementation).
44
+ * @param {object} options - Configuration object passed during instantiation.
45
+ */
46
+ export class Positional {
47
+ constructor (options) {
48
+ if (!(typeof options === 'object' && options)) {
49
+ options = {};
50
+ }
51
+ this.property = options.property;
52
+ }
53
+
54
+ /**
55
+ * Coerce and validate a raw input value intended for a positional argument slot.
56
+ * This method delegates the actual transformation to the associated `Parameter` object.
57
+ * @param {mixed} value - The raw input value provided by the caller.
58
+ * @returns {mixed} A value that has been successfully coerced and validated against the parameter constraints.
59
+ */
60
+ parse (value) {
61
+ return value;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * @class PositionalValue
67
+ * @extends Positional
68
+ * @description Represents a single, fixed-slot positional argument. This structure
69
+ * is used when the parsing system is explicitly expected to consume exactly one
70
+ * input value from the command line arguments.
71
+ *
72
+ * @property {Parameter} argument - The underlying type definition (e.g., string, number) that dictates the constraints of the expected single value.
73
+ * @property {*} [default] - The fallback default value assigned to this parameter if no value is provided by the caller and the argument is not required.
74
+ */
75
+ export class PositionalValue extends Positional {
76
+
77
+ /**
78
+ * Initializes a new PositionalValue instance, configuring its type constraints and default values.
79
+ * @param {object} options - The configuration object for the positional argument.
80
+ * @param {* | undefined} [options.default=undefined] - The default value to assign if the argument is omitted during a function call.
81
+ * @param {Parameter | undefined} [options.argument=undefined] - The required type definition for the argument, adhering to the `Parameter` class structure.
82
+ * @throws {Error} If options.argument is not a valid Parameter object.
83
+ */
84
+ constructor(options) {
85
+ options = merge ({
86
+ //'default': undefined,
87
+ //'required': undefined,
88
+ //'parameter': undefined
89
+ }, options);
90
+
91
+ assertTypes (options.parameter, ['undefined', Parameter], 'options.parameter');
92
+
93
+ super (options);
94
+ this.default = options.default;
95
+ this.parameter = options.parameter;
96
+ this._required = options.required;
97
+ }
98
+
99
+ /**
100
+ * Gets the predefined type constant for a single, fixed-value positional argument.
101
+ * @returns {('VALUE'|'VARIADIC')} The constant identifier 'value'.
102
+ */
103
+ get type () {
104
+ return POSITIONAL_TYPE.VALUE;
105
+ }
106
+ /**
107
+ * Determines if the positional value must be explicitly provided by the caller.
108
+ * It considers the argument required if the `_required` flag is set to true, or if no default value has been assigned.
109
+ * @returns {boolean} True if the value is mandatory (required).
110
+ */
111
+ get required () {
112
+ if (typeof this._required !== 'undefined') {
113
+ return this._required ? true : false;
114
+ }
115
+ return (typeof this.default === 'undefined');
116
+ }
117
+
118
+ /**
119
+ * Sets the explicit required status of the positional value.
120
+ * @param {boolean} value - The new required state.
121
+ */
122
+ set required (value) {
123
+ this._required = value;
124
+ }
125
+
126
+ /**
127
+ * Gets the minimum expected count. For PositionalValue, this is always 1 if required, 0 otherwise.
128
+ * @returns {number} The minimum number of expected values (1 or 0).
129
+ */
130
+ get min () {
131
+ return this.required ? 1 : 0;
132
+ }
133
+
134
+ /**
135
+ * Gets the maximum allowed count. For PositionalValue, this is always 1.
136
+ * @returns {number} The maximum number of allowed values (always 1).
137
+ */
138
+ get max () {
139
+ return 1;
140
+ }
141
+
142
+ /**
143
+ * Parses and validates the input value against the defined parameter constraints.
144
+ * @param {mixed} value - The raw value from the input source.
145
+ * @returns {mixed} The processed and validated value.
146
+ */
147
+ parse (value) {
148
+ if (!(this.parameter instanceof Parameter)) {
149
+ return value;
150
+ }
151
+ value = this.parameter.coerce(value);
152
+ this.parameter.validate(value);
153
+ return value;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @class PositionalVariadic
159
+ * @extends Positional
160
+ * @description Represents a variadic positional argument, which allows the parsing
161
+ * system to consume zero or more arguments into a list or array. This structure
162
+ * is essential for handling variable numbers of inputs, such as lists of file paths or names.
163
+ *
164
+ * @property {Parameter} parameters - The underlying type definition that dictates the constraints of the elements within the received arguments array.
165
+ * @property {number} min - The minimum required count of arguments that must be passed (this property is derived from the required state).
166
+ * @property {number} [max] - The maximum number of arguments that are allowed to be consumed.
167
+ */
168
+ export class PositionalVariadic extends Positional {
169
+ /**
170
+ * Initializes a new PositionalVariadic instance.
171
+ * @param {object} options - Configuration options for the variadic argument.
172
+ * @param {number} [options.min=0] - The absolute minimum number of values that must be passed for this slot. Must be >= 0.
173
+ * @param {number} [options.max=undefined] - The maximum number of values that this argument is allowed to accept.
174
+ * @param {Parameter | undefined} [options.parameters=undefined] - The base type definition used to validate every value collected in this array.
175
+ * @throws {Error} If options.min is not a number or options.max is not a number.
176
+ */
177
+ constructor(options) {
178
+ options = merge ({
179
+ 'min': 0,
180
+ //'max': undefined,
181
+ //'arguments': undefined
182
+ }, options);
183
+
184
+ assertType (options.min, 'number', 'options.min');
185
+ assertTypes(options.max, ['undefined', 'number'], 'options.max');
186
+ assertTypes (options.parameters, ['undefined', Parameter], 'options.parameters');
187
+
188
+ super (options);
189
+ this.min = options.min;
190
+ this.max = options.max;
191
+ this.parameters = options.parameters;
192
+ }
193
+
194
+ /**
195
+ * Gets the predefined type constant for a variadic positional argument.
196
+ * @returns {('VALUE'|'VARIADIC')} The constant identifier 'variadic'.
197
+ */
198
+ get type () {
199
+ return POSITIONAL_TYPE.VARIADIC;
200
+ }
201
+
202
+ /**
203
+ * Checks if the variadic argument is mandatory.
204
+ * This is determined by whether the `min` count is greater than zero.
205
+ * @returns {boolean} True if at least one value is mandatory (min > 0).
206
+ */
207
+ get required () {
208
+ return this.min > 0;
209
+ }
210
+
211
+ /**
212
+ * Updates the minimum required count for this variadic argument.
213
+ * @param {boolean} value - If true, sets the minimum to at least 1; if false, sets it to 0.
214
+ */
215
+ set required (value) {
216
+ if (value) {
217
+ this.min = Math.max(1, this.min);
218
+ } else {
219
+ this.min = 0;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Parses and validates a raw input value within the context of a variadic argument.
225
+ * Note: This function typically runs during consumption, not the initial parsing phase.
226
+ * @param {any} value - A single raw value item.
227
+ * @returns {any} The single value, having been coerced and validated.
228
+ */
229
+ parse (value) {
230
+ if (!(this.parameters instanceof Parameter)) {
231
+ return value;
232
+ }
233
+ value = this.parameters.coerce (value);
234
+ this.parameters.validate(value);
235
+ return value;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Internal utility function to automatically infer the positional argument type
241
+ * (Value or Variadic) when the type is not explicitly defined in the options object.
242
+ * This function is strictly internal and should not be called externally.
243
+ * @private
244
+ * @param {object} [options] - The configuration options object to inspect for type clues.
245
+ * @returns {('VALUE'|'VARIADIC')} The detected type of the positional argument.
246
+ */
247
+ function autodetectType (options) {
248
+ if ('type' in options) {
249
+ return options.type;
250
+ }
251
+
252
+ for (const property of ['parameter', 'default']) {
253
+ if (property in options) {
254
+ return POSITIONAL_TYPE.VALUE;
255
+ }
256
+ }
257
+
258
+ for (const property of ['parameters', 'min', 'max']) {
259
+ if (property in options) {
260
+ return POSITIONAL_TYPE.VARIADIC;
261
+ }
262
+ }
263
+
264
+ return POSITIONAL_TYPE.VALUE;
265
+ }
266
+
267
+ /**
268
+ * Creates and initializes a specific positional argument object, either PositionalValue or PositionalVariadic,
269
+ * based on the provided type descriptor and configuration options.
270
+ *
271
+ * @param {object | string} type - The explicit type identifier. This can be a string ('value', 'variadic') or a configuration object which must contain a 'type' property.
272
+ * @param {object} [options] - The options object containing detailed configuration parameters for the positional argument (e.g., default values, min/max constraints).
273
+ * @returns {PositionalValue | PositionalVariadic} A fully initialized instance of the correct positional argument class.
274
+ * @throws {Error} If the provided type identifier cannot be resolved or handled by this function.
275
+ */
276
+ export function createPositional(type, options) {
277
+ if (typeof type === 'object') {
278
+ options = type;
279
+ type = type.type;
280
+ }
281
+
282
+ if (typeof type === 'undefined') {
283
+ type = autodetectType(options || {});
284
+ }
285
+
286
+
287
+ switch (type) {
288
+ case POSITIONAL_TYPE.VALUE: {
289
+ transformParameterProperty.apply (this, [options, 'parameter']);
290
+ return new PositionalValue(options);
291
+ }
292
+ case POSITIONAL_TYPE.VARIADIC: {
293
+ transformParameterProperty.apply (this, [options, 'parameters']);
294
+ return new PositionalVariadic(options);
295
+ }
296
+ }
297
+ throw new Error ('Invalid positional argument type ' + type);
298
+ }
299
+
300
+ /**
301
+ * @param {Array<Positional>} positionals - An array of Positional objects defining the expected argument slots.
302
+ * @param {Array<any>} values - The raw array of values received from the caller.
303
+ * @param {object} [options] - Optional settings to control the dispatch process.
304
+ * @param {boolean} [options.dispatched=false] - The {values} argument is already dispatched
305
+ * @param {boolean} [options.dispatchOnly=false] - If true, the function will only match values to positional slots and return the intermediate dispatch state, without creating the final result object. Useful for debugging.
306
+ * @param {boolean} [options.skipValidation=false] - If true, the system will bypass all internal validation checks (e.g., type coercion checks) during the argument processing phase.
307
+ * @returns {Proxy} A Proxy object that mimics an array-like structure for the collection of all received values.
308
+ * @throws {Error} If the number of values provided does not match the minimum required count, or if too many values are provided for the current structure.
309
+ */
310
+ export function createPositionalArgumentResult(positionals, values, options) {
311
+ options = merge ({}, options);
312
+ const dispatched = options.dispatched
313
+ ? Array.from (values)
314
+ : dispatchPositionalValues(positionals, values, options);
315
+
316
+ if (options.dispatchOnly) {
317
+ return dispatched;
318
+ }
319
+
320
+
321
+ const object = {};
322
+ const array = [];
323
+ dispatched.forEach((e, i) => {
324
+ const p = positionals[i];
325
+ if (e.values.length == 0) {
326
+ if ('default' in p) {
327
+ e.values.push (p.default);
328
+ }
329
+ }
330
+ if (e.property) {
331
+ object[e.property] = (p.type == POSITIONAL_TYPE.VALUE)
332
+ ? e.values[0]
333
+ : e.values;
334
+
335
+ }
336
+
337
+
338
+ e.values.forEach (v => array.push (v));
339
+ });
340
+
341
+
342
+ return new Proxy ({
343
+ object,
344
+ array
345
+ }, {
346
+ has (o, k) {
347
+ return (k == 'length')
348
+ || [Symbol.iterator,
349
+ 'keys', 'entries', 'values',
350
+ 'length'].includes (k);
351
+ },
352
+ get (o, k) {
353
+ if (k in o.object) {
354
+ return o.object[k];
355
+ }
356
+
357
+ if (typeof k === 'symbol') {
358
+ switch (k) {
359
+ case Symbol.iterator: return o.array[k];
360
+ }
361
+ }
362
+
363
+ if (!isNaN(k)) {
364
+ if (k < 0 || k >= o.array.length) {
365
+ throw new RangeError (k + ' is not a valid offset. Expected [0, ' + o.array.length + ']');
366
+ }
367
+ return o.array[k];
368
+ }
369
+
370
+
371
+ switch (k) {
372
+ case 'length':
373
+ case 'values': {
374
+ return o.array[k];
375
+ }
376
+ case 'entries':
377
+ case 'keys': {
378
+ return Object[k](o.object);
379
+ }
380
+ }
381
+ }
382
+ });
383
+ }
384
+
385
+ /**
386
+ * Dispatches raw values into the positional argument slots based on minimum requirements and maximum capacities.
387
+ * This function handles the core logic of matching inputs to defined slots.
388
+ * @param {Array<Positional>} positionals - The array of positional definitions.
389
+ * @param {Array<any>} values - The input array of raw values.
390
+ * @param {object} [options] - Optional parameters, including `skipValidation`.
391
+ * @returns {Array<{property: string, values: Array<any>}>} A list of intermediate dispatch objects, each containing the target property name and the collected values.
392
+ * @throws {Error} If the number of input values is insufficient (less than required minimum) or excessive.
393
+ */
394
+ function dispatchPositionalValues (positionals, values, options) {
395
+ let min = 0;
396
+ const dispatched = [];
397
+ positionals.forEach (p => {
398
+ min += p.min;
399
+ dispatched.push ({
400
+ 'property': p.property,
401
+ 'values': []
402
+ });
403
+ });
404
+
405
+ if (values.length < min) {
406
+ const c = min - values.length;
407
+ throw new Error ('Missing ' + (min - values.length)
408
+ + ' required positional argument' + ((c != 1) ? 's' : '') );
409
+ }
410
+
411
+ if (positionals.length == 0) {
412
+ if (values.length) {
413
+ throw new Error ('Too many positional argument values');
414
+ }
415
+ return dispatched;
416
+ }
417
+ if (values.length == 0) {
418
+ return dispatched;
419
+ }
420
+
421
+ let sp = 0;
422
+ let ep = positionals.length - 1;
423
+ const P = Array.from (positionals);
424
+ const V = Array.from (values);
425
+ let p;
426
+ let v;
427
+
428
+
429
+ while (P.length
430
+ && V.length
431
+ && (p = P[0])
432
+ && (dispatched[sp].values.length < p.min)
433
+ ) {
434
+ v = V.shift();
435
+ if (!options.skipValidation) {
436
+ v = p.parse (v);
437
+ }
438
+
439
+ dispatched[sp].values.push (v);
440
+ if (dispatched[sp].values.length >= p.min) {
441
+ P.shift();
442
+ ++sp;
443
+ }
444
+ }
445
+
446
+
447
+ while (P.length
448
+ && V.length
449
+ && (ep > sp)
450
+ && (p = P[P.length - 1])
451
+ && (dispatched[ep].values.length < p.min)
452
+ ) {
453
+ v = V.pop();
454
+ dispatched[ep].values.push (v);
455
+ if (dispatched[ep].values.length >= p.min) {
456
+ P.pop();
457
+ --ep;
458
+ }
459
+ }
460
+
461
+
462
+ while (P.length && V.length) {
463
+ if (dispatched[sp].values.length >= P[0].max) {
464
+ P.shift();
465
+ ++sp;
466
+ continue;
467
+ }
468
+ dispatched[sp].values.push (V.shift());
469
+ }
470
+
471
+ if (V.length) {
472
+ throw new Error ('Too many positional argument values');
473
+ }
474
+ if (P.length) {
475
+ let remainingRequired = 0;
476
+ P.forEach(p => {
477
+ remainingRequired += p.min;
478
+ });
479
+ if (remainingRequired) {
480
+ throw new Error ('Too few positional argument values');
481
+ }
482
+ }
483
+
484
+ return dispatched;
485
+ }
486
+
487
+ export default {
488
+ Positional,
489
+ PositionalValue,
490
+ PositionalVariadic,
491
+ createPositional,
492
+ createPositionalArgumentResult,
493
+ };