Giter Club home page Giter Club logo

getopts's Introduction

Getopts

Parse CLI arguments.

  • Lightweight drop-in replacement for minimist and clones.
  • Small (180 LOC), focused, no dependencies.
  • Up to 6x faster than alternatives!

Break up command-line arguments into key-value pairs for easy look-up and retrieval. Built upon utility conventions that have been used for decades, Getopts sane defaults help you write CLI tools that look and feel like the real deal.

$ example --type=module -o main.js *.{js,json}
import getopts from "getopts"

const options = getopts(process.argv.slice(2), {
  alias: {
    output: ["o", "f"],
    type: "t",
  },
})

The result is an object populated with all the parsed arguments.

{
  _: ["index.js", "package.json"],
  output: "main.js",
  type: "module",
  o: "main.js",
  f: "main.js",
  t: "module",
}

Installation

npm install getopts

Parsing rules

Short options

A short option consists of a - followed by a single alphabetic character. Multiple options can be grouped together without spaces. Short options are boolean by default unless followed by an operand (non-option) or if adjacent to any non-alphabetic characters:

getopts(["-ab", "-c"]) //=> { _: [], a:true, b:true, c:true }
getopts(["-a", "alpha"]) //=> { _: [], a:"alpha" }
getopts(["-abc1"]) //=> { _: [], a:true, b:true, c:1 }

Use opts.string to parse an option as a string regardless.

getopts(["-kF12"], {
  string: ["k"],
}) //=> { _: [], k:"F12" }

The first operand following an option will be used as its value. Use opts.boolean to specify that an option should be parsed as a boolean regardless, causing the following argument to be treated as an operand instead.

getopts(["-a", "alpha"], {
  boolean: ["a"],
}) //=> { _: ["alpha"], a:true }

Any character listed in the ASCII table can be used as a short option if it's the first character after the dash.

getopts(["-9", "-#10", "-%0.01"]) //=> { _:[], 9:true, #:10, %:0.01 }

Long options

A long option consists of a -- followed by a name and a value separated by an =. Long options without a value are boolean by default.

getopts(["--turbo", "--warp=10"]) //=> { _: [], turbo:true, warp:10 }
getopts(["--warp=e=mc^2"]) //=> { _: [], warp:"e=mc^2" }
getopts(["--@", "alpha"]) //=> { _: [], @:"alpha" }

Negated options start with --no- and are always false.

getopts(["--no-turbo"]) //=> { _: [], turbo:false }

Operands

Every non-option argument is an operand. Operands are saved to the result._ operands array.

getopts(["alpha", "-w9", "bravo"]) //=> { _: ["alpha", "bravo"], w:9 }
getopts(["--code=alpha", "bravo"]) //=> { _: ["bravo"], code:"alpha" }

Everything after a standalone -- is an operand.

getopts(["--alpha", "--", "--bravo", "--turbo"]) //=> { _:["--bravo", "--turbo"], alpha:true }

A single - is also treated as an operand.

getopts(["--turbo", "-"]) //=> { _:["-"], turbo:true }

Other

Options specified as boolean or string will be added to the result object as false or "" (even if missing from the arguments array).

getopts([], {
  string: ["a"],
  boolean: ["b"],
}) //=> { _:[], a:"", b:false }

Repeated options are stored as arrays with every value in order of appearance.

