@@ -2,9 +2,7 @@ import { ParseContext, ParseResult } from './state.js';
22import type { CmdArgument , CmdOption , CmdVerb } from './types.ts' ;
33
44/**
5- * Parse argument. This will throw an error if there's no at least 1
6- * argument left on the token stream. This will not mutate `result`
7- * on transformation error.
5+ * Parse argument.
86 * @param ctx The parsing context.
97 * @param result Where to set the result.
108 * @param argDef The argument definition.
@@ -13,16 +11,11 @@ import type { CmdArgument, CmdOption, CmdVerb } from './types.ts';
1311export function parseArgument ( ctx : ParseContext , result : ParseResult ,
1412 argDef : CmdArgument ) : void
1513{
16- if ( ctx . isEndOfTokens )
17- throw 'Insufficient arguments' ;
1814 result . set ( argDef . id , argDef . type ( ctx ) ) ;
1915}
2016
2117/**
22- * Parse option arguments. This will parse all the required arguments
23- * mandatorily. Optional arguments will parse until one fails. After
24- * a failed optional argument, only the defaults will be set. It will
25- * also require parsing the first argument if `reqFirstOptArg` is set.
18+ * Parse option arguments.
2619 * @param ctx The parsing context.
2720 * @param result Where to set the results.
2821 * @param optArgDefs The option-argument definitions.
@@ -67,7 +60,7 @@ export function parseLongOption(ctx: ParseContext, result: ParseResult,
6760 optDefs : CmdOption [ ] ) : void
6861{
6962 const state = ctx . getCurrentState ( ) ;
70- if ( state . stopOptions || ctx . isEndOfTokens )
63+ if ( ctx . isEndOfTokens )
7164 return ;
7265
7366 const tok = ctx . currentToken ;
@@ -105,7 +98,7 @@ export function parseShortOption(ctx: ParseContext, result: ParseResult,
10598 optDefs : CmdOption [ ] ) : void
10699{
107100 const state = ctx . getCurrentState ( ) ;
108- if ( state . stopOptions || ctx . isEndOfTokens )
101+ if ( ctx . isEndOfTokens )
109102 return ;
110103
111104 const tok = ctx . currentToken ;
@@ -147,60 +140,190 @@ export function parseShortOption(ctx: ParseContext, result: ParseResult,
147140 */
148141export function findOption ( optDefs : CmdOption [ ] , optName : string ) : CmdOption
149142{
150- const def = optDefs . find ( def => def . names . includes ( optName ) ) ;
143+ const def = optDefs ? .find ( def => def . names . includes ( optName ) ) ;
151144 if ( ! def ) throw 'Unknown option: ' + optName ;
152145 return def ;
153146}
154147
148+ /**
149+ * Parse a verb.
150+ * @param ctx The parsing context.
151+ * @param result Where to set the results.
152+ * @param verbDef The verb's definition.
153+ * @throws This function can throw errors.
154+ */
155+ export function parseVerb ( ctx : ParseContext , result : ParseResult ,
156+ verbDef : CmdVerb ) : void
157+ {
158+ let stopOptions = false ;
159+ let argIdx = 0 ;
155160
156- declare const scriptArgs : string [ ] ;
161+ while ( ! ctx . isEndOfTokens ) {
162+ const tok = ctx . currentToken ;
157163
158- const info : CmdOption [ ] = [
159- {
160- id : 'opt' ,
161- names : [ '--opt' , '--bac' , '-o' ] ,
162- args : [
163- {
164- id : 'num' ,
165- type : ( ctx ) => + ctx . consumeToken ( ) ,
166- } ,
167- {
168- id : 'hello' ,
169- type : ( ctx ) => ctx . consumeToken ( ) ,
170- default : 1 ,
171- optional : true ,
164+ if ( tok [ 0 ] == '-' && tok . length >= 2 && ! stopOptions ) {
165+ /* short opts */
166+ if ( tok [ 1 ] != '-' ) {
167+ parseShortOption ( ctx , result , verbDef . options ) ;
168+ continue ;
172169 }
173- ]
174- } ,
175- {
176- id : 'another' ,
177- names : [ '--another' , '--ano' , '-a' ] ,
178- args : [
179- {
180- id : 'val' ,
181- type : ( ctx ) => {
182- const tok = ctx . consumeToken ( ) ;
183- if ( ! / ^ [ + - ] ? [ 0 - 9 ] + (?: \. [ 0 - 9 ] + ) ? $ / . test ( tok ) )
184- throw 'invalid number: ' + tok ;
185- return + tok ;
186- } ,
187- optional : true ,
188- default : 0 ,
170+ /* end-of-options delimeter */
171+ if ( tok . length == 2 ) {
172+ stopOptions = true ;
173+ ctx . consumeToken ( ) ;
174+ continue ;
189175 }
190- ]
191- } ,
192- {
193- id : 'verbose' ,
194- names : [ '-v' , '--verbose' ] ,
176+ /* long opts */
177+ parseLongOption ( ctx , result , verbDef . options ) ;
178+ continue ;
179+ }
180+
181+ /* positional arguments */
182+ if ( argIdx < verbDef . args ?. length ) {
183+ const argDef = verbDef . args [ argIdx ++ ] ;
184+ parseArgument ( ctx , result , argDef ) ;
185+ continue ;
186+ }
187+
188+ /* try subcommands */
189+ if ( verbDef . subverbs ?. length ) {
190+ parseSubVerb ( ctx , result , verbDef . subverbs ) ;
191+ break ;
192+ }
193+
194+ throw 'Too many arguments' ;
195195 }
196- ] ;
196+
197+ /* set the defaults of other optional positional params */
198+ while ( argIdx < verbDef . args ?. length ) {
199+ const argDef = verbDef . args [ argIdx ++ ] ;
200+ if ( ! argDef . optional )
201+ throw 'Insufficient arguments' ;
202+ result . set ( argDef . id , argDef . default ) ;
203+ }
204+ }
205+
206+ /**
207+ * Parse a subverb.
208+ * @param ctx The parsing context.
209+ * @param result Where to set the results.
210+ * @param verbDefs Where to lookup subverb defs.
211+ * @throws This function can throw errors.
212+ */
213+ export function parseSubVerb ( ctx : ParseContext , result : ParseResult ,
214+ verbDefs : CmdVerb [ ] ) : void
215+ {
216+ const subName = ctx . consumeToken ( ) ;
217+ const verbDef = verbDefs . find ( def =>
218+ def . name == subName || def . aliases ?. includes ( subName ) ) ;
219+
220+ if ( ! verbDef )
221+ throw 'Unknown subcommand: ' + subName ;
222+
223+ const subRes = new ParseResult ( ) ;
224+ parseVerb ( ctx , subRes , verbDef ) ;
225+ result . set ( verbDef . id , subRes ) ;
226+ }
227+
228+
229+
230+
231+
232+
233+ declare const scriptArgs : string [ ] ;
234+
235+ const info : CmdVerb = {
236+ id : 'cmd' ,
237+ name : 'cmd' ,
238+ options : [
239+ {
240+ id : 'opt' ,
241+ names : [ '--opt' , '--bac' , '-o' ] ,
242+ args : [
243+ {
244+ id : 'num' ,
245+ type : ( ctx ) => + ctx . consumeToken ( ) ,
246+ } ,
247+ {
248+ id : 'hello' ,
249+ type : ( ctx ) => ctx . consumeToken ( ) ,
250+ default : 1 ,
251+ optional : true ,
252+ }
253+ ]
254+ } ,
255+ {
256+ id : 'another' ,
257+ names : [ '--another' , '--ano' , '-a' ] ,
258+ args : [
259+ {
260+ id : 'val' ,
261+ type : ( ctx ) => {
262+ const tok = ctx . consumeToken ( ) ;
263+ if ( ! / ^ [ + - ] ? [ 0 - 9 ] + (?: \. [ 0 - 9 ] + ) ? $ / . test ( tok ) )
264+ throw 'invalid number: ' + tok ;
265+ return + tok ;
266+ } ,
267+ optional : true ,
268+ default : 0 ,
269+ }
270+ ]
271+ } ,
272+ {
273+ id : 'verbose' ,
274+ names : [ '-v' , '--verbose' ] ,
275+ }
276+ ] ,
277+ args : [
278+ {
279+ id : 'arg1S' ,
280+ type : ( ctx ) => ctx . consumeToken ( ) ,
281+ } ,
282+ {
283+ id : 'arg2B' ,
284+ type : ( ctx ) => {
285+ const tok = ctx . consumeToken ( ) ;
286+ const val = [ 'false' , 'true' ] . indexOf ( tok ) ;
287+ if ( val == - 1 ) throw 'invalid boolean: ' + tok ;
288+ return ! ! val ;
289+ } ,
290+ } ,
291+ {
292+ id : 'arg3N' ,
293+ type : ( ctx ) => + ctx . consumeToken ( ) ,
294+ optional : true ,
295+ default : 391
296+ }
297+ ] ,
298+ subverbs : [
299+ {
300+ id : 'sub' ,
301+ name : 'sub' ,
302+ args : [
303+ {
304+ id : 'subArg' ,
305+ type : ( ctx ) => ctx . consumeToken ( ) ,
306+ optional : true ,
307+ }
308+ ] ,
309+ options : [
310+ {
311+ id : 'subOpt' ,
312+ names : [ '--sub' , '-s' ] ,
313+ args : [
314+ {
315+ id : 'subOptArg' ,
316+ type : ( ctx ) => ctx . consumeToken ( ) ,
317+ }
318+ ]
319+ }
320+ ]
321+ }
322+ ]
323+ } ;
197324
198325const ctx = new ParseContext ( scriptArgs . slice ( 1 ) ) ;
199326const result = new ParseResult ( ) ;
200327
201- while ( ! ctx . isEndOfTokens ) {
202- parseLongOption ( ctx , result , info ) ;
203- if ( ctx . currentToken ?. [ 1 ] != '-' )
204- parseShortOption ( ctx , result , info ) ;
205- }
328+ parseVerb ( ctx , result , info ) ;
206329console . log ( JSON . stringify ( result . getMap ( ) ) ) ;
0 commit comments