Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for strict mode #41

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions .editorconfig
@@ -0,0 +1,11 @@
# EditorConfig is awesome:
http://EditorConfig.org

# top-most EditorConfig file
root = true

[*.js]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
87 changes: 81 additions & 6 deletions index.js
Expand Up @@ -25,6 +25,8 @@ module.exports = function (args, opts) {

var flags = {
bools: {},
// known: {},
numbers: {},
strings: {},
unknownFn: null,
};
Expand Down Expand Up @@ -64,6 +66,7 @@ module.exports = function (args, opts) {
});
});

// populating flags.strings with explicit keys and aliases
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
Expand All @@ -73,15 +76,36 @@ module.exports = function (args, opts) {
}
});

// populating flags.numbers with explicit keys and aliases
[].concat(opts.number).filter(Boolean).forEach(function (key) {
flags.numbers[key] = true;
if (aliases[key]) {
[].concat(aliases[key]).forEach(function (k) {
flags.numbers[k] = true;
});
}
});

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

var defaults = opts.default || {};

var argv = { _: [] };

function argDefined(key, arg) {
return (flags.allBools && (/^--[^=]+$/).test(arg))
|| flags.strings[key]
function keyDefined(key) {
return flags.strings[key]
|| flags.numbers[key]
|| flags.bools[key]
|| aliases[key];
// || flags.known[key];
}

function argDefined(key, arg) {
// legacy test for whether to call unknownFn
return (flags.allBools && (/^--[^=]+$/).test(arg))
|| keyDefined(key);
}

function setKey(obj, keys, value) {
Expand Down Expand Up @@ -120,14 +144,42 @@ module.exports = function (args, opts) {
}
}

function checkStrictVal(key, val) {
// Have a separate routine from setArg to avoid affecting non-strict results,
// as the strict checks need less processed values.
if (opts.strict) {
if (flags.strings[key] && val === true) {
throw new Error('Missing option value for option "' + key + '"');
}
if (flags.numbers[key] && !(isNumber(val) || val === false)) {
throw new Error('Expecting number value for option "' + key + '"');
}
if (isBooleanKey(key) && typeof val === 'string' && !(/^(true|false)$/).test(val)) {
throw new Error('Unexpected option value for option "' + key + '"');
}
if (!keyDefined(key)) {
throw new Error('Unknown option "' + key + '"');
}
}
}

function setArg(key, val, arg) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg) === false) { return; }
}

var value = !flags.strings[key] && isNumber(val)
? Number(val)
: val;
var value = val;
if (flags.numbers[key]) {
if (isNumber(val)) {
value = Number(val);
} else if (value === false) {
value = val; // --no-foo
} else {
value = NaN;
}
} else if (!flags.strings[key] && isNumber(val)) {
value = Number(val);
}
setKey(argv, key.split('.'), value);

