argparse: Add support for string[], number of args
This commit is contained in:
parent
78f39517ce
commit
7182684b4d
@ -142,6 +142,98 @@ describe('argparse', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('string[] and n', () => {
|
||||
it('has a value of n = 1 by default', () => {
|
||||
const {parse, help} = argparse({
|
||||
value: {
|
||||
type: 'string[]',
|
||||
},
|
||||
})
|
||||
expect(parse([]).value).toEqual([])
|
||||
expect(parse(['--value', 'one']).value).toEqual(['one'])
|
||||
expect(help()).toEqual([
|
||||
'[OPTIONS] ',
|
||||
'',
|
||||
'Options:',
|
||||
' --value [VALUE] ',
|
||||
].join('\n'))
|
||||
})
|
||||
it('can be used to extract finite number of values', () => {
|
||||
const {parse, help} = argparse({
|
||||
value: {
|
||||
type: 'string[]',
|
||||
n: 3,
|
||||
},
|
||||
other: {
|
||||
type: 'number',
|
||||
alias: 'o',
|
||||
},
|
||||
})
|
||||
expect(parse([]).value).toEqual([])
|
||||
expect(parse(['--value', 'a', 'b', '--other', '-o', '3'])).toEqual({
|
||||
value: ['a', 'b', '--other'],
|
||||
other: 3,
|
||||
})
|
||||
expect(help()).toEqual([
|
||||
'[OPTIONS] ',
|
||||
'',
|
||||
'Options:',
|
||||
' --value [VALUE1 VALUE2 VALUE3] ',
|
||||
'-o, --other number ',
|
||||
].join('\n'))
|
||||
})
|
||||
it('can be used to collect any remaining arguments when n = "+"', () => {
|
||||
const {parse, help} = argparse({
|
||||
value: arg('string[]', {n: '+', required: true}),
|
||||
other: arg('number'),
|
||||
})
|
||||
expect(() => parse([])).toThrowError(/Missing required args: value/)
|
||||
expect(parse(['--value', 'a', '--other', '3'])).toEqual({
|
||||
value: ['a', '--other', '3'],
|
||||
other: NaN,
|
||||
})
|
||||
expect(parse(['--other', '2', '--value', 'a', '--other', '3'])).toEqual({
|
||||
value: ['a', '--other', '3'],
|
||||
other: 2,
|
||||
})
|
||||
expect(help()).toEqual([
|
||||
'[OPTIONS] ',
|
||||
'',
|
||||
'Options:',
|
||||
' --value VALUE... (required)',
|
||||
' --other number ',
|
||||
].join('\n'))
|
||||
})
|
||||
it('can collect remaining positional arguments when n = "*"', () => {
|
||||
const {parse, help} = argparse({
|
||||
value: arg('string[]', {n: '*', required: true, positional: true}),
|
||||
other: arg('number'),
|
||||
})
|
||||
expect(parse(['a', 'b']).value).toEqual(['a', 'b'])
|
||||
expect(() => parse(['--other', '3']).value)
|
||||
.toThrowError(/Missing.*: value/)
|
||||
expect(parse(['--other', '2', '--', '--other', '3'])).toEqual({
|
||||
value: ['--other', '3'],
|
||||
other: 2,
|
||||
})
|
||||
expect(parse(['--', '--other', '3'])).toEqual({
|
||||
value: ['--other', '3'],
|
||||
other: NaN,
|
||||
})
|
||||
expect(parse(['--other', '3', 'a', 'b', 'c'])).toEqual({
|
||||
value: ['a', 'b', 'c'],
|
||||
other: 3,
|
||||
})
|
||||
expect(help()).toEqual([
|
||||
'[OPTIONS] [VALUE...]',
|
||||
'',
|
||||
'Options:',
|
||||
' --value [VALUE...] (required)',
|
||||
' --other number ',
|
||||
].join('\n'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('positional', () => {
|
||||
it('can be defined', () => {
|
||||
const {parse} = argparse({
|
||||
|
||||
@ -1,13 +1,21 @@
|
||||
export type TArgTypeName = 'string' | 'number' | 'boolean'
|
||||
export type TArgTypeName = 'string' | 'string[]' | 'number' | 'boolean'
|
||||
export type TArgType<T extends TArgTypeName> =
|
||||
T extends 'string'
|
||||
? string
|
||||
: T extends 'string[]'
|
||||
? string[]
|
||||
: T extends 'number'
|
||||
? number
|
||||
: T extends 'boolean'
|
||||
? boolean
|
||||
: never
|
||||
|
||||
export const N_ONE_OR_MORE = '+'
|
||||
export const N_ZERO_OR_MORE = '*'
|
||||
export const N_DEFAULT_VALUE = 1
|
||||
|
||||
export type TNumberOfArgs = number | '+' | '*'
|
||||
|
||||
export let exit = () => process.exit()
|
||||
|
||||
export interface IArgParam<T extends TArgTypeName> {
|
||||
@ -17,18 +25,19 @@ export interface IArgParam<T extends TArgTypeName> {
|
||||
choices?: Array<TArgType<T>>
|
||||
required?: boolean
|
||||
positional?: boolean
|
||||
n?: TNumberOfArgs
|
||||
}
|
||||
|
||||
export interface IArgConfig<T extends TArgTypeName> extends IArgParam<T> {
|
||||
export interface IArgument<T extends TArgTypeName> extends IArgParam<T> {
|
||||
type: T
|
||||
}
|
||||
|
||||
export interface IArgsConfig {
|
||||
[arg: string]: IArgConfig<TArgTypeName>
|
||||
[arg: string]: IArgument<TArgTypeName>
|
||||
}
|
||||
|
||||
export type TArgs<T extends IArgsConfig> = {
|
||||
[k in keyof T]: T[k] extends IArgConfig<infer A> ?
|
||||
[k in keyof T]: T[k] extends IArgument<infer A> ?
|
||||
TArgType<A> : never
|
||||
}
|
||||
|
||||
@ -71,6 +80,8 @@ function getDefaultValue(type: TArgTypeName) {
|
||||
return ''
|
||||
case 'boolean':
|
||||
return false
|
||||
case 'string[]':
|
||||
return [] as string[]
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,6 +119,29 @@ function getValue(
|
||||
return isPositional ? argument : it.next()
|
||||
}
|
||||
|
||||
function extractArray(
|
||||
it: IIterator<string>,
|
||||
argument: string,
|
||||
isPositional: boolean,
|
||||
n: TNumberOfArgs = N_DEFAULT_VALUE,
|
||||
): string[] {
|
||||
function getLimit() {
|
||||
const l = typeof n === 'number' ? n : Infinity
|
||||
return isPositional ? l - 1 : l
|
||||
}
|
||||
const limit = getLimit()
|
||||
const array = isPositional ? [argument] : []
|
||||
let i = 0
|
||||
for (; i < limit && it.hasNext(); i++) {
|
||||
array.push(it.next())
|
||||
}
|
||||
if (typeof n === 'number') {
|
||||
assert(i === limit,
|
||||
`Expected ${limit} arguments for ${argument}, but got ${i}`)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
export function isHelp(argv: string[]) {
|
||||
return argv.some(a => /^(-h|--help)$/.test(a))
|
||||
}
|
||||
@ -130,14 +164,35 @@ export function padRight(str: string, chars: number) {
|
||||
export function help(config: IArgsConfig) {
|
||||
const keys = Object.keys(config)
|
||||
|
||||
function getArrayHelp(
|
||||
k: string,
|
||||
required?: boolean,
|
||||
n: TNumberOfArgs = N_DEFAULT_VALUE,
|
||||
) {
|
||||
k = k.toUpperCase()
|
||||
if (n === N_ZERO_OR_MORE) {
|
||||
return `[${k}...]`
|
||||
}
|
||||
if (n === N_ONE_OR_MORE) {
|
||||
return required ? `${k}...` : `[${k}...]`
|
||||
}
|
||||
if (n === 1) {
|
||||
return required ? k : `[${k}]`
|
||||
}
|
||||
|
||||
const limit: number = n
|
||||
const array = []
|
||||
for (let i = 0; i < limit; i++) {
|
||||
array.push(k + (i + 1))
|
||||
}
|
||||
return required ? array.join(' ') : `[${array.join(' ')}]`
|
||||
}
|
||||
|
||||
const positionalHelp = [
|
||||
'[OPTIONS]',
|
||||
keys
|
||||
.filter(k => config[k].positional)
|
||||
.map(k => config[k].required
|
||||
? `${k.toUpperCase()}`
|
||||
: `[${k.toUpperCase()}]`,
|
||||
)
|
||||
.map(k => getArrayHelp(k, config[k].required, config[k].n))
|
||||
.join(' '),
|
||||
].join(' ')
|
||||
|
||||
@ -160,7 +215,10 @@ export function help(config: IArgsConfig) {
|
||||
const description = argConfig.description
|
||||
? ' ' + argConfig.description : ''
|
||||
const sample = samples.length ? ` (${samples.join(', ')})` : ''
|
||||
return padRight(name + ' ' + type, 30) + ' ' + description + sample
|
||||
const argType = type === 'string[]'
|
||||
? getArrayHelp(argument, argConfig.required, argConfig.n)
|
||||
: type
|
||||
return padRight(name + ' ' + argType, 30) + ' ' + description + sample
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
@ -172,7 +230,7 @@ export function help(config: IArgsConfig) {
|
||||
export function arg<T extends TArgTypeName>(
|
||||
type: T,
|
||||
config: IArgParam<T> = {},
|
||||
): IArgConfig<T> {
|
||||
): IArgument<T> {
|
||||
return {
|
||||
...config,
|
||||
type,
|
||||
@ -245,9 +303,14 @@ export function argparse<T extends IArgsConfig>(config: T) {
|
||||
return p!
|
||||
}
|
||||
|
||||
let onlyPositionals = false
|
||||
while (it.hasNext()) {
|
||||
const argument = it.next()
|
||||
const isPositional = argument.substring(0, 1) !== '-'
|
||||
if (argument === '--' && !onlyPositionals) {
|
||||
onlyPositionals = true
|
||||
continue
|
||||
}
|
||||
const isPositional = argument.substring(0, 1) !== '-' || onlyPositionals
|
||||
const argName = !isPositional
|
||||
? processFlags(argument)
|
||||
: getNextPositional()
|
||||
@ -260,6 +323,10 @@ export function argparse<T extends IArgsConfig>(config: T) {
|
||||
assert(!!result[argName],
|
||||
'Value of argument must be a string: ' + argument)
|
||||
break
|
||||
case 'string[]':
|
||||
result[argName] = extractArray(
|
||||
it, argument, isPositional, argConfig.n)
|
||||
break
|
||||
case 'number':
|
||||
const num = parseInt(getValue(it, argument, isPositional), 10)
|
||||
assert(!isNaN(num),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user