Add better help for positional args

This commit is contained in:
Jerko Steiner 2019-08-14 10:17:06 +07:00
parent 9fc4b064da
commit 64d244fe91
3 changed files with 116 additions and 53 deletions

View File

@ -255,6 +255,9 @@ describe('argparse', () => {
expect(log.mock.calls[0][0]).toEqual([
`${CMD} [OPTIONS] [VALUE...]`,
'',
'Positional arguments:',
' VALUE string[] (required)',
'',
'Options:',
' --other number',
' --help boolean',
@ -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({
@ -377,6 +385,10 @@ 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)',

View File

@ -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')
}

View File

@ -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()
}