Skip to content

Commit

Permalink
feat: parse async (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
RasPhilCo committed Jan 27, 2021
1 parent 8828ca9 commit 57924df
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 246 deletions.
2 changes: 1 addition & 1 deletion src/command.ts
Expand Up @@ -154,7 +154,7 @@ export default abstract class Command {
g['http-call']!.userAgent = this.config.userAgent
}

protected parse<F, A extends { [name: string]: any }>(options?: Interfaces.Input<F>, argv = this.argv): Interfaces.ParserOutput<F, A> {
protected async parse<F, A extends { [name: string]: any }>(options?: Interfaces.Input<F>, argv = this.argv): Promise<Interfaces.ParserOutput<F, A>> {
if (!options) options = this.constructor as any
return Parser.parse(argv, {context: this, ...options})
}
Expand Down
6 changes: 3 additions & 3 deletions src/flags.ts
Expand Up @@ -14,7 +14,7 @@ export function option<T>(options: {parse: OptionFlag<T>['parse']} & Partial<Opt

const _enum = <T = string>(opts: EnumFlagOptions<T>): OptionFlag<T> => {
return build<T>({
parse(input) {
async parse(input) {
if (!opts.options.includes(input)) throw new Error(`Expected --${this.name}=${input} to be one of: ${opts.options.join(', ')}`)
return input as unknown as T
},
Expand All @@ -33,7 +33,7 @@ export const version = (opts: Partial<BooleanFlag<boolean>> = {}) => {
// char: 'v',
description: 'show CLI version',
...opts,
parse: (_: any, cmd: Command) => {
parse: async (_: any, cmd: Command) => {
cmd.log(cmd.config.userAgent)
cmd.exit(0)
},
Expand All @@ -45,7 +45,7 @@ export const help = (opts: Partial<BooleanFlag<boolean>> = {}) => {
// char: 'h',
description: 'show CLI help',
...opts,
parse: (_: any, cmd: Command) => {
parse: async (_: any, cmd: Command) => {
(cmd as any)._help()
},
})
Expand Down
8 changes: 4 additions & 4 deletions src/interfaces/parser.ts
@@ -1,7 +1,7 @@
import {AlphabetLowercase, AlphabetUppercase} from './alphabet'
import {Config} from './config'

export type ParseFn<T> = (input: string) => T
export type ParseFn<T> = (input: string) => Promise<T>

export interface Arg<T = string> {
name: string;
Expand All @@ -18,7 +18,7 @@ export interface ArgBase<T> {
description?: string;
hidden?: boolean;
parse: ParseFn<T>;
default?: T | (() => T);
default?: T | (() => Promise<T>);
input?: string;
options?: string[];
}
Expand Down Expand Up @@ -77,7 +77,7 @@ export type DefaultContext<T> = {
flags: { [k: string]: string };
}

export type Default<T> = T | ((context: DefaultContext<T>) => T)
export type Default<T> = T | ((context: DefaultContext<T>) => Promise<T>)

export type FlagBase<T, I> = {
name: string;
Expand All @@ -93,7 +93,7 @@ export type FlagBase<T, I> = {
* also accept an environment variable as input
*/
env?: string;
parse(input: I, context: any): T;
parse(input: I, context: any): Promise<T>;
}

export type BooleanFlag<T> = FlagBase<T, boolean> & {
Expand Down
6 changes: 3 additions & 3 deletions src/parser/flags.ts
Expand Up @@ -11,7 +11,7 @@ export function build(
export function build<T>(defaults: Partial<OptionFlag<T>>): Definition<T> {
return (options: any = {}): any => {
return {
parse: (i: string, _: any) => i,
parse: async (i: string, _: any) => i,
...defaults,
...options,
input: [] as string[],
Expand All @@ -25,15 +25,15 @@ export function boolean<T = boolean>(
options: Partial<BooleanFlag<T>> = {},
): BooleanFlag<T> {
return {
parse: (b, _) => b,
parse: async (b, _) => b,
...options,
allowNo: Boolean(options.allowNo),
type: 'boolean',
} as BooleanFlag<T>
}

export const integer = build({
parse: input => {
parse: async input => {
if (!/^-?\d+$/.test(input))
throw new Error(`Expected an integer but received: ${input}`)
return parseInt(input, 10)
Expand Down
4 changes: 2 additions & 2 deletions src/parser/index.ts
Expand Up @@ -15,7 +15,7 @@ const m = Deps()
// eslint-disable-next-line node/no-missing-require
.add('validate', () => require('./validate').validate as typeof Validate.validate)

export function parse<TFlags, TArgs extends { [name: string]: string }>(argv: string[], options: Input<TFlags>): ParserOutput<TFlags, TArgs> {
export async function parse<TFlags, TArgs extends { [name: string]: string }>(argv: string[], options: Input<TFlags>): Promise<ParserOutput<TFlags, TArgs>> {
const input = {
argv,
context: options.context,
Expand All @@ -28,7 +28,7 @@ export function parse<TFlags, TArgs extends { [name: string]: string }>(argv: st
strict: options.strict !== false,
}
const parser = new Parser(input)
const output = parser.parse()
const output = await parser.parse()
m.validate({input, output})
return output as any
}
Expand Down
29 changes: 18 additions & 11 deletions src/parser/parse.ts
Expand Up @@ -45,7 +45,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
this.metaData = {}
}

public parse() {
public async parse() {
this._debugInput()

const findLongFlag = (arg: string) => {
Expand Down Expand Up @@ -125,9 +125,9 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
if (arg) arg.input = input
this.raw.push({type: 'arg', input})
}
const argv = this._argv()
const argv = await this._argv()
const args = this._args(argv)
const flags = this._flags()
const flags = await this._flags()
this._debugOutput(argv, args, flags)
return {
args,
Expand All @@ -147,7 +147,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
return args
}

private _flags(): TFlags {
private async _flags(): Promise<TFlags> {
const flags = {} as any
this.metaData.flags = {} as any
for (const token of this._flagTokens) {
Expand All @@ -159,13 +159,15 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
} else {
flags[token.flag] = true
}
flags[token.flag] = flag.parse(flags[token.flag], this.context)
// eslint-disable-next-line no-await-in-loop
flags[token.flag] = await flag.parse(flags[token.flag], this.context)
} else {
const input = token.input
if (flag.options && !flag.options.includes(input)) {
throw new m.errors.FlagInvalidOptionError(flag, input)
}
const value = flag.parse ? flag.parse(input, this.context) : input
// eslint-disable-next-line no-await-in-loop
const value = flag.parse ? await flag.parse(input, this.context) : input
if (flag.multiple) {
flags[token.flag] = flags[token.flag] || []
flags[token.flag].push(value)
Expand All @@ -179,12 +181,14 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
if (flags[k]) continue
if (flag.type === 'option' && flag.env) {
const input = process.env[flag.env]
if (input) flags[k] = flag.parse(input, this.context)
// eslint-disable-next-line no-await-in-loop
if (input) flags[k] = await flag.parse(input, this.context)
}
if (!(k in flags) && flag.default !== undefined) {
this.metaData.flags[k] = {setFromDefault: true}
if (typeof flag.default === 'function') {
flags[k] = flag.default({options: flag, flags, ...this.context})
// eslint-disable-next-line no-await-in-loop
flags[k] = await flag.default({options: flag, flags, ...this.context})
} else {
flags[k] = flag.default
}
Expand All @@ -193,7 +197,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
return flags
}

private _argv(): any[] {
private async _argv(): Promise<any[]> {
const args: any[] = []
const tokens = this._argTokens
for (let i = 0; i < Math.max(this.input.args.length, tokens.length); i++) {
Expand All @@ -204,13 +208,16 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
if (arg.options && !arg.options.includes(token.input)) {
throw new m.errors.ArgInvalidOptionError(arg, token.input)
}
args[i] = arg.parse(token.input)
// eslint-disable-next-line no-await-in-loop
args[i] = await arg.parse(token.input)
} else {
args[i] = token.input
}
} else if ('default' in arg) {
if (typeof arg.default === 'function') {
args[i] = arg.default()
// eslint-disable-next-line no-await-in-loop
const f = await arg.default()
args[i] = f
} else {
args[i] = arg.default
}
Expand Down
2 changes: 1 addition & 1 deletion test/command/command.test.ts
Expand Up @@ -208,7 +208,7 @@ describe('command', () => {
}

async run() {
const {flags} = this.parse(CMD)
const {flags} = await this.parse(CMD)
this.log(flags.foo)
}
}
Expand Down

0 comments on commit 57924df

Please sign in to comment.