Skip to content

Commit

Permalink
refactor(command): add ArgumentsParser util class
Browse files Browse the repository at this point in the history
  • Loading branch information
c4spar committed Jun 24, 2020
1 parent 8e4167f commit c30e474
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 148 deletions.
55 changes: 7 additions & 48 deletions packages/command/commands/help.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { encode } from 'https://deno.land/std@v0.52.0/encoding/utf8.ts';
import { blue, bold, dim, green, magenta, red, yellow } from 'https://deno.land/std@v0.52.0/fmt/colors.ts';
import { blue, bold, dim, magenta, red, yellow } from 'https://deno.land/std@v0.52.0/fmt/colors.ts';
import { IFlagOptions, IFlags } from '../../flags/lib/types.ts';
import { Table } from '../../table/lib/table.ts';
import format from '../../x/format.ts';
import { ArgumentsParser } from '../lib/arguments-parser.ts';
import { BaseCommand } from '../lib/base-command.ts';
import { IArgumentDetails, IEnvVariable, IHelpCommand, IOption } from '../lib/types.ts';
import { IEnvVariable, IHelpCommand, IOption } from '../lib/types.ts';
import { CommandListType } from '../types/command-list.ts';

/**
Expand All @@ -17,7 +18,7 @@ export class HelpCommand extends BaseCommand implements IHelpCommand {
super();

this
.type( 'command', new CommandListType( this.parent ) )
.type( 'command', new CommandListType() )
.arguments( '[command:command]' )

.description( 'Show this help or the help of a sub-command.' )
Expand Down Expand Up @@ -134,7 +135,7 @@ export class HelpCommand extends BaseCommand implements IHelpCommand {
return [
...cmd.getOptions( false ).map( ( option: IOption ) => [
option.flags.split( /,? +/g ).map( flag => blue( flag ) ).join( ', ' ),
this.highlight( option.typeDefinition || '' ),
ArgumentsParser.highlightArguments( option.typeDefinition || '' ),
red( bold( '-' ) ),
option.description.split( '\n' ).shift() as string,
getHints( option )
Expand All @@ -147,7 +148,7 @@ export class HelpCommand extends BaseCommand implements IHelpCommand {
return [
...cmd.getCommands( false ).map( ( command: BaseCommand ) => [
[ command.getName(), ...command.getAliases() ].map( name => blue( name ) ).join( ', ' ),
this.highlight( command.getArgsDefinition() || '' ),
ArgumentsParser.highlightArguments( command.getArgsDefinition() || '' ),
red( bold( '-' ) ),
command.getDescription().split( '\n' ).shift() as string
] )
Expand All @@ -159,7 +160,7 @@ export class HelpCommand extends BaseCommand implements IHelpCommand {
return [
...cmd.getEnvVars( false ).map( ( envVar: IEnvVariable ) => [
envVar.names.map( name => blue( name ) ).join( ', ' ),
this.highlightDetails( envVar.details ),
ArgumentsParser.highlightArgumentDetails( envVar.details ),
`${ red( bold( '-' ) ) } ${ envVar.description }`
] )
];
Expand Down Expand Up @@ -202,48 +203,6 @@ export class HelpCommand extends BaseCommand implements IHelpCommand {

return output;
}

/**
* Colorize argument type's.
*/
protected highlight( type: string = '' ): string {

if ( !type ) {
return type;
}

return this.parseArgsDefinition( type ).map( ( arg: IArgumentDetails ) => this.highlightDetails( arg ) ).join( ' ' );
}

/**
* Colorize argument type's.
*/
protected highlightDetails( arg: IArgumentDetails ): string {

let str = '';

str += yellow( arg.optionalValue ? '[' : '<' );

let name = '';
name += arg.name;
if ( arg.variadic ) {
name += '...';
}
name = magenta( name );

str += name;

str += yellow( ':' );
str += red( arg.type );

if ( arg.list ) {
str += green( '[]' );
}

str += yellow( arg.optionalValue ? ']' : '>' );

return str;
}
}

