Skip to content

Add ability to provide own parsing logic #46

@ajafff

Description

@ajafff

While trying to port my CLI, I noticed there is no way to declare options that may not consume the next argument if not necessary.

A popular example are TypeScript's compilerOptions:

tsc --project tsconfig.json
# parses as {strict: undefined, project: 'tsconfig.json'}

tsc --strict --project tsconfig.json
# parses as {strict: true, project: 'tsconfig.json'}

tsc --strict true --project tsconfig.json
# parses as {strict: true, project: 'tsconfig.json'}

tsc --strict false --project tsconfig.json
# parses as {strict: false, project: 'tsconfig.json'}

In my use case this is not limited to a switch option (boolean value). It's actually an optional boolean or number:

wotan foo.ts
# parses as {fix: false, files: ['foo.ts']}

wotan --fix foo.ts
# parses as {fix: true, files: ['foo.ts']}

wotan --fix foo.ts
# parses as {fix: true, files: ['foo.ts']}

wotan --fix false foo.ts
# parses as {fix: false, files: ['foo.ts']}

wotan --fix 5 foo.ts
# parses as {fix: 5, files: ['foo.ts']}

I can think of 2 possible solutions:

Add optional flag

class MyOptions extends Options {
  @option({optional: true, default: false, validator: /^(true|false|\d+)$/})
  public fix: boolean | number;
}

If optional is true and validating the argument using the validator fails, do not consume the argument and use the default value instead.

Add parse function

class MyOptions extends Options {
  @option({
    parse(consume: () => string | undefined, unconsume: (arg: string) => void) {
      const option = consume();
      if (option === undefined)
        return false; // no more arguments available
      if (option === 'true')
        return true;
      if (option === 'false')
        return false;
      const numeric = +option;
      if (!Number.isNaN(numeric))
        return numeric;
      // argument is not in the expected format, push it back so that it may be parsed as another option or parameter
      unconsume(option);
      return false; // use a default value
    },
    validator(value: boolean | number) {
      if (typeof value === 'number' && value < 0)
        throw new ExpectedError('--fix must be a positive number'); 
    }
  })
  public fix: boolean | number;
}

If a parse function is provided, this function is responsible for consuming the arguments, parsing and converting the value. By calling consume() more than once the parser may consume 0..n of the remaining arguments.
validators are still executed after parsing. That means errors about invalid option values can be reported. That's not possible with the optional proposal.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions