Skip to content

Commit

Permalink
breaking(command): remove BaseCommand class (#27)
Browse files Browse the repository at this point in the history
All commands have to be created with the `Command` class for now.
The `help` and `completions` commands are now optional and can be registered as descripted in the example below.
The `--help` and `--version` option will be registered only on the main command for now. The `--help` option is a global option and available on all child-command's.

```typescript
import { Command, HelpCommand, CompletionsCommand } from 'https://deno.land/x/cliffy/command.ts';

await new Command()
    .command( 'help', new HelpCommand() )
    .command( 'completions', new CompletionsCommand() )
    .parse()
```
  • Loading branch information
c4spar committed Jun 24, 2020
1 parent 2bc4660 commit 029aac5
Show file tree
Hide file tree
Showing 23 changed files with 141 additions and 104 deletions.
6 changes: 3 additions & 3 deletions packages/command/commands/completions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { bold, dim, italic } from 'https://deno.land/std@v0.52.0/fmt/colors.ts';
import { BaseCommand } from '../lib/base-command.ts';
import { Command } from '../lib/command.ts';
import { BashCompletionsCommand } from './completions/bash.ts';
import { CompleteCommand } from './completions/complete.ts';
import { ZshCompletionsCommand } from './completions/zsh.ts';

/**
* Generates source code for interactive shell completions used in multiple shell's.
*/
export class CompletionsCommand extends BaseCommand {
export class CompletionsCommand extends Command {

public constructor( cmd?: BaseCommand ) {
public constructor( cmd?: Command ) {

super();

Expand Down
6 changes: 3 additions & 3 deletions packages/command/commands/completions/bash.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { BaseCommand } from '../../lib/base-command.ts';
import { Command } from '../../lib/command.ts';

/**
* Generates bash completion code.
*/
export class BashCompletionsCommand extends BaseCommand {
export class BashCompletionsCommand extends Command {

public constructor( _cmd?: BaseCommand ) {
public constructor( _cmd?: Command ) {
super();
this.description( 'Generate bash shell completions.' )
.action( () => {
Expand Down
14 changes: 7 additions & 7 deletions packages/command/commands/completions/complete.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { encode } from 'https://deno.land/std@v0.52.0/encoding/utf8.ts';
import { IFlags } from '../../../flags/lib/types.ts';
import { BaseCommand } from '../../lib/base-command.ts';
import { Command } from '../../lib/command.ts';
import { ICompleteSettings } from '../../lib/types.ts';

/**
* Execute complete method for specific action and command.
*/
export class CompleteCommand extends BaseCommand {
export class CompleteCommand extends Command {

public constructor( cmd?: BaseCommand ) {
public constructor( cmd?: Command ) {
super();
this.description( 'Get completions for given action from given command.' )
.arguments( '<action:action> [command...:command]' )
.action( async ( options: IFlags, action: string, commandNames: string[] ) => {

let parent: BaseCommand | undefined;
let completeCommand: BaseCommand = commandNames
.reduce( ( cmd: BaseCommand, name: string ): BaseCommand => {
let parent: Command | undefined;
let completeCommand: Command = commandNames
.reduce( ( cmd: Command, name: string ): Command => {
parent = cmd;
const childCmd: BaseCommand | undefined = cmd.getCommand( name, false );
const childCmd: Command | undefined = cmd.getCommand( name, false );
if ( !childCmd ) {
throw new Error( `Auto-completion failed. Command not found: ${ commandNames.join( ' ' ) }` );
}
Expand Down
6 changes: 3 additions & 3 deletions packages/command/commands/completions/zsh.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { BaseCommand } from '../../lib/base-command.ts';
import { Command } from '../../lib/command.ts';
import { ZshCompletionsGenerator } from '../../lib/zsh-completions-generator.ts';

/**
* Generates zsh completion code.
*/
export class ZshCompletionsCommand extends BaseCommand {
export class ZshCompletionsCommand extends Command {

public constructor( cmd?: BaseCommand ) {
public constructor( cmd?: Command ) {
super();
this.description( 'Generate zsh shell completions.' )
.action( () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/command/commands/help.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { IFlags } from '../../flags/lib/types.ts';
import { BaseCommand } from '../lib/base-command.ts';
import { Command } from '../lib/command.ts';
import { ParentCommandListType } from '../types/parent-command-list.ts';

/**
* Generates well formatted and colored help output for specified command.
*/
export class HelpCommand extends BaseCommand {
export class HelpCommand extends Command {

public constructor( cmd?: BaseCommand ) {
public constructor( cmd?: Command ) {
super();
this.type( 'command', new ParentCommandListType() )
.arguments( '[command:command]' )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const hasEnvPermissions: boolean = !!envPermissionStatus && envPermissionStatus.
/**
* Base command implementation without pre configured command's and option's.
*/
export class BaseCommand<O = any, A extends Array<any> = any> {
export class Command<O = any, A extends Array<any> = any> {

private types: ITypeMap = new Map<string, ITypeSettings>( [
[ 'string', { name: 'string', handler: new StringType() } ],
Expand All @@ -32,18 +32,18 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
// @TODO: get script name: https://github.com/denoland/deno/pull/5034
// private name: string = location.pathname.split( '/' ).pop() as string;
private _name: string = 'COMMAND';
private _parent?: BaseCommand;
private _globalParent?: BaseCommand;
private _parent?: Command;
private _globalParent?: Command;
private ver: string = '0.0.0';
private desc: IDescription = '';
private fn: IAction<O, A> | undefined;
private options: IOption<O, A>[] = [];
private commands: Map<string, BaseCommand> = new Map();
private commands: Map<string, Command> = new Map();
private examples: IExample[] = [];
private envVars: IEnvVariable[] = [];
private aliases: string[] = [];
private completions: Map<string, ICompleteSettings> = new Map();
private cmd: BaseCommand = this;
private cmd: Command = this;
private argsDefinition: string | undefined;
private isExecutable: boolean = false;
private throwOnError: boolean = false;
Expand All @@ -54,11 +54,12 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
private args: IArgumentDetails[] = [];
private isHidden: boolean = false;
private isGlobal: boolean = false;
private hasDefaults: Boolean = false;

/**
* Add new sub-command.
*/
public command( nameAndArguments: string, cmd?: BaseCommand | string, override?: boolean ): this {
public command( nameAndArguments: string, cmd?: Command | string, override?: boolean ): this {

let executableDescription: string | undefined;

Expand All @@ -83,7 +84,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
this.removeCommand( name );
}

const subCommand = ( cmd || new BaseCommand() ).reset();
const subCommand = ( cmd || new Command() ).reset();

subCommand._name = name;
subCommand._parent = this;
Expand Down Expand Up @@ -294,7 +295,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
this.cmd.types.set( name, { ...options, name, handler } );

if ( handler instanceof Type && typeof handler.complete !== 'undefined' ) {
this.complete( name, ( cmd: BaseCommand, parent?: BaseCommand ) => handler.complete?.( cmd, parent ) || [], options );
this.complete( name, ( cmd: Command, parent?: Command ) => handler.complete?.( cmd, parent ) || [], options );
}

return this;
Expand Down Expand Up @@ -341,7 +342,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {

public getGlobalCompletions(): ICompleteSettings[] {

const getCompletions = ( cmd: BaseCommand | undefined, completions: ICompleteSettings[] = [], names: string[] = [] ): ICompleteSettings[] => {
const getCompletions = ( cmd: Command | undefined, completions: ICompleteSettings[] = [], names: string[] = [] ): ICompleteSettings[] => {

if ( cmd ) {
if ( cmd.completions.size ) {
Expand Down Expand Up @@ -537,7 +538,8 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
*/
public async parse( args: string[] = Deno.args, dry?: boolean ): Promise<IParseResult<O, A>> {

this.reset();
this.reset()
.registerDefaults();

this.rawArgs = args;

Expand Down Expand Up @@ -582,6 +584,31 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
}
}

private registerDefaults(): this {
if ( this.getParent() || this.hasDefaults ) {
return this;
}
this.hasDefaults = true;

this.option( '-h, --help', 'Show this help.', {
standalone: true,
global: true,
action: function ( this: Command ) {
this.help();
Deno.exit( 0 );
}
} )
.option( '-V, --version', 'Show the version number for this program.', {
standalone: true,
action: () => {
this.log( this.getVersion() );
Deno.exit( 0 );
}
} );

return this;
};

/**
* Execute command.
*
Expand Down Expand Up @@ -826,7 +853,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
/**
* Get parent command.
*/
public getParent(): BaseCommand | undefined {
public getParent(): Command | undefined {
return this._parent;
}

Expand All @@ -835,14 +862,14 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
* Be sure, to call this method only inside an action handler. Unless this or any child command was executed,
* this method returns always undefined.
*/
public getGlobalParent(): BaseCommand | undefined {
public getGlobalParent(): Command | undefined {
return this._globalParent;
}

/**
* Get main command.
*/
public getMainCommand(): BaseCommand {
public getMainCommand(): Command {
return this._parent?.getMainCommand() ?? this;
}

Expand Down Expand Up @@ -943,7 +970,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {

public getGlobalOptions( hidden?: boolean ): IOption[] {

const getOptions = ( cmd: BaseCommand | undefined, options: IOption[] = [], names: string[] = [] ): IOption[] => {
const getOptions = ( cmd: Command | undefined, options: IOption[] = [], names: string[] = [] ): IOption[] => {

if ( cmd ) {
if ( cmd.options.length ) {
Expand Down Expand Up @@ -1052,23 +1079,23 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
/**
* Get sub-commands.
*/
public getCommands( hidden?: boolean ): BaseCommand[] {
public getCommands( hidden?: boolean ): Command[] {

return this.getGlobalCommands( hidden ).concat( this.getBaseCommands( hidden ) );
}

public getBaseCommands( hidden?: boolean ): BaseCommand[] {
public getBaseCommands( hidden?: boolean ): Command[] {
const commands = Array.from( this.commands.values() );
return hidden ? commands : commands.filter( cmd => !cmd.isHidden );
}

public getGlobalCommands( hidden?: boolean ): BaseCommand[] {
public getGlobalCommands( hidden?: boolean ): Command[] {

const getCommands = ( cmd: BaseCommand | undefined, commands: BaseCommand[] = [], names: string[] = [] ): BaseCommand[] => {
const getCommands = ( cmd: Command | undefined, commands: Command[] = [], names: string[] = [] ): Command[] => {

if ( cmd ) {
if ( cmd.commands.size ) {
cmd.commands.forEach( ( cmd: BaseCommand ) => {
cmd.commands.forEach( ( cmd: Command ) => {
if (
cmd.isGlobal &&
this !== cmd &&
Expand Down Expand Up @@ -1108,25 +1135,25 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
* @param name Name of the sub-command.
* @param hidden Include hidden commands.
*/
public getCommand<O = any>( name: string, hidden?: boolean ): BaseCommand<O> | undefined {
public getCommand<O = any>( name: string, hidden?: boolean ): Command<O> | undefined {

return this.getBaseCommand( name, hidden ) ?? this.getGlobalCommand( name, hidden );
}

public getBaseCommand<O = any>( name: string, hidden?: boolean ): BaseCommand<O> | undefined {
public getBaseCommand<O = any>( name: string, hidden?: boolean ): Command<O> | undefined {

let cmd: BaseCommand | undefined = this.commands.get( name );
let cmd: Command | undefined = this.commands.get( name );

return cmd && ( hidden || !cmd.isHidden ) ? cmd : undefined;
}

public getGlobalCommand<O = any>( name: string, hidden?: boolean ): BaseCommand<O> | undefined {
public getGlobalCommand<O = any>( name: string, hidden?: boolean ): Command<O> | undefined {

if ( !this._parent ) {
return;
}

let cmd: BaseCommand | undefined = this._parent.getBaseCommand( name, hidden );
let cmd: Command | undefined = this._parent.getBaseCommand( name, hidden );

if ( !cmd?.isGlobal ) {
return this._parent.getGlobalCommand( name, hidden );
Expand All @@ -1140,7 +1167,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
*
* @param name Name of the command.
*/
public removeCommand<O = any>( name: string ): BaseCommand<O> | undefined {
public removeCommand<O = any>( name: string ): Command<O> | undefined {

const command = this.getBaseCommand( name, true );

Expand All @@ -1161,7 +1188,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {

public getGlobalTypes(): ITypeSettings[] {

const getTypes = ( cmd: BaseCommand | undefined, types: ITypeSettings[] = [], names: string[] = [] ): ITypeSettings[] => {
const getTypes = ( cmd: Command | undefined, types: ITypeSettings[] = [], names: string[] = [] ): ITypeSettings[] => {

if ( cmd ) {
if ( cmd.types.size ) {
Expand Down Expand Up @@ -1238,7 +1265,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {

public getGlobalEnvVars( hidden?: boolean ): IEnvVariable[] {

const getEnvVars = ( cmd: BaseCommand | undefined, envVars: IEnvVariable[] = [], names: string[] = [] ): IEnvVariable[] => {
const getEnvVars = ( cmd: Command | undefined, envVars: IEnvVariable[] = [], names: string[] = [] ): IEnvVariable[] => {

if ( cmd ) {
if ( cmd.envVars.length ) {
Expand Down
10 changes: 5 additions & 5 deletions packages/command/lib/help-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import { IFlagOptions } from '../../flags/lib/types.ts';
import { Table } from '../../table/lib/table.ts';
import format from '../../x/format.ts';
import { ArgumentsParser } from './arguments-parser.ts';
import { BaseCommand } from './base-command.ts';
import { Command } from './command.ts';
import { IEnvVariable, IExample, IOption } from './types.ts';

export class HelpGenerator {

private indent: number = 2;

public static generate( cmd: BaseCommand ): string {
public static generate( cmd: Command ): string {
return new HelpGenerator( cmd ).generate();
}

private constructor( protected cmd: BaseCommand ) {}
private constructor( protected cmd: Command ) {}

private generate(): string {

Expand Down Expand Up @@ -106,7 +106,7 @@ export class HelpGenerator {
if ( hasTypeDefinitions ) {
return this.label( 'Commands' ) +
Table.from( [
...commands.map( ( command: BaseCommand ) => [
...commands.map( ( command: Command ) => [
[ command.getName(), ...command.getAliases() ].map( name => blue( name ) ).join( ', ' ),
ArgumentsParser.highlightArguments( command.getArgsDefinition() || '' ),
red( bold( '-' ) ) + ' ' + command.getDescription().split( '\n' ).shift() as string
Expand All @@ -119,7 +119,7 @@ export class HelpGenerator {

return this.label( 'Commands' ) +
Table.from( [
...commands.map( ( command: BaseCommand ) => [
...commands.map( ( command: Command ) => [
[ command.getName(), ...command.getAliases() ].map( name => blue( name ) ).join( ', ' ),
red( bold( '-' ) ) + ' ' + command.getDescription().split( '\n' ).shift() as string
] )
Expand Down

0 comments on commit 029aac5

Please sign in to comment.