@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.
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ import {
4
+ assertTypes
5
+ } from '@noresources/core';
6
+
7
+ /**
8
+ * @class Documentation
9
+ * @description Represents a structured descriptor for documenting software components, . This object holds two distinct fields: a brief, punchy summary
10
+ * (the abstract) and a detailed, contextual explanation.
11
+ * @property {string} abstract - A concise, single-line summary of the component's function. This is typically used in quick listings.
12
+ * @property {string | undefined} details - A comprehensive, multi-line description providing in-depth usage context, requirements, and side effects.
13
+ */
14
+ export class Documentation {
15
+ constructor () {
16
+ this.abstract = '';
17
+ this.details = undefined;
18
+ }
19
+
20
+ /**
21
+ * @description Serializes the structured documentation descriptor into a single, human-readable string.
22
+ * This method concatenates the primary abstract description and the detailed context,
23
+ * ensuring proper formatting for consumption by logging systems or UI rendering.
24
+ * @returns {string} The complete, formatted documentation string, combining abstract and details.
25
+ */
26
+ toString () {
27
+ let s = this.abstract || '';
28
+ if (typeof this.details === 'string') {
29
+ s += '\n' + this.details;
30
+ }
31
+ return s;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @description Factory function designed to instantiate a Documentation object from various input formats.
37
+ * It provides a crucial mechanism for normalizing data, allowing documentation to be defined
38
+ * either as a single raw string or a pre-structured JavaScript object.
39
+ * @param {string | object} content - The raw content used to build the descriptor. If a string, the first line is assigned to 'abstract'; the rest is 'details'. If an object, the 'abstract' and 'details' properties are used directly.
40
+ * @returns {Documentation} A fully initialized and structured instance of the Documentation class, ready for use.
41
+ * @throws {Error} Throws an assertion error if the provided 'content' parameter is not of type string or object.
42
+ */
43
+ export function createDocumentation (content) {
44
+ assertTypes (content, ['string', 'object'], 'content');
45
+ const d = new Documentation;
46
+ if (typeof content === 'string') {
47
+ const lines = content.split('\n');
48
+ d.abstract = lines.shift();
49
+ if (lines.length) {
50
+ d.details = lines.join('\n');
51
+ }
52
+ } else if (typeof content == 'object') {
53
+ d.abstract = content.abstract || '';
54
+ d.details = content.details;
55
+ }
56
+
57
+ return d;
58
+ }
59
+
60
+ export default {
61
+ Documentation,
62
+ createDocumentation
63
+ };
package/src/Input.mjs ADDED
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @class Input
5
+ * @description Command line parsing result.
6
+ *
7
+ * @property {Array<Object>} commands - A read-only list of all recognized subcommands
8
+ * that the current command handler can delegate to. Each
9
+ * element represents a potential command structure.
10
+ * @property {Object<string, any>} options - A mapping object containing all boolean flags
11
+ * and named options parsed from the command line
12
+ * (e.g., options.verbose, options.configPath).
13
+ * @property {Object} arguments - A structured data representation of all positional
14
+ * arguments provided by the user. This structure supports
15
+ * access methods by both numerical index and defined semantic names.
16
+ *
17
+ * @property {number} arguments.length - The total, zero-based count of positional arguments
18
+ * that were successfully parsed and received.
19
+ * @property {Iterable<string>} arguments.keys - An iterable (like a Set) containing the
20
+ * semantic names defined for all positional arguments.
21
+ * @property {Array<{name: string, value: any}>} arguments.entries - An iterator yielding pairs
22
+ * of positional name and the corresponding
23
+ * values.
24
+ *
25
+ * @example
26
+ * // To access the value of the third positional argument:
27
+ * const thirdValue = input.arguments[2];
28
+ *
29
+ * // To check if the user provided a 'force' flag:
30
+ * if (input.options.force) { ... }
31
+ *
32
+ * @since 1.0.0
33
+ */
34
+ export class Input {
35
+ constructor () {
36
+ this.commands = [],
37
+ this.options = {},
38
+ this.arguments = [];
39
+ }
40
+ }
41
+
42
+ export default Input;
@@ -0,0 +1,515 @@
1
+ 'use strict';
2
+
3
+ import {
4
+ transformParameterProperty
5
+ } from './utilities.mjs';
6
+
7
+ import {
8
+ merge,
9
+ toCamelCase,
10
+ isTraversable,
11
+ assertType,
12
+ assertTypes
13
+ } from '@noresources/core';
14
+
15
+ import {
16
+ createDocumentation
17
+ } from './Documentation.mjs';
18
+
19
+ import {
20
+ Parameter
21
+ } from './Parameters.mjs';
22
+
23
+
24
+ /**
25
+ * @typedef {Object.<string, string>} OptionTypeMap
26
+ *
27
+ * @description Defines all recognized, predefined.
28
+ *
29
+ * types for command line options.
30
+ * @readonly
31
+ */
32
+ export const OPTION_TYPE = {
33
+ /**
34
+ * @type {string}
35
+ * @description Represents the 'SWITCH' type.
36
+ * Used for boolean flags; true if present,
37
+ * false if absent.
38
+ */
39
+ 'SWITCH': 'switch',
40
+ /**
41
+ * @type {string}
42
+ * @description Represents the 'COUNTER' type.
43
+ * Used for options whose values should be incremented
44
+ * during parsing.
45
+ */
46
+ 'COUNTER': 'counter',
47
+ /**
48
+ * @type {string}
49
+ * @description Represents the 'VALUE' type.
50
+ * Used for options requiring exactly one argument.
51
+ */
52
+ 'VALUE': 'value',
53
+ /**
54
+ * @type {string}
55
+ * @description Represents the 'VARIADIC' type.
56
+ * Used for options accepting zero or more arguments.
57
+ */
58
+ 'VARIADIC': 'variadic'
59
+ };
60
+
61
+
62
+ /**
63
+ * @class Option
64
+ *
65
+ * @abstract
66
+ * @description The base class for all command line
67
+ * option types. It provides the common interface and
68
+ * state management (like names, required status)
69
+ * that all specialized option classes must inherit.
70
+ */
71
+ export class Option {
72
+ /**
73
+ * Creates an instance of Option. Initializes the
74
+ * base properties required for any option type.
75
+ *
76
+ * @param {Object} options - A configuration object.
77
+ * @param {string} options.property - The canonical
78
+ * name property TO USE in A COMMAND LINE PARSING RESULT OBJECT.
79
+ * @PARAM ôARRAY<STRING> [OPTIONS.NAME] - AN ARRAY OF NAMES THAT CAN BE USED IN THE COMMAND LINE TO REPRESENT THE OPTION.
80
+ * @param {boolean} [options.required] - A flag
81
+ * indicating if the option must be present.
82
+ *
83
+ * @throws {Error} If required types within options
84
+ * are invalid (e.g., property is not a string).
85
+ */
86
+ constructor (options) {
87
+ if ( ! (typeof options === 'object' && options)) {
88
+ options = {};
89
+ }
90
+
91
+ assertTypes(options.property, ['string', 'undefined'], 'options.property');
92
+ this.property = options.property;
93
+
94
+ if (typeof options.documentation !== 'undefined') {
95
+ this.documentation = options.documentation;
96
+ }
97
+
98
+ this._names = [];
99
+ if (isTraversable(options.names)) {
100
+ for (const name of options.names) {
101
+ assertType(name, 'string', 'options.names[]');
102
+ this._names.push (name);
103
+ }
104
+ }
105
+
106
+ this.required = options.required ? true : false;
107
+ }
108
+
109
+ /**
110
+ * @returns {string} The constant identifier for
111
+ * this option's type.
112
+ * @throws {Error} Always throws, as subclasses
113
+ * are required to implement this getter.
114
+ */
115
+ get type () {
116
+ throw new Error (this.constructor.name + ' must override type getter.');
117
+ }
118
+
119
+ /**
120
+ * @returns {Array<string>} An immutable array
121
+ * containing all possible names (flags).
122
+ */
123
+ get names () {
124
+ return this._names;
125
+ }
126
+
127
+ /**
128
+ * @returns {string} The standardized property name.
129
+ * This name is determined by properties set during
130
+ * construction, falling back to flag names.
131
+ */
132
+ get property () {
133
+ if (typeof this._property == 'string') {
134
+ return this._property;
135
+ }
136
+ let fallback;
137
+ for (const n of this._names) {
138
+ if (n.length > 1) {
139
+ return toCamelCase(n);
140
+ }
141
+ if (!fallback) {
142
+ fallback = n;
143
+ }
144
+ }
145
+
146
+ return fallback;
147
+ }
148
+
149
+ /**
150
+ * Programmatically sets the property name.
151
+ *
152
+ * @param {string} value - The desired property name.
153
+ * @throws {Error} If the provided value is not a string.
154
+ */
155
+ set property (value) {
156
+ assertTypes(value, ['string', 'undefined'], 'value');
157
+ this._property = value;
158
+ }
159
+
160
+
161
+ /**
162
+ * Gets the option {Documentation} object.
163
+ *
164
+ * @returns {Documentation}
165
+ */
166
+ get documentation () {
167
+ return this._documentation;
168
+ }
169
+
170
+ /**
171
+ * Sets the option documentation.
172
+ *
173
+ * The input value is normalized to a {Documentation} object.
174
+ *
175
+ * @param {Documentation|string|undefined value - User-defined documentation.
176
+ * If undefined, the documentation object is cleared.
177
+ */
178
+ set documentation (value) {
179
+ if (!value) {
180
+ this._documentation = undefined;
181
+ return;
182
+ }
183
+
184
+ this._documentation = createDocumentation(value);
185
+ }
186
+
187
+ /**
188
+ * Coerces the raw input value into the correct data
189
+ * type and validates it against the option's rules.
190
+ *
191
+ * @param {mixed} value - The raw value supplied by the
192
+ * command line parser.
193
+ * @returns {mixed} The coerced and validated value.
194
+ */
195
+ parse (value) {
196
+ return value;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @class SwitchOption
202
+ * @extends {Option}
203
+ * @description Represents a simple boolean flag. When
204
+ * this option is encountered, the associated value is
205
+ * strictly a boolean: true if present, false if absent.
206
+ * This is the standard mechanism for flags like
207
+ * `--verbose` or `--dry-run`.
208
+ */
209
+ export class SwitchOption extends Option {
210
+ /**
211
+ * Creates a SwitchOption instance.
212
+ *
213
+ * @param {Object} options - Configuration options
214
+ * passed to the base Option class.
215
+ * @constructor
216
+ */
217
+ constructor (options) {
218
+ super (options);
219
+ }
220
+
221
+ /**
222
+ * @returns {string} The constant representing the
223
+ * SWITCH type.
224
+ */
225
+ get type() {
226
+ return OPTION_TYPE.SWITCH;
227
+ }
228
+
229
+ /**
230
+ * Executes the parsing logic for a Switch option.
231
+ * It converts the presence of the flag into a boolean.
232
+ *
233
+ * @param {mixed} value - The raw input value (typically
234
+ * a truthy value if present).
235
+ * @returns {boolean} True if the flag was provided,
236
+ * false otherwise.
237
+ */
238
+ parse (value) {
239
+ return value ? true : false;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * @class CounterOption
245
+ * @extends {Option}
246
+ * @description Represents a numeric counter. This
247
+ * option tracks recurrence; the value is incremented
248
+ * each time the option is used. Common usage is
249
+ * for tracking options like `--port` or `--retries`.
250
+ *
251
+ * @param {Object} options - Configuration object.
252
+ * @param {number} [options.initial=0] - The starting
253
+ * integer value before any arguments are processed.
254
+ *
255
+ * @throws {Error} If the `initial` value provided
256
+ * is not a number.
257
+ */
258
+ export class CounterOption extends Option {
259
+ /**
260
+ * Creates a CounterOption instance.
261
+ *
262
+ * @param {Object} options - Configuration object.
263
+ * @constructor
264
+ */
265
+ constructor (options) {
266
+ options = merge ({
267
+ 'initial': 0
268
+ }, options);
269
+
270
+ assertType(options.initial, 'number', 'options.initial');
271
+
272
+ super (options);
273
+ this.initial = options.initial;
274
+ }
275
+
276
+ /**
277
+ * @returns {string} The constant representing the
278
+ * COUNTER type.
279
+ */
280
+ get type () {
281
+ return OPTION_TYPE.COUNTER;
282
+ }
283
+
284
+ /**
285
+ * Parses the input value, ensuring it is a valid
286
+ * integer.
287
+ *
288
+ * @param {mixed} value - The raw string input from
289
+ * the command line argument.
290
+ * @returns {number} The integer value parsed from the input.
291
+ * @throws {Error} If the provided value cannot be
292
+ * successfully parsed into an integer.
293
+ */
294
+ parse (value) {
295
+ value = parseInt (value);
296
+ if (isNaN(value)) {
297
+ throw new Error ('Could not convert value to integer');
298
+ }
299
+ return value;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * @class ValueOption
305
+ * @extends {Option}
306
+ * @description Represents a standard option requiring
307
+ * one mandatory associated value. This option relies on
308
+ * a separate {@link Parameter} descriptor to manage data
309
+ * type, validation rules, and coercion logic for the value.
310
+ *
311
+ * @param {Object} options - Configuration object.
312
+ * @param {Parameter} options.parameter - The required
313
+ * descriptor instance defining the value's structure.
314
+ *
315
+ * @throws {Error} If the `parameter` descriptor is missing
316
+ * or is not a valid Parameter object.
317
+ */
318
+ export class ValueOption extends Option {
319
+ /**
320
+ * Creates a ValueOption instance.
321
+ *
322
+ * @param {Object} options - The configuration object.
323
+ * @param {mixed} [options.default=undefined] - An optional
324
+ * default value to use if the option is not provided.
325
+ * @param {Parameter} [options.parameters] - The parameter
326
+ * descriptor, used to validate and coerce the input value.
327
+ *
328
+ * @throws {Error} If the `parameter` descriptor is missing.
329
+ */
330
+ constructor (options) {
331
+ options = merge({
332
+ //'defaul': undefined,
333
+ //'parameter': undefined
334
+ }, options);
335
+
336
+ assertTypes(options.parameter, ['undefined', Parameter], 'options.parameter');
337
+
338
+ super (options);
339
+ this.default = options.default;
340
+ this.parameter = options.parameter;
341
+ }
342
+
343
+ /**
344
+ * @returns {string} The constant representing the VALUE type.
345
+ */
346
+ get type() {
347
+ return OPTION_TYPE.VALUE;
348
+ }
349
+
350
+ /**
351
+ * Executes the parsing logic. The value is coerced
352
+ * and then validated against the associated
353
+ * Parameter descriptor.
354
+ *
355
+ * @param {mixed} value - The raw value provided by
356
+ * the command line.
357
+ * @returns {mixed} The fully coerced and validated value.
358
+ */
359
+ parse (value) {
360
+ if (!(this.parameter instanceof Parameter)) {
361
+ return value;
362
+ }
363
+ value = this.parameter.coerce (value);
364
+ this.parameter.validate(value);
365
+ return value;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * @class VariadicOption
371
+ * @extends {Option}
372
+ * @description Represents an option capable of accepting
373
+ * a variable number of associated values (a list). This
374
+ * mechanism is designed to collect multiple inputs
375
+ * (like filenames or paths) into a single array.
376
+ *
377
+ * @param {Object} options - Configuration object.
378
+ * @param {number} [options.min=0] - The minimum number
379
+ * of values that must be provided. Must be 0 or greater.
380
+ * @param {Parameter} [options.parameters] - The descriptor
381
+ * defining the type and validation rules for each element.
382
+ *
383
+ * @throws {Error} If the `parameters` descriptor is
384
+ * missing or is not a valid Parameter object.
385
+ */
386
+ export class VariadicOption extends Option {
387
+ /**
388
+ * Creates a VariadicOption instance.
389
+ *
390
+ * @param {Object} options - Configuration object.
391
+ * @constructor
392
+ */
393
+ constructor (options) {
394
+ options = merge ({
395
+ 'min': 0
396
+ }, options);
397
+
398
+ assertType(options.min, 'number', 'options.min');
399
+ assertTypes (options.parameters, ['undefined', Parameter], 'options.parameters');
400
+
401
+ super (options);
402
+ this.min = options.min;
403
+ this.parameters = options.parameters;
404
+ }
405
+
406
+ /**
407
+ * @returns {string} The constant representing the
408
+ * VARIADIC type.
409
+ */
410
+ get type () {
411
+ return OPTION_TYPE.VARIADIC;
412
+ }
413
+
414
+ /**
415
+ * Parses the input values, collecting them and
416
+ * ensuring each value adheres to the rules defined
417
+ * in the Parameter descriptor.
418
+ *
419
+ * @param {Array<mixed>} value - The array of raw values
420
+ * provided by the command line.
421
+ * @returns {Array<mixed>} The array of values after
422
+ * successful coercion and validation.
423
+ */
424
+ parse (value) {
425
+ if (!(this.parameters instanceof Parameter)) {
426
+ return value;
427
+ }
428
+ value = this.parameters.coerce (value);
429
+ this.parameters.validate(value);
430
+ return value;
431
+ }
432
+ }
433
+
434
+ function autodetectType (options) {
435
+ const mapping = [
436
+ ['parameters', OPTION_TYPE.VARIADIC],
437
+ ['parameter', OPTION_TYPE.VALUE],
438
+ ['initial', OPTION_TYPE.COUNTER],
439
+ ['default', OPTION_TYPE.VALUE],
440
+ ['min', OPTION_TYPE.VARIADIC],
441
+ ['max', OPTION_TYPE.VARIADIC]
442
+ ];
443
+
444
+ let t;
445
+ for (const m of mapping) {
446
+ const [name, type] = m;
447
+ if (typeof options[name] !== 'undefined') {
448
+ if (typeof t === 'string' && (t != type)) {
449
+ throw new Error ('Ambiguous option descriptor. Could be ' + t + ' or ' + type);
450
+ }
451
+ t = type;
452
+ }
453
+ }
454
+
455
+ return t || OPTION_TYPE.SWITCH;
456
+ }
457
+
458
+ /**
459
+ * @function createOption
460
+ * @description A factory function that creates and
461
+ * configures the appropriate option instance based on
462
+ * the provided type and configuration. This handles the
463
+ * runtime instantiation of Option subclasses.
464
+ *
465
+ * @param {(string|Object)} type - The type of option
466
+ * to create. This can be a raw string constant
467
+ * (e.g., 'switch') or a configuration object.
468
+ * @param {Object} [options] - Optional configuration
469
+ * object, containing settings for the option's
470
+ * behavior (e.g., names, required status).
471
+ *
472
+ * @returns {SwitchOption|CounterOption|ValueOption|VariadicOption}
473
+ * A fully initialized and configured option instance.
474
+ *
475
+ * @throws {Error} If the provided option type is
476
+ * unrecognized or unsupported by this factory.
477
+ */
478
+ export function createOption () {
479
+ let type = arguments[0];
480
+ let options = arguments[1] || {};
481
+
482
+ if (typeof type === 'object') {
483
+ options = type;
484
+ type = options.type;
485
+ }
486
+
487
+ if (typeof type !== 'string') {
488
+ type = autodetectType(options || {});
489
+ }
490
+
491
+ switch (type) {
492
+ case OPTION_TYPE.SWITCH: return new SwitchOption (options);
493
+ case OPTION_TYPE.COUNTER: return new CounterOption(options);
494
+ case OPTION_TYPE.VALUE: {
495
+ transformParameterProperty.apply (this, [options, 'parameter']);
496
+ return new ValueOption (options);
497
+ }
498
+ case OPTION_TYPE.VARIADIC: {
499
+ transformParameterProperty.apply (this, [options, 'parameters']);
500
+ return new VariadicOption(options);
501
+ }
502
+ default:
503
+ throw new Error ('Invalid option type: ' + type);
504
+ }
505
+ }
506
+
507
+ export default {
508
+ OPTION_TYPE,
509
+ Option,
510
+ SwitchOption,
511
+ CounterOption,
512
+ ValueOption,
513
+ VariadicOption,
514
+ createOption
515
+ };