@flowscripter/dynamic-cli-framework 1.0.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.
Files changed (133) hide show
  1. package/.github/workflows/check-bun-dependencies.yml +9 -0
  2. package/.github/workflows/lint-pr-message.yml +11 -0
  3. package/.github/workflows/release-bun-library.yml +8 -0
  4. package/.github/workflows/validate-bun-library-pr.yml +8 -0
  5. package/LICENSE +21 -0
  6. package/README.md +1475 -0
  7. package/bun.lock +63 -0
  8. package/index.ts +89 -0
  9. package/package.json +23 -0
  10. package/src/api/CLI.ts +17 -0
  11. package/src/api/CLIConfig.ts +19 -0
  12. package/src/api/Context.ts +25 -0
  13. package/src/api/RunResult.ts +142 -0
  14. package/src/api/argument/Argument.ts +36 -0
  15. package/src/api/argument/ArgumentValueTypes.ts +87 -0
  16. package/src/api/argument/ComplexOption.ts +31 -0
  17. package/src/api/argument/GlobalCommandArgument.ts +17 -0
  18. package/src/api/argument/Option.ts +32 -0
  19. package/src/api/argument/Positional.ts +27 -0
  20. package/src/api/argument/SubCommandArgument.ts +20 -0
  21. package/src/api/command/Command.ts +22 -0
  22. package/src/api/command/GlobalCommand.ts +33 -0
  23. package/src/api/command/GlobalModifierCommand.ts +12 -0
  24. package/src/api/command/GroupCommand.ts +20 -0
  25. package/src/api/command/SubCommand.ts +41 -0
  26. package/src/api/command/UsageExample.ts +21 -0
  27. package/src/api/service/ServiceProvider.ts +52 -0
  28. package/src/api/service/core/AsciiBannerGeneratorService.ts +35 -0
  29. package/src/api/service/core/KeyValueService.ts +28 -0
  30. package/src/api/service/core/PrinterService.ts +266 -0
  31. package/src/api/service/core/ShutdownService.ts +12 -0
  32. package/src/api/service/core/SyntaxHighlighterService.ts +42 -0
  33. package/src/cli/BaseCLI.ts +312 -0
  34. package/src/cli/DefaultRuntimeCLI.ts +54 -0
  35. package/src/command/MultiCommandCliHelpCommand.ts +394 -0
  36. package/src/command/SingleCommandCliHelpCommand.ts +183 -0
  37. package/src/command/UsageCommand.ts +35 -0
  38. package/src/command/VersionCommand.ts +22 -0
  39. package/src/launcher.ts +127 -0
  40. package/src/runtime/DefaultContext.ts +32 -0
  41. package/src/runtime/argument/ArgumentTypeGuards.ts +8 -0
  42. package/src/runtime/command/CommandTypeGuards.ts +30 -0
  43. package/src/runtime/command/CommandValidator.ts +549 -0
  44. package/src/runtime/parser.ts +219 -0
  45. package/src/runtime/registry/CommandRegistry.ts +121 -0
  46. package/src/runtime/registry/DefaultCommandRegistry.ts +318 -0
  47. package/src/runtime/registry/DefaultServiceProviderRegistry.ts +26 -0
  48. package/src/runtime/registry/ServiceProviderRegistry.ts +11 -0
  49. package/src/runtime/runner.ts +697 -0
  50. package/src/runtime/scanner.ts +417 -0
  51. package/src/runtime/values/ValuePopulationResult.ts +41 -0
  52. package/src/runtime/values/argumentValueMerge.ts +201 -0
  53. package/src/runtime/values/argumentValueValidation.ts +610 -0
  54. package/src/runtime/values/globalCommandValuePopulation.ts +96 -0
  55. package/src/runtime/values/subCommandValuePopulation.ts +680 -0
  56. package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +45 -0
  57. package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +38 -0
  58. package/src/service/asciiBannerGenerator/standard.flf.json +3 -0
  59. package/src/service/banner/BannerServiceProvider.ts +99 -0
  60. package/src/service/banner/command/NoBannerCommand.ts +42 -0
  61. package/src/service/configuration/ConfigurationServiceProvider.ts +571 -0
  62. package/src/service/configuration/DefaultKeyValueService.ts +60 -0
  63. package/src/service/configuration/command/ConfigCommand.ts +43 -0
  64. package/src/service/configuration/command/DumpConfigCommand.ts +47 -0
  65. package/src/service/printer/DefaultPrinterService.ts +256 -0
  66. package/src/service/printer/PrinterServiceProvider.ts +61 -0
  67. package/src/service/printer/command/DarkModeCommand.ts +43 -0
  68. package/src/service/printer/command/LogLevelCommand.ts +68 -0
  69. package/src/service/printer/command/NoColorCommand.ts +43 -0
  70. package/src/service/printer/terminal/Ansi.ts +10 -0
  71. package/src/service/printer/terminal/Color.ts +14 -0
  72. package/src/service/printer/terminal/Progress.ts +373 -0
  73. package/src/service/printer/terminal/Spinner.ts +100 -0
  74. package/src/service/printer/terminal/Styler.ts +5 -0
  75. package/src/service/printer/terminal/Terminal.ts +8 -0
  76. package/src/service/printer/terminal/Theme.ts +34 -0
  77. package/src/service/printer/terminal/TtyStyler.ts +39 -0
  78. package/src/service/printer/terminal/TtyTerminal.ts +65 -0
  79. package/src/service/shutdown/DefaultShutdownService.ts +9 -0
  80. package/src/service/shutdown/ShutdownServiceProvider.ts +55 -0
  81. package/src/service/syntaxHighlighter/DefaultSyntaxHighlighterService.ts +52 -0
  82. package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +51 -0
  83. package/src/util/configHelper.ts +95 -0
  84. package/src/util/envVarHelper.ts +338 -0
  85. package/src/util/helpHelper.ts +751 -0
  86. package/src/util/logger.ts +84 -0
  87. package/src/util/runnerHelper.ts +112 -0
  88. package/tests/cli/BaseCLI.test.ts +344 -0
  89. package/tests/command/MultiCommandCliHelpCommand.test.ts +650 -0
  90. package/tests/command/SingleCommandCliHelpCommand.test.ts +384 -0
  91. package/tests/command/UsageCommand.test.ts +53 -0
  92. package/tests/command/VersionCommand.test.ts +36 -0
  93. package/tests/fixtures/CLIConfig.ts +9 -0
  94. package/tests/fixtures/Command.ts +229 -0
  95. package/tests/fixtures/CommandRegistry.ts +8 -0
  96. package/tests/fixtures/ConfigurationServiceProvider.ts +28 -0
  97. package/tests/fixtures/Context.ts +33 -0
  98. package/tests/fixtures/ServiceProviderRegistry.ts +16 -0
  99. package/tests/fixtures/StreamString.ts +37 -0
  100. package/tests/fixtures/util.ts +41 -0
  101. package/tests/runtime/DefaultContext.test.ts +20 -0
  102. package/tests/runtime/argument/ArgumentTypeGuards.test.ts +49 -0
  103. package/tests/runtime/command/CommandTypeGuards.test.ts +43 -0
  104. package/tests/runtime/command/CommandValidator.test.ts +816 -0
  105. package/tests/runtime/parser.test.ts +766 -0
  106. package/tests/runtime/registry/DefaultCommandRegistry.test.ts +166 -0
  107. package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +42 -0
  108. package/tests/runtime/runner.test.ts +1298 -0
  109. package/tests/runtime/scanner.test.ts +1113 -0
  110. package/tests/runtime/values/argumentValueMerge.test.ts +314 -0
  111. package/tests/runtime/values/argumentValueValidation.test.ts +1020 -0
  112. package/tests/runtime/values/globalCommandValuePopulation.test.ts +662 -0
  113. package/tests/runtime/values/subCommandValuePopulation.test.ts +2658 -0
  114. package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +22 -0
  115. package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +70 -0
  116. package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +22 -0
  117. package/tests/service/asciiBannerGenerator/small.flf.json +3 -0
  118. package/tests/service/configuration/ConfigurationServiceProvider.test.ts +249 -0
  119. package/tests/service/configuration/DefaultKeyValueService.test.ts +65 -0
  120. package/tests/service/launchBanner/BannerServiceProvider.test.ts +49 -0
  121. package/tests/service/launchBanner/__snapshots__/BannerServiceProvider.test.ts.snap +3 -0
  122. package/tests/service/launchBanner/command/NoBannerCommand.test.ts +21 -0
  123. package/tests/service/printer/DefaultPrinterService.test.ts +409 -0
  124. package/tests/service/printer/PrinterServiceProvider.test.ts +29 -0
  125. package/tests/service/printer/command/LogLevelCommand.test.ts +58 -0
  126. package/tests/service/printer/terminal/Progress.test.ts +40 -0
  127. package/tests/service/printer/terminal/Spinner.test.ts +57 -0
  128. package/tests/service/shutdown/ShutdownServiceProvider.test.ts +13 -0
  129. package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +123 -0
  130. package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +41 -0
  131. package/tests/util/configHelper.test.ts +275 -0
  132. package/tests/util/envVarHelper.test.ts +318 -0
  133. package/tests/util/logger.test.ts +10 -0