function capitalize( string: string ): string {
Expand Down
118 changes: 118 additions & 0 deletions packages/command/lib/arguments-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { green, magenta, red, yellow } from 'https://deno.land/std@v0.52.0/fmt/colors.ts';
import { OptionType } from '../../flags/lib/types.ts';
import { IArgumentDetails } from './types.ts';

export class ArgumentsParser {

private static readonly ARGUMENT_REGEX = /^[<\[].+[\]>]$/;
private static readonly ARGUMENT_DETAILS_REGEX = /[<\[:>\]]/;

public static splitArguments( args: string ): { args: string[], typeDefinition: string } {

const parts = args.trim().split( /[, =] */g );
const typeParts = [];

while ( parts[ parts.length - 1 ] && this.ARGUMENT_REGEX.test( parts[ parts.length - 1 ] ) ) {
typeParts.unshift( parts.pop() );
}

const typeDefinition: string = typeParts.join( ' ' );

return { args: parts, typeDefinition };
}

public static parseArgumentsDefinition( argsDefinition: string ): IArgumentDetails[] {

const argumentDetails: IArgumentDetails[] = [];

let hasOptional = false;
let hasVariadic = false;
const parts: string[] = argsDefinition.split( / +/ );

for ( const arg of parts ) {

if ( hasVariadic ) {
throw new Error( 'An argument can not follow an variadic argument.' );
}

const parts: string[] = arg.split( this.ARGUMENT_DETAILS_REGEX );
const type: OptionType | string | undefined = parts[ 2 ] ? parts[ 2 ] : OptionType.STRING;

let details: IArgumentDetails = {
optionalValue: arg[ 0 ] !== '<',
name: parts[ 1 ],
action: parts[ 3 ] || type,
variadic: false,
list: type ? arg.indexOf( type + '[]' ) !== -1 : false,
type
};

if ( !details.optionalValue && hasOptional ) {
throw new Error( 'An required argument can not follow an optional argument.' );
}

if ( arg[ 0 ] === '[' ) {
hasOptional = true;
}

if ( details.name.length > 3 ) {

const istVariadicLeft = details.name.slice( 0, 3 ) === '...';
const istVariadicRight = details.name.slice( -3 ) === '...';

hasVariadic = details.variadic = istVariadicLeft || istVariadicRight;

if ( istVariadicLeft ) {
details.name = details.name.slice( 3 );
} else if ( istVariadicRight ) {
details.name = details.name.slice( 0, -3 );
}
}

if ( details.name ) {
argumentDetails.push( details );
}
}

return argumentDetails;
}

public static highlightArguments( argsDefinition: string ) {

if ( !argsDefinition ) {
return '';
}

return this.parseArgumentsDefinition( argsDefinition )
.map( ( arg: IArgumentDetails ) => this.highlightArgumentDetails( arg ) ).join( ' ' );
}

public static highlightArgumentDetails( arg: IArgumentDetails ): string {

let str = '';

str += yellow( arg.optionalValue ? '[' : '<' );

let name = '';
name += arg.name;
if ( arg.variadic ) {
name += '...';
}
name = magenta( name );

str += name;

// if ( arg.name !== arg.type ) {
str += yellow( ':' );
str += red( arg.type );
// }

if ( arg.list ) {
str += green( '[]' );
}

str += yellow( arg.optionalValue ? ']' : '>' );

return str;
}
}
107 changes: 7 additions & 100 deletions packages/command/lib/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { BooleanType } from '../types/boolean.ts';
import { NumberType } from '../types/number.ts';
import { StringType } from '../types/string.ts';
import { Type } from '../types/type.ts';
import { ArgumentsParser } from './arguments-parser.ts';
import { IAction, IArgumentDetails, ICommandOption, ICompleteHandler, ICompleteOptions, ICompleteSettings, IDescription, IEnvVariable, IEnvVarOption, IExample, IHelpCommand, IOption, IParseResult, isHelpCommand, ITypeMap, ITypeOption, ITypeSettings } from './types.ts';

const permissions: any = ( Deno as any ).permissions;
Expand Down Expand Up @@ -64,7 +65,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
cmd = undefined;
}

const result = this.splitArguments( nameAndArguments );
const result = ArgumentsParser.splitArguments( nameAndArguments );

const name: string | undefined = result.args.shift();
const aliases: string[] = result.args;
Expand Down Expand Up @@ -413,9 +414,9 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
return this.option( flags, desc, { value: opts } );
}

const result = this.splitArguments( flags );
const result = ArgumentsParser.splitArguments( flags );

const args: IArgumentDetails[] = result.typeDefinition ? this.parseArgsDefinition( result.typeDefinition ) : [];
const args: IArgumentDetails[] = result.typeDefinition ? ArgumentsParser.parseArgumentsDefinition( result.typeDefinition ) : [];

