fix: let help flag ignore requirements

This commit is contained in:
2023-12-12 03:03:15 +02:00
parent 8d971fca3a
commit 119f20156b
5 changed files with 58 additions and 29 deletions

View File

@@ -318,13 +318,17 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
* To parse the arguments without running any commands and only get the output args,
* use `getArgs` instead.
*/
parse(argv: string[], args?: Partial<Args>, parent?: MassargCommand<Args>): Promise<void> | void {
parse(
argv = process.argv.slice(2),
args?: Partial<Args>,
parent?: MassargCommand<Args>,
): Promise<void> | void {
this.getArgs(argv, args, parent, true)
}
private parseOption(arg: string, argv: string[]) {
const prefixes = { ...this.optionPrefixes }
const option = this.options.find((o) => o._match(arg, prefixes))
const option = this.options.find((o) => o.isMatch(arg, prefixes))
if (!option) {
throw new ValidationError({
path: [MassargOption.findNameInArg(arg, prefixes)],
@@ -375,8 +379,9 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
// parse options
while (_argv.length) {
const arg = _argv.shift()!
// make sure option exists
const found = this.options.find((o) => o._isOption(arg, { ...this.optionPrefixes }))
const found = this.options.find((o) => o.isMatch(arg, this.optionPrefixes))
if (found) {
if (this.helpConfig.bindOption && found.name === 'help') {
if (parseCommands) {

View File

@@ -177,7 +177,7 @@ export class MassargOption<OptionType extends any = unknown, Args extends ArgsOb
parseDetails(argv: string[], options: ArgsObject, prefixes: Prefixes): ArgvValue<OptionType> {
let input = ''
try {
if (!this._match(argv[0], prefixes)) {
if (!this.isMatch(argv[0], prefixes)) {
throw new ParseError({
path: [this.name],
code: 'invalid_option',
@@ -207,20 +207,17 @@ export class MassargOption<OptionType extends any = unknown, Args extends ArgsOb
return `--${this.name}${aliases} ${this.description}`
}
_match(arg: string, prefixes: Prefixes): boolean {
/** Returns true if the flag (including any prefixes) matches the name or aliases */
isMatch(arg: string, prefixes: Prefixes): boolean {
const name = MassargOption.findNameInArg(arg, prefixes)
return name === this.name || this.aliases.includes(name)
}
_isOption(arg: string, prefixes: Prefixes): boolean {
return (
arg.startsWith(prefixes.optionPrefix) ||
arg.startsWith(prefixes.aliasPrefix) ||
arg.startsWith(prefixes.negateFlagPrefix) ||
arg.startsWith(prefixes.negateAliasPrefix)
)
}
/**
* Returns the name of the flag, removing any prefixes. It is discriminate of if the option
* exists, as it is a static method; it only returns the name of the flag if it matches the
* prefixes format.
*/
static findNameInArg(arg: string, prefixes: Prefixes): string {
const { optionPrefix, aliasPrefix, negateFlagPrefix, negateAliasPrefix } = prefixes
// negate full prefix
@@ -337,7 +334,7 @@ export class MassargFlag extends MassargOption<boolean> {
received: JSON.stringify(argv[0]),
})
}
if (!this._match(argv[0], prefixes)) {
if (!this.isMatch(argv[0], prefixes)) {
throw new ParseError({
path: [this.name],
code: 'invalid_option',

View File

@@ -91,17 +91,15 @@ export function deepMerge<T1, T2>(obj1: T1, obj2: T2): NonNullable<T1> & NonNull
* regular spaced strings.
*/
export function splitWords(str: string): string[] {
return (
str
.replace(/([a-z])([A-Z])/g, '$1 $2')
// .replace(/([a-zA-Z])([0-9])/g, '$1 $2')
.replace(/([0-9])([a-zA-Z])/g, '$1 $2')
.replace(/([a-z])([_-])/g, '$1 $2')
.replace(/([_-])([a-zA-Z])/g, '$1 $2')
.split(/[_-]/)
.map((s) => s.trim())
.filter(Boolean)
)
return str
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/([a-zA-Z])([0-9])/g, '$1 $2')
.replace(/([0-9])([a-zA-Z])/g, '$1 $2')
.replace(/([a-z])([_-])/g, '$1 $2')
.replace(/([_-])([a-zA-Z])/g, '$1 $2')
.split(/[_\- ]/)
.map((s) => s.trim())
.filter(Boolean)
}
export function toCamelCase(str: string): string {

View File

@@ -38,9 +38,10 @@ test('prints help from command', () => {
const command = massarg(opts).help({
bindCommand: true,
})
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
command.parse(['help'])
expect(log).toHaveBeenCalled()
log.mockRestore()
})
test('binds option', () => {
@@ -52,12 +53,26 @@ test('binds option', () => {
expect(command.options.find((o) => o.name === 'help')).toBeTruthy()
})
test('overrides required options', () => {
const command = massarg(opts)
.option({
name: 'required',
aliases: ['r'],
description: 'required',
required: true,
})
.help({
bindOption: true,
})
expect(() => command.getArgs(['--help'])).not.toThrow()
})
describe('prints help from option', () => {
test('when no main command', () => {
const command = massarg(opts).help({
bindOption: true,
})
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
command.parse(['--help'])
expect(log).toHaveBeenCalled()
})
@@ -80,7 +95,7 @@ test('help string', () => {
})
test('print help', () => {
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
const command = massarg(opts)
command.printHelp()
expect(log).toHaveBeenCalled()

14
test/utils.test.ts Normal file
View File

@@ -0,0 +1,14 @@
import { toCamelCase } from '../src/utils'
describe('toCamelCase', () => {
test('works', () => {
expect(toCamelCase('foo')).toBe('foo')
expect(toCamelCase('foo-bar')).toBe('fooBar')
expect(toCamelCase('foo-bar baz')).toBe('fooBarBaz')
expect(toCamelCase('foo-bar baz-qux')).toBe('fooBarBazQux')
expect(toCamelCase('foo123')).toBe('foo123')
expect(toCamelCase('foo-123')).toBe('foo123')
expect(toCamelCase('foo-123 bar')).toBe('foo123Bar')
})
})