diff options
Diffstat (limited to 'node_modules/clap/index.js')
| -rw-r--r-- | node_modules/clap/index.js | 950 |
1 files changed, 950 insertions, 0 deletions
diff --git a/node_modules/clap/index.js b/node_modules/clap/index.js new file mode 100644 index 00000000..5d48311c --- /dev/null +++ b/node_modules/clap/index.js @@ -0,0 +1,950 @@ +var MAX_LINE_WIDTH = process.stdout.columns || 200; +var MIN_OFFSET = 25; + +var errorHandler; +var commandsPath; + +var reAstral = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; +var ansiRegex = /\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[m|K]/g; +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function stringLength(str){ + return str + .replace(ansiRegex, '') + .replace(reAstral, ' ') + .length; +} + +function camelize(name){ + return name.replace(/-(.)/g, function(m, ch){ + return ch.toUpperCase(); + }); +} + +function assign(dest, source){ + for (var key in source) + if (hasOwnProperty.call(source, key)) + dest[key] = source[key]; + + return dest; +} + +function returnFirstArg(value){ + return value; +} + +function pad(width, str){ + return str + Array(Math.max(0, width - stringLength(str)) + 1).join(' '); +} + +function noop(){ + // nothing todo +} + +function parseParams(str){ + // params [..<required>] [..[optional]] + // <foo> - require + // [foo] - optional + var tmp; + var left = str.trim(); + var result = { + minArgsCount: 0, + maxArgsCount: 0, + args: [] + }; + + do { + tmp = left; + left = left.replace(/^<([a-zA-Z][a-zA-Z0-9\-\_]*)>\s*/, function(m, name){ + result.args.push(new Argument(name, true)); + result.minArgsCount++; + result.maxArgsCount++; + + return ''; + }); + } + while (tmp != left); + + do { + tmp = left; + left = left.replace(/^\[([a-zA-Z][a-zA-Z0-9\-\_]*)\]\s*/, function(m, name){ + result.args.push(new Argument(name, false)); + result.maxArgsCount++; + + return ''; + }); + } + while (tmp != left); + + if (left) + throw new SyntaxError('Bad parameter description: ' + str); + + return result.args.length ? result : false; +} + +/** +* @class +*/ + +var SyntaxError = function(message){ + this.message = message; +}; +SyntaxError.prototype = Object.create(Error.prototype); +SyntaxError.prototype.name = 'SyntaxError'; +SyntaxError.prototype.clap = true; + +/** +* @class +*/ +var Argument = function(name, required){ + this.name = name; + this.required = required; +}; +Argument.prototype = { + required: false, + name: '', + normalize: returnFirstArg, + suggest: function(){ + return []; + } +}; + +/** +* @class +* @param {string} usage +* @param {string} description +*/ +var Option = function(usage, description){ + var self = this; + var params; + var left = usage.trim() + // short usage + // -x + .replace(/^-([a-zA-Z])(?:\s*,\s*|\s+)/, function(m, name){ + self.short = name; + + return ''; + }) + // long usage + // --flag + // --no-flag - invert value if flag is boolean + .replace(/^--([a-zA-Z][a-zA-Z0-9\-\_]+)\s*/, function(m, name){ + self.long = name; + self.name = name.replace(/(^|-)no-/, '$1'); + self.defValue = self.name != self.long; + + return ''; + }); + + if (!this.long) + throw new SyntaxError('Usage has no long name: ' + usage); + + try { + params = parseParams(left); + } catch(e) { + throw new SyntaxError('Bad paramenter description in usage for option: ' + usage, e); + } + + if (params) + { + left = ''; + this.name = this.long; + this.defValue = undefined; + + assign(this, params); + } + + if (left) + throw new SyntaxError('Bad usage description for option: ' + usage); + + if (!this.name) + this.name = this.long; + + this.description = description || ''; + this.usage = usage.trim(); + this.camelName = camelize(this.name); +}; + +Option.prototype = { + name: '', + description: '', + short: '', + long: '', + + beforeInit: false, + required: false, + minArgsCount: 0, + maxArgsCount: 0, + args: null, + + defValue: undefined, + normalize: returnFirstArg +}; + + +// +// Command +// + +function createOption(usage, description, opt_1, opt_2){ + var option = new Option(usage, description); + + // if (option.bool && arguments.length > 2) + // throw new SyntaxError('bool flags can\'t has default value or validator'); + + if (arguments.length == 3) + { + if (opt_1 && opt_1.constructor === Object) + { + for (var key in opt_1) + if (key == 'normalize' || + key == 'defValue' || + key == 'beforeInit') + option[key] = opt_1[key]; + + // old name for `beforeInit` setting is `hot` + if (opt_1.hot) + option.beforeInit = true; + } + else + { + if (typeof opt_1 == 'function') + option.normalize = opt_1; + else + option.defValue = opt_1; + } + } + + if (arguments.length == 4) + { + if (typeof opt_1 == 'function') + option.normalize = opt_1; + + option.defValue = opt_2; + } + + return option; +} + +function addOptionToCommand(command, option){ + var commandOption; + + // short + if (option.short) + { + commandOption = command.short[option.short]; + + if (commandOption) + throw new SyntaxError('Short option name -' + option.short + ' already in use by ' + commandOption.usage + ' ' + commandOption.description); + + command.short[option.short] = option; + } + + // long + commandOption = command.long[option.long]; + + if (commandOption) + throw new SyntaxError('Long option --' + option.long + ' already in use by ' + commandOption.usage + ' ' + commandOption.description); + + command.long[option.long] = option; + + // camel + commandOption = command.options[option.camelName]; + + if (commandOption) + throw new SyntaxError('Name option ' + option.camelName + ' already in use by ' + commandOption.usage + ' ' + commandOption.description); + + command.options[option.camelName] = option; + + // set default value + if (typeof option.defValue != 'undefined') + command.setOption(option.camelName, option.defValue, true); + + // add to suggestions + command.suggestions.push('--' + option.long); + + return option; +} + +function findVariants(obj, entry){ + return obj.suggestions.filter(function(item){ + return item.substr(0, entry.length) == entry; + }); +} + +function processArgs(command, args, suggest){ + function processOption(option, command){ + var params = []; + + if (option.maxArgsCount) + { + for (var j = 0; j < option.maxArgsCount; j++) + { + var suggestPoint = suggest && i + 1 + j >= args.length - 1; + var nextToken = args[i + 1]; + + // TODO: suggestions for options + if (suggestPoint) + { + // search for suggest + noSuggestions = true; + i = args.length; + return; + } + + if (!nextToken || nextToken[0] == '-') + break; + + params.push(args[++i]); + } + + if (params.length < option.minArgsCount) + throw new SyntaxError('Option ' + token + ' should be used with at least ' + option.minArgsCount + ' argument(s)\nUsage: ' + option.usage); + + if (option.maxArgsCount == 1) + params = params[0]; + } + else + { + params = !option.defValue; + } + + //command.values[option.camelName] = newValue; + resultToken.options.push({ + option: option, + value: params + }); + } + + var resultToken = { + command: command, + args: [], + literalArgs: [], + options: [] + }; + var result = [resultToken]; + + var suggestStartsWith = ''; + var noSuggestions = false; + var collectArgs = false; + var commandArgs = []; + var noOptionsYet = true; + var option; + + commandsPath = [command.name]; + + for (var i = 0; i < args.length; i++) + { + var suggestPoint = suggest && i == args.length - 1; + var token = args[i]; + + if (collectArgs) + { + commandArgs.push(token); + continue; + } + + if (suggestPoint && (token == '--' || token == '-' || token[0] != '-')) + { + suggestStartsWith = token; + break; // returns long option & command list outside the loop + } + + if (token == '--') + { + resultToken.args = commandArgs; + commandArgs = []; + noOptionsYet = false; + collectArgs = true; + continue; + } + + if (token[0] == '-') + { + noOptionsYet = false; + + if (commandArgs.length) + { + //command.args_.apply(command, commandArgs); + resultToken.args = commandArgs; + commandArgs = []; + } + + if (token[1] == '-') + { + // long option + option = command.long[token.substr(2)]; + + if (!option) + { + // option doesn't exist + if (suggestPoint) + return findVariants(command, token); + else + throw new SyntaxError('Unknown option: ' + token); + } + + // process option + processOption(option, command); + } + else + { + // short flags sequence + if (!/^-[a-zA-Z]+$/.test(token)) + throw new SyntaxError('Wrong short option sequence: ' + token); + + if (token.length == 2) + { + option = command.short[token[1]]; + + if (!option) + throw new SyntaxError('Unknown short option name: -' + token[1]); + + // single option + processOption(option, command); + } + else + { + // short options sequence + for (var j = 1; j < token.length; j++) + { + option = command.short[token[j]]; + + if (!option) + throw new SyntaxError('Unknown short option name: -' + token[j]); + + if (option.maxArgsCount) + throw new SyntaxError('Non-boolean option -' + token[j] + ' can\'t be used in short option sequence: ' + token); + + processOption(option, command); + } + } + } + } + else + { + if (command.commands[token] && (!command.params || commandArgs.length >= command.params.minArgsCount)) + { + if (noOptionsYet) + { + resultToken.args = commandArgs; + commandArgs = []; + } + + if (command.params && resultToken.args.length < command.params.minArgsCount) + throw new SyntaxError('Missed required argument(s) for command `' + command.name + '`'); + + // switch control to another command + command = command.commands[token]; + noOptionsYet = true; + + commandsPath.push(command.name); + + resultToken = { + command: command, + args: [], + literalArgs: [], + options: [] + }; + result.push(resultToken); + } + else + { + if (noOptionsYet && command.params && commandArgs.length < command.params.maxArgsCount) + { + commandArgs.push(token); + continue; + } + + if (suggestPoint) + return findVariants(command, token); + else + throw new SyntaxError('Unknown command: ' + token); + } + } + } + + if (suggest) + { + if (collectArgs || noSuggestions) + return []; + + return findVariants(command, suggestStartsWith); + } + else + { + if (!noOptionsYet) + resultToken.literalArgs = commandArgs; + else + resultToken.args = commandArgs; + + if (command.params && resultToken.args.length < command.params.minArgsCount) + throw new SyntaxError('Missed required argument(s) for command `' + command.name + '`'); + } + + return result; +} + +function setFunctionFactory(name){ + return function(fn){ + var property = name + '_'; + + if (this[property] !== noop) + throw new SyntaxError('Method `' + name + '` could be invoked only once'); + + if (typeof fn != 'function') + throw new SyntaxError('Value for `' + name + '` method should be a function'); + + this[property] = fn; + + return this; + } +} + +/** +* @class +*/ +var Command = function(name, params){ + this.name = name; + this.params = false; + + try { + if (params) + this.params = parseParams(params); + } catch(e) { + throw new SyntaxError('Bad paramenter description in command definition: ' + this.name + ' ' + params); + } + + this.commands = {}; + + this.options = {}; + this.short = {}; + this.long = {}; + this.values = {}; + this.defaults_ = {}; + + this.suggestions = []; + + this.option('-h, --help', 'Output usage information', function(){ + this.showHelp(); + process.exit(0); + }, undefined); +}; + +Command.prototype = { + params: null, + commands: null, + options: null, + short: null, + long: null, + values: null, + defaults_: null, + suggestions: null, + + description_: '', + version_: '', + initContext_: noop, + init_: noop, + delegate_: noop, + action_: noop, + args_: noop, + end_: null, + + option: function(usage, description, opt_1, opt_2){ + addOptionToCommand(this, createOption.apply(null, arguments)); + + return this; + }, + shortcut: function(usage, description, fn, opt_1, opt_2){ + if (typeof fn != 'function') + throw new SyntaxError('fn should be a function'); + + var command = this; + var option = addOptionToCommand(this, createOption(usage, description, opt_1, opt_2)); + var normalize = option.normalize; + + option.normalize = function(value){ + var values; + + value = normalize.call(command, value); + values = fn(value); + + for (var name in values) + if (hasOwnProperty.call(values, name)) + if (hasOwnProperty.call(command.options, name)) + command.setOption(name, values[name]); + else + command.values[name] = values[name]; + + command.values[option.name] = value; + + return value; + }; + + return this; + }, + hasOption: function(name){ + return hasOwnProperty.call(this.options, name); + }, + hasOptions: function(){ + return Object.keys(this.options).length > 0; + }, + setOption: function(name, value, isDefault){ + if (!this.hasOption(name)) + throw new SyntaxError('Option `' + name + '` is not defined'); + + var option = this.options[name]; + var oldValue = this.values[name]; + var newValue = option.normalize.call(this, value, oldValue); + + this.values[name] = option.maxArgsCount ? newValue : value; + + if (isDefault && !hasOwnProperty.call(this.defaults_, name)) + this.defaults_[name] = this.values[name]; + }, + setOptions: function(values){ + for (var name in values) + if (hasOwnProperty.call(values, name) && this.hasOption(name)) + this.setOption(name, values[name]); + }, + reset: function(){ + this.values = {}; + + assign(this.values, this.defaults_); + }, + + command: function(nameOrCommand, params){ + var name; + var command; + + if (nameOrCommand instanceof Command) + { + command = nameOrCommand; + name = command.name; + } + else + { + name = nameOrCommand; + + if (!/^[a-zA-Z][a-zA-Z0-9\-\_]*$/.test(name)) + throw new SyntaxError('Wrong command name: ' + name); + } + + // search for existing one + var subcommand = this.commands[name]; + + if (!subcommand) + { + // create new one if not exists + subcommand = command || new Command(name, params); + subcommand.end_ = this; + this.commands[name] = subcommand; + this.suggestions.push(name); + } + + return subcommand; + }, + end: function() { + return this.end_; + }, + hasCommands: function(){ + return Object.keys(this.commands).length > 0; + }, + + version: function(version, usage, description){ + if (this.version_) + throw new SyntaxError('Version for command could be set only once'); + + this.version_ = version; + this.option( + usage || '-v, --version', + description || 'Output version', + function(){ + console.log(this.version_); + process.exit(0); + }, + undefined + ); + + return this; + }, + description: function(description){ + if (this.description_) + throw new SyntaxError('Description for command could be set only once'); + + this.description_ = description; + + return this; + }, + + init: setFunctionFactory('init'), + initContext: setFunctionFactory('initContext'), + args: setFunctionFactory('args'), + delegate: setFunctionFactory('delegate'), + action: setFunctionFactory('action'), + + extend: function(fn){ + fn.apply(null, [this].concat(Array.prototype.slice.call(arguments, 1))); + return this; + }, + + parse: function(args, suggest){ + if (!args) + args = process.argv.slice(2); + + if (!errorHandler) + return processArgs(this, args, suggest); + else + try { + return processArgs(this, args, suggest); + } catch(e) { + errorHandler(e.message || e); + } + }, + run: function(args, context){ + var commands = this.parse(args); + + if (!commands) + return; + + var prevCommand; + var context = assign({}, context || this.initContext_()); + for (var i = 0; i < commands.length; i++) + { + var item = commands[i]; + var command = item.command; + + // reset command values + command.reset(); + command.context = context; + command.root = this; + + if (prevCommand) + prevCommand.delegate_(command); + + // apply beforeInit options + item.options.forEach(function(entry){ + if (entry.option.beforeInit) + command.setOption(entry.option.camelName, entry.value); + }); + + command.init_(item.args.slice()); // use slice to avoid args mutation in handler + + if (item.args.length) + command.args_(item.args.slice()); // use slice to avoid args mutation in handler + + // apply regular options + item.options.forEach(function(entry){ + if (!entry.option.beforeInit) + command.setOption(entry.option.camelName, entry.value); + }); + + prevCommand = command; + } + + // return last command action result + if (command) + return command.action_(item.args, item.literalArgs); + }, + + normalize: function(values){ + var result = {}; + + if (!values) + values = {}; + + for (var name in this.values) + if (hasOwnProperty.call(this.values, name)) + result[name] = hasOwnProperty.call(values, name) && hasOwnProperty.call(this.options, name) + ? this.options[name].normalize.call(this, values[name]) + : this.values[name]; + + for (var name in values) + if (hasOwnProperty.call(values, name) && !hasOwnProperty.call(result, name)) + result[name] = values[name]; + + return result; + }, + + showHelp: function(){ + console.log(showCommandHelp(this)); + } +}; + + +// +// help +// + +/** + * Return program help documentation. + * + * @return {String} + * @api private + */ + +function showCommandHelp(command){ + function breakByLines(str, offset){ + var words = str.split(' '); + var maxWidth = MAX_LINE_WIDTH - offset || 0; + var lines = []; + var line = ''; + + while (words.length) + { + var word = words.shift(); + if (!line || (line.length + word.length + 1) < maxWidth) + { + line += (line ? ' ' : '') + word; + } + else + { + lines.push(line); + words.unshift(word); + line = ''; + } + } + + lines.push(line); + + return lines.map(function(line, idx){ + return (idx && offset ? pad(offset, '') : '') + line; + }).join('\n'); + } + + function args(command){ + return command.params.args.map(function(arg){ + return arg.required + ? '<' + arg.name + '>' + : '[' + arg.name + ']'; + }).join(' '); + } + + function commandsHelp(){ + if (!command.hasCommands()) + return ''; + + var maxNameLength = MIN_OFFSET - 2; + var lines = Object.keys(command.commands).sort().map(function(name){ + var subcommand = command.commands[name]; + + var line = { + name: chalk.green(name) + chalk.gray( + (subcommand.params ? ' ' + args(subcommand) : '') + // (subcommand.hasOptions() ? ' [options]' : '') + ), + description: subcommand.description_ || '' + }; + + maxNameLength = Math.max(maxNameLength, stringLength(line.name)); + + return line; + }); + + return [ + '', + 'Commands:', + '', + lines.map(function(line){ + return ' ' + pad(maxNameLength, line.name) + ' ' + breakByLines(line.description, maxNameLength + 4); + }).join('\n'), + '' + ].join('\n'); + } + + function optionsHelp(){ + if (!command.hasOptions()) + return ''; + + var hasShortOptions = Object.keys(command.short).length > 0; + var maxNameLength = MIN_OFFSET - 2; + var lines = Object.keys(command.long).sort().map(function(name){ + var option = command.long[name]; + var line = { + name: option.usage + .replace(/^(?:-., |)/, function(m){ + return m || (hasShortOptions ? ' ' : ''); + }) + .replace(/(^|\s)(-[^\s,]+)/ig, function(m, p, flag){ + return p + chalk.yellow(flag); + }), + description: option.description + }; + + maxNameLength = Math.max(maxNameLength, stringLength(line.name)); + + return line; + }); + + // Prepend the help information + return [ + '', + 'Options:', + '', + lines.map(function(line){ + return ' ' + pad(maxNameLength, line.name) + ' ' + breakByLines(line.description, maxNameLength + 4); + }).join('\n'), + '' + ].join('\n'); + } + + var output = []; + var chalk = require('chalk'); + + chalk.enabled = module.exports.color && process.stdout.isTTY; + + if (command.description_) + output.push(command.description_ + '\n'); + + output.push( + 'Usage:\n\n ' + + chalk.cyan(commandsPath ? commandsPath.join(' ') : command.name) + + (command.params ? ' ' + chalk.magenta(args(command)) : '') + + (command.hasOptions() ? ' [' + chalk.yellow('options') + ']' : '') + + (command.hasCommands() ? ' [' + chalk.green('command') + ']' : ''), + commandsHelp() + + optionsHelp() + ); + + return output.join('\n'); +}; + + +// +// export +// + +module.exports = { + color: true, + + Error: SyntaxError, + Argument: Argument, + Command: Command, + Option: Option, + + error: function(fn){ + if (errorHandler) + throw new SyntaxError('Error handler should be set only once'); + + if (typeof fn != 'function') + throw new SyntaxError('Error handler should be a function'); + + errorHandler = fn; + + return this; + }, + + create: function(name, params){ + return new Command(name || require('path').basename(process.argv[1]) || 'cli', params); + }, + + confirm: function(message, fn){ + process.stdout.write(message); + process.stdin.setEncoding('utf8'); + process.stdin.once('data', function(val){ + process.stdin.pause(); + fn(/^y|yes|ok|true$/i.test(val.trim())); + }); + process.stdin.resume(); + } +}; |
