Skip to content

Commit

Permalink
feat(command): implement .stopEarly() method (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
c4spar committed Jun 4, 2020
1 parent ee683d3 commit 45f28e7
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 6 deletions.
16 changes: 16 additions & 0 deletions examples/command/stop-early.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env -S deno run

import { Command } from '../../packages/command/lib/command.ts';

await new Command()
.version( '0.1.0' )

.stopEarly()
.option( '-d, --debug-level <level:string>', 'Debug level.' )
.arguments( '[script:string] [...args:number]' )
.action( ( options: any, script: string, args: string[] ) => {
console.log( 'options:', options );
console.log( 'script:', script );
console.log( 'args:', args );
} )
.parse( Deno.args );
25 changes: 25 additions & 0 deletions packages/command/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
- [Specify the argument syntax](#specify-the-argument-syntax)
- [Global commands](#global-commands)
- [Hidden commands](#hidden-commands)
- [Stop early](#stop-early)
- [Git-style executable sub-commands](#git-style-executable-sub-commands)
- [Override exit handling](#override-exit-handling)
- [Specify environment variables](#specify-the-argument-syntax)
Expand Down Expand Up @@ -869,6 +870,30 @@ await new Command()
$ deno run https://deno.land/x/cliffy/examples/command/hidden-commands.ts -h
```

# Stop early

If enabled, all arguments starting from the first non option argument will be interpreted as raw argument.

```typescript
await new Command()
.stopEarly() // <-- enable stop early
.option( '-d, --debug-level <level:string>', '...' )
.arguments( '[script:string] [...args:number]' )
.action( ( options: any, script: string, args: string[] ) => {
console.log( 'options:', options );
console.log( 'script:', script );
console.log( 'args:', args );
} )
.parse( Deno.args );
```

```
$ deno run https://deno.land/x/cliffy/examples/command/stop-early.ts -d warning server -p 80
options: { debugLevel: "warning" }
script: server
args: [ "-p", "80" ]
```

### Git-style executable sub-commands

When `.command()` is invoked with a description argument, this tells cliffy that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools. Cliffy will search the executables in the directory of the entry script (like `./examples/pm`) with the name program-sub-command, like `pm-install`, `pm-search`. You can specify a custom name with the `executable` configuration option.
Expand Down
28 changes: 22 additions & 6 deletions packages/command/lib/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
protected isExecutable: boolean = false;
protected throwOnError: boolean = false;
protected _allowEmpty: boolean = true;
protected _stopEarly: boolean = false;
protected defaultCommand: string | undefined;
protected _useRawArgs: boolean = false;
protected args: IArgumentDetails[] = [];
Expand Down Expand Up @@ -247,6 +248,23 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
return this;
}

/**
* If enabled, all arguments starting from the first non option argument will be interpreted as raw argument.
*
* For example:
* `command --debug-level warning server --port 80`
*
* Will result in:
* - options: `{debugLevel: 'warning'}`
* - args: `['server', '--port', '80']`
*
* @param stopEarly
*/
public stopEarly( stopEarly: boolean = true ): this {
this.cmd._stopEarly = stopEarly;
return this;
}

/**
* Disable parsing arguments. If enabled the raw arguments will be passed to the action handler.
* This has no effect for parent or child commands. Only for the command on which this method was called.
Expand Down Expand Up @@ -650,15 +668,13 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
* Parse command line args.
*
* @param args Command line args.
* @param stopEarly Stop early.
* @param knownFlaks Known command line args.
*/
protected parseFlags( args: string[], stopEarly?: boolean, knownFlaks?: IFlags ): IFlagsResult<O> {
protected parseFlags( args: string[] ): IFlagsResult<O> {

try {
return parseFlags<O>( args, {
stopEarly,
knownFlaks,
stopEarly: this._stopEarly,
// knownFlaks,
allowEmpty: this._allowEmpty,
flags: this.getOptions( true ),
parse: ( type: string, option: IFlagOptions, arg: IFlagArgument, nextValue: string ) =>
Expand Down Expand Up @@ -736,7 +752,7 @@ export class BaseCommand<O = any, A extends Array<any> = any> {
typeParts.unshift( parts.pop() );
}

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

return { args: parts, typeDefinition };
}
Expand Down
56 changes: 56 additions & 0 deletions packages/command/test/command/stop-early_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Command } from '../../lib/command.ts';
import { assertEquals, assertThrowsAsync } from '../lib/assert.ts';

Deno.test( 'flags stopEarly disable', async () => {

const { options, args, literal } = await new Command()
.throwErrors()
.option( '-f, --flag [value:boolean]', 'description ...' )
.option( '-s, --script-arg1 [value:boolean]', 'description ...' )
.option( '-S, --script-arg2 [value:boolean]', 'description ...' )
.arguments( '[script:string] [args...:string]' )
.action( () => {} )
.parse( [
'-f', 'true', 'run', 'script-name', '--script-arg1', '--script-arg2', '--', '--literal-arg1', '--literal-arg2'
] );

assertEquals( options, { flag: true, scriptArg1: true, scriptArg2: true } );
assertEquals( args, [ 'run', [ 'script-name' ] ] );
assertEquals( literal, [ '--literal-arg1', '--literal-arg2' ] );
} );

Deno.test( 'flags stopEarly enabled', async () => {

const { options, args, literal } = await new Command()
.throwErrors()
.stopEarly()
.option( '-f, --flag [value:boolean]', 'description ...' )
.option( '-s, --script-arg1 [value:boolean]', 'description ...' )
.option( '-S, --script-arg2 [value:boolean]', 'description ...' )
.arguments( '[script:string] [args...:string]' )
.action( () => {} )
.parse( [
'-f', 'true', 'run', 'script-name', '--script-arg1', '--script-arg2', '--script-arg3', '--', '--literal-arg1', '--literal-arg2'
] );

assertEquals( options, { flag: true } );
assertEquals( args, [ 'run', [ 'script-name', '--script-arg1', '--script-arg2', '--script-arg3' ] ] );
assertEquals( literal, [ '--literal-arg1', '--literal-arg2' ] );
} );

Deno.test( 'flags stopEarly unknown option', async () => {

const cmd = new Command()
.throwErrors()
.stopEarly()
.option( '-f, --flag [value:boolean]', 'description ...' )
.option( '-s, --script-arg1 [value:boolean]', 'description ...' )
.option( '-S, --script-arg2 [value:boolean]', 'description ...' )
.action( () => {} );

await assertThrowsAsync( async () => {
await cmd.parse( [
'-f', 'true', '-t', 'true', 'run', 'script-name', '--script-arg1', '--script-arg2', '--script-arg3', '--', '--literal-arg1', '--literal-arg2'
] );
}, Error, 'Unknown option: -t' );
} );

0 comments on commit 45f28e7

Please sign in to comment.