Giter VIP home page Giter VIP logo

mri's Introduction

mri CI licenses

Quickly scan for CLI flags and arguments

This is a fast and lightweight alternative to minimist and yargs-parser.

It only exists because I find that I usually don't need most of what minimist and yargs-parser have to offer. However, mri is similar enough that it might function as a "drop-in replacement" for you, too!

See Comparisons for more info.

Install

$ npm install --save mri

Usage

$ demo-cli --foo --bar=baz -mtv -- hello world
const mri = require('mri');

const argv = process.argv.slice(2);

mri(argv);
//=> { _: ['hello', 'world'], foo:true, bar:'baz', m:true, t:true, v:true }

mri(argv, { boolean:['bar'] });
//=> { _: ['baz', 'hello', 'world'], foo:true, bar:true, m:true, t:true, v:true }

mri(argv, {
  alias: {
    b: 'bar',
    foo: ['f', 'fuz']
  }
});
//=> { _: ['hello', 'world'], foo:true, f:true, fuz:true, b:'baz', bar:'baz', m:true, t:true, v:true }

API

mri(args, options)

Return: Object

args

Type: Array
Default: []

An array of arguments to parse. For CLI usage, send process.argv.slice(2). See process.argv for info.

options.alias

Type: Object
Default: {}

An object of keys whose values are Strings or Array<String> of aliases. These will be added to the parsed output with matching values.

options.boolean

Type: Array|String
Default: []

A single key (or array of keys) that should be parsed as Booleans.

options.default

Type: Object
Default: {}

An key:value object of defaults. If a default is provided for a key, its type (typeof) will be used to cast parsed arguments.

mri(['--foo', 'bar']);
//=> { _:[], foo:'bar' }

mri(['--foo', 'bar'], {
  default: { foo:true, baz:'hello', bat:42 }
});
//=> { _:['bar'], foo:true, baz:'hello', bat:42 }

Note: Because --foo has a default of true, its output is cast to a Boolean. This means that foo=true, making 'bar' an extra argument (_ key).

options.string

Type: Array|String
Default: []

A single key (or array of keys) that should be parsed as Strings.

options.unknown

Type: Function
Default: undefined

Callback that is run when a parsed flag has not been defined as a known key or alias. Its only parameter is the unknown flag itself; eg --foobar or -f.

Once an unknown flag is encountered, parsing will terminate, regardless of your return value.

Note: mri only checks for unknown flags if options.unknown and options.alias are populated. Otherwise, everything will be accepted.

Comparisons

minimist

  • mri is 5x faster (see benchmarks)
  • Numerical values are cast as Numbers when possible
    • A key (and its aliases) will always honor opts.boolean or opts.string
  • Short flag groups are treated as Booleans by default:
    minimist(['-abc', 'hello']);
    //=> { _:[], a:'', b:'', c:'hello' }
    
    mri(['-abc', 'hello']);
    //=> { _:[], a:true, b:true, c:'hello' }
  • The opts.unknown behaves differently:
    • Unlike minimist, mri will not continue continue parsing after encountering an unknown flag
  • Missing options:
    • opts.stopEarly
    • opts['--']
  • Ignores newlines (\n) within args (see test)
  • Ignores slashBreaks within args (see test)
  • Ignores dot-nested flags (see test)

yargs-parser

  • mri is 40x faster (see benchmarks)
  • Numerical values are cast as Numbers when possible
    • A key (and its aliases) will always honor opts.boolean or opts.string
  • Missing options:
    • opts.array
    • opts.config
    • opts.coerce
    • opts.count
    • opts.envPrefix
    • opts.narg
    • opts.normalize
    • opts.configuration
    • opts.number
    • opts['--']
  • Missing parser.detailed() method
  • No additional configuration object
  • Added options.unknown feature

Benchmarks

Running Node.js v10.13.0

Load Times:
  nopt          3.179ms
  yargs-parser  2.137ms
  minimist      0.746ms
  mri           0.517ms

Benchmark:
  minimist      x    328,747 ops/sec ±1.09% (89 runs sampled)
  mri           x  1,622,801 ops/sec ±0.94% (92 runs sampled)
  nopt          x    888,223 ops/sec ±0.22% (92 runs sampled)
  yargs-parser  x     30,538 ops/sec ±0.81% (91 runs sampled)

License

MIT © Luke Edwards

mri's People

Contributors