(aliases[key] || []).forEach(function (x) {
Expand Down Expand Up @@ -162,12 +214,14 @@ module.exports = function (args, opts) {
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
key = m[1];
var value = m[2];
checkStrictVal(key, value);
if (isBooleanKey(key)) {
value = value !== 'false';
}
setArg(key, value, arg);
} else if ((/^--no-.+/).test(arg)) {
key = arg.match(/^--no-(.+)/)[1];
checkStrictVal(key, false);
setArg(key, false, arg);
} else if ((/^--.+/).test(arg)) {
key = arg.match(/^--(.+)/)[1];
Expand All @@ -178,12 +232,20 @@ module.exports = function (args, opts) {
&& !isBooleanKey(key)
&& !flags.allBools
) {
checkStrictVal(key, next);
setArg(key, next, arg);
i += 1;
} else if ((/^(true|false)$/).test(next)) {
checkStrictVal(key, next);
setArg(key, next === 'true', arg);
i += 1;
} else if (flags.numbers[key] && isNumber(next)) {
// This is a second look to pick up negative numbers.
checkStrictVal(key, next);
setArg(key, next, arg);
i += 1;
} else {
checkStrictVal(key, true);
setArg(key, flags.strings[key] ? '' : true, arg);
}
} else if ((/^-[^-]+/).test(arg)) {
Expand All @@ -194,11 +256,13 @@ module.exports = function (args, opts) {
next = arg.slice(j + 2);

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

if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
checkStrictVal(letters[j], next.slice(1));
setArg(letters[j], next.slice(1), arg);
broken = true;
break;
Expand All @@ -208,16 +272,19 @@ module.exports = function (args, opts) {
(/[A-Za-z]/).test(letters[j])
&& (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
) {
checkStrictVal(letters[j], next);
setArg(letters[j], next, arg);
broken = true;
break;
}

if (letters[j + 1] && letters[j + 1].match(/\W/)) {
checkStrictVal(letters[j], arg.slice(j + 2));
setArg(letters[j], arg.slice(j + 2), arg);
broken = true;
break;
} else {
checkStrictVal(letters[j], true);
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
}
}
Expand All @@ -229,12 +296,20 @@ module.exports = function (args, opts) {
&& !(/^(-|--)[^-]/).test(args[i + 1])
&& !isBooleanKey(key)
) {
checkStrictVal(key, args[i + 1]);
setArg(key, args[i + 1], arg);
i += 1;
} else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
checkStrictVal(key, args[i + 1]);
setArg(key, args[i + 1] === 'true', arg);
i += 1;
} else if (flags.numbers[key] && isNumber(args[i + 1])) {
// This is a second look to pick up negative numbers.
checkStrictVal(key, args[i + 1]);
setArg(key, args[i + 1], arg);
i += 1;
} else {
checkStrictVal(key, true);
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
Expand Down
49 changes: 49 additions & 0 deletions test/bool.js
Expand Up @@ -143,6 +143,55 @@ test('boolean --boool=false', function (t) {
t.end();
});

test('boolean --boool=other', function (t) {
// legacy edge case
var parsed = parse(['--boool=other'], {
default: {
boool: false,
},
boolean: ['boool'],
});

t.same(parsed.boool, true);
t.end();
});

test('boolean -b=true', function (t) {
var parsed = parse(['-b=true'], {
default: {
b: false,
},
boolean: ['b'],
});

t.same(parsed.b, 'true'); // [sic] legacy behaviour
t.end();
});

test('boolean -b=false', function (t) {
var parsed = parse(['-b=false'], {
default: {
b: true,
},
boolean: ['b'],
});

t.same(parsed.b, 'false'); // [sic] legacy behaviour
t.end();
});

test('boolean -b=other', function (t) {
var parsed = parse(['-b=other'], {
default: {
b: false,
},
boolean: ['b'],
});

t.same(parsed.b, 'other'); // [sic] legacy behaviour
t.end();
});

test('boolean using something similar to true', function (t) {
var opts = { boolean: 'h' };
var result = parse(['-h', 'true.txt'], opts);
Expand Down
78 changes: 77 additions & 1 deletion test/num.js
Expand Up @@ -3,7 +3,7 @@
var parse = require('../');
var test = require('tape');

test('nums', function (t) {
test('implicit nums', function (t) {
var argv = parse([
'-x', '1234',
'-y', '5.67',
Expand Down Expand Up @@ -36,3 +36,79 @@ test('already a number', function (t) {
t.deepEqual(typeof argv._[0], 'number');
t.end();
});

test('number type: short option', function (t) {
var options = { number: 'n' };
var argv = parse(['-n', '123'], options);
t.deepEqual(argv, { n: 123, _: [] });

argv = parse(['-n', '-123'], options);
t.deepEqual(argv, { n: -123, _: [] });

argv = parse(['-n=123'], options);
t.deepEqual(argv, { n: 123, _: [] });

argv = parse(['-n', 'xyz'], options);
t.deepEqual(argv, { n: NaN, _: [] });

argv = parse(['-n=xyz'], options);
t.deepEqual(argv, { n: NaN, _: [] });

// Special case of missing argument value
argv = parse(['-n'], options);
t.deepEqual(argv, { n: NaN, _: [] });

t.end();
});

test('number type: long option', function (t) {
var options = { number: 'num' };
var argv = parse(['--num', '123'], options);
t.deepEqual(argv, { num: 123, _: [] });

argv = parse(['--num', '-123'], options);
t.deepEqual(argv, { num: -123, _: [] });

argv = parse(['--num=123'], options);
t.deepEqual(argv, { num: 123, _: [] });

argv = parse(['--num', 'xyz'], options);
t.deepEqual(argv, { num: NaN, _: [] });

argv = parse(['--num=xyz'], options);
t.deepEqual(argv, { num: NaN, _: [] });

// Special case of missing argument value
argv = parse(['--num'], options);
t.deepEqual(argv, { num: NaN, _: [] });

// Special case of negated
argv = parse(['--no-num'], options);
t.deepEqual(argv, { num: false, _: [] });

t.end();
});

test('number: alias', function (t) {
var options = { number: 'num', alias: { num: 'n' } };
var argv = parse(['-n', '123'], options);
t.deepEqual(argv, { n: 123, num: 123, _: [] });

// argv = parse(['-n', '-123'], options);
// t.deepEqual(argv, { n: -123, num: 123, _: [] });

argv = parse(['-n=123'], options);
t.deepEqual(argv, { n: 123, num: 123, _: [] });

argv = parse(['-n', 'xyz'], options);
t.deepEqual(argv, { n: NaN, num: NaN, _: [] });

argv = parse(['-n=xyz'], options);
t.deepEqual(argv, { n: NaN, num: NaN, _: [] });

// Special case of missing argument value
argv = parse(['-n'], options);
t.deepEqual(argv, { n: NaN, num: NaN, _: [] });

t.end();
});