Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New spec #1

Merged
merged 3 commits into from
Sep 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 30 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ $ npm install zarg

## Usage

`zarg()` takes 1-3 arguments:
`zarg()` takes 1 or 2 arguments:

1. An array of CLI arguments (_Optional_, defaults to `process.argv.slice(2)`)
2. Options argument (see below)
3. Function to call for unknown options (_Optional_, raises a descriptive error by default)

It returns an object with any values present on the command-line (missing options are thus
missing from the resulting object). Zarg performs no validation/requirement checking - we
Expand All @@ -35,42 +34,56 @@ in which case an empty array is returned).
```javascript
const zarg = require('zarg');

const args = zarg([argument_array,] options [, unknown_callback_fn]);
const args = zarg([argument_array,] options);
```

For example:

```console
$ node ./hello.js --port=1234 -n 'My name' foo bar --tag qux --tag=qix -- --foobar
```

```javascript
// node ./hello.js --port=1234 -n 'My name' foo bar
// hello.js
const zarg = require('zarg');

const args = zarg({
help: Boolean, // --help
version: [Boolean, '-v'], // --version or -v
port: Number, // --port <number> or --port=<number>
name: [String, '-n', '--label'] // --name <string>, --name=<string>, -n <string>,
// --label <string>, or --label=<string>
// Types
'--help': Boolean,
'--version': Boolean,
'--port': Number, // --port <number> or --port=<number>
'--name': String, // --name <string> or --name=<string>
'--tag': [String], // --tag <string> or --tag=<string>

// Aliases
'-v': '--version',
'-n': '--name', // -n <string>; result is stored in --name
'--label': '--name' // --label <string> or --label=<string>;
// result is stored in --name
});

console.log(args);
/*
{
_: ["foo", "bar"],
port: 1234,
name: "My name"
_: ["foo", "bar", "--foobar"],
'--port': 1234,
'--name': "My name",
'--tag': ["qux", "qix"]
}
*/
```

The options object defaults to having its keys as long arguments.

The values for each key=&gt;value pair is either a type function or an array.
The values for each key=&gt;value pair is either a type (function or [function]) or a string (indicating an alias).

- In the case of a function, the string value of the argument's value is passed to it,
and the return value is used as the ultimate value.

- In the case of an array, the first element _must_ be a type function,
and any subsequent strings are used as aliases.
- In the case of an array, the only element _must_ be a type function. Array types indicate
that the argument may be passed multiple times, and as such the resulting value in the returned
object is an array with all of the values that were passed using the specified flag.

- In the case of a string, an alias is established. If a flag is passed that matches the _key_,
then the _value_ is substituted in its place.

Type functions are passed three arguments:

Expand All @@ -80,20 +93,6 @@ Type functions are passed three arguments:

This means the built-in `String`, `Number`, and `Boolean` type constructors "just work" as type functions.

If a parameter is present in the argument array but isn't configured in the options object,
the function supplied in the third argument (if present) is called with two arguments:

1. The name of the option that was unknown
2. The argument value (only if the option was formatted as `--long-name=something`)

For example, this is the default unknown handler:

```javascript
function defaultUnknownHandler(name, /* , val */) {
throw new Error(`Unknown or unexpected option: ${name}`);
}
```

# License

Copyright &copy; 2017 by ZEIT, Inc. Released under the [MIT License](LICENSE.md).
92 changes: 48 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,36 @@
function defaultUnknownHandler(name, /* , val */) {
throw new Error(`Unknown or unexpected option: ${name}`);
}

function zarg(argv, opts, unknownHandler) {
if (!Array.isArray(argv)) {
unknownHandler = opts;
opts = argv;
argv = null;
}
function zarg(argv, opts) {
const result = {_: []};

if (typeof opts === 'function') {
unknownHandler = opts;
opts = null;
/* eslint-disable default-case */
switch (arguments.length) {
case 0:
return result;
case 1:
opts = argv;
argv = null;
break;
}
/* eslint-enable default-case */

argv = argv || process.argv.slice(2);
opts = opts || {};
unknownHandler = unknownHandler || defaultUnknownHandler;

const aliases = {};
const handlers = {};
const setType = (name, type, dest) => {
if (name in handlers) {
const odest = handlers[name][1];
const extended = `--${dest}` === name ? '' : `alias for --${dest}, `;
throw new Error(`Duplicate option configuration: ${name} (${extended}originally for --${odest})`);
}

handlers[name] = [type, dest];
};

for (const key of Object.keys(opts)) {
const [type, aliases] = Array.isArray(opts[key]) ? [opts[key][0], opts[key].slice(1)] : [opts[key], []];

const name = `--${key}`;

if (!type || typeof type !== 'function') {
throw new Error(`Type missing or not a function: ${name}`);
if (typeof opts[key] === 'string') {
aliases[key] = opts[key];
continue;
}

setType(name, type, key);
const type = opts[key];

for (const alias of aliases) {
setType(alias, type, key);
if (!type || (typeof type !== 'function' && !(Array.isArray(type) && type.length === 1 && typeof type[0] === 'function'))) {
throw new Error(`Type missing or not a function or valid array type: ${key}`);
}
}

const result = {_: []};
handlers[key] = type;
}

for (let i = 0, len = argv.length; i < len; i++) {
const arg = argv[i];
Expand All @@ -61,27 +46,46 @@ function zarg(argv, opts, unknownHandler) {
}

if (arg[0] === '-') {
const [argName, argStr] = arg[1] === '-' ? arg.split('=', 2) : [arg, undefined];
const [originalArgName, argStr] = arg[1] === '-' ? arg.split('=', 2) : [arg, undefined];

let argName = originalArgName;
while (argName in aliases) {
argName = aliases[argName];
}

if (!(argName in handlers)) {
unknownHandler(argName, argStr);
continue;
throw new Error(`Unknown or unexpected option: ${originalArgName}`);
}

const [type, dest] = handlers[argName];
/* eslint-disable operator-linebreak */
const [type, isArray] = Array.isArray(handlers[argName])
? [handlers[argName][0], true]
: [handlers[argName], false];
/* eslint-enable operator-linebreak */

let value;
if (type === Boolean) {
result[dest] = true;
value = true;
} else if (argStr === undefined) {
if (argv.length < i + 2 || (argv[i + 1].length > 1 && argv[i + 1][0] === '-')) {
const extended = `--${dest}` === argName ? '' : ` (alias for --${dest})`;
throw new Error(`Option requires argument: ${argName}${extended}`);
const extended = originalArgName === argName ? '' : ` (alias for ${argName})`;
throw new Error(`Option requires argument: ${originalArgName}${extended}`);
}

result[dest] = type(argv[i + 1], argName, result[dest]);
value = type(argv[i + 1], argName, result[argName]);
++i;
} else {
result[dest] = type(argStr, argName, result[dest]);
value = type(argStr, argName, result[argName]);
}

if (isArray) {
if (result[argName]) {
result[argName].push(value);
} else {
result[argName] = [value];
}
} else {
result[argName] = value;
}
} else {
result._.push(arg);
Expand Down
Loading