Skip to content

Commit

Permalink
fix(flags): default option incompatible with depends option and boole…
Browse files Browse the repository at this point in the history
…an flag's

* fix(flags): fix default value for boolean flags with optional value
* fix(flags): default option is incompatible with depends option (#3)
* test(command): add test's for `default` and `depends` option's
  • Loading branch information
c4spar committed May 3, 2020
1 parent 153edef commit b76a9a7
Show file tree
Hide file tree
Showing 7 changed files with 337 additions and 37 deletions.
58 changes: 58 additions & 0 deletions packages/command/test/option/default_test.ts
@@ -0,0 +1,58 @@
import { Command } from '../../lib/command.ts';
import { assertEquals } from '../lib/assert.ts';

Deno.test( 'command: option -> default', async () => {

const { options, args } = await new Command()
.throwErrors()

.option( '--flag1', 'flag 1' )
.option( '--flag2 <val:string>', 'flag 2', { default: 'example' } )

.parse( [] );

assertEquals( options, { flag2: 'example' } );
assertEquals( args, [] );
} );

Deno.test( 'command: option -> default', async () => {

const { options, args } = await new Command()
.throwErrors()

.option( '--flag1', 'flag 1' )
.option( '--flag2 <val:string>', 'flag 2', { default: 'example' } )

.parse( [ '--flag1' ] );

assertEquals( options, { flag1: true, flag2: 'example' } );
assertEquals( args, [] );
} );

Deno.test( 'command: option -> default', async () => {

const { options, args } = await new Command()
.throwErrors()

.option( '--flag1', 'flag 1' )
.option( '--flag2 <val:string>', 'flag 2', { default: 'example' } )

.parse( [ '--flag2', 'test' ] );

assertEquals( options, { flag2: 'test' } );
assertEquals( args, [] );
} );

Deno.test( 'command: option -> default', async () => {

const { options, args } = await new Command()
.throwErrors()

.option( '--flag1', 'flag 1' )
.option( '--flag2 <val:string>', 'flag 2', { default: 'example' } )

.parse( [ '--flag1', '--flag2', 'test' ] );

assertEquals( options, { flag1: true, flag2: 'test' } );
assertEquals( args, [] );
} );
49 changes: 49 additions & 0 deletions packages/command/test/option/depends_test.ts
@@ -0,0 +1,49 @@
import { assertThrows } from '../../../flags/test/lib/assert.ts';
import { Command } from '../../lib/command.ts';
import { assertEquals } from '../lib/assert.ts';

const cmd: Command = new Command()
.throwErrors()
.option( '--flag1', 'flag 1' )
.option( '--flag2 <val:string>', 'flag 2', { depends: [ 'flag1' ], default: 'example' } );

Deno.test( 'command depends option with default value: should accept no arguments', async () => {

const { options, args } = await cmd.parse( [] );

assertEquals( options, { flag2: 'example' } );
assertEquals( args, [] );
} );

Deno.test( 'command depends option with default value: should accept -h', async () => {

const { options, args } = await cmd.parse( [ '-h' ] );

assertEquals( options, { flag2: 'example' } );
assertEquals( args, [] );
} );

Deno.test( 'command depends option with default value: should accept --flag1', async () => {

const { options, args } = await cmd.parse( [ '--flag1' ] );

assertEquals( options, { flag1: true, flag2: 'example' } );
assertEquals( args, [] );
} );

Deno.test( 'command depends option with default value: should accept --flag1 --flag2 test', async () => {

const { options, args } = await cmd.parse( [ '--flag1', '--flag2', 'test' ] );

assertEquals( options, { flag1: true, flag2: 'test' } );
assertEquals( args, [] );
} );

Deno.test( 'command depends option with default value: should not accept --flag2 test', async () => {

assertThrows(
() => cmd.parse( [ '--flag2', 'test' ] ),
Error,
'Option --flag2 depends on option: --flag1'
);
} );
8 changes: 6 additions & 2 deletions packages/flags/lib/flags.ts
Expand Up @@ -29,6 +29,7 @@ export function parseFlags( args: string[], opts: IParseOptions = {} ): IFlagsRe
let negate = false;

const flags: IFlags = {};
const defaultValues: IGenericObject<boolean> = {};
const literal: string[] = [];
const unknown: string[] = [];

Expand Down Expand Up @@ -111,6 +112,7 @@ export function parseFlags( args: string[], opts: IParseOptions = {} ): IFlagsRe

if ( typeof option.default !== 'undefined' ) {
flags[ friendlyName ] = typeof option.default === 'function' ? option.default() : option.default;
defaultValues[ friendlyName ] = true;
} else if ( option.args && option.args[ 0 ].optionalValue ) {
flags[ friendlyName ] = true;
} else {
Expand Down Expand Up @@ -172,6 +174,8 @@ export function parseFlags( args: string[], opts: IParseOptions = {} ): IFlagsRe
} else {
if ( hasNext() ) {
result = parseValue( next() );
} else if ( arg.optionalValue && arg.type === OptionType.BOOLEAN ) {
result = true;
}
}

Expand Down Expand Up @@ -219,7 +223,7 @@ export function parseFlags( args: string[], opts: IParseOptions = {} ): IFlagsRe
throw new Error( 'Wrongly used parseValue.' );
}

