Skip to content

Commit

Permalink
🚧 [util-cli] feat: support parent options in action callback see tj/c…
Browse files Browse the repository at this point in the history
  • Loading branch information
lemon-clown committed Jul 12, 2020
1 parent 236d2d1 commit 943f9c0
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"Builtins",
"Lerna",
"Subcommand",
"autoprefixer",
"autorestart",
"axios",
Expand Down Expand Up @@ -34,13 +35,15 @@
"postbuild",
"postcss",
"prebuild",
"probaby",
"rapit",
"readdir",
"recognised",
"rimraf",
"rollup",
"sourcemap",
"styl",
"subcommands",
"tada",
"tsbuildinfo",
"typeof",
Expand Down
96 changes: 94 additions & 2 deletions utils/cli/src/commander.ts
@@ -1,5 +1,97 @@
import commander from 'commander'
import { ColorfulChalkLogger } from '@barusu/chalk-logger'
import { CommandOptionConfig } from './option'


/**
* Callback for handling the command
*
* @param args command arguments
* @param options command options
* @param extra extra args (neither declared command arguments nor command options)
*/
export type CommandActionCallback<T extends CommandOptionConfig> = (
args: string[],
options: T,
extra: string[],
) => void | Promise<void> | never


export class Command extends commander.Command {
/**
* Register callback `fn` for the command.
*
* Examples:
*
* program
* .command('help')
* .description('display verbose help')
* .action(function() {
* // output help here
* });
*
* @param {Function} fn
* @return {Command} `this` command for chaining
* @api public
*/
public action<T extends CommandOptionConfig>(fn: CommandActionCallback<T>): this {
const self = this

const listener = (args: string[]): void => {
// The .action callback takes an extra parameter which is the command or options.
const expectedArgsCount = self._args.length

// const actionArgs: (string | Record<string, unknown> | string[])[] = [
const actionArgs: [string[], T, string[]] = [
// Command arguments
args.slice(0, expectedArgsCount),

// Command options
self.opts() as T,

// Extra arguments so available too.
args.slice(expectedArgsCount)
]

const actionResult = fn.apply(self, actionArgs)

// Remember result in case it is async. Assume parseAsync getting called on root.
let rootCommand: Command = self
while (rootCommand.parent != null) {
rootCommand = rootCommand.parent
}
rootCommand._actionResults.push(actionResult)
}

self._actionHandler = listener
return self
}

public opts(): Record<string, unknown> {
const self = this
const nodes: Command[] = [self]
for (let parent = self.parent; parent != null; parent = parent.parent) {
nodes.push(parent)
}

const options: Record<string, unknown> = {}
for (let i = nodes.length - 1; i >= 0; --i) {
const o = nodes[i]
if (o._storeOptionsAsProperties) {
for (const option of o.options) {
const key = option.attributeName()
options[key] = (key === o._versionOptionName) ? o._version : o[key]
}
} else {
for (const key of Object.getOwnPropertyNames(o._optionValues)) {
options[key] = o._optionValues[key]
}
}
}

return options
}
}


export { commander }
Expand All @@ -15,8 +107,8 @@ export function createTopCommand(
commandName: string,
version: string,
logger: ColorfulChalkLogger,
): commander.Command {
const program = new commander.Command()
): Command {
const program = new Command()

program
.storeOptionsAsProperties(false)
Expand Down

0 comments on commit 943f9c0

Please sign in to comment.