GithubHelp home page GithubHelp logo

arg's Introduction

Arg

arg is an unopinionated, no-frills CLI argument parser.

Installation

npm install arg

Usage

arg() takes either 1 or 2 arguments:

  1. Command line specification object (see below)
  2. Parse options (Optional, defaults to {permissive: false, argv: process.argv.slice(2), stopAtPositional: false})

It returns an object with any values present on the command-line (missing options are thus missing from the resulting object). Arg performs no validation/requirement checking - we leave that up to the application.

All parameters that aren't consumed by options (commonly referred to as "extra" parameters) are added to result._, which is always an array (even if no extra parameters are passed, in which case an empty array is returned).

const arg = require('arg');

// `options` is an optional parameter
const args = arg(
	spec,
	(options = { permissive: false, argv: process.argv.slice(2) })
);

For example:

$ node ./hello.js --verbose -vvv --port=1234 -n 'My name' foo bar --tag qux --tag=qix -- --foobar
// hello.js
const arg = require('arg');

const args = arg({
	// Types
	'--help': Boolean,
	'--version': Boolean,
	'--verbose': arg.COUNT, // Counts the number of times --verbose is passed
	'--port': Number, // --port <number> or --port=<number>
	'--name': String, // --name <string> or --name=<string>
	'--tag': [String], // --tag <string> or --tag=<string>

	// Aliases
	'-v': '--verbose',
	'-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", "--foobar"],
	'--port': 1234,
	'--verbose': 4,
	'--name': "My name",
	'--tag': ["qux", "qix"]
}
*/

The values for each key=>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 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:

  1. The parameter value (always a string)
  2. The parameter name (e.g. --label)
  3. The previous value for the destination (useful for reduce-like operations or for supporting -v multiple times, etc.)

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

Note that Boolean and [Boolean] have special treatment - an option argument is not consumed or passed, but instead true is returned. These options are called "flags".

For custom handlers that wish to behave as flags, you may pass the function through arg.flag():

const arg = require('arg');

const argv = [
	'--foo',
	'bar',
	'-ff',
	'baz',
	'--foo',
	'--foo',
	'qux',
	'-fff',
	'qix'
];

function myHandler(value, argName, previousValue) {
	/* `value` is always `true` */
	return 'na ' + (previousValue || 'batman!');
}

const args = arg(
	{
		'--foo': arg.flag(myHandler),
		'-f': '--foo'
	},
	{
		argv
	}
);

console.log(args);
/*
{
	_: ['bar', 'baz', 'qux', 'qix'],
	'--foo': 'na na na na na na na na batman!'
}
*/

As well, arg supplies a helper argument handler called arg.COUNT, which equivalent to a [Boolean] argument's .length property - effectively counting the number of times the boolean flag, denoted by the key, is passed on the command line.. For example, this is how you could implement ssh's multiple levels of verbosity (-vvvv being the most verbose).

const arg = require('arg');

const argv = ['-AAAA', '-BBBB'];

const args = arg(
	{
		'-A': arg.COUNT,
		'-B': [Boolean]
	},
	{
		argv
	}
);

console.log(args);
/*
{
	_: [],
	'-A': 4,
	'-B': [true, true, true, true]
}
*/

Options

If a second parameter is specified and is an object, it specifies parsing options to modify the behavior of arg().

argv

If you have already sliced or generated a number of raw arguments to be parsed (as opposed to letting arg slice them from process.argv) you may specify them in the argv option.

For example:

const args = arg(
	{
		'--foo': String
	},
	{
		argv: ['hello', '--foo', 'world']
	}
);

results in:

const args = {
	_: ['hello'],
	'--foo': 'world'
};

permissive

When permissive set to true, arg will push any unknown arguments onto the "extra" argument array (result._) instead of throwing an error about an unknown flag.

For example:

const arg = require('arg');

const argv = [
	'--foo',
	'hello',
	'--qux',
	'qix',
	'--bar',
	'12345',
	'hello again'
];

const args = arg(
	{
		'--foo': String,
		'--bar': Number
	},
	{
		argv,
		permissive: true
	}
);

results in:

const args = {
	_: ['--qux', 'qix', 'hello again'],
	'--foo': 'hello',
	'--bar': 12345
};

stopAtPositional

When stopAtPositional is set to true, arg will halt parsing at the first positional argument.

For example:

const arg = require('arg');

const argv = ['--foo', 'hello', '--bar'];

const args = arg(
	{
		'--foo': Boolean,
		'--bar': Boolean
	},
	{
		argv,
		stopAtPositional: true
	}
);

results in:

const args = {
	_: ['hello', '--bar'],
	'--foo': true
};

Errors

Some errors that arg throws provide a .code property in order to aid in recovering from user error, or to differentiate between user error and developer error (bug).

ARG_UNKNOWN_OPTION

If an unknown option (not defined in the spec object) is passed, an error with code ARG_UNKNOWN_OPTION will be thrown:

// cli.js
try {
	require('arg')({ '--hi': String });
} catch (err) {
	if (err.code === 'ARG_UNKNOWN_OPTION') {
		console.log(err.message);
	} else {
		throw err;
	}
}
node cli.js --extraneous true
Unknown or unexpected option: --extraneous

FAQ

A few questions and answers that have been asked before:

How do I require an argument with arg?

Do the assertion yourself, such as:

const args = arg({ '--name': String });

if (!args['--name']) throw new Error('missing required argument: --name');

License

Released under the MIT License.

arg's People

Contributors

0xflotus avatar arjunsajeev avatar blakeembrey avatar dependabot[bot] avatar flexdinesh avatar herber avatar j12934 avatar jakehamilton avatar leerob avatar leo avatar macklinu avatar matheuss avatar pacocoursey avatar qix- avatar timneutkens avatar yuler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

arg's Issues

Typescript build error

When implement the module in typescript and built it, I get the next error when I trying to run

const args = (0, arg_1.default)({ '--v': Boolean, '--a': Boolean, '-v': '--v', '-a': '--a' });
UnhandledPromiseRejectionWarning: TypeError: (0 , arg_1.default) is not a function

Better name needed

After checking out the new spec and reading about @rauchg's and @Qix-' arguments for this, I think it makes sense to try it out in more of our projects in the future.

However, in order for this to happen, we need a name that's not ZEIT-dependent. Open source projects that focus on the author won't make it far (no matter if it's the code or just the name). The name (just like the functionality) needs to be much more general and independent (see "chalk", "hazel", "serve", "release", "micro", etc). People won't be interested in contributing otherwise.

In addition, "zarg" (spelled "z-arg") is not that easy to say, imo.

What's the default value for Boolean type?

Hi, I don't understand this part from Readme.md

// hello.js
const arg = require('arg');

const args = arg({
	// Types
	'--help':    Boolean,
	'--version': Boolean,
	'--verbose': arg.COUNT,   // Counts the number of times --verbose is passed
	'--port':    Number,      // --port <number> or --port=<number>
	'--name':    String,      // --name <string> or --name=<string>
	'--tag':     [String],    // --tag <string> or --tag=<string>

	// Aliases
	'-v':        '--verbose',
	'-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", "--foobar"],
	'--port': 1234,
	'--verbose': 4,
	'--name': "My name",
	'--tag': ["qux", "qix"]
}
*/

I think --verbose should be true, not 4, because I got true from my practice.

// t.ts
const arg = require("arg");

const args = arg(
  {
    "--project": String,
    "--pass": Boolean,

    "-p": "--project"
  },
  {
    argv: process.argv.slice(2)
  }
);

// package.json
"start": "ts-node ./t.ts -p hello --pass"

// Result
{ _: [], '--project': 'hello', '--pass': true }

So, can someone tell me why it is 4? If it should'be fixed, I'll send a pr.

Handle boolean array arguments better

This is justified since Boolean types already get a bit of special treatment.

In the case I want multiple levels of verbosity, for example, the following should work:

// ./my-program -vvvv

const arg = require('arg');

const args = arg({
    '-v': [Boolean]
});

console.log(args);
/*
    {
        _: [],
        '-v': 4
    }
*/

Currently, you have to do -v -v -v -v separately, and in turn you get [true, true, true, true], which isn't exactly elegant.

Is there other way to input array of strings?

For example, I have this arg "--tte": [String], so whenever I call the program I need to write myScript --tte event is there a way to write multiple string without repeating --tte multiple times and splinting with commas?

Ability to "stop early"

In some projects, I need the ability to "stop early" when parsing the arguments. I want it to parse up until the first _ argument and then break. This is useful for projects such as https://github.com/TypeStrong/ts-node (like node.js core), where the arguments before the script file are part of the execution and after the script name part of the script arguments.

ReDoS vulnerability in index.js

Description

ReDoS vulnerability is an algorithmic complexity vulnerability that usually appears in backtracking-kind regex engines, e.g. the javascript default regex engine. The attacker can construct malicious input to trigger the worst-case time complexity of the regex engine to make a denial-of-service attack.

In this project, here has used the ReDoS vulnerable regex ^-?\d*(\.(?=\d))?\d*$ that can be triggered by the below PoC:

const arg = require('arg');
const args = arg(
    {
        '--foo': String
    },        {
        argv: ['hello', '--foo', '-' + '0'.repeat(60000) + '-']
    }
);

How to repair

The cause of this vulnerability is the use of the backtracking-kind regex engine. I recommend the author to use the RE2 regex engine developed by google, but it doesn't support lookaround and backreference extension features, so we need to change the original regex and add additional code constraints. Here is my repair solution:

function safeMatch(string) {
    const RE2 = require("re2")
    let re = new RE2(/^-?\d*(\.)?(\d*)$/)
    let res = re.match(string)
    if (res != null) {
        group1 = res[1]
        if (group1 !== null) {
            group2 = res[2]
            if (/^\d/.test(group2)) {
                return res
            } else {
                return null
            }
        }
        return res
    }
    return res
}

console.log(safeMatch("-1.1")) // [ '-1.1', '.', '1', index: 0, input: '-1.1', groups: undefined ]
console.log(safeMatch("-1."))  // null
console.log(safeMatch("."))    // null

Using this code snippet to replace the code in line 156 argv[i + 1].match(/^-?\d*(\.(?=\d))?\d*$/) can repair this vulnerability. The match semantics of the new regex + code constraint above is equivalent to the original regex.

I hope the author can adopt this repair solution and I would be very grateful. Thanks!

Support for required arguments?

Would it make sense to support required arguments? For example:

const args = arg({
	'--help':    Boolean,
	'--port':    arg.req(Number),
	'--name':    String,
	'--tag':     arg.req([String]),
});

Alternatively:

const args = arg({
	'--help': Boolean,
	'--path': String,
});

const {
	'--help': printHelp = false,
	'--path': thePath = arg.required('--path', String),
} = args;

arg.required() would print an error message and exit Node.js. Downsides of this approach: Doesn’t handle aliases, redundancy.

If this is out of scope, it would be a good FAQ, because many shell argument parsing libraries support this feature.

Short args can't be used with =

Short args can't be used with =. Is this intentional?

const arg = require('arg')

arg({
  '-a': String
})
node example.js -a=a

Results in:

                                        throw new TypeError(`Option requires argument (but was followed by another short argument): ${originalArgName}`);
                                        ^

TypeError: Option requires argument (but was followed by another short argument): -a
    at arg (<path>/node_modules/arg/index.js:97:12)
    at Object.<anonymous> (<path>/example.js:5:1)

A similar error happens for aliases:

const arg = require('arg')

arg({
  '--a': String,
  '-a': '--a'
})

Add `.npmignore`

I'm a huge fan of whitelists to make sure no garbage ever ends up in npm accidentally.
It would look approximately like this:

*
!index.js
!index.d.ts

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet. We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please delete the greenkeeper/initial branch in this repository, and then remove and re-add this repository to the Greenkeeper App’s white list on Github. You'll find this list on your repo or organization’s settings page, under Installed GitHub Apps.

Make the subcommand use case work

Consider:

now --debug scale --no-verify

We need to be able to parse this only with the known global options. For example:

{
  "--debug": Boolean
}

When it inevitable throws on the unknown --no-verify option, we need to recover everything else it was able to parse.

Print help?

I'm looking for a package to help me write CLIs and very much like arg's simplicity for parsing args. I was thinking that if the spec format was a bit enhanced, it could perhaps help me print help as well, like this:

const spec = {
  "--help": { type: Boolean, description: "Show help" },
  "--port": { type: Number, description: "Port number" }
};

const args = arg(spec);

if (args['--help']) {
  console.log(getHelp(spec));
}

What do you think? I know it might be out of the scope of this package but arg parsing and printing help are closely related things and I'd like to stay DRY.

TypeScript typings error for handlers

The current types in index.d.ts produce an error when attempting to use them in a purely TypeScript project:

Exported variable 'args' has or is using name 'flagSymbol' from external module
"/path/to/project/node_modules/arg/index" but cannot be named.ts(4023)

After a brief bit of tweaking, I found that this can be fixed by moving flagSymbol into the arg namespace and exporting it along with other members. This makes the type accessible and resolves the error. I'm unsure if there is a better way to handle it, though this should be fine.

My Local arg.d.ts file
declare module "arg" {
	function arg<T extends arg.Spec>(
		spec: T,
		options?: arg.Options
	): arg.Result<T>;

	namespace arg {
		export const flagSymbol: unique symbol;

		export function flag<T>(fn: T): T & { [arg.flagSymbol]: true };

		export const COUNT: Handler<number> & { [arg.flagSymbol]: true };

		export type Handler<T = any> = (
			value: string,
			name: string,
			previousValue?: T
		) => T;

		export class ArgError extends Error {
			constructor(message: string, code: string);

			code: string;
		}

		export interface Spec {
			[key: string]: string | Handler | [Handler];
		}

		export type Result<T extends Spec> = { _: string[] } & {
			[K in keyof T]?: T[K] extends Handler
				? ReturnType<T[K]>
				: T[K] extends [Handler]
				? Array<ReturnType<T[K][0]>>
				: never;
		};

		export interface Options {
			argv?: string[];
			permissive?: boolean;
			stopAtPositional?: boolean;
		}
	}

	export = arg;
}

Note: This is occurring for me using typescript@^4.6.3. I'm not sure if this is an issue with a new version of TypeScript or has always been a problem since I've typically only used arg in JavaScript projects up until now.

Issues parsing negative numbers when passed without `=`

in node shell:

> const arg = require('arg')
undefined
> arg({"--int": parseInt}, {argv:["--int=-100"]})
{ _: [], '--int': -100 }
> arg({"--int": parseInt}, {argv:["--int -100"]})
Thrown:
{ Error: Unknown or unexpected option: --int -100
    at arg (/run/media/karfau/hdd-data/dev/node-cli-arguments-options/arg/node_modules/arg/index.js:88:19) code: 'ARG_UNKNOWN_OPTION' }
> arg({"--num": parseFloat}, {argv:["--num=-1.0"]})
{ _: [], '--num': -1 }
> arg({"--num": parseFloat}, {argv:["--num -1.0"]})
Thrown:
{ Error: Unknown or unexpected option: --num -1.0
    at arg (node-cli-arguments-options/arg/node_modules/arg/index.js:88:19) code: 'ARG_UNKNOWN_OPTION' }

I also tried to wrap the numbers with different kinds of quotes, but it didn't have any effect.
I don't know if this is a "feature" or a "bug".

Update: The example above is bad in the sense that argv is different from what it would look like when using from the shell, which influences the error messages. A better example is:

> const arg = require('arg')
undefined
> arg({"--int": parseInt}, {argv:["--int", "-100"]})
Thrown:
Error: Option requires argument: --int
    at arg (node-cli-arguments-options/arg/node_modules/arg/index.js:105:13)
>  arg({"--float": parseFloat}, {argv:["--float", "-0.1"]})
Thrown:
Error: Option requires argument: --float
    at arg (node-cli-arguments-options/arg/node_modules/arg/index.js:105:13)

Since the repo doesn't seem to be maintained (sorry for that, it's not true, must have switched the name with one of the other repos), just filing this to make people aware since I discovered it while comparing features of different argument parsers (WIP)

Short options get splited in permissive being true

As my realization, the {permissive: true} should left the unknown option being unparsed and treat them as positional arguments, but currently the permissive will still split the unknown short options. Is this a expected behavor? or can we change this behavor?

const arg = require('arg')
const args = arg({}, {argv: '-abc', permissive: true})
console.assert(args._[0] != '-abc')
console.assert(args._.join(' ') == '-a -b -c')

Support defaults

It'd be sweet if this library also supported default arguments. The API could look like this:

arg({
  '--help': Boolean,
  '-h': '--help',

  '--branch': String,
  '-b': '--branch',

  '--config': './config',
  '-c': '--config'
})

You could distinguish between String and a String argument with a default by doing:

if (value === String) {
  // it's a string argument
}

if (typeof value === 'string') {
  // it's a string argument with a default
}

Everything after "=" gets stripped off in long args with "=" in value

When an arg has = in the value, everything after (inc) = gets stripped off from the arg.

Pass this command to arg()

yarn keystone dev --connect-to=mongodb://user:pass@localhost:27017/keystone?authSource=admin&w=1

Current output
--connect-to = mongodb://user:pass@localhost:27017/keystone?authSource

Expected output
--connect-to = mongodb://user:pass@localhost:27017/keystone?authSource=admin&w=1

This is essential for passing URLs as arg value.

Flags with optional values

Is there a way to have optional flag values? Something like the following:

const arg = require('arg');

const args = arg({
	'--port': Number, // --port <number>
	'--host': String.optional, // --host [string]
});

Given --port 5432 --host localhost it should return { _: [], port: 5432, host: 'localhost' },
given --port 5432 --host it should return something like { _: [], port: 5432, host: true },
and given --port 5432 it should return { _: [], port: 5432 },

Add support for running in the browser

arg/index.js

Line 13 in 99b578e

function arg(opts, {argv = process.argv.slice(2), permissive = false, stopAtPositional = false} = {}) {

The library takes the default argument vector from process.env. As a result, the library does not work in the browser. Removing the default fixes this.

I understand that this is an unconventional use-case and that arg is an opinionated library so this might be outside the scope, but if it isn't I'm more than happy to make a PR from my fork (that I currently use to work around this limitation).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.