let result = opts.parse ?
let result: IFlagValueType = opts.parse ?
opts.parse( arg.type || OptionType.STRING, option, arg, nextValue ) :
parseFlagValue( option, arg, nextValue );

Expand All @@ -237,7 +241,7 @@ export function parseFlags( args: string[], opts: IParseOptions = {} ): IFlagsRe
}

if ( opts.flags && opts.flags.length ) {
validateFlags( opts.flags, flags, opts.knownFlaks, opts.allowEmpty );
validateFlags( opts.flags, flags, defaultValues, opts.knownFlaks, opts.allowEmpty );
}

return { flags, unknown, literal };
Expand Down
15 changes: 12 additions & 3 deletions packages/flags/lib/validate-flags.ts
@@ -1,7 +1,7 @@
import camelCase from '../../x/camelCase.ts';
import paramCase from '../../x/paramCase.ts';
import { getOption } from './flags.ts';
import { IFlagArgument, IFlagOptions, IFlags, IFlagValue } from './types.ts';
import { IFlagArgument, IFlagOptions, IFlags, IFlagValue, IGenericObject } from './types.ts';

// @TODO: add support for knownFlaks

Expand All @@ -15,16 +15,18 @@ interface IFlagOptionsMap {
*
* @param flags Available flag options.
* @param values Flag to validate.
* @param defaultValues Values marked as default value.
* @param knownFlaks Don't throw an error if a missing flag is defined in knownFlags (currently not implemented).
* @param allowEmpty Don't throw an error if values is empty.
*/
export function validateFlags( flags: IFlagOptions[], values: IFlags, knownFlaks?: IFlags, allowEmpty?: boolean ): void {
export function validateFlags( flags: IFlagOptions[], values: IFlags, defaultValues: IGenericObject<boolean> = {}, knownFlaks?: IFlags, allowEmpty?: boolean ): void {

// Set default value's
for ( const option of flags ) {
const name: string = camelCase( option.name );
if ( typeof values[ name ] === 'undefined' && typeof option.default !== 'undefined' ) {
values[ name ] = typeof option.default === 'function' ? option.default() : option.default;
defaultValues[ name ] = true;
}
}

Expand All @@ -44,6 +46,12 @@ export function validateFlags( flags: IFlagOptions[], values: IFlags, knownFlaks

if ( option.standalone ) {
if ( keys.length > 1 ) {

// dont't throw an error if all values are coming from the default option.
if ( options.every( ( { option } ) => option && ( option.standalone || defaultValues[ option.name ] ) ) ) {
return;
}

throw new Error( `Option --${ option.name } cannot be combined with other options.` );
}
return;
Expand All @@ -56,7 +64,8 @@ export function validateFlags( flags: IFlagOptions[], values: IFlags, knownFlaks
} );

option.depends?.forEach( ( flag: string ) => {
if ( !isset( flag ) ) {
// dont't throw an error if the value is coming from the default option.
if ( !isset( flag ) && !defaultValues[ option.name ] ) {
throw new Error( `Option --${ option.name } depends on option: --${ flag }` );
}
} );
Expand Down
21 changes: 19 additions & 2 deletions packages/flags/test/option/default_test.ts
Expand Up @@ -42,7 +42,7 @@ const options = <IParseOptions>{
} ]
};

Deno.test( 'flags optionDefault defaultValues', () => {
Deno.test( 'flags default option: no arguments', () => {

const { flags, unknown, literal } = parseFlags( [], options );

Expand All @@ -59,7 +59,7 @@ Deno.test( 'flags optionDefault defaultValues', () => {
assertEquals( literal, [] );
} );

Deno.test( 'flags optionDefault defaultValues', () => {
Deno.test( 'flags default option: override default values', () => {

const { flags, unknown, literal } = parseFlags( [ '-b', '1', '-s', '1', '-n', '1', '-B', '0', '-S', '0', '-N', '0', '-m', '0' ], options );

Expand All @@ -75,3 +75,20 @@ Deno.test( 'flags optionDefault defaultValues', () => {
assertEquals( unknown, [] );
assertEquals( literal, [] );
} );

Deno.test( 'flags optionDefault override default value with optional value', () => {

const { flags, unknown, literal } = parseFlags( [ '-b' ], options );

assertEquals( flags, {
boolean: true,
string: '0',
number: 0,
boolean2: true,
string2: '1',
number2: 1,
method: 1
} );
assertEquals( unknown, [] );
assertEquals( literal, [] );
} );

0 comments on commit b76a9a7

Please sign in to comment.