-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathargs.ts
More file actions
123 lines (105 loc) · 3.53 KB
/
Copy pathargs.ts
File metadata and controls
123 lines (105 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import type { GlobalFlags } from './types/flags';
import type { OptionDef } from './command';
function kebabToCamel(str: string): string {
return str.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
}
/** Extract camelCase flag name from an OptionDef.flag string, e.g. '--max-tokens <n>' → 'maxTokens' */
function flagKey(def: OptionDef): string | null {
const m = def.flag.match(/^--([a-z][a-z0-9-]*)/i);
return m ? kebabToCamel(m[1]!) : null;
}
/** Boolean when no value placeholder and type is not string/number/array */
function isBooleanDef(def: OptionDef): boolean {
if (def.type === 'boolean') return true;
if (def.type === 'string' || def.type === 'number' || def.type === 'array') return false;
return !def.flag.includes('<') && !def.flag.includes('[');
}
interface FlagSchema {
booleans: Set<string>;
numbers: Set<string>;
arrays: Set<string>;
}
function buildSchema(options: OptionDef[]): FlagSchema {
const booleans = new Set<string>();
const numbers = new Set<string>();
const arrays = new Set<string>();
for (const opt of options) {
const key = flagKey(opt);
if (!key) continue;
if (isBooleanDef(opt)) booleans.add(key);
else if (opt.type === 'number') numbers.add(key);
else if (opt.type === 'array') arrays.add(key);
}
return { booleans, numbers, arrays };
}
/**
* Quick scan: collect positional (non-dash) args to determine the command path.
* Does not consume flag values — just skips dash-prefixed tokens.
*/
export function scanCommandPath(argv: string[]): string[] {
const path: string[] = [];
for (const arg of argv) {
if (arg === '--') break;
if (!arg.startsWith('-')) path.push(arg);
}
return path;
}
/**
* Full flag parse. Types are derived entirely from the provided OptionDef schema:
* - boolean: no <value> placeholder in flag string (or type: 'boolean')
* - number: type: 'number'
* - array: type: 'array' (repeatable via multiple --flag occurrences)
* - default: string
*/
export function parseFlags(argv: string[], options: OptionDef[]): GlobalFlags {
const schema = buildSchema(options);
const flags: GlobalFlags = {
quiet: false,
verbose: false,
noColor: false,
yes: false,
dryRun: false,
help: false,
nonInteractive: false,
async: false,
};
let i = 0;
while (i < argv.length) {
const arg = argv[i]!;
if (arg === '--help' || arg === '-h') { flags.help = true; i++; continue; }
if (arg === '--') { i++; break; }
if (arg.startsWith('--')) {
const eqIdx = arg.indexOf('=');
let key: string;
let value: string | undefined;
if (eqIdx !== -1) {
key = arg.slice(2, eqIdx);
value = arg.slice(eqIdx + 1);
} else {
key = arg.slice(2);
}
const camelKey = kebabToCamel(key);
if (schema.booleans.has(camelKey)) {
(flags as Record<string, unknown>)[camelKey] = true;
i++;
continue;
}
if (value === undefined) {
i++;
value = argv[i];
}
if (value === undefined) throw new Error(`Flag --${key} requires a value.`);
if (schema.arrays.has(camelKey)) {
const arr = (flags as Record<string, unknown>)[camelKey] as string[] | undefined;
if (arr) arr.push(value);
else (flags as Record<string, unknown>)[camelKey] = [value];
} else if (schema.numbers.has(camelKey)) {
(flags as Record<string, unknown>)[camelKey] = Number(value);
} else {
(flags as Record<string, unknown>)[camelKey] = value;
}
}
i++;
}
return flags;
}