Skip to content

Commit

Permalink
Added ability to count boolean options and rolled minimist library ba…
Browse files Browse the repository at this point in the history
…ck into project.

I know that substack was interested in splitting off the main option parsing algorithm into the minimist module but I personally think that is getting to pedantic. I'm also going to roll shellQuote into yargs at some point so that yargs will understand string input as well as array input.
  • Loading branch information
Alex Ford committed Nov 22, 2013
1 parent 49f0dce commit 623dc26
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 2 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,47 @@ console.log(argv._);
(0.54,1.12)
[ 'foo', 'bar', 'baz' ]

Order now, and as a special bonus Optimist will count boolean options!
----------------------------------------------------------------------

count.js

````javascript
#!/usr/bin/env node
var argv = require('optimist')
.count('verbose')
.alias('v', 'verbose')
.argv;

VERBOSE_LEVEL = argv.verbose;

function WARN() { VERBOSE_LEVEL >= 0 && console.log.apply(console, arguments); }
function INFO() { VERBOSE_LEVEL >= 1 && console.log.apply(console, arguments); }
function DEBUG() { VERBOSE_LEVEL >= 2 && console.log.apply(console, arguments); }

WARN("Showing only important stuff");
INFO("Showing semi-mportant stuff too");
DEBUG("Extra chatty mode");
````

***
$ node count.js
Showing only important stuff

$ node count.js -v
Showing only important stuff
Showing semi-important stuff too

$ node count.js -vv
Showing only important stuff
Showing semi-important stuff too
Extra chatty mode

$ node count.js -v --verbose
Showing only important stuff
Showing semi-important stuff too
Extra chatty mode

Plus, Optimist comes with .usage() and .demand()!
-------------------------------------------------

Expand Down
15 changes: 15 additions & 0 deletions example/count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env node
var argv = require('optimist')
.count('verbose')
.alias('v', 'verbose')
.argv;

VERBOSE_LEVEL = argv.verbose;

function WARN() { VERBOSE_LEVEL >= 0 && console.log.apply(console, arguments); }
function INFO() { VERBOSE_LEVEL >= 1 && console.log.apply(console, arguments); }
function DEBUG() { VERBOSE_LEVEL >= 2 && console.log.apply(console, arguments); }

WARN("Showing only important stuff");
INFO("Showing semi-important stuff too");
DEBUG("Extra chatty mode");
13 changes: 11 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var path = require('path');
var minimist = require('minimist');
var minimist = require('./lib/minimist');
var wordwrap = require('./lib/wordwrap');

