args-tokens 0.2.5 → 0.3.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,225 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Modifier: kazuya kawaguchi (a.k.a. kazupon)
3
+ // Forked from `nodejs/node` (`pkgjs/parseargs`)
4
+ // Repository url: https://github.com/nodejs/node (https://github.com/pkgjs/parseargs)
5
+ // Author: Node.js contributors
6
+ // Code url: https://github.com/nodejs/node/blob/main/lib/internal/util/parse_args/parse_args.js
7
+ const HYPHEN_CHAR = '-';
8
+ const HYPHEN_CODE = HYPHEN_CHAR.codePointAt(0);
9
+ const EQUAL_CHAR = '=';
10
+ const EQUAL_CODE = EQUAL_CHAR.codePointAt(0);
11
+ const TERMINATOR = '--';
12
+ const SHORT_OPTION_PREFIX = HYPHEN_CHAR;
13
+ const LONG_OPTION_PREFIX = '--';
14
+ /**
15
+ * Parse command line arguments
16
+ * @example
17
+ * ```js
18
+ * import { parseArgs } from 'args-tokens' // for Node.js and Bun
19
+ * // import { parseArgs } from 'jsr:@kazupon/args-tokens' // for Deno
20
+ *
21
+ * const tokens = parseArgs(['--foo', 'bar', '-x', '--bar=baz'])
22
+ * // do something with using tokens
23
+ * // ...
24
+ * console.log('tokens:', tokens)
25
+ * ```
26
+ * @param args command line arguments
27
+ * @param options parse options
28
+ * @returns argument tokens
29
+ */
30
+ export function parseArgs(args, options = {}) {
31
+ const { allowCompatible = false } = options;
32
+ const tokens = [];
33
+ const remainings = [...args];
34
+ let index = -1;
35
+ let groupCount = 0;
36
+ let hasShortValueSeparator = false;
37
+ while (remainings.length > 0) {
38
+ const arg = remainings.shift();
39
+ if (arg == undefined) {
40
+ break;
41
+ }
42
+ const nextArg = remainings[0];
43
+ if (groupCount > 0) {
44
+ groupCount--;
45
+ }
46
+ else {
47
+ index++;
48
+ }
49
+ // check if `arg` is an options terminator.
50
+ // guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
51
+ if (arg === TERMINATOR) {
52
+ tokens.push({
53
+ kind: 'option-terminator',
54
+ index
55
+ });
56
+ const mapped = remainings.map(arg => {
57
+ return { kind: 'positional', index: ++index, value: arg };
58
+ });
59
+ tokens.push(...mapped);
60
+ break;
61
+ }
62
+ if (isShortOption(arg)) {
63
+ const shortOption = arg.charAt(1);
64
+ let value;
65
+ let inlineValue;
66
+ if (groupCount) {
67
+ // e.g. `-abc`
68
+ tokens.push({
69
+ kind: 'option',
70
+ name: shortOption,
71
+ rawName: arg,
72
+ index,
73
+ value,
74
+ inlineValue
75
+ });
76
+ if (groupCount === 1 && hasOptionValue(nextArg)) {
77
+ value = remainings.shift();
78
+ if (hasShortValueSeparator) {
79
+ inlineValue = true;
80
+ hasShortValueSeparator = false;
81
+ }
82
+ tokens.push({
83
+ kind: 'option',
84
+ index,
85
+ value,
86
+ inlineValue
87
+ });
88
+ }
89
+ }
90
+ else {
91
+ // e.g. `-a`
92
+ tokens.push({
93
+ kind: 'option',
94
+ name: shortOption,
95
+ rawName: arg,
96
+ index,
97
+ value,
98
+ inlineValue
99
+ });
100
+ }
101
+ // eslint-disable-next-line unicorn/no-null
102
+ if (value != null) {
103
+ ++index;
104
+ }
105
+ continue;
106
+ }
107
+ if (isShortOptionGroup(arg)) {
108
+ // expend short option group (e.g. `-abc` => `-a -b -c`, `-f=bar` => `-f bar`)
109
+ const expanded = [];
110
+ let shortValue = '';
111
+ for (let i = 1; i < arg.length; i++) {
112
+ const shortableOption = arg.charAt(i);
113
+ if (hasShortValueSeparator) {
114
+ shortValue += shortableOption;
115
+ }
116
+ else {
117
+ if (!allowCompatible && shortableOption.codePointAt(0) === EQUAL_CODE) {
118
+ hasShortValueSeparator = true;
119
+ }
120
+ else {
121
+ expanded.push(`${SHORT_OPTION_PREFIX}${shortableOption}`);
122
+ }
123
+ }
124
+ }
125
+ if (shortValue) {
126
+ expanded.push(shortValue);
127
+ }
128
+ remainings.unshift(...expanded);
129
+ groupCount = expanded.length;
130
+ continue;
131
+ }
132
+ if (isLongOption(arg)) {
133
+ // e.g. `--foo`
134
+ const longOption = arg.slice(2);
135
+ tokens.push({
136
+ kind: 'option',
137
+ name: longOption,
138
+ rawName: arg,
139
+ index,
140
+ value: undefined,
141
+ inlineValue: undefined
142
+ });
143
+ continue;
144
+ }
145
+ if (isLongOptionAndValue(arg)) {
146
+ // e.g. `--foo=bar`
147
+ const equalIndex = arg.indexOf(EQUAL_CHAR);
148
+ const longOption = arg.slice(2, equalIndex);
149
+ const value = arg.slice(equalIndex + 1);
150
+ tokens.push({
151
+ kind: 'option',
152
+ name: longOption,
153
+ rawName: `${LONG_OPTION_PREFIX}${longOption}`,
154
+ index,
155
+ value,
156
+ inlineValue: true
157
+ });
158
+ continue;
159
+ }
160
+ tokens.push({
161
+ kind: 'positional',
162
+ index,
163
+ value: arg
164
+ });
165
+ }
166
+ return tokens;
167
+ }
168
+ /**
169
+ * Check if `arg` is a short option (e.g. `-f`)
170
+ * @param arg the argument to check
171
+ * @returns whether `arg` is a short option
172
+ */
173
+ export function isShortOption(arg) {
174
+ return (arg.length === 2 && arg.codePointAt(0) === HYPHEN_CODE && arg.codePointAt(1) !== HYPHEN_CODE);
175
+ }
176
+ /**
177
+ * Check if `arg` is a short option group (e.g. `-abc`)
178
+ * @param arg the argument to check
179
+ * @returns whether `arg` is a short option group
180
+ */
181
+ function isShortOptionGroup(arg) {
182
+ if (arg.length <= 2) {
183
+ return false;
184
+ }
185
+ if (arg.codePointAt(0) !== HYPHEN_CODE) {
186
+ return false;
187
+ }
188
+ if (arg.codePointAt(1) === HYPHEN_CODE) {
189
+ return false;
190
+ }
191
+ return true;
192
+ }
193
+ /**
194
+ * Check if `arg` is a long option (e.g. `--foo`)
195
+ * @param arg the argument to check
196
+ * @returns whether `arg` is a long option
197
+ */
198
+ function isLongOption(arg) {
199
+ return hasLongOptionPrefix(arg) && !arg.includes(EQUAL_CHAR, 3);
200
+ }
201
+ /**
202
+ * Check if `arg` is a long option with value (e.g. `--foo=bar`)
203
+ * @param arg the argument to check
204
+ * @returns whether `arg` is a long option
205
+ */
206
+ function isLongOptionAndValue(arg) {
207
+ return hasLongOptionPrefix(arg) && arg.includes(EQUAL_CHAR, 3);
208
+ }
209
+ /**
210
+ * Check if `arg` is a long option prefix (e.g. `--`)
211
+ * @param arg the argument to check
212
+ * @returns whether `arg` is a long option prefix
213
+ */
214
+ export function hasLongOptionPrefix(arg) {
215
+ return arg.length > 2 && ~arg.indexOf(LONG_OPTION_PREFIX);
216
+ }
217
+ /**
218
+ * Check if a `value` is an option value
219
+ * @param value a value to check
220
+ * @returns whether a `value` is an option value
221
+ */
222
+ function hasOptionValue(value) {
223
+ // eslint-disable-next-line unicorn/no-null
224
+ return !(value == null) && value.codePointAt(0) !== HYPHEN_CODE;
225
+ }
@@ -0,0 +1,50 @@
1
+ import type { ArgToken } from './parser';
2
+ /**
3
+ * An option schema for an argument.
4
+ */
5
+ export interface ArgOptionSchema {
6
+ /**
7
+ * Type of argument.
8
+ */
9
+ type: 'string' | 'boolean' | 'number';
10
+ /**
11
+ * A single character alias for the option.
12
+ */
13
+ short?: string;
14
+ /**
15
+ * Whether the argument is required or not.
16
+ */
17
+ required?: true;
18
+ /**
19
+ * The default value of the argument.
20
+ */
21
+ default?: string | boolean | number;
22
+ }
23
+ /**
24
+ * An object that contains {@link ArgOptionSchema | options schema}.
25
+ */
26
+ export interface ArgOptions {
27
+ [option: string]: ArgOptionSchema;
28
+ }
29
+ /**
30
+ * An object that contains the values of the arguments.
31
+ */
32
+ export type ArgValues<T> = T extends ArgOptions ? ResolveArgValues<T, {
33
+ [Option in keyof T]: ExtractOptionValue<T[Option]>;
34
+ }> : {
35
+ [option: string]: string | boolean | number | undefined;
36
+ };
37
+ type ExtractOptionValue<O extends ArgOptionSchema> = O['type'] extends 'string' ? string : O['type'] extends 'boolean' ? boolean : O['type'] extends 'number' ? number : string | boolean | number;
38
+ type ResolveArgValues<O extends ArgOptions, V extends Record<keyof O, unknown>> = {
39
+ -readonly [Option in keyof O]?: V[Option];
40
+ } & FilterArgs<O, V, 'default'> & FilterArgs<O, V, 'required'> extends infer P ? {
41
+ [K in keyof P]: P[K];
42
+ } : never;
43
+ type FilterArgs<O extends ArgOptions, V extends Record<keyof O, unknown>, K extends keyof ArgOptionSchema> = {
44
+ [Option in keyof O as O[Option][K] extends {} ? Option : never]: V[Option];
45
+ };
46
+ export declare function resolveArgs<T extends ArgOptions>(options: T, tokens: ArgToken[]): {
47
+ values: ArgValues<T>;
48
+ positionals: string[];
49
+ };
50
+ export {};
@@ -0,0 +1,164 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Modifier: kazuya kawaguchi (a.k.a. kazupon)
3
+ import { hasLongOptionPrefix, isShortOption } from './parser.js';
4
+ export function resolveArgs(options, tokens) {
5
+ const values = {};
6
+ const positionals = [];
7
+ const longOptionTokens = [];
8
+ const shortOptionTokens = [];
9
+ let currentShortOption;
10
+ const expandableShortOptions = [];
11
+ function toValue() {
12
+ if (expandableShortOptions.length === 0) {
13
+ return undefined;
14
+ }
15
+ else {
16
+ const value = expandableShortOptions.map(token => token.name).join('');
17
+ expandableShortOptions.length = 0;
18
+ return value;
19
+ }
20
+ }
21
+ function applyShortOptionValue() {
22
+ if (currentShortOption) {
23
+ currentShortOption.value = toValue();
24
+ shortOptionTokens.push({ ...currentShortOption });
25
+ currentShortOption = undefined;
26
+ }
27
+ }
28
+ /**
29
+ * separate tokens into positionals, long options, and short options, after that resolve values
30
+ */
31
+ // eslint-disable-next-line unicorn/no-for-loop
32
+ for (let i = 0; i < tokens.length; i++) {
33
+ const token = tokens[i];
34
+ if (token.kind === 'positional') {
35
+ positionals.push(token.value);
36
+ applyShortOptionValue(); // check if previous short option is not resolved
37
+ }
38
+ else if (token.kind === 'option') {
39
+ if (token.rawName) {
40
+ if (hasLongOptionPrefix(token.rawName)) {
41
+ longOptionTokens.push({ ...token });
42
+ applyShortOptionValue(); // check if previous short option is not resolved
43
+ }
44
+ else if (isShortOption(token.rawName)) {
45
+ if (currentShortOption) {
46
+ if (currentShortOption.index === token.index) {
47
+ expandableShortOptions.push({ ...token });
48
+ }
49
+ else {
50
+ currentShortOption.value = toValue();
51
+ shortOptionTokens.push({ ...currentShortOption });
52
+ currentShortOption = { ...token };
53
+ }
54
+ }
55
+ else {
56
+ currentShortOption = { ...token };
57
+ }
58
+ }
59
+ }
60
+ else {
61
+ // short option value
62
+ if (currentShortOption && currentShortOption.index == token.index) {
63
+ currentShortOption.value = token.value;
64
+ shortOptionTokens.push({ ...currentShortOption });
65
+ currentShortOption = undefined;
66
+ }
67
+ }
68
+ }
69
+ else {
70
+ applyShortOptionValue(); // check if previous short option is not resolved
71
+ }
72
+ }
73
+ /**
74
+ * check if the last short option is not resolved
75
+ */
76
+ applyShortOptionValue();
77
+ /**
78
+ * resolve values
79
+ */
80
+ for (const [option, schema] of Object.entries(options)) {
81
+ if (longOptionTokens.length === 0 && shortOptionTokens.length === 0 && schema.required) {
82
+ throw createRequireError(option, schema);
83
+ }
84
+ // eslint-disable-next-line unicorn/no-for-loop
85
+ for (let i = 0; i < longOptionTokens.length; i++) {
86
+ const token = longOptionTokens[i];
87
+ // eslint-disable-next-line unicorn/no-null
88
+ if (option === token.name && token.rawName != null && hasLongOptionPrefix(token.rawName)) {
89
+ validateRequire(token, option, schema);
90
+ if (schema.type !== 'boolean') {
91
+ validateValue(token, option, schema);
92
+ }
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
94
+ ;
95
+ values[option] = resolveOptionValue(token, schema);
96
+ continue;
97
+ }
98
+ }
99
+ // eslint-disable-next-line unicorn/no-for-loop
100
+ for (let i = 0; i < shortOptionTokens.length; i++) {
101
+ const token = shortOptionTokens[i];
102
+ // eslint-disable-next-line unicorn/no-null
103
+ if (schema.short === token.name && token.rawName != null && isShortOption(token.rawName)) {
104
+ validateRequire(token, option, schema);
105
+ if (schema.type !== 'boolean') {
106
+ validateValue(token, option, schema);
107
+ }
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
109
+ ;
110
+ values[option] = resolveOptionValue(token, schema);
111
+ continue;
112
+ }
113
+ }
114
+ // eslint-disable-next-line unicorn/no-null
115
+ if (values[option] == null && schema.default != null) {
116
+ // check if the default value is in values
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
118
+ ;
119
+ values[option] = schema.default;
120
+ }
121
+ }
122
+ return { values, positionals };
123
+ }
124
+ function createRequireError(option, schema) {
125
+ return new Error(`Option '--${option}' ${schema.short ? `or '-${schema.short}'` : ''} is required`);
126
+ }
127
+ function validateRequire(token, option, schema) {
128
+ if (schema.required && schema.type !== 'boolean' && !token.value) {
129
+ throw createRequireError(option, schema);
130
+ }
131
+ }
132
+ function validateValue(token, option, schema) {
133
+ switch (schema.type) {
134
+ case 'number': {
135
+ if (!isNumeric(token.value)) {
136
+ throw createTypeError(option, schema);
137
+ }
138
+ break;
139
+ }
140
+ case 'string': {
141
+ if (typeof token.value !== 'string') {
142
+ throw createTypeError(option, schema);
143
+ }
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ function isNumeric(str) {
149
+ // @ts-ignore
150
+ // eslint-disable-next-line unicorn/prefer-number-properties
151
+ return str.trim() !== '' && !isNaN(str);
152
+ }
153
+ function createTypeError(option, schema) {
154
+ return new TypeError(`Option '--${option}' ${schema.short ? `or '-${schema.short}'` : ''} should be '${schema.type}'`);
155
+ }
156
+ function resolveOptionValue(token, schema) {
157
+ if (token.value) {
158
+ return schema.type === 'number' ? +token.value : token.value;
159
+ }
160
+ if (schema.type === 'boolean') {
161
+ return true;
162
+ }
163
+ return schema.type === 'number' ? +(schema.default || '') : schema.default;
164
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { expectTypeOf, test } from 'vitest';
2
+ test('ArgValues', () => {
3
+ expectTypeOf().toEqualTypeOf();
4
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "args-tokens",
3
3
  "description": "parseArgs tokens compatibility and more high-performance parser",
4
- "version": "0.2.5",
4
+ "version": "0.3.0",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -41,6 +41,18 @@
41
41
  "require": "./lib/cjs/index.js",
42
42
  "default": "./lib/cjs/index.js"
43
43
  },
44
+ "./parser": {
45
+ "types": "./lib/cjs/parser.d.ts",
46
+ "import": "./lib/esm/parser.js",
47
+ "require": "./lib/cjs/parser.js",
48
+ "default": "./lib/cjs/parser.js"
49
+ },
50
+ "./resolver": {
51
+ "types": "./lib/cjs/resolver.d.ts",
52
+ "import": "./lib/esm/resolver.js",
53
+ "require": "./lib/cjs/resolver.js",
54
+ "default": "./lib/cjs/resolver.js"
55
+ },
44
56
  "./package.json": "./package.json",
45
57
  "./*": "./*"
46
58
  },
@@ -93,7 +105,7 @@
93
105
  ]
94
106
  },
95
107
  "scripts": {
96
- "bench:mitata": "node --expose-gc bench/index.mjs",
108
+ "bench:mitata": "node --expose-gc bench/index.js",
97
109
  "bench:vitest": "vitest bench --run",
98
110
  "build": "pnpm run --parallel --color \"/^build:/\"",
99
111
  "build:cjs": "tsc -p ./tsconfig.cjs.json",