235 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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-<whatever> 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);
 | |
| 	});
 | |
| }
 |