11import { z } from 'zod'
22import { isZodError , ParseError , ValidationError } from './error'
3- import { HelpGenerator } from './help'
3+ import { defaultHelpConfig , HelpConfig , HelpGenerator } from './help'
44import MassargOption , {
55 MassargFlag ,
66 OptionConfig ,
77 TypedOptionConfig ,
88 MassargHelpFlag ,
99} from './option'
10- import { setOrPush } from './utils'
10+ import { setOrPush , deepMerge } from './utils'
1111import MassargExample , { ExampleConfig } from './example'
1212
1313export const CommandConfig = < RunArgs extends z . ZodType > ( args : RunArgs ) =>
@@ -26,18 +26,7 @@ export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
2626 . function ( )
2727 . args ( args , z . any ( ) )
2828 . returns ( z . union ( [ z . promise ( z . void ( ) ) , z . void ( ) ] ) ) as z . ZodType < Runner < z . infer < RunArgs > > > ,
29- /**
30- * Whether to bind the help command to this command
31- *
32- * Set this to `true` to automatically add a `help` command to this command's subcommands.
33- */
34- bindHelpCommand : z . boolean ( ) . optional ( ) ,
35- /**
36- * Whether to bind the help option to this command
37- *
38- * Set this to `true` to automatically add a `--help` option to this command's options.
39- */
40- bindHelpOption : z . boolean ( ) . optional ( ) ,
29+ helpConfig : HelpConfig . optional ( ) ,
4130 // argsHint: z.string().optional(),
4231 } )
4332
@@ -59,19 +48,15 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
5948 options : MassargOption [ ] = [ ]
6049 examples : MassargExample [ ] = [ ]
6150 args : Partial < Args > = { }
51+ helpConfig : Required < HelpConfig >
6252
6353 constructor ( options : CommandConfig < Args > ) {
6454 CommandConfig ( z . any ( ) ) . parse ( options )
6555 this . name = options . name
6656 this . description = options . description
6757 this . aliases = options . aliases ?? [ ]
6858 this . _run = options . run
69- if ( options . bindHelpCommand ) {
70- this . command ( new MassargHelpCommand ( ) )
71- }
72- if ( options . bindHelpOption ) {
73- this . option ( new MassargHelpFlag ( ) )
74- }
59+ this . helpConfig = HelpConfig . required ( ) . parse ( defaultHelpConfig )
7560 }
7661
7762 command < A extends ArgsObject = Args > ( config : CommandConfig < A > ) : MassargCommand < Args >
@@ -110,15 +95,13 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
11095 ) : MassargCommand < Args > {
11196 try {
11297 const flag = config instanceof MassargFlag ? config : new MassargFlag ( config )
113- if ( flag . isDefault ) {
114- const defaultOption = this . options . find ( ( o ) => o . isDefault )
115- if ( defaultOption ) {
116- throw new ValidationError ( {
117- code : 'duplicate_default_option' ,
118- message : `Option "${ flag . name } " cannot be set as default because option "${ defaultOption . name } " is already set as default` ,
119- path : [ this . name , flag . name ] ,
120- } )
121- }
98+ const existing = this . options . find ( ( c ) => c . name === flag . name )
99+ if ( existing ) {
100+ throw new ValidationError ( {
101+ code : 'duplicate_flag' ,
102+ message : `Flag "${ flag . name } " already exists` ,
103+ path : [ this . name , flag . name ] ,
104+ } )
122105 }
123106 this . options . push ( flag as MassargOption )
124107 return this
@@ -140,6 +123,14 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
140123 try {
141124 const option =
142125 config instanceof MassargOption ? config : MassargOption . fromTypedConfig ( config )
126+ const existing = this . options . find ( ( c ) => c . name === option . name )
127+ if ( existing ) {
128+ throw new ValidationError ( {
129+ code : 'duplicate_option' ,
130+ message : `Option "${ option . name } " already exists` ,
131+ path : [ this . name , option . name ] ,
132+ } )
133+ }
143134 if ( option . isDefault ) {
144135 const defaultOption = this . options . find ( ( o ) => o . isDefault )
145136 if ( defaultOption ) {
@@ -169,6 +160,20 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
169160 return this
170161 }
171162
163+ help ( config : HelpConfig ) : MassargCommand < Args > {
164+ this . helpConfig = HelpConfig . required ( ) . parse (
165+ deepMerge ( defaultHelpConfig , config ) as HelpConfig ,
166+ )
167+
168+ if ( this . helpConfig . bindCommand ) {
169+ this . command ( new MassargHelpCommand ( ) )
170+ }
171+ if ( this . helpConfig . bindOption ) {
172+ this . option ( new MassargHelpFlag ( ) )
173+ }
174+ return this
175+ }
176+
172177 main < A extends ArgsObject = Args > ( run : Runner < A > ) : MassargCommand < Args > {
173178 this . _run = run
174179 return this
@@ -217,46 +222,50 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
217222 let _args : Args = { ...this . args , ...args } as Args
218223 let _argv = [ ...argv ]
219224 const _a = this . args as Record < string , string [ ] >
225+
226+ // fill defaults
227+ for ( const option of this . options ) {
228+ if ( option . defaultValue !== undefined && _a [ option . name ] === undefined ) {
229+ _args [ option . name as keyof Args ] = option . defaultValue as Args [ keyof Args ]
230+ }
231+ }
232+
233+ // parse options
220234 while ( _argv . length ) {
221235 const arg = _argv . shift ( ) !
222236 const found = this . options . some ( ( o ) => o . _isOption ( arg ) )
223237 if ( found ) {
224- const option = this . options . find ( ( o ) => o . _match ( arg ) )
225- if ( ! option ) {
226- throw new ValidationError ( {
227- path : [ MassargOption . getName ( arg ) ] ,
228- code : 'unknown_option' ,
229- message : 'Unknown option' ,
230- } )
231- }
232- const res = option . _parseDetails ( argv )
233- _args [ res . key as keyof Args ] = setOrPush < Args [ keyof Args ] > (
234- res . value ,
235- _args [ res . key as keyof Args ] ,
236- option . isArray ,
237- )
238+ _argv = this . parseOption ( arg , _argv )
239+ _args = { ..._args , ...this . args }
238240 continue
239241 }
240242
241243 const command = this . commands . find ( ( c ) => c . name === arg || c . aliases . includes ( arg ) )
242244 if ( command ) {
245+ // this is dry run, just exit
243246 if ( ! parseCommands ) {
244247 break
245248 }
249+ // this is real run, parse command, pass unparsed args
246250 return command . parse ( _argv , this . args , parent ?? this )
247251 }
252+ // default option - passes arg value even without flag name
248253 const defaultOption = this . options . find ( ( o ) => o . isDefault )
249254 if ( defaultOption ) {
250255 _argv = this . parseOption ( `--${ defaultOption . name } ` , [ arg , ..._argv ] )
251256 continue
252257 }
258+ // not parsed by any step, add to extra key
253259 _a . extra ??= [ ]
254260 _a . extra . push ( arg )
255261 }
262+ this . args = { ...this . args , ..._args }
263+ // dry run, just exit
256264 if ( ! parseCommands ) {
257- return _args
265+ return this . args as Args
258266 }
259- this . args = { ...this . args , ..._args }
267+
268+ // no sub command found, run main command
260269 if ( this . _run ) {
261270 this . _run ( this . args , parent ?? this )
262271 }
0 commit comments