Skip to content

Commit

Permalink
feat: ignore callback (#19)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: replace `ignoreUnknown` with `ignore` callback and mutates passed in `argv`
  • Loading branch information
privatenumber authored Nov 2, 2022
1 parent 584bc5d commit 185d911
Show file tree
Hide file tree
Showing 8 changed files with 630 additions and 330 deletions.
238 changes: 128 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ Typed command-line arguments parser
[Try it out online](https://stackblitz.com/edit/type-flag-demo?devtoolsheight=50&file=src/index.ts&view=editor)


> 🔥 Looking for something more robust?
> _Looking for something more robust?_ 👀
>
> Checkout [_Cleye_](https://github.com/privatenumber/cleye), a CLI development tool powered by type-flag. It comes with argument parsing and a beautiful `--help` documentation generator.
> Try [**Cleye**](https://github.com/privatenumber/cleye)—a CLI development tool powered by _type-flag_.
>
> In addition to flag parsing, it supports argument parsing and has a beautiful `--help` documentation generator.

<sub>Support this project by ⭐️ starring and sharing it. [Follow me](https://github.com/privatenumber) to see what other cool projects I'm working on! ❤️</sub>
Expand Down Expand Up @@ -56,7 +58,7 @@ console.log(parsed._)

### getFlag

Want something even simpler?
_Want something even simpler?_

_type-flag_ also exports a `getFlag` function that returns a single flag value.

Expand Down Expand Up @@ -100,42 +102,34 @@ typeFlag({
```

#### Array type
To accept multiple values of a flag, use an array type by simply wrapping the type in an array:
To accept multiple values of a flag, wrap the type with an array:

```ts
const parsed = typeFlag({
stringFlag: [String]
myFlag: [String]
})
```

This allows usage like this:
```ts
// $ node ./cli --string-flag A --string-flag B

parsed.flags.stringFlag // => ['A', 'B']
// $ node ./cli --my-flag A --my-flag B
parsed.flags.myFlag // => ['A', 'B']
```

#### Aliases
Flags are often given single-character aliases for shorthand usage (eg. `--help` to `-h`). To give a flag an alias, use the object syntax and set the `alias` property to a single-character name.

```ts
typeFlag({
stringFlag: {
myFlag: {
type: String,
alias: 's'
alias: 'm'
}
})
```

This allows usage like this:
```ts
// $ node ./cli -s A

parsed.flags.stringFlag // => 'A'
// $ node ./cli -m hello
parsed.flags.myFlag // => 'hello'
```

#### Default values
Flags that are not passed in will default to being `undefined`. To set a different default value, use the object syntax and pass in a value as the `default` property. When a default is provided, the flag type will include that instead of `undefined`.
Flags that are not passed in will default to being `undefined`. To set a different default value, use the object syntax and pass in a value as the `default` property. When a default is provided, the return type will reflect that instead of `undefined`.

When using mutable values (eg. objects/arrays) as a default, pass in a function that creates it to avoid mutation-related bugs.

Expand All @@ -159,80 +153,108 @@ To get `undefined` in the parsed flag type, make sure [`strict`](https://www.typ

### kebab-case flags mapped to camelCase
When passing in the flags, they can be in kebab-case and will automatically map to the camelCase equivalent.
```sh
# These two map to the same flag
$ node ./cli --someString hello --some-string world
```ts
const parsed = typeFlag({
someString: [String]
})

// $ node ./cli --someString hello --some-string world
parsed.flags.someString // => ['hello', 'world']
```

### Unknown flags
When unrecognized flags are passed in, they are interpreted as a boolean, or a string if explicitly passed in. Unknown flags are not converted to camelCase to allow for accurate error handling.

```sh
$ node ./cli --unknown-flag --unknown-flag=2
```
```ts
const parsed = typeFlag({})

This outputs the following:
```json5
{
unknownFlags: {
'unknown-flag': [true, '2']
},
// ...
}
// $ node ./cli --some-flag --some-flag=1234
parsed.unknownFlags // => { 'some-flag': [true, '1234'] }
```

#### Ignoring unknown flags
Sometimes it may be undesirable to parse out unknown flags. For example, when an argument follows an unknown boolean flag, type-flag may assume that the argument was passed into the flag.
### Arguments
Arguments are values passed in that are not associated with any flags. All arguments are stored in the `_` property.

In these cases, you can ignore them so they're treated as arguments.
Everything after `--` (end-of-flags) is treated as an argument (including flags) and will be stored in the `_['--']` property.

```ts
const parsed = typeFlag({}, {
ignoreUnknown: true
const parsed = typeFlag({
myFlag: [String]
})

// $ node ./cli --unknown hello
console.log(parsed._) // => ['--unknown', 'hello]
// $ node ./cli --my-flag value arg1 -- --my-flag world
parsed.flags.myFlag // => ['value']
parsed._ // => ['arg1', '--my-flag', 'world']
parsed._['--'] // => ['--my-flag', 'world']
```


### Arguments
All argument values are stored in the `_` property.

Everything after `--` (end-of-flags) is treated as arguments and will be stored in the `_['--']` property.
### Flag value delimiters
The characters `=`, `:` and `.` are reserved for delimiting the value from the flag.

```sh
$ node ./cli --boolean value --string "hello world" "another value" -- --string "goodbye world"
$ node ./cli --flag=value --flag:value --flag.value
```

This outputs the following:
<!-- eslint-skip -->
```json5
{
_: [
'value',
'another value',
'--string',
'goodbye world',
'--': [
'--string',
'goodbye world'
]
]
// ...
}
This allows for usage like `--flag:key=value` or `--flag.property=value` to be possible.

### Mutated argv array

When `type-flag` iterates over the argv array, it removes the tokens it parses out via mutation.

By default, `type-flag` works on a new copy of `process.argv.slice(2)` so this doesn't have any side-effects. But if you want to leverage this behavior to extract certain flags and arguments, you can pass in your own copy of `process.argv.slice(2)`.

This may be useful for filtering out certain flags before passing down the `argv` to a child process.

#### Ignoring unknown flags
Sometimes it may be undesirable to parse unknown flags. In these cases, you can ignore them so they're left unparsed in the `argv` array.

```ts
const argv = process.argv.slice(2)
const parsed = typeFlag(
{},
argv,
{
ignore: type => type === 'unknown-flag'
}
)

// $ node ./cli --unknown=hello
parsed._ // => []
argv // => ['--unknown=hello']
```

Note: `value` after `--boolean` is parsed as an argument because the boolean flag doesn't accept a value.
#### Ignoring everything after the first argument

### Flag value delimiters
The characters `=`, `:` and `.` are reserved for delimiting the value from the flag.
Similarly to how Node.js only reads flags passed in before the first argument, _type-flag_ can be configured to ignore everything after the first argument.

```sh
$ node ./cli --flag=value --flag:value --flag.value
```ts
const argv = process.argv.slice(2)

let stopParsing = false
const parsed = typeFlag(
{
myFlag: [Boolean]
},
argv,
{
ignore(type) {
if (stopParsing) {
return true
}
const isArgument = type === 'argument'
if (isArgument) {
stopParsing = isArgument
return stopParsing
}
}
}
)

// $ node ./cli --my-flag ./file.js --my-flag
parsed.flags.myFlag // => [true]
argv // => ['./file.js', '--my-flag']
```

This allows for usage like `--flag:key=value` or `--flag.property=value` to be possible.

## 👨🏻‍🏫 Examples

Expand Down Expand Up @@ -271,7 +293,8 @@ type Parsed = {
```
### Optional value flag
It's common to have flags that act as a boolean when no value is passed in. This can be done by creating a custom type that returns both types.
To create a string flag that acts as a boolean when nothing is passed in, create a custom type that returns both types.
```ts
function OptionalString(value: string) {
Expand All @@ -283,38 +306,29 @@ function OptionalString(value: string) {
}

const parsed = typeFlag({
optionalString: OptionalString
string: OptionalString
})
```

```ts
// $ node ./cli --optional-string
parsed.flags.optionalString // => true
// $ node ./cli --string
parsed.flags.string // => true

// $ node ./cli --optional-string hello
parsed.flags.optionalString // => 'hello'
// $ node ./cli --string hello
parsed.flags.string // => 'hello'
```

### Accepting flag values with `=` in it
In use-cases where flag values contain `=`, you can use `:` instead. This allows flags like `--define:K=V`.

```sh
$ node ./cli --define:key=value
```

```ts
const parsed = typeFlag({
define: String
})

console.log(parsed.flags.define) // => 'key=value'
// $ node ./cli --define:key=value
parsed.flags.define // => 'key=value'
```

### Dot-nested flags
```sh
$ node ./cli --env.TOKEN=123 --env.CI
```

```ts
type Environment = {
TOKEN?: string
Expand All @@ -332,31 +346,34 @@ const parsed = typeFlag({
env: [EnvironmentObject]
})

const environment = parsed.flags.env.reduce((agg, next) => Object.assign(agg, next), {})
const env = parsed.flags.env.reduce(
(agg, next) => Object.assign(agg, next),
{}
)

console.log(environment) // { TOKEN: 123, CI: true }
// $ node ./cli --env.TOKEN=123 --env.CI
env // => { TOKEN: 123, CI: true }
```

### Inverting a boolean
To invert a boolean flag, `false` must be passed in with the `=` operator (or any other value delimiters).

```sh
$ node ./cli --boolean-flag=false
```

Note, without the `=`, the `false` will be parsed as a separate argument.
```ts
const parsed = typeFlag({
booleanFlag: Boolean
})

```sh
$ node ./cli --boolean-flag false
// $ node ./cli --boolean-flag=false
parsed.flags.booleanFlag // => false
```

```json5
{
flags: {
booleanFlag: true
},
_: ['false']
}
Without explicitly specfying the flag value via `=`, the `false` will be parsed as a separate argument.

```ts
// $ node ./cli --boolean-flag false
parsed.flags.booleanFlag // => true
parsed._ // => ['false']
```

### Counting flags
Expand All @@ -370,11 +387,8 @@ const parsed = typeFlag({
}
})

console.log(parsed.flags.verbose.length)
```

```sh
$ node ./cli -vvv # logs 3
// $ node ./cli -vvv
parsed.flags.verbose.length // => 3
```

## ⚙️ API
Expand Down Expand Up @@ -416,15 +430,19 @@ Type: `string[]`
Default: `process.argv.slice(2)`
The argv array to parse.
The argv array to parse. The array is mutated to remove the parsed flags.
#### options
Type:
```ts
type Options = {
// Whether not to parse unknown flags (treat as arguments)
ignoreUnknown?: boolean
// Callback to skip parsing on certain argv tokens
ignore?: (
type: 'known-flag' | 'unknown-flag' | 'argument',
flagOrArgv: string,
value: string | undefined,
) => boolean | void
}
```
Expand Down Expand Up @@ -452,4 +470,4 @@ Type: `string[]`
Default: `process.argv.slice(2)`
The argv array to parse.
The argv array to parse. The array is mutated to remove the parsed flags.
Loading

0 comments on commit 185d911

Please # to comment.