var path = require('path'); var fs = require('fs'); module.exports = function (args, opts) { if (!opts) opts = {}; var flags = { bools : {}, strings : {}, counts: {}, normalize: {}, configs: {} }; [].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; }); [].concat(opts.normalize).filter(Boolean).forEach(function (key) { flags.normalize[key] = true; }); [].concat(opts.config).filter(Boolean).forEach(function (key) { flags.configs[key] = true; }); function toCamelCase(str) { return str.split('-').map(function(word, i) { return (i ? word[0].toUpperCase() + word.slice(1) : word); }).join(''); } var aliases = {}, newAliases = {}; Object.keys(opts.alias || {}).forEach(function (key) { aliases[key] = [].concat(opts.alias[key]); // For "--option-name", also set argv.optionName aliases[key].concat(key).forEach(function (x) { if (/-/.test(x)) { var c = toCamelCase(x); aliases[key].push(c); newAliases[c] = true; } }); aliases[key].forEach(function (x) { aliases[x] = [key].concat(aliases[key].filter(function (y) { return x !== y; })); }); }); var defaults = opts['default'] || {}; Object.keys(defaults || {}).forEach(function (key) { if (/-/.test(key) && !opts.alias[key]) { var c = toCamelCase(key); aliases[key] = aliases[key] || []; // don't allow the same key to be added multiple times. if (aliases[key].indexOf(c) === -1) { aliases[key] = (aliases[key] || []).concat(c); newAliases[c] = true; } } (aliases[key] || []).forEach(function (alias) { defaults[alias] = defaults[key]; }); }); 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) { if (/-/.test(key) && !(aliases[key] && aliases[key].length)) { var c = toCamelCase(key); aliases[key] = [c]; newAliases[c] = true; } 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; }; } if (flags.configs[key]) { try { var config = JSON.parse(fs.readFileSync(val, 'utf8')); Object.keys(config).forEach(function (key) { setArg(key, config[key]); }); } catch (ex) { console.error('Invalid JSON config file: ' + val); throw ex; } } setKey(argv, key.split('.'), value); (aliases[key] || []).forEach(function (x) { setKey(argv, x.split('.'), value); }); var keys = [key].concat(aliases[key] || []); for (var i = 0, l = keys.length; i < l; i++) { if (flags.normalize[keys[i]]) { keys.forEach(function(key) { argv.__defineSetter__(key, function(v) { val = path.normalize(v); }); argv.__defineGetter__(key, function () { return typeof val === 'string' ? path.normalize(val) : val; }); }); break; } } } 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: argv, aliases: aliases, newAliases: newAliases }; }; 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 })); }