all files / lib/ Option.js

93.75% Statements 75/80
95.38% Branches 62/65
100% Functions 4/4
93.75% Lines 75/80
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167                                                43×   43× 43× 43× 43× 43× 43× 43×   43×   39×       39×       39×                     46× 46×                               39×                     49× 49× 38× 38× 38× 38× 33×     33×     16× 16× 12× 12× 12× 12×               23× 23× 23× 21× 21×     13× 11× 11× 11×           28×         42× 42×    
#!/usr/bin/node
var debug = require('debug')('Option'),
	helpers = require('./Helpers'),
 
	//Error Types
	ConfigError = require('./error/config'),
	InvalidInputError = require('./error/invalid-input');
 
/**
 * Option
 *
 * Options are key/value pairs, e.g. -n myName. They can be in any order.
 *
 *  * name The name of the option
 *  * [description] Description of the option, used in help text and error messages.
 *  * short Short version of Option, usually a "-" and a single character.
 *  * [long] Long version of the Option, a "--" and multiple characters.
 *  * [type]
 *  * [length] Number of values, how many values should follow the option, -1 is unlimited
 *  * [required] If this option is required
 *
 * @param {{name, description, [shortFlag], [longFlag], [type], [length], [required]}} options
 *
 * @constructor
 */
function Option(options) {
	if (options === undefined) {
		options = {};
	}
	this.name = options.name;
	this.description = options.description;
	this.shortFlag = options.shortFlag;
	this.longFlag = options.longFlag;
	this.type = options.type || 'string';
	this.length = (options.length !== undefined) ? options.length : 1;
	this.required = (options.required !== undefined) ? options.required : true;
 
	if(this.length === 0) {
		throw new ConfigError("A length of 0 doesn't make sense for an option, use a flag instead or set required to false. Option " + this.name);
	}
	Iif (this.shortFlag != null && (this.shortFlag.length != 2 || this.shortFlag[0] !== '-')) {
		throw new InvalidInputError("The option: " + this.name + "'s short property must start with a '-' and be one more character.");
	}
 
	Iif (this.longFlag != null && (this.longFlag.length <= 2 || this.longFlag.substr(0, 2) !== '--')) {
		throw new InvalidInputError("The option: " + this.name + "'s long property must start with '--' and be one or more character");
	}
 
	if ((this.shortFlag == null || this.shortFlag.length === 0) && (this.longFlag == null || this.longFlag.length === 0)) {
		throw new InvalidInputError("The option: " + this.name + "'s must have a shortFlag or longFlag property");
	}
}
 
/**
 * Validate Type
 *
 * Very simple validation for if something is a number or a string.
 * @param {*} value
 * @returns {Number|String}
 */
Option.prototype.validateType = function (value) {
	var numberRegex
	switch (this.type.toLowerCase()) {
		case 'integer':
		case 'int':
			numberRegex = new RegExp(/^[-+]?[0-9]+$/);
			if (numberRegex.test(value)) {
				return parseInt(value);
			}
			else {
				throw new InvalidInputError("Option with " + value + " wasn't of type " + this.type);
			}
			break;
		case 'number':
		case 'float':
		case 'double':
			numberRegex = new RegExp(/^[-+]?[0-9]*([\.,][0-9]+)?$/);
			if (numberRegex.test(value)) {
				return parseFloat(value);
			}
			else {
				throw new InvalidInputError("Option with " + value + " wasn't of type " + this.type);
			}
			break;
		case 'string':
			//Anything is a string
			return value;
		default:
			return value;
			break;
	}
};
 
/**
 * @param {Array} args
 * @param {Option[]} options
 * @type {Function}
 */
Option.parse = function (args, options) {
	var params = {};
	options.forEach(function (option) {
		debug('Testing option: ' + option.shortFlag, args);
		var optionIndex = args.indexOf(option.shortFlag);
		var optionLongIndex = args.indexOf(option.longFlag);
		if (optionIndex !== -1 || optionLongIndex !== -1) {
			var index = (optionIndex !== -1) ? optionIndex : optionLongIndex,
				value,
				parsedValue;
			switch (option.length) {
				case -1:
					//Multiple
					debug("\t-Multiple mode " + option.length);
					params[option.name] = [];
					args.splice(index, 1);
					while (true) {
						debug("\t\t-Testing: " + args[index]);
						if (args[index] && args[index][0] != '-') {
							value = args.splice(index, 1)[0];
							parsedValue = option.validateType(value.toString());
							debug("\t\t\t-Adding: " + parsedValue);
							params[option.name].push(parsedValue);
						}
						else {
							break;
						}
					}
					if(option.required && params[option.name].length === 0) {
						throw new InvalidInputError('Option ' + option.name + ' requires at least one argument.');
					}
					break;
				case 1:
					//Just one
					debug("\t-Single mode");
					value = args[index + 1];
					params[option.name] = option.validateType(value.toString());
					args.splice(index, 2);
					break;
				default:
					//A fixed number, will return an array of values
					debug("\t-Fixed mode");
					params[option.name] = [];
					args.shift();
					for (var i = 1; i <= option.length; i++) {
						if (args && args[0] && args[0][0] != '-') {
							value = args.shift();
							parsedValue = option.validateType(value.toString());
							params[option.name].push(parsedValue);
						}
						else {
							throw new InvalidInputError('Option ' + option.name + ' requires ' + option.length + ' but stopped at ' + args[index + i]);
						}
					}
			}
			if (params[option.name] instanceof Array && params[option.name].length === 0) {
				params[option.name] = null;
			}
		}
		else if (option.required) {
			debug("Option " + option.shortFlag + "/" + option.longFlag + " not found");
			throw new InvalidInputError(option.shortFlag + "/" + option.longFlag + " is required, and wasn't found");
		}
	}.bind(this));
	debug('Args after option: ' + JSON.stringify(args) + "\n");
	return params;
};
 
module.exports = Option;