'use strict'; var CLI = module.exports; var defaultConf; var defaultOpts; var bags = []; CLI.parse = function(conf) { var opts = (defaultOpts = {}); defaultConf = conf; Object.keys(conf).forEach(function(k) { var v = conf[k]; var aliases = v[5]; var bag; var bagName; // the name of the argument set is now the 0th argument v.unshift(k); // v[0] flagname // v[1] short flagname // v[2] description // v[3] type // v[4] default value // v[5] aliases if ('bag' === v[3]) { bag = v[0]; // 'bag-option-xxxx' => '--bag-option-' bag = '--' + bag.replace(/xxx.*/, ''); bags.push(bag); bagName = toBagName(bag.replace(/^--/, '')); opts[bagName] = {}; } if ('json' === v[3]) { bagName = toBagName(v[0].replace(/-json$/, '')); // 'bag-option-json' => 'bagOptionOpts' opts[bagName] = {}; } else if ('ignore' !== v[3] && 'undefined' !== typeof v[4]) { // set the default values (where 'undefined' is not an allowed value) opts[toCamel(k)] = v[4]; } if (!aliases) { aliases = []; } else if ('string' === typeof aliases) { aliases = aliases.split(','); } aliases.forEach(function(alias) { if (alias in conf) { throw new Error( "Cannot alias '" + alias + "' from '" + k + "': option already exists" ); } conf[alias] = v; }); }); }; CLI.main = function(cb, args) { var leftovers = []; var conf = defaultConf; var opts = defaultOpts; if (!opts) { throw new Error("you didn't call `CLI.parse(configuration)`"); } // TODO what's the existing API for this? if (!args) { args = process.argv.slice(2); } var flag; var cnf; var typ; function grab(bag) { var bagName = toBagName(bag); if (bag !== flag.slice(0, bag.length)) { return false; } console.log(bagName, toCamel(flag.slice(bag.length))); opts[bagName][toCamel(flag.slice(bag.length))] = args.shift(); return true; } while (args.length) { // take one off the top flag = args.shift(); // mind the gap if ('--' === flag) { leftovers = leftovers.concat(args); break; } // help! if ( '--help' === flag || '-h' === flag || '/?' === flag || 'help' === flag ) { printHelp(conf); process.exit(1); } // only long names are actually used if ('--' !== flag.slice(0, 2)) { console.error("Unrecognized argument '" + flag + "'"); process.exit(1); } cnf = conf[flag.slice(2)]; if (!cnf) { // look for arbitrary flags if (bags.some(grab)) { continue; } // other arbitrary args are not used console.error("Unrecognized flag '" + flag + "'"); process.exit(1); } // encourage switching to non-aliased version if (flag !== '--' + cnf[0]) { console.warn( "use of '" + flag + "' is deprecated, use '--" + cnf[0] + "' instead" ); } // look for xxx-json flags if ('json' === cnf[3]) { try { var json = JSON.parse(args.shift()); var bagName = toBagName(cnf[0].replace(/-json$/, '')); Object.keys(json).forEach(function(k) { opts[bagName][k] = json[k]; }); } catch (e) { console.error("Could not parse option '" + flag + "' as JSON:"); console.error(e.message); process.exit(1); } continue; } // set booleans, otherwise grab the next arg in line typ = cnf[3]; // TODO --no- to negate if (Boolean === typ || 'boolean' === typ) { opts[toCamel(cnf[0])] = true; continue; } opts[toCamel(cnf[0])] = args.shift(); continue; } cb(leftovers, opts); }; function toCamel(str) { return str.replace(/-([a-z0-9])/g, function(m) { return m[1].toUpperCase(); }); } function toBagName(bag) { // trim leading and trailing '-' bag = bag.replace(/^-+/g, '').replace(/-+$/g, ''); return toCamel(bag) + 'Opts'; // '--bag-option-' => bagOptionOpts } function printHelp(conf) { var flagLen = 0; var typeLen = 0; var defLen = 0; Object.keys(conf).forEach(function(k) { flagLen = Math.max(flagLen, conf[k][0].length); typeLen = Math.max(typeLen, conf[k][3].length); if ('undefined' !== typeof conf[k][4]) { defLen = Math.max( defLen, '(Default: )'.length + String(conf[k][4]).length ); } }); Object.keys(conf).forEach(function(k) { var v = conf[k]; // skip aliases if (v[0] !== k) { return; } var def = v[4]; if ('undefined' === typeof def) { def = ''; } else { def = '(default: ' + JSON.stringify(def) + ')'; } var msg = ' --' + v[0].padEnd(flagLen) + ' ' + v[3].padStart(typeLen + 1) + ' ' + (v[2] || '') + ' ' + def; /*.padStart(defLen)*/ // v[0] flagname // v[1] short flagname // v[2] description // v[3] type // v[4] default value // v[5] aliases console.info(msg); }); }