1module.exports = function (args, opts) { 2 if (!opts) opts = {}; 3 4 var flags = { bools : {}, strings : {}, unknownFn: null }; 5 6 if (typeof opts['unknown'] === 'function') { 7 flags.unknownFn = opts['unknown']; 8 } 9 10 if (typeof opts['boolean'] === 'boolean' && opts['boolean']) { 11 flags.allBools = true; 12 } else { 13 [].concat(opts['boolean']).filter(Boolean).forEach(function (key) { 14 flags.bools[key] = true; 15 }); 16 } 17 18 var aliases = {}; 19 Object.keys(opts.alias || {}).forEach(function (key) { 20 aliases[key] = [].concat(opts.alias[key]); 21 aliases[key].forEach(function (x) { 22 aliases[x] = [key].concat(aliases[key].filter(function (y) { 23 return x !== y; 24 })); 25 }); 26 }); 27 28 [].concat(opts.string).filter(Boolean).forEach(function (key) { 29 flags.strings[key] = true; 30 if (aliases[key]) { 31 flags.strings[aliases[key]] = true; 32 } 33 }); 34 35 var defaults = opts['default'] || {}; 36 37 var argv = { _ : [] }; 38 Object.keys(flags.bools).forEach(function (key) { 39 setArg(key, defaults[key] === undefined ? false : defaults[key]); 40 }); 41 42 var notFlags = []; 43 44 if (args.indexOf('--') !== -1) { 45 notFlags = args.slice(args.indexOf('--')+1); 46 args = args.slice(0, args.indexOf('--')); 47 } 48 49 function argDefined(key, arg) { 50 return (flags.allBools && /^--[^=]+$/.test(arg)) || 51 flags.strings[key] || flags.bools[key] || aliases[key]; 52 } 53 54 function setArg (key, val, arg) { 55 if (arg && flags.unknownFn && !argDefined(key, arg)) { 56 if (flags.unknownFn(arg) === false) return; 57 } 58 59 var value = !flags.strings[key] && isNumber(val) 60 ? Number(val) : val 61 ; 62 setKey(argv, key.split('.'), value); 63 64 (aliases[key] || []).forEach(function (x) { 65 setKey(argv, x.split('.'), value); 66 }); 67 } 68 69 function setKey (obj, keys, value) { 70 var o = obj; 71 for (var i = 0; i < keys.length-1; i++) { 72 var key = keys[i]; 73 if (key === '__proto__') return; 74 if (o[key] === undefined) o[key] = {}; 75 if (o[key] === Object.prototype || o[key] === Number.prototype 76 || o[key] === String.prototype) o[key] = {}; 77 if (o[key] === Array.prototype) o[key] = []; 78 o = o[key]; 79 } 80 81 var key = keys[keys.length - 1]; 82 if (key === '__proto__') return; 83 if (o === Object.prototype || o === Number.prototype 84 || o === String.prototype) o = {}; 85 if (o === Array.prototype) o = []; 86 if (o[key] === undefined || flags.bools[key] || typeof o[key] === 'boolean') { 87 o[key] = value; 88 } 89 else if (Array.isArray(o[key])) { 90 o[key].push(value); 91 } 92 else { 93 o[key] = [ o[key], value ]; 94 } 95 } 96 97 function aliasIsBoolean(key) { 98 return aliases[key].some(function (x) { 99 return flags.bools[x]; 100 }); 101 } 102 103 for (var i = 0; i < args.length; i++) { 104 var arg = args[i]; 105 106 if (/^--.+=/.test(arg)) { 107 // Using [\s\S] instead of . because js doesn't support the 108 // 'dotall' regex modifier. See: 109 // http://stackoverflow.com/a/1068308/13216 110 var m = arg.match(/^--([^=]+)=([\s\S]*)$/); 111 var key = m[1]; 112 var value = m[2]; 113 if (flags.bools[key]) { 114 value = value !== 'false'; 115 } 116 setArg(key, value, arg); 117 } 118 else if (/^--no-.+/.test(arg)) { 119 var key = arg.match(/^--no-(.+)/)[1]; 120 setArg(key, false, arg); 121 } 122 else if (/^--.+/.test(arg)) { 123 var key = arg.match(/^--(.+)/)[1]; 124 var next = args[i + 1]; 125 if (next !== undefined && !/^-/.test(next) 126 && !flags.bools[key] 127 && !flags.allBools 128 && (aliases[key] ? !aliasIsBoolean(key) : true)) { 129 setArg(key, next, arg); 130 i++; 131 } 132 else if (/^(true|false)$/.test(next)) { 133 setArg(key, next === 'true', arg); 134 i++; 135 } 136 else { 137 setArg(key, flags.strings[key] ? '' : true, arg); 138 } 139 } 140 else if (/^-[^-]+/.test(arg)) { 141 var letters = arg.slice(1,-1).split(''); 142 143 var broken = false; 144 for (var j = 0; j < letters.length; j++) { 145 var next = arg.slice(j+2); 146 147 if (next === '-') { 148 setArg(letters[j], next, arg) 149 continue; 150 } 151 152 if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) { 153 setArg(letters[j], next.split('=')[1], arg); 154 broken = true; 155 break; 156 } 157 158 if (/[A-Za-z]/.test(letters[j]) 159 && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { 160 setArg(letters[j], next, arg); 161 broken = true; 162 break; 163 } 164 165 if (letters[j+1] && letters[j+1].match(/\W/)) { 166 setArg(letters[j], arg.slice(j+2), arg); 167 broken = true; 168 break; 169 } 170 else { 171 setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg); 172 } 173 } 174 175 var key = arg.slice(-1)[0]; 176 if (!broken && key !== '-') { 177 if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1]) 178 && !flags.bools[key] 179 && (aliases[key] ? !aliasIsBoolean(key) : true)) { 180 setArg(key, args[i+1], arg); 181 i++; 182 } 183 else if (args[i+1] && /^(true|false)$/.test(args[i+1])) { 184 setArg(key, args[i+1] === 'true', arg); 185 i++; 186 } 187 else { 188 setArg(key, flags.strings[key] ? '' : true, arg); 189 } 190 } 191 } 192 else { 193 if (!flags.unknownFn || flags.unknownFn(arg) !== false) { 194 argv._.push( 195 flags.strings['_'] || !isNumber(arg) ? arg : Number(arg) 196 ); 197 } 198 if (opts.stopEarly) { 199 argv._.push.apply(argv._, args.slice(i + 1)); 200 break; 201 } 202 } 203 } 204 205 Object.keys(defaults).forEach(function (key) { 206 if (!hasKey(argv, key.split('.'))) { 207 setKey(argv, key.split('.'), defaults[key]); 208 209 (aliases[key] || []).forEach(function (x) { 210 setKey(argv, x.split('.'), defaults[key]); 211 }); 212 } 213 }); 214 215 if (opts['--']) { 216 argv['--'] = new Array(); 217 notFlags.forEach(function(key) { 218 argv['--'].push(key); 219 }); 220 } 221 else { 222 notFlags.forEach(function(key) { 223 argv._.push(key); 224 }); 225 } 226 227 return argv; 228}; 229 230function hasKey (obj, keys) { 231 var o = obj; 232 keys.slice(0,-1).forEach(function (key) { 233 o = (o[key] || {}); 234 }); 235 236 var key = keys[keys.length - 1]; 237 return key in o; 238} 239 240function isNumber (x) { 241 if (typeof x === 'number') return true; 242 if (/^0x[0-9a-f]+$/i.test(x)) return true; 243 return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x); 244} 245 246