getopts(["-x?alpha=bravo", "-x3.14", "-x"] //=> { _:[], a:["?alpha=bravo", 3.14, true] }

A value may contain newlines or other control characters.

getopts(["--text=top\n\tbottom"]) //=> { _:[], text:"top\n\tbottom" }

="false" is converted to boolean by default.

getopts(["--turbo=false"]) //=> { _:[], turbo:false }

API

getopts(argv, opts)

Parse command-line arguments. Returns an object mapping argument names to their values.

argv[]

An array of arguments, usually process.argv.

opts.alias

An object of option aliases. An alias can be a string or an array of strings. Aliases let you declare substitute names for an option, e.g., the short (abbreviated) and long (canonical) variations.

getopts(["-t"], {
  alias: {
    turbo: ["t", "T"],
  },
}) //=> { _:[], t:true, T:true, turbo:true }

opts.boolean

An array of options to parse as boolean. In the example below, t is parsed as a boolean, causing the following argument to be treated as an operand.

getopts(["-t", "alpha"], {
  boolean: ["t"],
}) //=> { _:["alpha"], t:true }

opts.string

An array of flags to parse as strings. In the example below, t is parsed as a string, causing all adjacent characters to be treated as a single value and not as individual options.

getopts(["-atabc"], {
  string: ["t"],
}) //=> { _:[], a:true, t:"abc" }

opts.default

An object of default values for options not present in the arguments array.

getopts(["--warp=10"], {
  default: {
    warp: 15,
    turbo: true,
  },
}) //=> { _:[], warp:10, turbo:true }

opts.unknown()

We call this function for each unknown option. Return false to discard the option. Unknown options are those that appear in the arguments array, but are not in opts.string, opts.boolean, opts.default, or opts.alias.

getopts(["-abc"], {
  unknown: (option) => "a" === option,
}) //=> { _:[], a:true }

opts.stopEarly

A boolean property. If true, the operands array _ will be populated with all the arguments after the first operand.

getopts(["-w9", "alpha", "--turbo", "bravo"], {
  stopEarly: true,
}) //=> { _:["alpha", "--turbo", "bravo"], w:9 }

This property is useful when implementing sub-commands in a CLI.

import getopts from "getopts"
import { install, update, uninstall } from "./commands.js"

const options = getopts(process.argv.slice(2), {
  stopEarly: true,
})

const [command, subargv] = options._

if (command === "install") {
  install(subargv)
} else if (command === "update") {
  update(subargv)
} else if (command === "uninstall") {
  uninstall(subargv)
}

Benchmarks

npm --prefix bench start

License

MIT

getopts's People

Contributors

dependabot[bot] avatar jimt avatar jorgebucaran avatar jridgewell avatar ovyerus avatar toddself 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

getopts's Issues

Document supported Node versions

Currently it is very unclear which Node versions are supported. CI is executed on 12 and 14, but quick glance at a source code seems to indicate that it is likely to work on Node 6+ as well. Would it be possible to clarify that in documentation and/or package.json engine entry?

Add an option to remove duplicate items from the `options` array

IMHO, the output of getopts() should contain only one value per option. If one sets an alias, currently it creates two items in the array (e.g. h and help), while I think it should be only one (usually help).

I suggest to add an option to create an array with only either long or short options. If an option would not have set an alias, it would add it nevertheless.

Add options.array

It would set the property to an array regardless if one flag is passed or multiple flags are passed.

getopts(["-t", "lunch"], { array: ["t"] }) //=> { t: ["lunch"] }

The current behavior is that t's value would be the string "lunch". This would help me eliminate some boilerplate code that checks if the value is a string or array.

Way to disable built-in default values

getopts assigns default values to string (''), number (0) and boolean (false) options.

Issues:

  1. Not compatible with JS default values:
const { foo = 'bar' } = getopts(argv, { string: [ 'foo' ] });
  1. Cannot distinguish between explicit options and default values:
const { baz } = getopts(argv, { string: [ 'baz' ] });
if (baz === undefined) {
  throw new Error('--baz must be passed explicitely');
}

With modern JS syntaxes like destructuring, default values and the nullish coalescing operator (??), I don't see much use default values being handled by getopts in the future.

In the meantime, a simple option like builtInDefaults: false would be good enough to cover my use case ๐Ÿ™‚

What's your opinion on this?

Returning the "cooked" args

I want to use getopts in pnpm. We currently use nopt (like npm) and it returns the cooked (expanded) args

We'd need this to be returned by getopts to make the switch.

getOpts(['install', '-P'], {alias: {P: 'save-prod'}})
> {_: ['install'], 'save-prod': true, cooked: ['install', '--save-prod']}

would it be ok to have this feature in getopts or should there be some mapping outside of getopts to get the cooked args?

Is the current API repetitive?

No intentions to release a new major soon, but I'd like to leave the question here lest I forget.

Don't you feel the current API is repetitive? We could trade less repetition for some verbosity:

getopts(process.argv.slice(2), {
  options: {
    foo: {
      alias: ["f", "F"],
      boolean: true,
      default: false
    },
    bar: {
      default: 42
    },
    baz: {
      alias: ["B", "bazmut"],
      string: true,
      default: "hello"
    }
  },
  stopEarly: true
})

For comparison, here's how you'd do it using the current API:

getopts(process.argv.slice(2), {
  alias: {
    foo: ["f", "F"],
    baz: ["B", "bazmut"]
  },
  default: {
    foo: false,
    bar: 42,
    baz: "hello"
  },
  boolean: ["foo"],
  stopEarly: true
})

Question: Is this package vulnerable to prototype pollution?

Popular argument parsing libraries such as minimist and yargs-parser have been hit by prototype pollution before. Is this library affected as well?

It seems that this is related to the "parse dot options as object" feature, so I suspect the answer is no. However, I'd like to ask just in case.

Also, I see multiple uses of for...in loops in the code. While it would not directly cause prototype pollution, would it be affected by objects that have inherited enumerable properties?

See also:

Option to remove aliases from the resulting object

Problem
After parsing arguments with getopts, there is usually some validation to perform. Validation libraries such as joi or ow can validate the entire resulting object. However, that validation gets harder since the resulting object includes the aliases' values.

Example:

// example.js
const getopts = require( 'getopts' );
const args = getopts(
    process.argv.splice(2),
    {
        alias: { maxRandomSize: [ "max-random-size", "s" ] }
    }
);
// node example.js -s 50
console.log( args ); // { _: [], s: 50, maxRandomSize: 50, 'max-random-size': 50 }

Proposed Solution
A boolean option such as removeAliases or hideAliases.

Example:

// example.js
const getopts = require( 'getopts' );
const args = getopts(
    process.argv.splice(2),
    {
        alias: { maxRandomSize: [ "max-random-size", "s" ] },
        removeAliases: true
    }
);
// node example.js -s 50
console.log( args ); // { _: [], maxRandomSize: 50 }

Current workaround

// example.js
const getopts = require( 'getopts' );
const aliases = {
	maxRandomSize: [ "max-random-size", "s" ],
	help: 'h'
};
const args = getopts(
    process.argv.splice(2),
    {
        alias: aliases
    }
);

for ( const k of Object.keys( aliases ) ) {
	const v = aliases[ k ];
	if ( 'string' === typeof v ) {
		delete args[ v ];
		continue;
	}	
	for ( const e of ( v || [] ) ) {
		delete args[ e ];
	}
}
// node example.js -s 50 -h
console.log( args ); // { _: [], maxRandomSize: 50, help: true }

Boolean options should count as known when not in argv

If an option is not present in argv, it's unknown unless it's defined in opts.default. This behavior should be true of opts.boolean (and opts.string #35).

In other words, the following is expected:

getopts([], { boolean: ["a"] }) //=> { _:[], a:false }

...while the current actual result is { _:[] }

Proper key for unknown arguments

Problem
After parsing arguments with getopts, there is usually some validation to perform. Validation libraries such as joi or ow can validate the entire resulting object. However, that validation gets harder since the resulting object includes all the unknown arguments.

Separating unknown arguments currently requires something like this:

Example:

// example.js
const getopts = require( 'getopts' );
const unexpected = {};
const args = getopts(
    process.argv.splice(2),
    {
        default: { good: 10 },
	unknown: k => unexpected[ k ] = 1
    }
);
// node example.js --bad 20
console.log( args ); // { _: [], good: 10, bad: 20 }
for ( const k of Object.keys( args ) ) {
	if ( unexpected[ k ] ) {
		unexpected[ k ] = args[ k ];
		delete args[ k ];
	}
}
console.log( args ); // { _: [], good: 10 }
console.log( unexpected ); // { bad: 20 }

Proposed Solution
Use a key such as __ (double underscore) for unknown arguments.

Example:

// example.js
const getopts = require( 'getopts' );
const args = getopts(
    process.argv.splice(2),
    {
        default: { good: 10 }
    }
);
// node example.js --bad 20
console.log( args ); // { _: [], __: { bad: 20 }, good: 10 }

Use Testmatrix

Switch to a declarative testing style where tests are represented as arrays of objects. See testmatrix.

exports.default = {
  "long options": [
    {
      name: "without value (boolean)",
      argv: ["--foo"],
      expected: {
        _: [],
        foo: true
      }
    },
    // ...
  ]
}

Instead of using the imperative approach common of tape/mocha/AVA, etc.

const test = require("tape")
const getopts = require("..")

test("long options", t => {
  t.deepEqual(getopts(["--foo"]), {
    _: [],
    foo: true
  }, "without value (boolean)")
  
  // ...

  t.end()
})

ESM imports TypeScript types

If I understand correctly, the TypeScript types are for the CommonJS version of the code. When using "module": "ES2022" in tsconfig.json with allowSyntheticDefaultImports: false, importing getopts fails unless the type definitions are changed from:

export = getopts

to

export default getopts

It might be possible to resolve this by providing different type definitions for CommonJS and ESM (different on only this one line) and using an extension to package.json that TypeScript 4.7 added (link) that lets TypeScript pick between type definition files based on the project's module type.

Alternatively, enabling allowSyntheticDefaultImports for the project seems to fix it, too.

ESM imports support

Would be cool to have an ESM version of getopts, so we can use named imports in Node with this module.

Boolean "swallows" values

Boolean options are considering any value as true. For example, let's consider an application that runs scripts by default and the user can filter the script to run:

{
  alias: { scriptFile: '--script-file' }, // filter the script files to run
  boolean: [ 'run' ], // indented to apply `--no-run` to avoid running script files
  default: { run: true }
}

A user then make a mistake an type the following for filtering the script to run:

node example.js --run=my-script.js

That only gives { "_": [], "run": true }, that is, when I validate run for reporting the invalid syntax, its value is true and getopts lacks the incorrect value (my-script.js) - so I can't detect and report it. That makes the application run without the user knowing about the problem (and the filter will not be applied...).

Wouldn't be a better approach to assign the received value, in order to be possible to validate it? It would still pass an if test (as true).

Support `stopEarly`

Hello,

It would be nice to support a stopEarly option, similar to minimist which would stop parsing at the first non-option argument.

This is especially useful when defining subcommands in a CLI:

function foo (args) {
  // handle foo options
  const options = getopts(args)
}

function main () {
  const options = getopts(process.argv.slice(2), {
    stopEarly: true,
  })

  const [subcommand, subargs] = options._
  if (subcommand === 'foo') {
    return foo(subargs)
  }
}

Built-in TypeScript support and autogeneration of arguments, based on params

  1. Add TypeScript typings
  2. Autogenerate arguments typings (so-called type inference) based on the supplied params.
A similar solution with yargs

This feature should be built-in for type safety.

export const builder = {
  dynamodb: {
    type: `boolean`,
    default: false,
    describe: `Set up local dynamodb instance`,
  },
  clearPorts: {
    type: `array`,
    default: [],
    describe: `Remove processes from the specified Ports Number[]`,
  },
} as const
type builder = typeof builder
type cmds = keyof builder
type getType<key extends cmds> = builder[key]["type"] extends "string"
  ? string
  : builder[key]["type"] extends "boolean"
  ? boolean
  : builder[key]["type"] extends "number"
  ? number
  : builder[key]["type"] extends "array"
  ? any[]
  : unknown
export type yargsArguments = {
  [key in cmds]: getType<key>
}
Screen Shot 2021-06-07 at 9 13 15 AM

Add options.string

Add a new string option to string type coerce flag/options.

getopts(["-vcv"], { string: ["c"] }) //=> { v: true, c: "v" }

For comparison, here is the default behavior without { string: ["c"] }

getopts(["-vcv"]) //=> { v: [true, true], c: true }

Implemented properly this should only slow us down ever so slightly.

Question about short options

  • -w9 results in {"w": 9}
  • Shouldn't -w=9 result in "w":9 ? It gives {"w": "=9"}
  • Is it a library option or some kind of convention?

Users usually expect that a short option behaves like a long one. Example:

  • --script-file="f1.js,f2.js" โ†’ {"script-file": "f1.js,f2.js"}
  • -F="f1.js,f2.js" โ†’ {"F": "=f1.js,f2.js"} (= is added to the content)

Users are obligated to use a space (-F "f1.js,f2.js") to have the same effect. Isn't it an inconsistency in the expected interface? I mean, wouldn't be better to detect the equal sign as a value separator for short options?

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.