Skip to content

Commit

Permalink
feat(ace): add command parser kernel
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Apr 8, 2019
1 parent bf5037c commit 4209e63
Show file tree
Hide file tree
Showing 7 changed files with 513 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"scripts": {
"postinstall": "lerna bootstrap",
"pretest": "lerna run build",
"test": "lerna run test --since d9db6c67d3de51b65ae0825d2c58d65eb26b3c26",
"test": "lerna run test --since bf5037cfa2df145c677f04eaf1c22d350a0f5f08",
"publish": "lerna run build && lerna publish"
}
}
18 changes: 18 additions & 0 deletions packages/ace/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/ace/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
"url": "https://github.com/adonisjs/ace/tree/master/packages/ace"
},
"license": "MIT",
"devDependencies": {
},
"dependencies": {
"getopts": "^2.2.4",
"kleur": "^3.0.3"
},
"nyc": {
"exclude": [
Expand Down
24 changes: 24 additions & 0 deletions packages/ace/src/BaseCommand/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* @adonisjs/ace
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { ParsedOptions } from 'getopts'
import { CommandContract, CommandArg, CommandFlag } from '../Contracts'

/**
* Abstract base class other classes must extend
*/
export abstract class BaseCommand implements CommandContract {
public static args: CommandArg[] = []
public static flags: CommandFlag[] = []
public static commandName: string = ''

public parsed?: ParsedOptions
public async handle () {
}
}
31 changes: 31 additions & 0 deletions packages/ace/src/Contracts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* @adonisjs/ace
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export type CommandArg = {
name: string,
required: boolean,
}

export type CommandFlag = {
name: string,
alias?: string,
default?: any,
type: 'string' | 'boolean',
}

export interface CommandConstructorContract {
args: CommandArg[],
flags: CommandFlag[],
commandName: string,
new (): CommandContract,
}

export interface CommandContract {
handle (): Promise<void>,
}
121 changes: 121 additions & 0 deletions packages/ace/src/Kernel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* @adonisjs/ace
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import * as getopts from 'getopts'
import { CommandConstructorContract, CommandContract } from '../Contracts'

/**
* Ace kernel class is used to register, find and invoke commands by
* parsing `process.argv` value.
*/
export class Kernel {
private _commands: { [name: string]: CommandConstructorContract } = {}

/**
* Register an array of commands
*/
public register (commands: CommandConstructorContract[]): this {
commands.forEach((command) => {
this._commands[command.commandName] = command
})

return this
}

/**
* Finds the command from the command line argv array. If command for
* the given name doesn't exists, then it will return `null`.
*/
public find (argv: string[]): CommandConstructorContract | null {
/**
* We though in `Unix` the command name may appear in between or last, with
* ace we always want the command name to the first argument. However, the
* arguments to the command itself can appear in any sequence. For example:
*
* Works
* - node ace make:controller foo
* - node ace make:controller --http foo
*
* Doesn't work
* - node ace foo make:controller
*/
const commandName = argv[0]
return this._commands[commandName] || null
}

/**
* Makes instance of a given command by processing command line arguments
* and setting them on the command instance
*/
public make<T extends CommandConstructorContract> (command: T, argv: string[]): CommandContract {
/**
* List of arguments that are required
*/
const requiredArgs = command.args.filter((arg) => arg.required)

/**
* Aliases list for `getopts` to properly parse
* flags
*/
const options = command.flags.reduce((result: getopts.Options, flag) => {
if (flag.alias) {
result.alias![flag.alias] = flag.name
}

if (flag.type === 'boolean') {
result.boolean!.push(flag.name)
}

if (flag.type === 'string') {
result.string!.push(flag.name)
}

if (flag.default !== undefined) {
result.default![flag.name] = flag.default
}

return result
}, { alias: {}, boolean: [], default: {} })

/**
* Parsed options via getopts
*/
const parsed = getopts(argv.slice(1), options)

/**
* Raise exception when required arguments are missing
*/
if (parsed._.length < requiredArgs.length) {
throw new Error(`Missing value for ${requiredArgs[parsed._.length].name} argument`)
}

const commandInstance = new command()

/**
* Sharing parsed output with the command
*/
commandInstance['parsed'] = parsed

/**
* Set value for args
*/
command.args.forEach((arg, index) => {
commandInstance[arg.name] = parsed._[index]
})

/**
* Set value for flags
*/
command.flags.forEach((flag) => {
commandInstance[flag.name] = parsed[flag.name]
})

return commandInstance
}
}
Loading

0 comments on commit 4209e63

Please sign in to comment.