jimmyolo avatar leo avatar lukeed avatar maraisr 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

mri's Issues

Open to native types?

Currently consumers consume the types from @types/mri — are you open to shipping types natively and or a typescript re-write?

Unknown handling seems wrong

It's a little odd that the unknown function requires that aliases be defined to match defined flags. It means that exact matches "fail", but because of the way the parser works, prefixed flags do not. Example:

const mri = require('mri')
const opts = { boolean: ['check'], unknown: (arg) => console.log('unknown arg:', arg) }

// this hits the unknown handler
mri(['--check'], opts)

// this just works
const args = mri(['--no-check'], opts)

I'm curious why the alias requirement exists. Why not just continue if there's an exact flag match?

arguments containing hyphens do not parse correctly

description

when passing options with hyphens mri behaves really weirdly

reproduce

code

import mri from "mri"

console.log(mri([ "-n" ], { alias: { n: "no-install" }}))
console.log(mri([ "--no-install" ], { alias: { n: "no-install" }}))

logs

$ node .
{ _: [], n: true, 'no-install': true }
{ _: [], install: false }

expectation

$ node .
{ _: [], n: true, 'no-install': true }
{ _: [], n: true, 'no-install': true}

parsing of strings containing flag notation

#!/usr/bin/env node
const mri = require('mri')
console.log(process.argv)
console.log(mri(process.argv.slice(2)))
./args.js -a -b '-c foo'
[
  '/usr/local/Cellar/node/16.0.0_1/bin/node',
  '/Users/j/web/pev2-cli/foo.js',
  '-a',
  '-b',
  '-c foo'
]
{
  _: [],
  a: true,
  b: true,
  c: true,
  ' ': true,
  f: true,
  o: [ true, true ]
}

Is that intended?

I would have expected this:

{
  _: ['-c foo'],
  a: true,
  b: true
}

Or maybe this (but I find the result above much more intuitive):

{
  _: [],
  a: true,
  b: true,
  c: 'foo'
}

with aliases, multiple values for the same flag don't work

I'm using [email protected].

const headerAliased = {alias: {H: 'header'}}

mri(['-H', 'one', '--header=two'])
// as expected: { _: [], H: 'one', header: 'two' }
mri(['-H', 'one', '--header', 'two'])
// as expected: { _: [], H: 'one', header: 'two' }
mri(['-H', 'one', '-H', 'two'], headerAliased)
// as expected: { _: [], H: [ 'one', 'two' ], header: [ 'one', 'two' ] }
mri(['--header', 'one', '--header', 'two'], headerAliased)
// as expected: { _: [], header: [ 'one', 'two' ], H: [ 'one', 'two' ] }

mri(['-H', 'one', '--header=two'], headerAliased)
// not expected: { _: [], H: 'one', header: 'one' }
mri(['-H', 'one', '--header', 'two'], headerAliased)
// not expected: { _: [], H: 'one', header: 'one' }

Number parser does not include 0!

const mri = require("mri")

