Skip to content
This repository has been archived by the owner on Aug 22, 2023. It is now read-only.

Commit

Permalink
fix: added options to flags/args
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Feb 1, 2018
1 parent de0a906 commit 6a33b06
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 25 deletions.
2 changes: 2 additions & 0 deletions src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface IArg<T = string> {
hidden?: boolean
parse?: ParseFn<T>
default?: T | (() => T)
options?: string[]
}

export interface ArgBase<T> {
Expand All @@ -16,6 +17,7 @@ export interface ArgBase<T> {
parse: ParseFn<T>
default?: T | (() => T)
input?: string
options?: string[]
}

export type RequiredArg<T> = ArgBase<T> & {
Expand Down
28 changes: 23 additions & 5 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {Arg} from './args'
import {deps} from './deps'
import {IFlag} from './flags'
import * as flags from './flags'
import {flagUsages} from './help'
import {ParserInput, ParserOutput} from './parse'

export interface ICLIParseErrorOptions {
parse: {
input: ParserInput
output: ParserOutput<any, any>
input?: ParserInput
output?: ParserOutput<any, any>
}
}

Expand Down Expand Up @@ -37,9 +37,9 @@ export class RequiredArgsError extends CLIParseError {
}

export class RequiredFlagError extends CLIParseError {
public flags: IFlag<any>[]
public flags: flags.IFlag<any>[]

constructor({flags, parse}: ICLIParseErrorOptions & { flags: IFlag<any>[] }) {
constructor({flags, parse}: ICLIParseErrorOptions & { flags: flags.IFlag<any>[] }) {
const usage = deps.renderList(flagUsages(flags, {displayRequired: false}))
const message = `Missing required flag${flags.length === 1 ? '' : 's'}:\n${usage}`
super({parse, message})
Expand All @@ -56,3 +56,21 @@ export class UnexpectedArgsError extends CLIParseError {
this.args = args
}
}

export class FlagInvalidOptionError extends CLIParseError {
public args: string[]

constructor(flag: flags.IOptionFlag<any>, input: string) {
const message = `Expected --${flag.name}=${input} to be one of: ${flag.options!.join(', ')}`
super({parse: {}, message})
}
}

export class ArgInvalidOptionError extends CLIParseError {
public args: string[]

constructor(arg: Arg<any>, input: string) {
const message = `Expected ${input} to be one of: ${arg.options!.join(', ')}`
super({parse: {}, message})
}
}
12 changes: 1 addition & 11 deletions src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type IOptionFlag<T> = IFlagBase<T, string> & {
default?: T | ((context: DefaultContext<T>) => T | undefined)
multiple: boolean
input: string[]
options?: string[]
}

export type Definition<T> = {
Expand Down Expand Up @@ -71,17 +72,6 @@ export const integer = build({
},
})

const _enum = <T = string>(opts: EnumFlagOptions<T>) => build<T>({
parse(input) {
if (!opts.options.includes(input)) throw new Error(`Expected --${this.name}=${input} to be one of: ${opts.options.join(', ')}`)
return input
},
helpValue: `(${opts.options.join('|')})`,
...opts as any,
optionType: 'enum',
})
export {_enum as enum}

export function option<T>(options: {parse: IOptionFlag<T>['parse']} & Partial<IOptionFlag<T>>) {
return build<T>({optionType: 'custom', ...options})()
}
Expand Down
12 changes: 10 additions & 2 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import * as _ from 'lodash'

import {Arg} from './args'
import * as Errors from './errors'
import * as Flags from './flags'

let debug: any
Expand Down Expand Up @@ -41,7 +42,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
private readonly argv: string[]
private readonly raw: ParsingToken[] = []
private readonly booleanFlags: { [k: string]: Flags.IBooleanFlag<any> }
constructor(readonly input: T) {
constructor(private readonly input: T) {
this.argv = input.argv.slice(0)
this._setNames()
this.booleanFlags = _.pickBy(input.flags, f => f.type === 'boolean') as any
Expand Down Expand Up @@ -155,7 +156,11 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
flags[token.flag] = true
}
} else {
const value = flag.parse ? flag.parse(token.input) : token.input
const input = token.input
if (flag.options && !flag.options.includes(input)) {
throw new Errors.FlagInvalidOptionError(flag, input)
}
const value = flag.parse ? flag.parse(input) : input
if (flag.multiple) {
flags[token.flag] = flags[token.flag] || []
flags[token.flag].push(value)
Expand Down Expand Up @@ -185,6 +190,9 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
const arg = this.input.args[i]
if (token) {
if (arg) {
if (arg.options && !arg.options.includes(token.input)) {
throw new Errors.ArgInvalidOptionError(arg, token.input)
}
args[i] = arg.parse(token.input)
} else {
args[i] = token.input
Expand Down
29 changes: 22 additions & 7 deletions test/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,22 +338,37 @@ See more help with --help`)
})
})

describe('enum', () => {
describe('flag options', () => {
it('accepts valid option', () => {
const foo = flags.enum<'myopt' | 'myotheropt'>({options: ['myopt', 'myotheropt']})
const out = parse(['--foo', 'myotheropt'], {
flags: {foo: foo()},
flags: {foo: flags.string({options: ['myopt', 'myotheropt']})},
})
expect(out.flags.foo).to.equal('myotheropt')
})

it('fails when invalid', () => {
const foo = flags.enum({options: ['myopt', 'myotheropt']})
expect(() => {
parse(['--foo', 'bar'], {
flags: {foo: foo()},
parse(['--foo', 'invalidopt'], {
flags: {foo: flags.string({options: ['myopt', 'myotheropt']})},
})
}).to.throw('Expected --foo=invalidopt to be one of: myopt, myotheropt')
})
})

describe('arg options', () => {
it('accepts valid option', () => {
const out = parse(['myotheropt'], {
args: [{name: 'foo', options: ['myopt', 'myotheropt']}],
})
expect(out.args.foo).to.equal('myotheropt')
})

it('fails when invalid', () => {
expect(() => {
parse(['invalidopt'], {
args: [{name: 'foo', options: ['myopt', 'myotheropt']}],
})
}).to.throw('Expected --foo=bar to be one of: myopt, myotheropt')
}).to.throw('Expected invalidopt to be one of: myopt, myotheropt')
})
})
})

0 comments on commit 6a33b06

Please sign in to comment.