@jakubmazanec/args 1.0.0-de51f16

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 (151) hide show
  1. package/LICENSE.md +18 -0
  2. package/README.md +84 -0
  3. package/dist/ParsingError.d.ts +4 -0
  4. package/dist/ParsingError.js +11 -0
  5. package/dist/ParsingError.js.map +1 -0
  6. package/dist/ValidationError.d.ts +4 -0
  7. package/dist/ValidationError.js +16 -0
  8. package/dist/ValidationError.js.map +1 -0
  9. package/dist/internals/Checker.d.ts +13 -0
  10. package/dist/internals/Checker.js +118 -0
  11. package/dist/internals/Checker.js.map +1 -0
  12. package/dist/internals/CommandScope.d.ts +14 -0
  13. package/dist/internals/CommandScope.js +48 -0
  14. package/dist/internals/CommandScope.js.map +1 -0
  15. package/dist/internals/OptionScope.d.ts +14 -0
  16. package/dist/internals/OptionScope.js +87 -0
  17. package/dist/internals/OptionScope.js.map +1 -0
  18. package/dist/internals/castValue.d.ts +7 -0
  19. package/dist/internals/castValue.js +25 -0
  20. package/dist/internals/castValue.js.map +1 -0
  21. package/dist/internals/commandToArguments.d.ts +7 -0
  22. package/dist/internals/commandToArguments.js +21 -0
  23. package/dist/internals/commandToArguments.js.map +1 -0
  24. package/dist/internals/createOptionShortNameMap.d.ts +2 -0
  25. package/dist/internals/createOptionShortNameMap.js +11 -0
  26. package/dist/internals/createOptionShortNameMap.js.map +1 -0
  27. package/dist/internals/expandArguments.d.ts +8 -0
  28. package/dist/internals/expandArguments.js +21 -0
  29. package/dist/internals/expandArguments.js.map +1 -0
  30. package/dist/internals/isCommand.d.ts +8 -0
  31. package/dist/internals/isCommand.js +11 -0
  32. package/dist/internals/isCommand.js.map +1 -0
  33. package/dist/internals/isFalseString.d.ts +6 -0
  34. package/dist/internals/isFalseString.js +10 -0
  35. package/dist/internals/isFalseString.js.map +1 -0
  36. package/dist/internals/isLongOption.d.ts +7 -0
  37. package/dist/internals/isLongOption.js +11 -0
  38. package/dist/internals/isLongOption.js.map +1 -0
  39. package/dist/internals/isOptionLike.d.ts +7 -0
  40. package/dist/internals/isOptionLike.js +11 -0
  41. package/dist/internals/isOptionLike.js.map +1 -0
  42. package/dist/internals/isShortOption.d.ts +7 -0
  43. package/dist/internals/isShortOption.js +11 -0
  44. package/dist/internals/isShortOption.js.map +1 -0
  45. package/dist/internals/isShortOptionsGroup.d.ts +8 -0
  46. package/dist/internals/isShortOptionsGroup.js +11 -0
  47. package/dist/internals/isShortOptionsGroup.js.map +1 -0
  48. package/dist/internals/isTrueString.d.ts +6 -0
  49. package/dist/internals/isTrueString.js +10 -0
  50. package/dist/internals/isTrueString.js.map +1 -0
  51. package/dist/internals.d.ts +14 -0
  52. package/dist/internals.js +16 -0
  53. package/dist/internals.js.map +1 -0
  54. package/dist/main.d.ts +4 -0
  55. package/dist/main.js +6 -0
  56. package/dist/main.js.map +1 -0
  57. package/dist/parseArguments.d.ts +10 -0
  58. package/dist/parseArguments.js +244 -0
  59. package/dist/parseArguments.js.map +1 -0
  60. package/dist/types/Arguments.d.ts +22 -0
  61. package/dist/types/Arguments.js +3 -0
  62. package/dist/types/Arguments.js.map +1 -0
  63. package/dist/types/ArgumentsCommand.d.ts +3 -0
  64. package/dist/types/ArgumentsCommand.js +3 -0
  65. package/dist/types/ArgumentsCommand.js.map +1 -0
  66. package/dist/types/ArgumentsOptions.d.ts +14 -0
  67. package/dist/types/ArgumentsOptions.js +3 -0
  68. package/dist/types/ArgumentsOptions.js.map +1 -0
  69. package/dist/types/ArgumentsParameters.d.ts +91 -0
  70. package/dist/types/ArgumentsParameters.js +3 -0
  71. package/dist/types/ArgumentsParameters.js.map +1 -0
  72. package/dist/types/ArgumentsUnknownOptions.d.ts +3 -0
  73. package/dist/types/ArgumentsUnknownOptions.js +3 -0
  74. package/dist/types/ArgumentsUnknownOptions.js.map +1 -0
  75. package/dist/types/Argv.d.ts +2 -0
  76. package/dist/types/Argv.js +3 -0
  77. package/dist/types/Argv.js.map +1 -0
  78. package/dist/types/CommandConfig.d.ts +2 -0
  79. package/dist/types/CommandConfig.js +3 -0
  80. package/dist/types/CommandConfig.js.map +1 -0
  81. package/dist/types/CommandsConfig.d.ts +3 -0
  82. package/dist/types/CommandsConfig.js +3 -0
  83. package/dist/types/CommandsConfig.js.map +1 -0
  84. package/dist/types/LongOptionName.d.ts +2 -0
  85. package/dist/types/LongOptionName.js +3 -0
  86. package/dist/types/LongOptionName.js.map +1 -0
  87. package/dist/types/NormalizedCommandConfig.d.ts +5 -0
  88. package/dist/types/NormalizedCommandConfig.js +3 -0
  89. package/dist/types/NormalizedCommandConfig.js.map +1 -0
  90. package/dist/types/NormalizedCommandsConfig.d.ts +3 -0
  91. package/dist/types/NormalizedCommandsConfig.js +3 -0
  92. package/dist/types/NormalizedCommandsConfig.js.map +1 -0
  93. package/dist/types/OptionConfig.d.ts +220 -0
  94. package/dist/types/OptionConfig.js +3 -0
  95. package/dist/types/OptionConfig.js.map +1 -0
  96. package/dist/types/OptionsConfig.d.ts +3 -0
  97. package/dist/types/OptionsConfig.js +3 -0
  98. package/dist/types/OptionsConfig.js.map +1 -0
  99. package/dist/types/ParameterConfig.d.ts +74 -0
  100. package/dist/types/ParameterConfig.js +3 -0
  101. package/dist/types/ParameterConfig.js.map +1 -0
  102. package/dist/types/ParametersConfig.d.ts +10 -0
  103. package/dist/types/ParametersConfig.js +3 -0
  104. package/dist/types/ParametersConfig.js.map +1 -0
  105. package/dist/types/ParserConfig.d.ts +16 -0
  106. package/dist/types/ParserConfig.js +3 -0
  107. package/dist/types/ParserConfig.js.map +1 -0
  108. package/dist/types/ShortOptionName.d.ts +2 -0
  109. package/dist/types/ShortOptionName.js +3 -0
  110. package/dist/types/ShortOptionName.js.map +1 -0
  111. package/dist/types.d.ts +17 -0
  112. package/dist/types.js +19 -0
  113. package/dist/types.js.map +1 -0
  114. package/package.json +42 -0
  115. package/src/ParsingError.ts +11 -0
  116. package/src/ValidationError.ts +22 -0
  117. package/src/internals/Checker.ts +145 -0
  118. package/src/internals/CommandScope.ts +61 -0
  119. package/src/internals/OptionScope.ts +101 -0
  120. package/src/internals/castValue.ts +36 -0
  121. package/src/internals/commandToArguments.ts +22 -0
  122. package/src/internals/createOptionShortNameMap.ts +13 -0
  123. package/src/internals/expandArguments.ts +22 -0
  124. package/src/internals/isCommand.ts +11 -0
  125. package/src/internals/isFalseString.ts +10 -0
  126. package/src/internals/isLongOption.ts +11 -0
  127. package/src/internals/isOptionLike.ts +11 -0
  128. package/src/internals/isShortOption.ts +11 -0
  129. package/src/internals/isShortOptionsGroup.ts +11 -0
  130. package/src/internals/isTrueString.ts +10 -0
  131. package/src/internals.ts +14 -0
  132. package/src/main.ts +4 -0
  133. package/src/parseArguments.ts +358 -0
  134. package/src/types/Arguments.ts +28 -0
  135. package/src/types/ArgumentsCommand.ts +8 -0
  136. package/src/types/ArgumentsOptions.ts +89 -0
  137. package/src/types/ArgumentsParameters.ts +158 -0
  138. package/src/types/ArgumentsUnknownOptions.ts +6 -0
  139. package/src/types/Argv.ts +2 -0
  140. package/src/types/CommandConfig.ts +2 -0
  141. package/src/types/CommandsConfig.ts +4 -0
  142. package/src/types/LongOptionName.ts +2 -0
  143. package/src/types/NormalizedCommandConfig.ts +5 -0
  144. package/src/types/NormalizedCommandsConfig.ts +4 -0
  145. package/src/types/OptionConfig.ts +279 -0
  146. package/src/types/OptionsConfig.ts +4 -0
  147. package/src/types/ParameterConfig.ts +100 -0
  148. package/src/types/ParametersConfig.ts +17 -0
  149. package/src/types/ParserConfig.ts +21 -0
  150. package/src/types/ShortOptionName.ts +54 -0
  151. package/src/types.ts +17 -0