@@ -0,0 +1,610 @@
1
+ import type {
2
+ ArgumentSingleValueType,
3
+ ArgumentValues,
4
+ ArgumentValueType,
5
+ PopulatedArgumentSingleValueType,
6
+ PopulatedArgumentValues,
7
+ PopulatedArgumentValueType,
8
+ } from "../../api/argument/ArgumentValueTypes.ts";
9
+ import {
10
+ ArgumentValueTypeName,
11
+ ComplexValueTypeName,
12
+ } from "../../api/argument/ArgumentValueTypes.ts";
13
+ import type Positional from "../../api/argument/Positional.ts";
14
+ import type Option from "../../api/argument/Option.ts";
15
+ import type ComplexOption from "../../api/argument/ComplexOption.ts";
16
+ import { isComplexOption } from "../argument/ArgumentTypeGuards.ts";
17
+ import type { InvalidArgument } from "../../api/RunResult.ts";
18
+ import { InvalidArgumentReason } from "../../api/RunResult.ts";
19
+ import type SubCommandArgument from "../../api/argument/SubCommandArgument.ts";
20
+ import type Argument from "../../api/argument/Argument.ts";
21
+ import type GlobalCommand from "../../api/command/GlobalCommand.ts";
22
+
23
+ interface ValidationResult {
24
+ invalidArgument?: InvalidArgument;
25
+ }
26
+
27
+ interface SingleValueValidationResult extends ValidationResult {
28
+ validValue?: ArgumentSingleValueType;
29
+ }
30
+
31
+ interface ArrayValueValidationResult extends ValidationResult {
32
+ validValue?: Array<ArgumentSingleValueType | ArgumentValues>;
33
+ }
34
+
35
+ interface ObjectValueValidationResult extends ValidationResult {
36
+ validValue?: ArgumentValues;
37
+ }
38
+
39
+ /**
40
+ * Validates the provided primitive value against the provided {@link Argument}.
41
+ *
42
+ * @param argument the {@link Argument} to validate against.
43
+ * @param value the value (if any) for the {@link Argument}.
44
+ */
45
+ function validatePrimitiveValue(
46
+ argument: Argument,
47
+ value: PopulatedArgumentSingleValueType,
48
+ ): SingleValueValidationResult {
49
+ let convertedValue;
50
+
51
+ if (value === undefined) {
52
+ return {
53
+ invalidArgument: {
54
+ argument,
55
+ reason: InvalidArgumentReason.MISSING_VALUE,
56
+ },
57
+ };
58
+ }
59
+
60
+ // type check and conversion
61
+ let castValue: string | undefined;
62
+ switch (argument.type) {
63
+ case ArgumentValueTypeName.BOOLEAN:
64
+ if ((value === true) || (value === false)) {
65
+ convertedValue = value;
66
+ break;
67
+ }
68
+ if (typeof value === "string") {
69
+ castValue = value as string;
70
+ castValue = castValue.toLowerCase();
71
+ }
72
+ if (castValue !== "true" && castValue !== "false") {
73
+ return {
74
+ invalidArgument: {
75
+ argument,
76
+ value,
77
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
78
+ },
79
+ };
80
+ }
81
+ convertedValue = castValue === "true";
82
+ break;
83
+ case ArgumentValueTypeName.INTEGER:
84
+ if (!Number.isInteger(Number(value))) {
85
+ return {
86
+ invalidArgument: {
87
+ argument,
88
+ value,
89
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
90
+ },
91
+ };
92
+ }
93
+ convertedValue = Number(value);
94
+ break;
95
+ case ArgumentValueTypeName.NUMBER:
96
+ if (!Number.isFinite(Number(value))) {
97
+ return {
98
+ invalidArgument: {
99
+ argument,
100
+ value,
101
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
102
+ },
103
+ };
104
+ }
105
+ convertedValue = Number(value);
106
+ break;
107
+ case ArgumentValueTypeName.STRING:
108
+ default:
109
+ convertedValue = String(value);
110
+ break;
111
+ }
112
+
113
+ // check if the value is valid
114
+ if (argument.allowableValues && !argument.allowableValues.includes(value)) {
115
+ return {
116
+ invalidArgument: {
117
+ argument,
118
+ value,
119
+ reason: InvalidArgumentReason.ILLEGAL_VALUE,
120
+ },
121
+ };
122
+ }
123
+ return { validValue: convertedValue };
124
+ }
125
+
126
+ function validateArrayValue(
127
+ subCommandArgument: SubCommandArgument | ComplexOption,
128
+ arrayValue: Array<
129
+ PopulatedArgumentSingleValueType | PopulatedArgumentValues | undefined
130
+ >,
131
+ ): ArrayValueValidationResult {
132
+ const convertedArrayValue: Array<ArgumentSingleValueType | ArgumentValues> =
133
+ [];
134
+
135
+ for (let i = 0; i < arrayValue.length; i += 1) {
136
+ const singleValue = arrayValue[i];
137
+
138
+ if (singleValue === undefined) {
139
+ return {
140
+ validValue: convertedArrayValue,
141
+ invalidArgument: {
142
+ argument: subCommandArgument,
143
+ name: `[${i}]`,
144
+ reason: InvalidArgumentReason.ILLEGAL_SPARSE_ARRAY,
145
+ },
146
+ };
147
+ }
148
+
149
+ let validationResult:
150
+ | SingleValueValidationResult
151
+ | ObjectValueValidationResult;
152
+
153
+ if (Array.isArray(singleValue)) {
154
+ throw new Error(
155
+ `Unexpected array value as array member, arrays of arrays are not supported. Argument: ${subCommandArgument.name}[${i}]`,
156
+ );
157
+ } else if (typeof singleValue === "object") {
158
+ if (subCommandArgument.type !== ComplexValueTypeName.COMPLEX) {
159
+ return {
160
+ validValue: convertedArrayValue,
161
+ invalidArgument: {
162
+ argument: subCommandArgument,
163
+ name: `[${i}]`,
164
+ value: arrayValue,
165
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
166
+ },
167
+ };
168
+ }
169
+ validationResult = validateObjectValue(
170
+ subCommandArgument as ComplexOption,
171
+ singleValue,
172
+ );
173
+ } // if not array and not object, then must be primitive
174
+ else {
175
+ if (isComplexOption(subCommandArgument)) {
176
+ return {
177
+ validValue: convertedArrayValue,
178
+ invalidArgument: {
179
+ argument: subCommandArgument,
180
+ name: `[${i}]`,
181
+ value: arrayValue,
182
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
183
+ },
184
+ };
185
+ }
186
+ validationResult = validatePrimitiveValue(
187
+ subCommandArgument as SubCommandArgument,
188
+ singleValue,
189
+ );
190
+ }
191
+
192
+ if (validationResult.validValue !== undefined) {
193
+ convertedArrayValue.push(validationResult.validValue);
194
+ }
195
+ if (validationResult.invalidArgument) {
196
+ // fast fail
197
+ if (validationResult.invalidArgument.value !== undefined) {
198
+ return {
199
+ validValue: convertedArrayValue,
200
+ invalidArgument: {
201
+ argument: validationResult.invalidArgument.argument,
202
+ name: `[${i}]${
203
+ validationResult.invalidArgument.name
204
+ ? validationResult.invalidArgument.name
205
+ : ""
206
+ }`,
207
+ value: validationResult.invalidArgument.value,
208
+ reason: validationResult.invalidArgument.reason,
209
+ },
210
+ };
211
+ } else {
212
+ return {
213
+ validValue: convertedArrayValue,
214
+ invalidArgument: {
215
+ argument: validationResult.invalidArgument.argument,
216
+ name: `[${i}]${
217
+ validationResult.invalidArgument.name
218
+ ? validationResult.invalidArgument.name
219
+ : ""
220
+ }`,
221
+ reason: validationResult.invalidArgument.reason,
222
+ },
223
+ };
224
+ }
225
+ }
226
+ }
227
+ return {
228
+ validValue: convertedArrayValue,
229
+ };
230
+ }
231
+
232
+ function validateObjectValue(
233
+ argument: ComplexOption,
234
+ objectValue: PopulatedArgumentValues,
235
+ ): ObjectValueValidationResult {
236
+ const convertedObjectValue: ArgumentValues = {};
237
+
238
+ for (let i = 0; i < argument.properties.length; i++) {
239
+ const propertyArg = argument.properties[i];
240
+ const propertyValue = objectValue[propertyArg.name];
241
+
242
+ if (propertyValue === undefined) {
243
+ return {
244
+ validValue: convertedObjectValue,
245
+ invalidArgument: {
246
+ argument: propertyArg,
247
+ name: `.${propertyArg.name}`,
248
+ reason: InvalidArgumentReason.MISSING_VALUE,
249
+ },
250
+ };
251
+ }
252
+
253
+ let validationResult:
254
+ | SingleValueValidationResult
255
+ | ObjectValueValidationResult
256
+ | ArrayValueValidationResult;
257
+
258
+ if (Array.isArray(propertyValue)) {
259
+ if (!propertyArg.isArray) {
260
+ return {
261
+ validValue: convertedObjectValue,
262
+ invalidArgument: {
263
+ argument: propertyArg,
264
+ name: `.${propertyArg.name}`,
265
+ value: propertyValue,
266
+ reason: InvalidArgumentReason.ILLEGAL_MULTIPLE_VALUES,
267
+ },
268
+ };
269
+ }
270
+ validationResult = validateArrayValue(propertyArg, propertyValue);
271
+ } else if (typeof propertyValue === "object") {
272
+ if (propertyArg.type !== ComplexValueTypeName.COMPLEX) {
273
+ return {
274
+ validValue: convertedObjectValue,
275
+ invalidArgument: {
276
+ argument: propertyArg,
277
+ name: `.${propertyArg.name}`,
278
+ value: propertyValue,
279
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
280
+ },
281
+ };
282
+ }
283
+ validationResult = validateObjectValue(
284
+ propertyArg as ComplexOption,
285
+ propertyValue as PopulatedArgumentValues,
286
+ );
287
+ } // if not array and not object, then must be primitive
288
+ else {
289
+ if (isComplexOption(propertyArg)) {
290
+ return {
291
+ validValue: convertedObjectValue,
292
+ invalidArgument: {
293
+ argument: propertyArg,
294
+ name: `.${propertyArg.name}`,
295
+ value: propertyValue,
296
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
297
+ },
298
+ };
299
+ }
300
+ validationResult = validatePrimitiveValue(
301
+ propertyArg as SubCommandArgument,
302
+ propertyValue,
303
+ );
304
+ }
305
+ if (validationResult.validValue !== undefined) {
306
+ convertedObjectValue[propertyArg.name] = validationResult.validValue as
307
+ | ArgumentValueType
308
+ | ArgumentValues
309
+ | Array<ArgumentValues>;
310
+ }
311
+ // fast fail
312
+ if (validationResult.invalidArgument !== undefined) {
313
+ if (validationResult.invalidArgument.value !== undefined) {
314
+ return {
315
+ validValue: convertedObjectValue,
316
+ invalidArgument: {
317
+ argument: validationResult.invalidArgument.argument,
318
+ name: `.${propertyArg.name}${
319
+ validationResult.invalidArgument.name
320
+ ? validationResult.invalidArgument.name
321
+ : ""
322
+ }`,
323
+ value: validationResult.invalidArgument.value,
324
+ reason: validationResult.invalidArgument.reason,
325
+ },
326
+ };
327
+ } else {
328
+ return {
329
+ validValue: convertedObjectValue,
330
+ invalidArgument: {
331
+ argument: validationResult.invalidArgument.argument,
332
+ name: `.${propertyArg.name}${
333
+ validationResult.invalidArgument.name
334
+ ? validationResult.invalidArgument.name
335
+ : ""
336
+ }`,
337
+ reason: validationResult.invalidArgument.reason,
338
+ },
339
+ };
340
+ }
341
+ }
342
+ }
343
+ return {
344
+ validValue: convertedObjectValue,
345
+ };
346
+ }
347
+
348
+ function doSubCommandArgumentValidation(
349
+ argument: SubCommandArgument | ComplexOption,
350
+ value:
351
+ | PopulatedArgumentValueType
352
+ | PopulatedArgumentValues
353
+ | Array<PopulatedArgumentValues | undefined>,
354
+ isArray: boolean,
355
+ isOptional: boolean,
356
+ invalidArguments: Array<InvalidArgument>,
357
+ ):
358
+ | PopulatedArgumentValueType
359
+ | PopulatedArgumentValues
360
+ | Array<PopulatedArgumentValues> {
361
+ // if there is a value, check if it is valid
362
+ if (value !== undefined) {
363
+ let validationResult;
364
+
365
+ if (Array.isArray(value)) {
366
+ if (!isArray) {
367
+ invalidArguments.push({
368
+ argument,
369
+ name: argument.name,
370
+ value,
371
+ reason: InvalidArgumentReason.ILLEGAL_MULTIPLE_VALUES,
372
+ });
373
+ return undefined;
374
+ }
375
+ validationResult = validateArrayValue(argument, value);
376
+ } else if (typeof value === "object") {
377
+ if (!isComplexOption(argument)) {
378
+ invalidArguments.push({
379
+ argument,
380
+ name: argument.name,
381
+ value,
382
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
383
+ });
384
+ return undefined;
385
+ }
386
+ validationResult = validateObjectValue(argument, value);
387
+ } else {
388
+ if (isComplexOption(argument)) {
389
+ invalidArguments.push({
390
+ argument,
391
+ name: argument.name,
392
+ value,
393
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
394
+ });
395
+ return undefined;
396
+ }
397
+ validationResult = validatePrimitiveValue(
398
+ argument as SubCommandArgument,
399
+ value,
400
+ );
401
+ }
402
+
403
+ if (validationResult.invalidArgument !== undefined) {
404
+ if (validationResult.invalidArgument.value !== undefined) {
405
+ validationResult.invalidArgument = {
406
+ argument: validationResult.invalidArgument.argument,
407
+ name: `${argument.name}${
408
+ validationResult.invalidArgument.name
409
+ ? validationResult.invalidArgument.name
410
+ : ""
411
+ }`,
412
+ value: validationResult.invalidArgument.value,
413
+ reason: validationResult.invalidArgument.reason,
414
+ };
415
+ } else {
416
+ validationResult.invalidArgument = {
417
+ argument: validationResult.invalidArgument.argument,
418
+ name: `${argument.name}${
419
+ validationResult.invalidArgument.name
420
+ ? validationResult.invalidArgument.name
421
+ : ""
422
+ }`,
423
+ reason: validationResult.invalidArgument.reason,
424
+ };
425
+ }
426
+ }
427
+
428
+ if (validationResult.invalidArgument) {
429
+ invalidArguments.push(validationResult.invalidArgument);
430
+ return undefined;
431
+ }
432
+ return validationResult.validValue as
433
+ | PopulatedArgumentValueType
434
+ | PopulatedArgumentValues
435
+ | undefined;
436
+ }
437
+
438
+ // if there is no value, check if it was optional
439
+ if (!isOptional) {
440
+ invalidArguments.push({
441
+ argument: argument,
442
+ name: argument.name,
443
+ reason: InvalidArgumentReason.MISSING_VALUE,
444
+ });
445
+ }
446
+ return undefined;
447
+ }
448
+
449
+ /**
450
+ * Validates the provided value against the provided {@link Option}.
451
+ *
452
+ * @param option the {@link Option} to validate against.
453
+ * @param value the value (if any) for the {@link Option}.
454
+ * @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
455
+ */
456
+ export function validateOptionValue(
457
+ option: Option | ComplexOption,
458
+ value:
459
+ | PopulatedArgumentValueType
460
+ | PopulatedArgumentValues
461
+ | Array<PopulatedArgumentValues | undefined>,
462
+ invalidArguments: Array<InvalidArgument>,
463
+ ):
464
+ | PopulatedArgumentValueType
465
+ | PopulatedArgumentValues
466
+ | Array<PopulatedArgumentValues> {
467
+ return doSubCommandArgumentValidation(
468
+ option,
469
+ value,
470
+ option.isArray || false,
471
+ option.isOptional || false,
472
+ invalidArguments,
473
+ );
474
+ }
475
+
476
+ /**
477
+ * Validates the provided value against the provided {@link Positional}.
478
+ *
479
+ * @param positional the {@link Positional} to validate against.
480
+ * @param value the value (if any) for the {@link Positional}.
481
+ * @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
482
+ */
483
+ export function validatePositionalValue(
484
+ positional: Positional,
485
+ value: PopulatedArgumentValueType,
486
+ invalidArguments: Array<InvalidArgument>,
487
+ ): PopulatedArgumentValueType {
488
+ return doSubCommandArgumentValidation(
489
+ positional,
490
+ value,
491
+ positional.isVarargMultiple || false,
492
+ positional.isVarargOptional || false,
493
+ invalidArguments,
494
+ ) as PopulatedArgumentValueType;
495
+ }
496
+
497
+ /**
498
+ * Validates the provided value against the {@link GlobalCommandArgument} provided by the {@link GlobalCommand} and returns the validated
499
+ * value or undefined if the value was invalid.
500
+ *
501
+ * @param globalCommand the {@link GlobalCommand} providing the {@link GlobalCommandArgument}.
502
+ * @param value the value (if any) for the {@link GlobalCommandArgument}.
503
+ * @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
504
+ */
505
+ export function validateGlobalCommandArgumentValue(
506
+ globalCommand: GlobalCommand,
507
+ value: PopulatedArgumentSingleValueType,
508
+ invalidArguments: Array<InvalidArgument>,
509
+ ): PopulatedArgumentSingleValueType {
510
+ // if this function is called it is because the argument is defined
511
+ const globalCommandArgument = globalCommand.argument!;
512
+
513
+ // if there is a value, check if it is valid
514
+ if (value !== undefined) {
515
+ const validationResult = validatePrimitiveValue(
516
+ globalCommandArgument,
517
+ value,
518
+ );
519
+
520
+ if (validationResult.invalidArgument) {
521
+ if (validationResult.invalidArgument.value !== undefined) {
522
+ invalidArguments.push({
523
+ argument: globalCommandArgument,
524
+ name: globalCommand.name,
525
+ value: validationResult.invalidArgument.value,
526
+ reason: validationResult.invalidArgument.reason,
527
+ });
528
+ } else {
529
+ invalidArguments.push({
530
+ argument: globalCommandArgument,
531
+ name: globalCommand.name,
532
+ reason: validationResult.invalidArgument.reason,
533
+ });
534
+ }
535
+ return undefined;
536
+ }
537
+
538
+ return validationResult.validValue as PopulatedArgumentSingleValueType;
539
+ }
540
+
541
+ // if there is no value, check if it was optional
542
+ if (!globalCommandArgument.isOptional) {
543
+ invalidArguments.push({
544
+ argument: globalCommandArgument,
545
+ name: globalCommand.name,
546
+ reason: InvalidArgumentReason.MISSING_VALUE,
547
+ });
548
+ }
549
+ return undefined;
550
+ }
551
+
552
+ export function getInvalidArgumentString(
553
+ invalidArgument: InvalidArgument,
554
+ skipArgName: boolean,
555
+ ): string {
556
+ let nameString = "";
557
+ if (!skipArgName && (invalidArgument.name !== undefined)) {
558
+ nameString = invalidArgument.name;
559
+ }
560
+ let valueString = "";
561
+ if (invalidArgument.value !== undefined) {
562
+ valueString = `'${invalidArgument.value}'`;
563
+ }
564
+ let argString = "";
565
+ if (nameString !== "") {
566
+ if (valueString !== "") {
567
+ argString = `${nameString}=${valueString} `;
568
+ } else {
569
+ argString = `${nameString} `;
570
+ }
571
+ } else if (valueString !== "") {
572
+ argString = `${valueString} `;
573
+ }
574
+
575
+ let invalidString;
576
+
577
+ switch (invalidArgument.reason) {
578
+ case InvalidArgumentReason.MISSING_VALUE:
579
+ invalidString = "(missing value)";
580
+ break;
581
+ case InvalidArgumentReason.INCORRECT_VALUE_TYPE:
582
+ invalidString = "(incorrect type)";
583
+ break;
584
+ case InvalidArgumentReason.ILLEGAL_MULTIPLE_VALUES:
585
+ invalidString = "(illegal multiple values)";
586
+ break;
587
+ case InvalidArgumentReason.ILLEGAL_VALUE:
588
+ invalidString = "(illegal value)";
589
+ break;
590
+ case InvalidArgumentReason.ILLEGAL_SPARSE_ARRAY:
591
+ invalidString = "(sparse array values)";
592
+ break;
593
+ case InvalidArgumentReason.UNKNOWN_PROPERTY:
594
+ invalidString = "(unknown property)";
595
+ break;
596
+ case InvalidArgumentReason.NESTING_DEPTH_EXCEEDED:
597
+ invalidString = "(nesting depth exceeded)";
598
+ break;
599
+ case InvalidArgumentReason.ARRAY_SIZE_EXCEEDED:
600
+ invalidString = "(array size exceeded)";
601
+ break;
602
+ case InvalidArgumentReason.OPTION_IS_COMPLEX:
603
+ invalidString = "(specified option is complex)";
604
+ break;
605
+ default:
606
+ invalidString = "";
607
+ break;
608
+ }
609
+ return `${argString}${invalidString}`;
610
+ }
@@ -0,0 +1,96 @@
1
+ import type GlobalCommand from "../../api/command/GlobalCommand.ts";
2
+ import type { GlobalCommandValuePopulationResult } from "./ValuePopulationResult.ts";
3
+ import {
4
+ ArgumentValueTypeName,
5
+ } from "../../api/argument/ArgumentValueTypes.ts";
6
+ import type {
7
+ PopulatedArgumentSingleValueType,
8
+ } from "../../api/argument/ArgumentValueTypes.ts";
9
+ import getLogger from "../../util/logger.ts";
10
+ import { InvalidArgumentReason } from "../../api/RunResult.ts";
11
+
12
+ const logger = getLogger("globalCommandValuePopulation");
13
+
14
+ /**
15
+ * Attempt to populate a {@link ArgumentValueType} for the provided {@link GlobalCommand} using the provided potential args.
16
+ *
17
+ * @param globalCommand the {@link GlobalCommand} for which {@link ArgumentValues} values should be populated.
18
+ * @param potentialArgs the potential args to use for population.
19
+ * @param defaultValue optional default value to use for population before parsing the provided arguments.
20
+ */
21
+ export default function populateGlobalCommandValue(
22
+ globalCommand: GlobalCommand,
23
+ potentialArgs: ReadonlyArray<string>,
24
+ defaultValue: PopulatedArgumentSingleValueType,
25
+ ): GlobalCommandValuePopulationResult {
26
+ logger.debug(() => {
27
+ const message =
28
+ `Populating value for global command: '${globalCommand.name}' using potential args: ${
29
+ potentialArgs.join(" ")
30
+ }`;
31
+ if (defaultValue !== undefined) {
32
+ return `${message} and configured value: '${defaultValue}'`;
33
+ }
34
+ return message;
35
+ });
36
+ const argument = globalCommand.argument!;
37
+
38
+ // default to defined argument default value
39
+ let populatedArgumentValue = argument.defaultValue;
40
+
41
+ // override with provided configured value
42
+ if (defaultValue !== undefined) {
43
+ populatedArgumentValue = defaultValue;
44
+ }
45
+ let unusedArgs: Array<string> = [];
46
+
47
+ if (potentialArgs.length > 0) {
48
+ const firstPotentialArg = potentialArgs[0];
49
+
50
+ // don't use potentialArg if it cannot be a boolean value...
51
+ if (argument.type === ArgumentValueTypeName.BOOLEAN) {
52
+ const firstPotentialArgLower = firstPotentialArg.toLowerCase();
53
+
54
+ if (
55
+ (firstPotentialArgLower !== "true") &&
56
+ (firstPotentialArgLower !== "false")
57
+ ) {
58
+ // ...instead, use the fact that the argument is present as the value true
59
+ return {
60
+ populatedArgumentValue: "true",
61
+ unusedArgs: potentialArgs,
62
+ };
63
+ }
64
+ }
65
+ // otherwise override with parsed argument value
66
+ populatedArgumentValue = firstPotentialArg;
67
+ unusedArgs = potentialArgs.slice(1);
68
+ } // check if argument type is boolean and therefore command being specified is an implicit value of true
69
+ else if (
70
+ (argument.type === ArgumentValueTypeName.BOOLEAN) &&
71
+ ((populatedArgumentValue === undefined) ||
72
+ (argument.defaultValue === false))
73
+ ) {
74
+ return {
75
+ populatedArgumentValue: "true",
76
+ unusedArgs,
77
+ };
78
+ }
79
+
80
+ // check if there is no value and the global command argument is not optional
81
+ if ((populatedArgumentValue === undefined) && !argument.isOptional) {
82
+ return {
83
+ populatedArgumentValue,
84
+ unusedArgs,
85
+ invalidArgument: {
86
+ name: globalCommand.name,
87
+ reason: InvalidArgumentReason.MISSING_VALUE,
88
+ },
89
+ };
90
+ }
91
+
92
+ return {
93
+ populatedArgumentValue,
94
+ unusedArgs,
95
+ };
96
+ }