console.log(mri(['--bar= 132568])) // { _: [], bar: 132568 }  TRUE   
console.log(mri(['--bar= 0132568])) // { _: [], bar: 132568 }  FALSE
console.log(mri(['--bar= "0132568"])) // { _: [], bar: 132568 } FALSE

as an example, number zero does not include in, even when I put a number in double quotations, mri convert the string to a number and this converting omit zero!

inconsistent parsing of numeric arguments

// x.js
import minimist from "minimist"
import mri from "mri"

const OPTIONS = { boolean: [ 'flag' ], array: [ { key: '_', number: true } ] }

console.log('minimist', minimist(process.argv.slice(2), OPTIONS))

console.log('mri', mri(process.argv.slice(2), OPTIONS))
$ node x.js 10 20 xx 40
minimist { _: [ 10, 20, 'xx', 40 ], flag: false }
mri { _: [ '10', '20', 'xx', '40' ] }
$ node x.js --flag 10 20 xx 40
minimist { _: [ 10, 20, 'xx', 40 ], flag: true }
mri { _: [ 10, '20', 'xx', '40' ], flag: true }
$ node x.js 10 --flag 20 xx 40
minimist { _: [ 10, 20, 'xx', 40 ], flag: true }
mri { _: [ '10', 20, 'xx', '40' ], flag: true }

It appears mri converts a single argument after a flag to number. I would expect it to convert all (like minimist) or none.

FR Doc: Explanation why speed is important; docs on replacing other parsers

Hi Luke, I'm pretty naive in the node.js and all the surrounding infrastructure (I have been programming mostly in C++, Haskell, Ocaml, C#, F# and Bash during the last 20 years); my node.js experience is limited to building a single production typescript app, albeit not quite a helloworld—integrating GCB build with a BitBucket.org repo to run configurable matrix CI pipelines and drop artifacts on premises, so there is actually more than a single app). I certainly liked the language, especially with TS type support crutch. I am now looking for a solid high-level cli parser for my next project.

What seems missing from the documentation (which may possibly only seem due to my naivete) is the reason why it is important to parse command line quickly. Frankly, seeing the benchmarks I started to worry, after a negative experience with Python's slowness (the language has got a very apt name!); but node's V8 engine surprised me with its speed. I never thought of a CLI parser as a speed bottleneck.

My eyes are currently at the https://github.com/yargs/yargs CLI framework, although I'm going to eval a few. I admit I cannot put the benchmark in context, because it does not define what an "op" is; if you could explain it better (e.g., how many Kops/Mops does it take to parse an averagely complex exemplar Git or Docker command line on a random modern box), but the ×40 perf difference is indeed impressive! What is hard to grok is how it relates to the real world applications and absolute time. 100ms vs 4s is indeed a huge difference (4s is a show-stopper), 10ms vs 400ms is a nuisance to do away with, but 1ms vs 40ms is rather in the "who cares" range.

Also, it would be great if you added a bit of instructions on replacing the https://github.com/yargs/yargs-parser with your one in the yargs context, if that's doable.

Thank you! And, of course, all this probably doesn't make sense if that's a basic knowledge among node.js programmers; I'm certainly not one.

Boolean defaults are parsed incorrectly

Any command that can receive optional parameters and no actual arguments, receives them as integers. Here is a simple test case where the action handler will always receive a number as a passed value although none have been passed. I'm not sure why it's a number instead of undefined or null.

const sade = require("sade");
const assert = require("assert");

var prog = sade("microbundle");
prog
  .option("--raw", "", false)
  .command("build [...entries]", "", {
    default: true
  })
  .action(str => {
    // ERROR: `str` will always be `1`
    assert.notEqual(str, 1);
  });

prog.parse(["", "", "build", "--raw"]);

Number parser does not parse "0"

var mri = require("mri")

console.log(mri(['--bar=1'])) // { _: [], bar: 1 }
console.log(mri(['--bar=0'])) // { _: [], bar: '0' }

Expected behavior:

Either add explicit number parsing, or fix the toNum function here to handle parsing the string '0'

Default value type not applied to aliases

Hey Luke 👋

Related: lukeed/sade#17

Was using sade in a project where I wanted an option to be coerced to a string always, so I specified that a default value of "". However I noticed that when using the alias of the option, the value was not turned into a string (repo below). For me, the use case is for simple file matching, where some file names may begin with a number. So the option may be passed a value of "01" and I want that as a string, not the number 1.

I think lukeed/sade#17 is related to what I want here, but seeing as how that issue is old and closed without discussion I thought maybe opening a new issue focused around the use case would be helpful. Apologies if I thought wrong here 😬

Repo

test.js:

const sade = require("sade"); // v1.7.3

// Using `isOne` just to simplify this program
const { args } = sade("test", true)
  .option("-a --arg", "Test option", "")
  .parse(process.argv, { lazy: true });

console.log(args);

Output with --arg:

$ node .\test.js --arg 01
[ { _: [], arg: '01', a: '01' } ]

Output with -a alias:
(expected to be the same as --arg output)

$ node .\test.js -a 01   
[ { _: [], a: 1, arg: 1 } ]

If you think this is something that should/can be fixed, happy to open a PR. My assumption is that we need to pass defaults for option names and aliases to mri?

Thanks!

P.S. Love all awesome libraries you build - thanks for giving these to us 😁

null default arguments cause an error

I encountered this error in microbundle, which uses mri via sade. One of microbundle's arguments is a boolean, but the default is null because the actual default depends on other arguments (--compress defaults to true when targetting web, and false when targetting node). A change introduced in [email protected] causes parsing to break with the error:

Cannot read property 'push' of undefined

...which is being thrown here. Presumably this is because it only accepts, strings numbers and booleans. I don't know if null is a valid value for defaults, but it seems like this is a regression.

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.