@@ -0,0 +1,17 @@
1
+ export * from './types/Arguments.js';
2
+ export * from './types/ArgumentsCommand.js';
3
+ export * from './types/ArgumentsOptions.js';
4
+ export * from './types/ArgumentsParameters.js';
5
+ export * from './types/ArgumentsUnknownOptions.js';
6
+ export * from './types/Argv.js';
7
+ export * from './types/CommandConfig.js';
8
+ export * from './types/CommandsConfig.js';
9
+ export * from './types/LongOptionName.js';
10
+ export * from './types/NormalizedCommandConfig.js';
11
+ export * from './types/NormalizedCommandsConfig.js';
12
+ export * from './types/OptionConfig.js';
13
+ export * from './types/OptionsConfig.js';
14
+ export * from './types/ParameterConfig.js';
15
+ export * from './types/ParametersConfig.js';
16
+ export * from './types/ParserConfig.js';
17
+ export * from './types/ShortOptionName.js';
package/dist/types.js ADDED
@@ -0,0 +1,19 @@
1
+ export * from './types/Arguments.js';
2
+ export * from './types/ArgumentsCommand.js';
3
+ export * from './types/ArgumentsOptions.js';
4
+ export * from './types/ArgumentsParameters.js';
5
+ export * from './types/ArgumentsUnknownOptions.js';
6
+ export * from './types/Argv.js';
7
+ export * from './types/CommandConfig.js';
8
+ export * from './types/CommandsConfig.js';
9
+ export * from './types/LongOptionName.js';
10
+ export * from './types/NormalizedCommandConfig.js';
11
+ export * from './types/NormalizedCommandsConfig.js';
12
+ export * from './types/OptionConfig.js';
13
+ export * from './types/OptionsConfig.js';
14
+ export * from './types/ParameterConfig.js';
15
+ export * from './types/ParametersConfig.js';
16
+ export * from './types/ParserConfig.js';
17
+ export * from './types/ShortOptionName.js';
18
+
19
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["export * from './types/Arguments.js';\nexport * from './types/ArgumentsCommand.js';\nexport * from './types/ArgumentsOptions.js';\nexport * from './types/ArgumentsParameters.js';\nexport * from './types/ArgumentsUnknownOptions.js';\nexport * from './types/Argv.js';\nexport * from './types/CommandConfig.js';\nexport * from './types/CommandsConfig.js';\nexport * from './types/LongOptionName.js';\nexport * from './types/NormalizedCommandConfig.js';\nexport * from './types/NormalizedCommandsConfig.js';\nexport * from './types/OptionConfig.js';\nexport * from './types/OptionsConfig.js';\nexport * from './types/ParameterConfig.js';\nexport * from './types/ParametersConfig.js';\nexport * from './types/ParserConfig.js';\nexport * from './types/ShortOptionName.js';\n"],"names":[],"mappings":"AAAA,cAAc,uBAAuB;AACrC,cAAc,8BAA8B;AAC5C,cAAc,8BAA8B;AAC5C,cAAc,iCAAiC;AAC/C,cAAc,qCAAqC;AACnD,cAAc,kBAAkB;AAChC,cAAc,2BAA2B;AACzC,cAAc,4BAA4B;AAC1C,cAAc,4BAA4B;AAC1C,cAAc,qCAAqC;AACnD,cAAc,sCAAsC;AACpD,cAAc,0BAA0B;AACxC,cAAc,2BAA2B;AACzC,cAAc,6BAA6B;AAC3C,cAAc,8BAA8B;AAC5C,cAAc,0BAA0B;AACxC,cAAc,6BAA6B"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@jakubmazanec/args",
3
+ "version": "1.0.0-de51f16",
4
+ "description": "TypeScript-first library for parsing command line arguments.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git@github.com:jakubmazanec/js-tools.git",
8
+ "directory": "packages/args"
9
+ },
10
+ "license": "LGPL-3.0-only",
11
+ "author": "Jakub Mazanec <jakub@mazanec.dev>",
12
+ "sideEffects": false,
13
+ "type": "module",
14
+ "exports": "./dist/main.js",
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "swc src -d dist && tsc",
21
+ "clean": "del-cli dist *.tsbuildinfo",
22
+ "document": "typedoc --options typedoc.config.cjs",
23
+ "format": "prettier . --write --ignore-unknown --ignore-path ../../.prettierignore",
24
+ "lint": "eslint .",
25
+ "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
26
+ "typecheck": "tsc --project tsconfig.typecheck.json"
27
+ },
28
+ "dependencies": {
29
+ "@jakubmazanec/error": "^2.0.0",
30
+ "@jakubmazanec/ts-utils": "^2.0.0",
31
+ "lodash.camelcase": "^4.3.0"
32
+ },
33
+ "devDependencies": {
34
+ "@jest/globals": "^29.5.0",
35
+ "@types/lodash.camelcase": "^4.3.7",
36
+ "@types/node": "^16.18.3"
37
+ },
38
+ "packageManager": "npm@8.19.4",
39
+ "engines": {
40
+ "node": ">=16.20.0"
41
+ }
42
+ }
@@ -0,0 +1,11 @@
1
+ import {createCustomError} from '@jakubmazanec/error';
2
+
3
+ /**
4
+ * A subclass of `Error` that indicates a parsing failure.
5
+ */
6
+ export const ParsingError = createCustomError('ParsingError', {
7
+ INVALID_PARAMETER: 'Invalid value "{0}" found. Allowed values for parameter "{1}" are: {2}.',
8
+ UNKNOWN_PARAMETER: 'Unknown parameter "{0}" found. Unknown parameters are not allowed.',
9
+ INVALID_OPTION: 'Invalid value "{0}" found. Allowed values for option "{1}" are: {2}.',
10
+ UNKNOWN_OPTION: 'Unknown option "{0}" found. Unknown options are not allowed.',
11
+ });
@@ -0,0 +1,22 @@
1
+ import {createCustomError} from '@jakubmazanec/error';
2
+
3
+ /**
4
+ * A subclass of `Error` that indicates a validation issue.
5
+ */
6
+ export const ValidationError = createCustomError('ValidationError', {
7
+ INVALID_COMMAND:
8
+ 'Command "{0}" has invalid format. Only words consisting of letters, numbers or dashes separated by single space are allowed.',
9
+ MISSING_PARAMETER: 'Parameter "{0}" is required but its value is undefined.',
10
+ INVALID_PARAMETER_DEFAULT_AND_CHOICES:
11
+ 'Parameter "{0}" has invalid default value "{1}". Default value must be one of the `choices` elements.',
12
+ INVALID_PARAMETER_ORDER:
13
+ 'Optional parameter "{0}" found before required parameter "{1}". Required parameter must be first.',
14
+ MISSING_OPTION: 'Option "{0}" is required but its value is undefined.',
15
+ INVALID_OPTION_DEFAULT_AND_CHOICES:
16
+ 'Option "{0}" has invalid default value "{1}". Default value must be one of the `choices` elements.',
17
+ INVALID_OPTION_NAME:
18
+ 'Option "{0}" has invalid name. Only names consisting of letters, numbers or dashes are allowed.',
19
+ REPEATED_OPTION_NAME:
20
+ 'Option "{0}" has invalid short name "{1}". Short option names must be unique.',
21
+ INVALID_OPTION_ARITY: 'Option "{0}" has invalid number of values. Found "{1}", require "{2}".',
22
+ });
@@ -0,0 +1,145 @@
1
+ import {type CommandsConfig, type OptionsConfig, type ParametersConfig} from '../types.js';
2
+ import {ValidationError} from '../ValidationError.js';
3
+ import {isCommand} from './isCommand.js';
4
+ import {isLongOption} from './isLongOption.js';
5
+
6
+ /**
7
+ * `Checker` is used for handling various check before, during, and after parsing of the arguments.
8
+ */
9
+ export class Checker {
10
+ errors: Error[] = [];
11
+
12
+ checkCommandsConfig(commandsConfig: CommandsConfig) {
13
+ for (let commandConfig of commandsConfig) {
14
+ if (!isCommand(commandConfig)) {
15
+ this.addError(new ValidationError('INVALID_COMMAND', {messageParameters: [commandConfig]}));
16
+ }
17
+ }
18
+ }
19
+
20
+ checkParametersConfig(parametersConfig: ParametersConfig | []) {
21
+ for (let [i, parameterConfig] of parametersConfig.entries()) {
22
+ // types of choices must be correct
23
+ if (
24
+ parameterConfig.type !== 'boolean' &&
25
+ parameterConfig.choices && // default values and chocies do not contradict each other
26
+ typeof parameterConfig.defaultValue !== 'undefined' &&
27
+ !parameterConfig.choices.includes(parameterConfig.defaultValue as never)
28
+ ) {
29
+ this.addError(
30
+ new ValidationError('INVALID_PARAMETER_DEFAULT_AND_CHOICES', {
31
+ messageParameters: [parameterConfig.label ?? i, parameterConfig.defaultValue],
32
+ }),
33
+ );
34
+ }
35
+
36
+ // if parameter is required, all parameters before must be required too
37
+ if (
38
+ i >= 1 &&
39
+ parameterConfig.required === true &&
40
+ parametersConfig[i - 1]?.required !== true
41
+ ) {
42
+ this.addError(
43
+ new ValidationError('INVALID_PARAMETER_ORDER', {
44
+ messageParameters: [
45
+ parametersConfig[i - 1]?.label ?? i - 1,
46
+ parameterConfig.label ?? i,
47
+ ],
48
+ }),
49
+ );
50
+ }
51
+ }
52
+ }
53
+
54
+ checkOptionsConfig(optionsConfig: OptionsConfig) {
55
+ let usedShortOptionNames: Set<string> = new Set([]);
56
+
57
+ for (let [optionName, optionConfig] of Object.entries(optionsConfig)) {
58
+ // option names must be in correct format
59
+ if (!isLongOption(`--${optionName}`)) {
60
+ this.addError(
61
+ new ValidationError('INVALID_OPTION_NAME', {
62
+ messageParameters: [optionName],
63
+ }),
64
+ );
65
+ }
66
+
67
+ // option short names must be unique
68
+ if (optionConfig.shortName && usedShortOptionNames.has(optionConfig.shortName)) {
69
+ this.addError(
70
+ new ValidationError('REPEATED_OPTION_NAME', {
71
+ messageParameters: [optionName, optionConfig.shortName],
72
+ }),
73
+ );
74
+ } else if (optionConfig.shortName) {
75
+ usedShortOptionNames.add(optionConfig.shortName);
76
+ }
77
+
78
+ // default values and chocies do not contradict each other
79
+ if (
80
+ (optionConfig.type === 'string' ||
81
+ (optionConfig.type === 'number' && !optionConfig.count)) &&
82
+ optionConfig.choices &&
83
+ typeof optionConfig.defaultValue !== 'undefined' &&
84
+ !optionConfig.choices.includes(optionConfig.defaultValue as never)
85
+ ) {
86
+ this.addError(
87
+ new ValidationError('INVALID_OPTION_DEFAULT_AND_CHOICES', {
88
+ messageParameters: [optionName, optionConfig.defaultValue],
89
+ }),
90
+ );
91
+ }
92
+ }
93
+ }
94
+
95
+ checkParsedParameters(parametersConfig: ParametersConfig | [], parsedParameters: unknown[]) {
96
+ for (let [i, parameterConfig] of parametersConfig.entries()) {
97
+ let parsedParameter = parsedParameters[i];
98
+
99
+ // required parameters must have value
100
+ if (parameterConfig.required === true && typeof parsedParameter === 'undefined') {
101
+ this.addError(
102
+ new ValidationError('MISSING_PARAMETER', {
103
+ messageParameters: [parameterConfig.label ?? i],
104
+ }),
105
+ );
106
+ }
107
+ }
108
+ }
109
+
110
+ checkParsedOptions(optionsConfig: OptionsConfig, parsedOptions: Record<string, unknown>) {
111
+ for (let [optionName, optionConfig] of Object.entries(optionsConfig)) {
112
+ let parsedOption = parsedOptions[optionName];
113
+
114
+ // check required
115
+ if (optionConfig.required && typeof parsedOption === 'undefined') {
116
+ this.addError(
117
+ new ValidationError('MISSING_OPTION', {
118
+ messageParameters: [optionName],
119
+ }),
120
+ );
121
+ }
122
+
123
+ // check arity
124
+ if (
125
+ typeof parsedOption !== 'undefined' &&
126
+ ((optionConfig.type === 'string' && optionConfig.multiple) ||
127
+ (optionConfig.type === 'number' && optionConfig.multiple)) &&
128
+ typeof optionConfig.arity === 'number' &&
129
+ Array.isArray(parsedOption) &&
130
+ (parsedOption as unknown[]).length > 0 &&
131
+ (parsedOption as unknown[]).length !== optionConfig.arity
132
+ ) {
133
+ this.addError(
134
+ new ValidationError('INVALID_OPTION_ARITY', {
135
+ messageParameters: [optionName, (parsedOption as unknown[]).length, optionConfig.arity],
136
+ }),
137
+ );
138
+ }
139
+ }
140
+ }
141
+
142
+ addError(error: Error) {
143
+ this.errors.push(error);
144
+ }
145
+ }
@@ -0,0 +1,61 @@
1
+ import {type NormalizedCommandConfig, type NormalizedCommandsConfig} from '../types.js';
2
+
3
+ /**
4
+ * `CommandScope` handles storing information about parsing of a command.
5
+ */
6
+ export class CommandScope {
7
+ private readonly args: string[];
8
+ readonly config: NormalizedCommandConfig;
9
+
10
+ constructor(arg: string, normalizedCommandsConfig: NormalizedCommandsConfig) {
11
+ let normalizedArgument = arg.toLowerCase();
12
+ let commandConfig = normalizedCommandsConfig.find(
13
+ (commandConfig) => commandConfig.args[0] === normalizedArgument,
14
+ );
15
+
16
+ this.config = commandConfig ?? ([] as unknown as NormalizedCommandConfig);
17
+ this.args = [normalizedArgument];
18
+ }
19
+
20
+ captureArg(arg: string) {
21
+ this.args.push(arg.toLowerCase());
22
+
23
+ return this;
24
+ }
25
+
26
+ isComplete() {
27
+ let isComplete = true;
28
+
29
+ for (let [i, arg] of this.config.args.entries()) {
30
+ if (this.args[i] !== arg) {
31
+ isComplete = false;
32
+
33
+ break;
34
+ }
35
+ }
36
+
37
+ return isComplete;
38
+ }
39
+
40
+ isDone() {
41
+ let isDone = false;
42
+
43
+ for (let [i, arg] of this.config.args.entries()) {
44
+ if (this.args[i] !== arg) {
45
+ isDone = true;
46
+
47
+ break;
48
+ }
49
+ }
50
+
51
+ return isDone;
52
+ }
53
+
54
+ getValue() {
55
+ return this.config.command;
56
+ }
57
+
58
+ getArgs() {
59
+ return [...this.args];
60
+ }
61
+ }
@@ -0,0 +1,101 @@
1
+ import camelCase from 'lodash.camelcase';
2
+
3
+ import {ParsingError} from '../ParsingError.js';
4
+ import {type OptionConfig, type OptionsConfig} from '../types.js';
5
+ import {castValue} from './castValue.js';
6
+
7
+ /**
8
+ * `CommandScope` handles storing information about parsing of an option..
9
+ */
10
+ export class OptionScope {
11
+ name: string;
12
+
13
+ values: unknown[] = [];
14
+ previousParsedValue?: unknown[] | unknown;
15
+ readonly config: OptionConfig;
16
+
17
+ constructor(optionName: string, optionsConfig: OptionsConfig, allowUnknownOptions = false) {
18
+ this.name = optionName;
19
+
20
+ // Convert option to camel case
21
+ if (this.name.includes('-')) {
22
+ this.name = camelCase(this.name);
23
+ }
24
+
25
+ let optionConfig = optionsConfig[this.name];
26
+
27
+ if (!optionConfig) {
28
+ if (!allowUnknownOptions) {
29
+ throw new ParsingError('UNKNOWN_OPTION', {messageParameters: [this.name]});
30
+ }
31
+
32
+ optionConfig = {
33
+ type: 'string',
34
+ };
35
+ }
36
+
37
+ this.config = optionConfig;
38
+ }
39
+
40
+ captureArg(arg: string) {
41
+ let value = castValue(arg, this.config.type);
42
+
43
+ if (
44
+ ((this.config.type === 'number' && !this.config.count) || this.config.type === 'string') &&
45
+ typeof this.config.choices !== 'undefined' &&
46
+ !this.config.choices.includes(value as never)
47
+ ) {
48
+ throw new ParsingError('INVALID_OPTION', {
49
+ messageParameters: [
50
+ value,
51
+ this.name,
52
+ this.config.choices.map((choice) => `"${choice}"`).join(', '),
53
+ ],
54
+ });
55
+ }
56
+
57
+ if (this.config.multiple) {
58
+ this.values.push(value);
59
+ } else {
60
+ this.values = [value];
61
+ }
62
+ }
63
+
64
+ isComplete() {
65
+ if (!this.config.multiple && this.values.length === 1) {
66
+ return true;
67
+ }
68
+
69
+ if (
70
+ this.config.multiple &&
71
+ typeof this.config.arity === 'number' &&
72
+ this.values.length + ((this.previousParsedValue as unknown[] | undefined) ?? []).length ===
73
+ this.config.arity
74
+ ) {
75
+ return true;
76
+ }
77
+
78
+ return false;
79
+ }
80
+
81
+ getValue() {
82
+ let {config, previousParsedValue} = this;
83
+ let {values} = this;
84
+
85
+ if (config.type === 'boolean' && !config.multiple && !values.length) {
86
+ values = [true];
87
+ } else if (config.type === 'number' && config.count && !values.length) {
88
+ values = [1];
89
+ }
90
+
91
+ if (config.multiple && previousParsedValue) {
92
+ values = [...(previousParsedValue as unknown[]), ...values];
93
+ }
94
+
95
+ if (config.type === 'number' && config.count && previousParsedValue) {
96
+ values = [(values[0] as number) + (previousParsedValue as number)];
97
+ }
98
+
99
+ return config.multiple ? values : values[0];
100
+ }
101
+ }
@@ -0,0 +1,36 @@
1
+ import {isFalseString} from './isFalseString.js';
2
+
3
+ /**
4
+ * A function that casts string value to a specified type.
5
+ *
6
+ * @param value The value to be converted.
7
+ * @param type The type to convert the value into.
8
+ * */
9
+ export function castValue<T extends 'boolean' | 'number' | 'string'>(
10
+ value: string,
11
+ type?: T,
12
+ ): T extends 'boolean' ? boolean : T extends 'number' ? number : string {
13
+ switch (type) {
14
+ case 'boolean': {
15
+ let booleanString = String(value).toLowerCase();
16
+
17
+ return !(isFalseString(booleanString) || booleanString === '') as T extends 'boolean'
18
+ ? boolean
19
+ : T extends 'number'
20
+ ? number
21
+ : string;
22
+ }
23
+
24
+ case 'number': {
25
+ return Number.parseFloat(value) as T extends 'boolean'
26
+ ? boolean
27
+ : T extends 'number'
28
+ ? number
29
+ : string;
30
+ }
31
+
32
+ default: {
33
+ return String(value) as T extends 'boolean' ? boolean : T extends 'number' ? number : string;
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A function that converts a string representing a command into the arguments, i.e. array of strings.
3
+ *
4
+ * @param value The value to convert.
5
+ * @return Arguments.
6
+ */
7
+ export function commandToArguments(value: string): string[] {
8
+ let rawArgs = value.split(' ');
9
+ let args: string[] = [];
10
+
11
+ for (let [, rawValue] of rawArgs.entries()) {
12
+ if (typeof rawValue !== 'undefined') {
13
+ if (rawValue === '' && args.at(-1)) {
14
+ args[args.length - 1] += ' ';
15
+ } else {
16
+ args.push(rawValue);
17
+ }
18
+ }
19
+ }
20
+
21
+ return args;
22
+ }
@@ -0,0 +1,13 @@
1
+ import {type OptionsConfig} from '../types.js';
2
+
3
+ export function createOptionShortNameMap(optionsConfig: OptionsConfig) {
4
+ let map: Record<string, string> = {};
5
+
6
+ for (let [optionName, optionConfig] of Object.entries(optionsConfig)) {
7
+ if (typeof optionConfig.shortName === 'string') {
8
+ map[optionConfig.shortName] = optionName;
9
+ }
10
+ }
11
+
12
+ return map;
13
+ }
@@ -0,0 +1,22 @@
1
+ import {type Argv} from '../types.js';
2
+ import {isShortOptionsGroup} from './isShortOptionsGroup.js';
3
+
4
+ /**
5
+ * A function that converts any argument that is a short options group into separate short options.
6
+ *
7
+ * @param argv Arguments.
8
+ * @return Arguments that contain no short options group
9
+ */
10
+ export function expandArguments(argv: Argv): Argv {
11
+ let expandedArguments = [];
12
+
13
+ for (let arg of argv) {
14
+ if (isShortOptionsGroup(arg)) {
15
+ expandedArguments.push(...[...arg].slice(1).map((value) => `-${value}`));
16
+ } else {
17
+ expandedArguments.push(arg);
18
+ }
19
+ }
20
+
21
+ return expandedArguments;
22
+ }
@@ -0,0 +1,11 @@
1
+ export const COMMAND_REGEXP = /^(?:[a-z][-a-z0-9]*[a-z])+(?:\s(?:[a-z][-a-z0-9]*[a-z])+)*$/iu;
2
+
3
+ /**
4
+ * A function that checks if a value is an argument that looks like command, i.e. space-separated words consisting only of letters, numbers (but not at the start of the word) or dashes.
5
+ *
6
+ * @param value The value to check.
7
+ * @return `true` if `value` is a command.
8
+ */
9
+ export function isCommand(value: string): boolean {
10
+ return COMMAND_REGEXP.test(value);
11
+ }
@@ -0,0 +1,10 @@
1
+ const FALSE_STRING_REGEXP = /^\s*(?:false|0|no|off)\s*$/iu;
2
+
3
+ /**
4
+ * A function that checks if a string value is a false string. A false string is `'false'`, `'0'`, `'no'`, or `'off'` (case insensitive).
5
+ *
6
+ * @param value The value to be tested.
7
+ * @return `true` if string is a false string. */
8
+ export function isFalseString(value: string) {
9
+ return FALSE_STRING_REGEXP.test(value);
10
+ }
@@ -0,0 +1,11 @@
1
+ const LONG_OPTION_REGEXP = /^--[a-z][-a-z0-9]+$/iu;
2
+
3
+ /**
4
+ * A function that checks if a value is an argument that looks like a long option, i.e. two dashes and a single word consisting only of letters, numbers (but not at the start of the word) or dashes; e.g. `'--some-option'`.
5
+ *
6
+ * @param value The value to check.
7
+ * @return `true` if `value` is a long option.
8
+ */
9
+ export function isLongOption(arg: string): boolean {
10
+ return LONG_OPTION_REGEXP.test(arg);
11
+ }
@@ -0,0 +1,11 @@
1
+ const OPTION_LIKE = /^-{1,2}[a-z][-a-z0-9]*=?/iu;
2
+
3
+ /**
4
+ * A function that checks if a value is an argument that looks like a long option, short option, or a short options group i.e. one or two dashes and a single word consisting only of letters, numbers (but not at the start of the word) or dashes, optionally followed by `=`.
5
+ *
6
+ * @param value The value to check.
7
+ * @return `true` if `value` is an option.
8
+ */
9
+ export function isOptionLike(value: string): boolean {
10
+ return OPTION_LIKE.test(value);
11
+ }
@@ -0,0 +1,11 @@
1
+ const SHORT_OPTION_REGEXP = /^-[a-z]{1}$/iu;
2
+
3
+ /**
4
+ * A function that checks if a value is an argument that looks like a short option, i.e. a dash and a single letter; e.g. `'-x'`.
5
+ *
6
+ * @param value The value to check.
7
+ * @return `true` if `value` is a short option.
8
+ */
9
+ export function isShortOption(arg: string): boolean {
10
+ return SHORT_OPTION_REGEXP.test(arg);
11
+ }
@@ -0,0 +1,11 @@
1
+ export const SHORT_OPTIONS_GROUP_REGEXP = /^-[a-z]{2,}$/iu;
2
+
3
+ /**
4
+ * A function that checks if a value is an argument that looks like a short options group, i.e. a dash and a single letter; e.g. `'-x'`.
5
+ *
6
+ * @param value The value to check.
7
+ * @return `true` if `value` is a short option.
8
+ */
9
+ export function isShortOptionsGroup(arg: string): boolean {
10
+ return SHORT_OPTIONS_GROUP_REGEXP.test(arg);
11
+ }
@@ -0,0 +1,10 @@
1
+ const TRUE_STRING_REGEXP = /^\s*(?:true|1|yes|on)\s*$/iu;
2
+
3
+ /**
4
+ * A function that checks if a string value is a true string. A true string is `'true'`, `'1'`, `'yes'`, or `'on'` (case insensitive).
5
+ *
6
+ * @param value The value to be tested.
7
+ * @return `true` if string is a true string. */
8
+ export function isTrueString(value: string) {
9
+ return TRUE_STRING_REGEXP.test(value);
10
+ }
@@ -0,0 +1,14 @@
1
+ export * from './internals/castValue.js';
2
+ export * from './internals/CommandScope.js';
3
+ export * from './internals/commandToArguments.js';
4
+ export * from './internals/createOptionShortNameMap.js';
5
+ export * from './internals/expandArguments.js';
6
+ export * from './internals/Checker.js';
7
+ export * from './internals/isCommand.js';
8
+ export * from './internals/isFalseString.js';
9
+ export * from './internals/isLongOption.js';
10
+ export * from './internals/isOptionLike.js';
11
+ export * from './internals/isShortOption.js';
12
+ export * from './internals/isShortOptionsGroup.js';
13
+ export * from './internals/isTrueString.js';
14
+ export * from './internals/OptionScope.js';
package/src/main.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './parseArguments.js';
2
+ export * from './ParsingError.js';
3
+ export * from './types.js';
4
+ export * from './ValidationError.js';