11import type { GlobalFlags } from './types/flags' ;
2+ import type { OptionDef } from './command' ;
23
3- export interface ParsedArgs {
4- commandPath : string [ ] ;
5- flags : GlobalFlags ;
4+ function kebabToCamel ( str : string ) : string {
5+ return str . replace ( / - ( [ a - z ] ) / g, ( _ , c : string ) => c . toUpperCase ( ) ) ;
6+ }
7+
8+ /** Extract camelCase flag name from an OptionDef.flag string, e.g. '--max-tokens <n>' → 'maxTokens' */
9+ function flagKey ( def : OptionDef ) : string | null {
10+ const m = def . flag . match ( / ^ - - ( [ a - z ] [ a - z 0 - 9 - ] * ) / i) ;
11+ return m ? kebabToCamel ( m [ 1 ] ! ) : null ;
12+ }
13+
14+ /** Boolean when no value placeholder and type is not string/number/array */
15+ function isBooleanDef ( def : OptionDef ) : boolean {
16+ if ( def . type === 'boolean' ) return true ;
17+ if ( def . type === 'string' || def . type === 'number' || def . type === 'array' ) return false ;
18+ return ! def . flag . includes ( '<' ) && ! def . flag . includes ( '[' ) ;
619}
720
8- export function parseArgs ( argv : string [ ] ) : ParsedArgs {
9- const commandPath : string [ ] = [ ] ;
21+ interface FlagSchema {
22+ booleans : Set < string > ;
23+ numbers : Set < string > ;
24+ arrays : Set < string > ;
25+ }
26+
27+ function buildSchema ( options : OptionDef [ ] ) : FlagSchema {
28+ const booleans = new Set < string > ( ) ;
29+ const numbers = new Set < string > ( ) ;
30+ const arrays = new Set < string > ( ) ;
31+ for ( const opt of options ) {
32+ const key = flagKey ( opt ) ;
33+ if ( ! key ) continue ;
34+ if ( isBooleanDef ( opt ) ) booleans . add ( key ) ;
35+ else if ( opt . type === 'number' ) numbers . add ( key ) ;
36+ else if ( opt . type === 'array' ) arrays . add ( key ) ;
37+ }
38+ return { booleans, numbers, arrays } ;
39+ }
40+
41+ /**
42+ * Quick scan: collect positional (non-dash) args to determine the command path.
43+ * Does not consume flag values — just skips dash-prefixed tokens.
44+ */
45+ export function scanCommandPath ( argv : string [ ] ) : string [ ] {
46+ const path : string [ ] = [ ] ;
47+ for ( const arg of argv ) {
48+ if ( arg === '--' ) break ;
49+ if ( ! arg . startsWith ( '-' ) ) path . push ( arg ) ;
50+ }
51+ return path ;
52+ }
53+
54+ /**
55+ * Full flag parse. Types are derived entirely from the provided OptionDef schema:
56+ * - boolean: no <value> placeholder in flag string (or type: 'boolean')
57+ * - number: type: 'number'
58+ * - array: type: 'array' (repeatable via multiple --flag occurrences)
59+ * - default: string
60+ */
61+ export function parseFlags ( argv : string [ ] , options : OptionDef [ ] ) : GlobalFlags {
62+ const schema = buildSchema ( options ) ;
1063 const flags : GlobalFlags = {
1164 quiet : false ,
1265 verbose : false ,
@@ -22,77 +75,49 @@ export function parseArgs(argv: string[]): ParsedArgs {
2275 while ( i < argv . length ) {
2376 const arg = argv [ i ] ! ;
2477
25- if ( arg === '--help' || arg === '-h' ) {
26- flags . help = true ;
27- i ++ ;
28- continue ;
29- }
30-
31- if ( arg === '--' ) {
32- i ++ ;
33- break ;
34- }
78+ if ( arg === '--help' || arg === '-h' ) { flags . help = true ; i ++ ; continue ; }
79+ if ( arg === '--' ) { i ++ ; break ; }
3580
3681 if ( arg . startsWith ( '--' ) ) {
37- const eqIndex = arg . indexOf ( '=' ) ;
82+ const eqIdx = arg . indexOf ( '=' ) ;
3883 let key : string ;
3984 let value : string | undefined ;
4085
41- if ( eqIndex !== - 1 ) {
42- key = arg . slice ( 2 , eqIndex ) ;
43- value = arg . slice ( eqIndex + 1 ) ;
86+ if ( eqIdx !== - 1 ) {
87+ key = arg . slice ( 2 , eqIdx ) ;
88+ value = arg . slice ( eqIdx + 1 ) ;
4489 } else {
4590 key = arg . slice ( 2 ) ;
4691 }
4792
4893 const camelKey = kebabToCamel ( key ) ;
4994
50- // Boolean flags
51- if ( [ 'quiet' , 'verbose' , 'noColor' , 'yes' , 'dryRun' , 'help' , 'stream' ,
52- 'subtitles' , 'wait' , 'noWait' , 'noBrowser' ,
53- 'nonInteractive' , 'async' ] . includes ( camelKey ) ) {
95+ if ( schema . booleans . has ( camelKey ) ) {
5496 ( flags as Record < string , unknown > ) [ camelKey ] = true ;
5597 i ++ ;
5698 continue ;
5799 }
58100
59- // Value flags
60101 if ( value === undefined ) {
61102 i ++ ;
62103 value = argv [ i ] ;
63104 }
64105
65- if ( value === undefined ) {
66- throw new Error ( `Flag --${ key } requires a value.` ) ;
67- }
106+ if ( value === undefined ) throw new Error ( `Flag --${ key } requires a value.` ) ;
68107
69- // Repeatable flags
70- if ( [ 'message' , 'tool' , 'pronunciation' ] . includes ( camelKey ) ) {
108+ if ( schema . arrays . has ( camelKey ) ) {
71109 const arr = ( flags as Record < string , unknown > ) [ camelKey ] as string [ ] | undefined ;
72- if ( arr ) {
73- arr . push ( value ) ;
74- } else {
75- ( flags as Record < string , unknown > ) [ camelKey ] = [ value ] ;
76- }
77- } else if ( [ 'maxTokens' , 'temperature' , 'topP' , 'speed' , 'volume' ,
78- 'pitch' , 'sampleRate' , 'bitrate' , 'channels' , 'n' ,
79- 'timeout' , 'pollInterval' ] . includes ( camelKey ) ) {
110+ if ( arr ) arr . push ( value ) ;
111+ else ( flags as Record < string , unknown > ) [ camelKey ] = [ value ] ;
112+ } else if ( schema . numbers . has ( camelKey ) ) {
80113 ( flags as Record < string , unknown > ) [ camelKey ] = Number ( value ) ;
81114 } else {
82115 ( flags as Record < string , unknown > ) [ camelKey ] = value ;
83116 }
84- i ++ ;
85- continue ;
86117 }
87118
88- // Positional argument — part of command path
89- commandPath . push ( arg ) ;
90119 i ++ ;
91120 }
92121
93- return { commandPath, flags } ;
94- }
95-
96- function kebabToCamel ( str : string ) : string {
97- return str . replace ( / - ( [ a - z ] ) / g, ( _ , c : string ) => c . toUpperCase ( ) ) ;
122+ return flags ;
98123}
0 commit comments