Add better help for positional args
This commit is contained in:
parent
9fc4b064da
commit
64d244fe91
@ -164,11 +164,11 @@ describe('argparse', () => {
|
||||
expect(parse([CMD, '--value', 'one']).value).toEqual(['one'])
|
||||
parse([CMD, '--help'])
|
||||
expect(log.mock.calls[0][0]).toEqual([
|
||||
`${CMD} [OPTIONS] `,
|
||||
`${CMD} [OPTIONS]`,
|
||||
'',
|
||||
'Options:',
|
||||
' --value [VALUE] ',
|
||||
' --help boolean ',
|
||||
' --value [VALUE]',
|
||||
' --help boolean',
|
||||
].join('\n'))
|
||||
})
|
||||
it('can be used to extract finite number of values', () => {
|
||||
@ -191,12 +191,12 @@ describe('argparse', () => {
|
||||
})
|
||||
parse([CMD, '--help'])
|
||||
expect(log.mock.calls[0][0]).toEqual([
|
||||
`${CMD} [OPTIONS] `,
|
||||
`${CMD} [OPTIONS]`,
|
||||
'',
|
||||
'Options:',
|
||||
' --value [VALUE1 VALUE2 VALUE3] ',
|
||||
'-o, --other number ',
|
||||
' --help boolean ',
|
||||
' --value [VALUE1 VALUE2 VALUE3]',
|
||||
'-o, --other number',
|
||||
' --help boolean',
|
||||
].join('\n'))
|
||||
})
|
||||
it('can be used to collect any remaining arguments when n = "+"', () => {
|
||||
@ -219,12 +219,12 @@ describe('argparse', () => {
|
||||
})
|
||||
parse([CMD, '--help'])
|
||||
expect(log.mock.calls[0][0]).toEqual([
|
||||
`${CMD} [OPTIONS] `,
|
||||
`${CMD} [OPTIONS]`,
|
||||
'',
|
||||
'Options:',
|
||||
' --value VALUE... (required)',
|
||||
' --other number ',
|
||||
' --help boolean ',
|
||||
' --other number',
|
||||
' --help boolean',
|
||||
].join('\n'))
|
||||
})
|
||||
it('can collect remaining positional arguments when n = "*"', () => {
|
||||
@ -255,9 +255,12 @@ describe('argparse', () => {
|
||||
expect(log.mock.calls[0][0]).toEqual([
|
||||
`${CMD} [OPTIONS] [VALUE...]`,
|
||||
'',
|
||||
'Positional arguments:',
|
||||
' VALUE string[] (required)',
|
||||
'',
|
||||
'Options:',
|
||||
' --other number ',
|
||||
' --help boolean ',
|
||||
' --other number',
|
||||
' --help boolean',
|
||||
].join('\n'))
|
||||
})
|
||||
})
|
||||
@ -269,9 +272,14 @@ describe('argparse', () => {
|
||||
type: 'number',
|
||||
positional: true,
|
||||
},
|
||||
})
|
||||
}, exit, log)
|
||||
expect(parse([CMD]).a).toBe(NaN)
|
||||
expect(parse([CMD, '12']).a).toBe(12)
|
||||
parse([CMD, '--help'])
|
||||
expect(log.mock.calls[0][0]).toEqual(`${CMD} [A]
|
||||
|
||||
Positional arguments:
|
||||
A number`)
|
||||
})
|
||||
it('works with booleans', () => {
|
||||
const {parse} = argparse({
|
||||
@ -344,13 +352,13 @@ describe('argparse', () => {
|
||||
parse([CMD, '--help'])
|
||||
expect(exit.mock.calls.length).toBe(1)
|
||||
expect(log.mock.calls[0][0]).toEqual([
|
||||
`${CMD} [OPTIONS] `,
|
||||
`${CMD} [OPTIONS]`,
|
||||
'',
|
||||
'Options:',
|
||||
' --one string ',
|
||||
' --two number ',
|
||||
' --three boolean ',
|
||||
' --help boolean ',
|
||||
' --one string',
|
||||
' --two number',
|
||||
' --three boolean',
|
||||
' --help boolean',
|
||||
].join('\n'))
|
||||
})
|
||||
it('returns help string with alias, description, and samples', () => {
|
||||
@ -377,10 +385,14 @@ describe('argparse', () => {
|
||||
expect(log.mock.calls[0][0]).toEqual([
|
||||
`${CMD} [OPTIONS] TWO [THREE]`,
|
||||
'',
|
||||
'Positional arguments:',
|
||||
' TWO number (required)',
|
||||
' THREE number',
|
||||
'',
|
||||
'Options:',
|
||||
'-o, --one string first argument ' +
|
||||
'(required, default: choice-1, choices: choice-1,choice-2)',
|
||||
' --help boolean ',
|
||||
' --help boolean',
|
||||
].join('\n'))
|
||||
})
|
||||
|
||||
|
||||
@ -184,23 +184,7 @@ export function help(command: string, config: IArgsConfig) {
|
||||
return required ? array.join(' ') : `[${array.join(' ')}]`
|
||||
}
|
||||
|
||||
const positionalHelp = [
|
||||
relative(process.cwd(), command),
|
||||
'[OPTIONS]',
|
||||
keys
|
||||
.filter(k => config[k].positional)
|
||||
.map(k => getArrayHelp(k, config[k].required, config[k].n))
|
||||
.join(' '),
|
||||
].join(' ')
|
||||
|
||||
const options = keys.filter(k => !config[k].positional)
|
||||
|
||||
const argsHelp = 'Options:\n' + options.map(argument => {
|
||||
const argConfig = config[argument]
|
||||
const {alias, type} = argConfig
|
||||
const name = alias
|
||||
? `-${alias}, --${argument}`
|
||||
: ` --${argument}`
|
||||
function getDescription(argConfig: IArgument<TArgTypeName>): string {
|
||||
const samples = []
|
||||
if (argConfig.required) {
|
||||
samples.push('required')
|
||||
@ -214,14 +198,66 @@ export function help(command: string, config: IArgsConfig) {
|
||||
const description = argConfig.description
|
||||
? ' ' + argConfig.description : ''
|
||||
const sample = samples.length ? ` (${samples.join(', ')})` : ''
|
||||
const argType = type === 'string[]'
|
||||
? getArrayHelp(argument, argConfig.required, argConfig.n)
|
||||
: type
|
||||
return padRight(name + ' ' + argType, 30) + ' ' + description + sample
|
||||
})
|
||||
.join('\n')
|
||||
return description + sample
|
||||
}
|
||||
|
||||
return [positionalHelp, argsHelp]
|
||||
function getPaddedName(nameAndType: string, description: string) {
|
||||
return description
|
||||
? padRight(nameAndType, 30) + ' ' + description
|
||||
: nameAndType
|
||||
}
|
||||
|
||||
function getArgType(
|
||||
type: TArgTypeName, argument: string, required?: boolean, n?: TNumberOfArgs,
|
||||
): string {
|
||||
return type === 'string[]'
|
||||
? getArrayHelp(argument, required, n)
|
||||
: type
|
||||
}
|
||||
|
||||
const positionalArgs = keys
|
||||
.filter(k => config[k].positional)
|
||||
.map(argument => {
|
||||
const argConfig = config[argument]
|
||||
const {type, required, n} = argConfig
|
||||
const nameAndType = ` ${argument.toUpperCase()} ${type}`
|
||||
const description = getDescription(argConfig)
|
||||
return getPaddedName(nameAndType, description)
|
||||
})
|
||||
|
||||
const options = keys
|
||||
.filter(k => !config[k].positional)
|
||||
.map(argument => {
|
||||
const argConfig = config[argument]
|
||||
const {alias, type, required, n} = argConfig
|
||||
const name = alias
|
||||
? `-${alias}, --${argument}`
|
||||
: ` --${argument}`
|
||||
const description = getDescription(argConfig)
|
||||
const argType = getArgType(type, argument, required, n)
|
||||
const nameAndType = `${name} ${argType}`
|
||||
return getPaddedName(nameAndType, description)
|
||||
})
|
||||
|
||||
const positionalHelp = positionalArgs.length
|
||||
? 'Positional arguments:\n' + positionalArgs.join('\n')
|
||||
: ''
|
||||
const optionsHelp = options.length
|
||||
? 'Options:\n' + options.join('\n')
|
||||
: ''
|
||||
|
||||
const commandHelp = [
|
||||
relative(process.cwd(), command),
|
||||
options.length ? '[OPTIONS]' : '',
|
||||
keys
|
||||
.filter(k => config[k].positional)
|
||||
.map(k => getArrayHelp(k, config[k].required, config[k].n))
|
||||
.join(' '),
|
||||
]
|
||||
.filter(k => k.length)
|
||||
.join(' ')
|
||||
|
||||
return [commandHelp, positionalHelp, optionsHelp]
|
||||
.filter(h => h.length)
|
||||
.join('\n\n')
|
||||
}
|
||||
|
||||
@ -5,29 +5,44 @@ import {TCommand} from './TCommand'
|
||||
import {argparse, arg} from '@rondo/argparse'
|
||||
|
||||
const {parse} = argparse({
|
||||
help: arg('boolean'),
|
||||
help: arg('boolean', {alias: 'h'}),
|
||||
debug: arg('boolean'),
|
||||
command: arg('string[]', {n: '+', required: true, positional: true}),
|
||||
command: arg('string[]', {
|
||||
n: '+',
|
||||
required: true,
|
||||
positional: true,
|
||||
description: 'Must be one of: ' + Object.keys(commands).join(', '),
|
||||
}),
|
||||
})
|
||||
|
||||
type TArgs = ReturnType<typeof parse>
|
||||
|
||||
async function run(args: TArgs) {
|
||||
async function run(args: TArgs, exit: (code: number) => void) {
|
||||
const commandName = args.command[0]
|
||||
if (!(commandName in commands)) {
|
||||
const c = Object.keys(commands).filter(cmd => !cmd.startsWith('_'))
|
||||
log.info(`Available commands:\n\n${c.join('\n')}`)
|
||||
exit(1)
|
||||
return
|
||||
}
|
||||
const command = (commands as any)[commandName] as TCommand
|
||||
await command(...args.command)
|
||||
}
|
||||
|
||||
if (typeof require !== 'undefined' && require.main === module) {
|
||||
const args = parse(process.argv.slice(1))
|
||||
run(args)
|
||||
.catch(err => {
|
||||
log.error('> ' + (args.debug ? err.stack : err.message))
|
||||
process.exit(1)
|
||||
})
|
||||
async function start(
|
||||
argv: string[] = process.argv.slice(1),
|
||||
exit = (code: number) => process.exit(code),
|
||||
) {
|
||||
let args: TArgs | null = null
|
||||
try {
|
||||
args = parse(argv)
|
||||
await run(args, exit)
|
||||
} catch (err) {
|
||||
log.error((args && args.debug ? err.stack : err.message))
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof require !== 'undefined' && require.main === module) {
|
||||
start()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user