const option: IOption = {
name: '',
Expand Down Expand Up @@ -493,7 +494,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
*/
public env( name: string, description: string, options?: IEnvVarOption ): this {

const result = this.splitArguments( name );
const result = ArgumentsParser.splitArguments( name );

if ( !result.typeDefinition ) {
result.typeDefinition = '<value:boolean>';
Expand All @@ -503,7 +504,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
throw this.error( new Error( `Environment variable already exists: ${ name }` ) );
}

const details: IArgumentDetails[] = this.parseArgsDefinition( result.typeDefinition );
const details: IArgumentDetails[] = ArgumentsParser.parseArgumentsDefinition( result.typeDefinition );

if ( details.length > 1 ) {
throw this.error( new Error( `An environment variable can only have one value but got: ${ name }` ) );
Expand Down Expand Up @@ -720,39 +721,6 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
}
}

/**
* Split arguments string into args and types: -v, --verbose [arg:boolean]
*
* @param args Arguments definition.
*/
protected splitArguments( args: string ) {

// const parts = args.trim().split( /[,<\[]/g ).map( ( arg: string ) => arg.trim() );
// const typeParts: string[] = [];
//
// while ( parts[ parts.length - 1 ] && parts[ parts.length - 1 ].match( /[\]>]$/ ) ) {
// let arg = parts.pop() as string;
// const lastPart = arg.slice( 0, -1 );
// arg = lastPart === ']' ? `[${ arg }` : `<${ arg }`;
// typeParts.unshift( arg );
// }
//
// const typeDefinition: string | undefined = typeParts.join( ' ' ) || undefined;
//
// return { args: parts, typeDefinition };

const parts = args.trim().split( /[, =] */g );
const typeParts = [];

while ( parts[ parts.length - 1 ] && parts[ parts.length - 1 ].match( /^[<\[].+[\]>]$/ ) ) {
typeParts.unshift( parts.pop() );
}

const typeDefinition: string = typeParts.join( ' ' );

return { args: parts, typeDefinition };
}

/**
* Match commands and arguments from command line arguments.
*/
Expand Down Expand Up @@ -820,67 +788,6 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
return params as A;
}

/**
* Parse command line args definition.
*
* @param argsDefinition Arguments definition: <arg1:string> [arg2:number]
*/
protected parseArgsDefinition( argsDefinition: string ): IArgumentDetails[] {

const args: IArgumentDetails[] = [];

let hasOptional = false;
let hasVariadic = false;
const parts: string[] = argsDefinition.split( / +/ );

for ( const arg of parts ) {

if ( hasVariadic ) {
throw this.error( new Error( 'An argument can not follow an variadic argument.' ) );
}

const parts: string[] = arg.split( /[<\[:>\]]/ );
const type: OptionType | string | undefined = parts[ 2 ] ? parts[ 2 ] : OptionType.STRING;

let details: IArgumentDetails = {
optionalValue: arg[ 0 ] !== '<',
name: parts[ 1 ],
action: parts[ 3 ] || type,
variadic: false,
list: type ? arg.indexOf( type + '[]' ) !== -1 : false,
type
};

if ( !details.optionalValue && hasOptional ) {
throw this.error( new Error( 'An required argument can not follow an optional argument.' ) );
}

if ( arg[ 0 ] === '[' ) {
hasOptional = true;
}

if ( details.name.length > 3 ) {

const istVariadicLeft = details.name.slice( 0, 3 ) === '...';
const istVariadicRight = details.name.slice( -3 ) === '...';

hasVariadic = details.variadic = istVariadicLeft || istVariadicRight;

if ( istVariadicLeft ) {
details.name = details.name.slice( 3 );
} else if ( istVariadicRight ) {
details.name = details.name.slice( 0, -3 );
}
}

if ( details.name ) {
args.push( details );
}
}

return args;
}

/**
* Execute help command if help flag is set.
*
Expand Down Expand Up @@ -964,7 +871,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
public getArguments(): IArgumentDetails[] {

if ( !this.args.length && this.argsDefinition ) {
this.args = this.parseArgsDefinition( this.argsDefinition );
this.args = ArgumentsParser.parseArgumentsDefinition( this.argsDefinition );
}

return this.args;
Expand Down

0 comments on commit c30e474

Please sign in to comment.