pkgjs / parseargs

Polyfill of `util.parseArgs()`
Apache License 2.0
121 stars 9 forks source link

parseArgs

Coverage

Polyfill of util.parseArgs()

util.parseArgs([config])

Stability: 1 - Experimental

Provides a higher level API for command-line argument parsing than interacting with process.argv directly. Takes a specification for the expected arguments and returns a structured object with the parsed options and positionals.

import { parseArgs } from 'node:util';
const args = ['-f', '--bar', 'b'];
const options = {
  foo: {
    type: 'boolean',
    short: 'f'
  },
  bar: {
    type: 'string'
  }
};
const {
  values,
  positionals
} = parseArgs({ args, options });
console.log(values, positionals);
// Prints: [Object: null prototype] { foo: true, bar: 'b' } []
const { parseArgs } = require('node:util');
const args = ['-f', '--bar', 'b'];
const options = {
  foo: {
    type: 'boolean',
    short: 'f'
  },
  bar: {
    type: 'string'
  }
};
const {
  values,
  positionals
} = parseArgs({ args, options });
console.log(values, positionals);
// Prints: [Object: null prototype] { foo: true, bar: 'b' } []

util.parseArgs is experimental and behavior may change. Join the conversation in pkgjs/parseargs to contribute to the design.

parseArgs tokens

Detailed parse information is available for adding custom behaviours by specifying tokens: true in the configuration. The returned tokens have properties describing:

The returned tokens are in the order encountered in the input args. Options that appear more than once in args produce a token for each use. Short option groups like -xy expand to a token for each option. So -xxx produces three tokens.

For example to use the returned tokens to add support for a negated option like --no-color, the tokens can be reprocessed to change the value stored for the negated option.

import { parseArgs } from 'node:util';

const options = {
  'color': { type: 'boolean' },
  'no-color': { type: 'boolean' },
  'logfile': { type: 'string' },
  'no-logfile': { type: 'boolean' },
};
const { values, tokens } = parseArgs({ options, tokens: true });

// Reprocess the option tokens and overwrite the returned values.
tokens
  .filter((token) => token.kind === 'option')
  .forEach((token) => {
    if (token.name.startsWith('no-')) {
      // Store foo:false for --no-foo
      const positiveName = token.name.slice(3);
      values[positiveName] = false;
      delete values[token.name];
    } else {
      // Resave value so last one wins if both --foo and --no-foo.
      values[token.name] = token.value ?? true;
    }
  });

const color = values.color;
const logfile = values.logfile ?? 'default.log';

console.log({ logfile, color });
const { parseArgs } = require('node:util');

const options = {
  'color': { type: 'boolean' },
  'no-color': { type: 'boolean' },
  'logfile': { type: 'string' },
  'no-logfile': { type: 'boolean' },
};
const { values, tokens } = parseArgs({ options, tokens: true });

// Reprocess the option tokens and overwrite the returned values.
tokens
  .filter((token) => token.kind === 'option')
  .forEach((token) => {
    if (token.name.startsWith('no-')) {
      // Store foo:false for --no-foo
      const positiveName = token.name.slice(3);
      values[positiveName] = false;
      delete values[token.name];
    } else {
      // Resave value so last one wins if both --foo and --no-foo.
      values[token.name] = token.value ?? true;
    }
  });

const color = values.color;
const logfile = values.logfile ?? 'default.log';

console.log({ logfile, color });

Example usage showing negated options, and when an option is used multiple ways then last one wins.

$ node negate.js
{ logfile: 'default.log', color: undefined }
$ node negate.js --no-logfile --no-color
{ logfile: false, color: false }
$ node negate.js --logfile=test.log --color
{ logfile: 'test.log', color: true }
$ node negate.js --no-logfile --logfile=test.log --color --no-color
{ logfile: 'test.log', color: false }

Table of Contents


Scope

It is already possible to build great arg parsing modules on top of what Node.js provides; the prickly API is abstracted away by these modules. Thus, process.parseArgs() is not necessarily intended for library authors; it is intended for developers of simple CLI tools, ad-hoc scripts, deployed Node.js applications, and learning materials.

It is exceedingly difficult to provide an API which would both be friendly to these Node.js users while being extensible enough for libraries to build upon. We chose to prioritize these use cases because these are currently not well-served by Node.js' API.


Version Matchups

Node.js @pkgjs/parseArgs Changes
v18.11.0 0.11.0 Add support for default values in input config.
v16.17.0, v18.7.0 0.10.0 Add support for returning detailed parse information using tokens in input config and returned properties.
v18.3.0 v0.9.1

πŸš€ Getting Started

  1. Install dependencies.

    npm install
  2. Open the index.js file and start editing!

  3. Test your code by calling parseArgs through our test file

    npm test

πŸ™Œ Contributing

Any person who wants to contribute to the initiative is welcome! Please first read the Contributing Guide

Additionally, reading the Examples w/ Output section of this document will be the best way to familiarize yourself with the target expected behavior for parseArgs() once it is fully implemented.

This package was implemented using tape as its test harness.


πŸ’‘ process.mainArgs Proposal

Note: This can be moved forward independently of the util.parseArgs() proposal/work.

Implementation:

process.mainArgs = process.argv.slice(process._exec ? 1 : 2)

πŸ“ƒ Examples

const { parseArgs } = require('@pkgjs/parseargs');
const { parseArgs } = require('@pkgjs/parseargs');
// specify the options that may be used
const options = {
  foo: { type: 'string'},
  bar: { type: 'boolean' },
};
const args = ['--foo=a', '--bar'];
const { values, positionals } = parseArgs({ args, options });
// values = { foo: 'a', bar: true }
// positionals = []
const { parseArgs } = require('@pkgjs/parseargs');
// type:string & multiple
const options = {
  foo: {
    type: 'string',
    multiple: true,
  },
};
const args = ['--foo=a', '--foo', 'b'];
const { values, positionals } = parseArgs({ args, options });
// values = { foo: [ 'a', 'b' ] }
// positionals = []
const { parseArgs } = require('@pkgjs/parseargs');
// shorts
const options = {
  foo: {
    short: 'f',
    type: 'boolean'
  },
};
const args = ['-f', 'b'];
const { values, positionals } = parseArgs({ args, options, allowPositionals: true });
// values = { foo: true }
// positionals = ['b']
const { parseArgs } = require('@pkgjs/parseargs');
// unconfigured
const options = {};
const args = ['-f', '--foo=a', '--bar', 'b'];
const { values, positionals } = parseArgs({ strict: false, args, options, allowPositionals: true });
// values = { f: true, foo: 'a', bar: true }
// positionals = ['b']

F.A.Qs

Links & Resources