/* Hack an instance of Argv with process.argv into Argv
Expand Down Expand Up @@ -44,7 +44,8 @@ function Argv (processArgs, cwd) {
boolean: [],
string: [],
alias: {},
default: []
default: [],
count: []
};
};
self.resetOptions();
Expand Down Expand Up @@ -82,6 +83,11 @@ function Argv (processArgs, cwd) {
}
return self;
};

self.count = function(counts) {
options.count.push.apply(options.count, [].concat(counts));
return self;
};

var demanded = {};
self.demand = function (keys) {
Expand Down Expand Up @@ -163,6 +169,9 @@ function Argv (processArgs, cwd) {
if (opt.string || opt.type === 'string') {
self.string(key);
}
if (opt.count || opt.type === 'count') {
self.count(key);
}

var desc = opt.describe || opt.description || opt.desc;
if (desc) {
Expand Down
208 changes: 208 additions & 0 deletions lib/minimist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
module.exports = function (args, opts) {
if (!opts) opts = {};

var flags = { bools : {}, strings : {}, counts: {} };

[].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});

[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
});

[].concat(opts.count).filter(Boolean).forEach(function (key) {
flags.counts[key] = true;
});

var aliases = {};
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});

var defaults = opts['default'] || {};

var argv = { _ : [] };
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});

var notFlags = [];

if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--')+1);
args = args.slice(0, args.indexOf('--'));
}

function setArg (key, val) {
var value = !flags.strings[key] && isNumber(val) ? Number(val) : val;

if (flags.counts[key] || flags.counts[aliases[key]]) {
value = function(orig) { return orig !== undefined ? orig + 1 : 0; };
}

setKey(argv, key.split('.'), value);

(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}

for (var i = 0; i < args.length; i++) {
var arg = args[i];

if (arg.match(/^--.+=/)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
setArg(m[1], m[2]);
}
else if (arg.match(/^--no-.+/)) {
var key = arg.match(/^--no-(.+)/)[1];
setArg(key, false);
}
else if (arg.match(/^--.+/)) {
var key = arg.match(/^--(.+)/)[1];
var next = args[i + 1];
if (next !== undefined && !next.match(/^-/)
&& !flags.bools[key]
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
setArg(key, next);
i++;
}
else if (/^(true|false)$/.test(next)) {
setArg(key, next === 'true');
i++;
}
else {
setArg(key, true);
}
}
else if (arg.match(/^-[^-]+/)) {
var letters = arg.slice(1,-1).split('');

var broken = false;
for (var j = 0; j < letters.length; j++) {
var next = arg.slice(j+2);

if (letters[j+1] && letters[j+1] === '=') {
setArg(letters[j], arg.slice(j+3));
broken = true;
break;
}

if (next === '-') {
setArg(letters[j], next)
continue;
}

if (/[A-Za-z]/.test(letters[j])
&& /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
setArg(letters[j], next);
broken = true;
break;
}

if (letters[j+1] && letters[j+1].match(/\W/)) {
setArg(letters[j], arg.slice(j+2));
broken = true;
break;
}
else {
setArg(letters[j], true);
}
}

var key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1])
&& !flags.bools[key]
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
setArg(key, args[i+1]);
i++;
}
else if (args[i+1] && /true|false/.test(args[i+1])) {
setArg(key, args[i+1] === 'true');
i++;
}
else {
setArg(key, true);
}
}
}
else {
argv._.push(
flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
);
}
}

Object.keys(defaults).forEach(function (key) {
if (!hasKey(argv, key.split('.'))) {
setKey(argv, key.split('.'), defaults[key]);

(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[key]);
});
}
});

Object.keys(flags.counts).forEach(function (key) {
setArg(key, defaults[key]);
});

notFlags.forEach(function(key) {
argv._.push(key);
});

return argv;
};

function hasKey (obj, keys) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
o = (o[key] || {});
});

var key = keys[keys.length - 1];
return key in o;
}

function setKey (obj, keys, value) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
if (o[key] === undefined) o[key] = {};
o = o[key];
});


var key = keys[keys.length - 1];
if (typeof value === 'function') {
o[key] = value(o[key]);
}
else if (o[key] === undefined || typeof o[key] === 'boolean') {
o[key] = value;
}
else if (Array.isArray(o[key])) {
o[key].push(value);
}
else {
o[key] = [ o[key], value ];
}
}

function isNumber (x) {
if (typeof x === 'number') return true;
if (/^0x[0-9a-f]+$/i.test(x)) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x);
}

function longest (xs) {
return Math.max.apply(null, xs.map(function (x) { return x.length }));
}
25 changes: 25 additions & 0 deletions test/count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var optimist = require('../index');
var path = require('path');
var test = require('tap').test;

var $0 = 'node ./' + path.relative(process.cwd(), __filename);

test('count', function(t) {
var parsed;

parsed = optimist(['-x']).count('verbose').argv;
console.error(parsed);
t.same(parsed.verbose, 0);

parsed = optimist(['--verbose']).count('verbose').argv;
t.same(parsed.verbose, 1);

parsed = optimist(['--verbose', '--verbose']).count('verbose').argv;
t.same(parsed.verbose, 2);

// w/alias
parsed = optimist(['--verbose', '--verbose', '-v', '--verbose']).count('verbose').alias('v', 'verbose').argv;
t.same(parsed.verbose, 4);

t.end();
});

0 comments on commit 623dc26

Please sign in to comment.