Add support for positional args in @rondo/argparse
This commit is contained in:
parent
7ea1c34428
commit
b50dfb1455
@ -125,6 +125,76 @@ describe('argparse', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('positional', () => {
|
||||
it('can be defined', () => {
|
||||
const parse = argparse({
|
||||
a: {
|
||||
type: 'number',
|
||||
positional: true,
|
||||
},
|
||||
})
|
||||
expect(parse([]).a).toBe(NaN)
|
||||
expect(parse(['12']).a).toBe(12)
|
||||
})
|
||||
it('works with booleans', () => {
|
||||
const parse = argparse({
|
||||
a: {
|
||||
type: 'boolean',
|
||||
positional: true,
|
||||
},
|
||||
})
|
||||
expect(parse([]).a).toBe(false)
|
||||
expect(parse(['true']).a).toBe(true)
|
||||
expect(parse(['false']).a).toBe(false)
|
||||
expect(() => parse(['invalid'])).toThrowError(/true or false/)
|
||||
})
|
||||
it('works with strings', () => {
|
||||
const parse = argparse({
|
||||
a: {
|
||||
type: 'string',
|
||||
positional: true,
|
||||
},
|
||||
})
|
||||
expect(parse([]).a).toBe('')
|
||||
expect(parse(['a']).a).toBe('a')
|
||||
})
|
||||
it('works with multiple positionals', () => {
|
||||
const parse = argparse({
|
||||
a: {
|
||||
type: 'string',
|
||||
positional: true,
|
||||
},
|
||||
b: {
|
||||
type: 'string',
|
||||
positional: true,
|
||||
},
|
||||
})
|
||||
expect(parse(['aaa', 'bbb'])).toEqual({
|
||||
a: 'aaa',
|
||||
b: 'bbb',
|
||||
})
|
||||
})
|
||||
it('works amongs regular arguments', () => {
|
||||
const parse = argparse({
|
||||
arg1: {
|
||||
type: 'string',
|
||||
},
|
||||
arg2: {
|
||||
type: 'number',
|
||||
positional: true,
|
||||
},
|
||||
arg3: {
|
||||
type: 'string',
|
||||
},
|
||||
})
|
||||
expect(parse(['--arg1', 'one', '2', '--arg3', 'three'])).toEqual({
|
||||
arg1: 'one',
|
||||
arg2: 2,
|
||||
arg3: 'three',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('throws when required args missing', () => {
|
||||
expect(() => argparse({
|
||||
one: {
|
||||
|
||||
@ -14,6 +14,7 @@ export interface IArgConfig<T extends TArgTypeName> {
|
||||
description?: string
|
||||
default?: TArgType<T>
|
||||
required?: boolean
|
||||
positional?: boolean
|
||||
}
|
||||
|
||||
export interface IArgsConfig {
|
||||
@ -64,6 +65,7 @@ export const argparse = <T extends IArgsConfig>(
|
||||
const it = iterate(args)
|
||||
|
||||
const aliases: Record<string, string> = {}
|
||||
const positional: string[] = []
|
||||
const requiredArgs = Object.keys(config).reduce((obj, arg) => {
|
||||
const argConfig = config[arg]
|
||||
result[arg] = argConfig.default !== undefined
|
||||
@ -75,6 +77,9 @@ export const argparse = <T extends IArgsConfig>(
|
||||
'Duplicate alias: ' + argConfig.alias)
|
||||
aliases[argConfig.alias] = arg
|
||||
}
|
||||
if (argConfig.positional) {
|
||||
positional.push(arg)
|
||||
}
|
||||
if (argConfig.required) {
|
||||
obj[arg] = true
|
||||
}
|
||||
@ -108,25 +113,47 @@ export const argparse = <T extends IArgsConfig>(
|
||||
return lastArgName
|
||||
}
|
||||
|
||||
function getNextPositional(): string {
|
||||
const p = positional.shift()
|
||||
assert(!!p, 'No defined positional arguments')
|
||||
return p!
|
||||
}
|
||||
|
||||
while (it.hasNext()) {
|
||||
const arg = it.next()
|
||||
assert(arg.substring(0, 1) === '-', 'Arguments must start with -')
|
||||
const argName: string = processFlags(arg)
|
||||
const isPositional = arg.substring(0, 1) !== '-'
|
||||
const argName = !isPositional
|
||||
? processFlags(arg)
|
||||
: getNextPositional()
|
||||
const argConfig = config[argName]
|
||||
assert(!!argConfig, 'Unknown argument: ' + arg)
|
||||
delete requiredArgs[argName]
|
||||
const peek = it.peek()
|
||||
switch (argConfig.type) {
|
||||
case 'string':
|
||||
if (isPositional) {
|
||||
result[argName] = arg
|
||||
} else {
|
||||
assert(it.hasNext(), 'Value of argument must be a string: ' + arg)
|
||||
result[argName] = it.next()
|
||||
}
|
||||
continue
|
||||
case 'number':
|
||||
const num = parseInt(it.next(), 10)
|
||||
const num = parseInt(isPositional ? arg : it.next(), 10)
|
||||
assert(!isNaN(num), 'Value of argument must be a number: ' + arg)
|
||||
result[argName] = num
|
||||
continue
|
||||
case 'boolean':
|
||||
if (isPositional) {
|
||||
if (arg === 'true') {
|
||||
result[argName] = true
|
||||
} else if (arg === 'false') {
|
||||
result[argName] = false
|
||||
} else {
|
||||
assert(false, 'Value of argument must be true or false: ' + arg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (peek === 'true') {
|
||||
it.next()
|
||||
result[argName] = true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user