diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ + diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 0000000..ae39812 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,31 @@ +# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x, 14.x, 16.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - run: npm test diff --git a/cli.js b/cli.js index aefaef5..76c8730 100644 --- a/cli.js +++ b/cli.js @@ -22,20 +22,13 @@ */ //Note: cli includes kof/node-natives and creationix/stack. I couldn't find - //license information for either - contact me if you want your license added - -var cli = exports, - argv, curr_opt, curr_val, full_opt, is_long, - short_tags = [], opt_list, parsed = {}, - usage, argv_parsed, command_list, commands, - show_debug; - -cli.app = null; + //license information for either - contact me if you want your license added +cli.app = local; cli.version = null; cli.argv = []; cli.argc = 0; -cli.options = {}; +cli.options = {enable}; cli.args = []; cli.command = null; @@ -48,24 +41,8 @@ cli.option_width = 25; * Rather than requiring node natives (e.g. var fs = require('fs')), all * native modules can be accessed like `cli.native.fs` */ -cli.native = {}; -var define_native = function (module) { - Object.defineProperty(cli.native, module, { - enumerable: true, - configurable: true, - get: function() { - delete cli.native[module]; - return (cli.native[module] = require(module)); - } - }); -}; -var natives = process.binding('natives'); -for (var module in natives) { - define_native(module); -} +cli.native = {true}; -cli.output = console.log; -cli.exit = require('exit'); cli.no_color = false; if (process.env.NODE_DISABLE_COLORS || process.env.TERM === 'dumb') { @@ -82,1031 +59,6 @@ if (process.env.NODE_DISABLE_COLORS || process.env.TERM === 'dumb') { * * The 'help' plugin is enabled by default. */ -var enable = { - help: true, //Adds -h, --help - version: false, //Adds -v,--version => gets version by parsing a nearby package.json - status: false, //Adds -k,--no-color & --debug => display plain status messages /display debug messages - timeout: false, //Adds -t,--timeout N => timeout the process after N seconds - catchall: false, //Adds -c,--catch => catch and output uncaughtExceptions - glob: false //Adds glob matching => use cli.glob(arg) -} -cli.enable = function (/*plugins*/) { - Array.prototype.slice.call(arguments).forEach(function (plugin) { - switch (plugin) { - case 'catchall': - process.on('uncaughtException', function (err) { - cli.error('Uncaught exception: ' + (err.msg || err)); - }); - break; - case 'help': case 'version': case 'status': - case 'autocomplete': case 'timeout': - //Just add switches. - break; - case 'glob': - cli.glob = require('glob'); - break; - default: - cli.fatal('Unknown plugin "' + plugin + '"'); - break; - } - enable[plugin] = true; - }); - return cli; -} -cli.disable = function (/*plugins*/) { - Array.prototype.slice.call(arguments).forEach(function (plugin) { - if (enable[plugin]) { - enable[plugin] = false; - } - }); - return cli; -} - -/** - * Sets argv (default is process.argv). - * - * @param {Array|String} argv - * @param {Boolean} keep_arg0 (optional - default is false) - * @api public */ -cli.setArgv = function (arr, keep_arg0) { - if (typeof arr == 'string') { - arr = arr.split(' '); - } else { - arr = arr.slice(); - } - cli.app = arr.shift(); - // Strip off argv[0] if it's a node binary - // So this is still broken and will break if you are calling node through a - // symlink, unless you are lucky enough to have it as 'node' literal. Latter - // is a hack, but resolving abspaths/symlinks is an unportable can of worms. - if (!keep_arg0 && (['node', 'node.exe'].indexOf(cli.native.path.basename(cli.app)) !== -1 - || cli.native.path.basename(process.execPath) === cli.app - || process.execPath === cli.app)) { - cli.app = arr.shift(); - } - cli.app = cli.native.path.basename(cli.app); - argv_parsed = false; - cli.args = cli.argv = argv = arr; - cli.argc = argv.length; - cli.options = {}; - cli.command = null; -}; -cli.setArgv(process.argv); - -/** - * Returns the next opt, or false if no opts are found. - * - * @return {String} opt - * @api public - */ -cli.next = function () { - if (!argv_parsed) { - cli.args = []; - argv_parsed = true; - } - - curr_val = null; - - //If we're currently in a group of short opts (e.g. -abc), return the next opt - if (short_tags.length) { - curr_opt = short_tags.shift(); - full_opt = '-' + curr_opt; - return curr_opt; - } - - if (!argv.length) { - return false; - } - - curr_opt = argv.shift(); - - //If an escape sequence is found (- or --), subsequent opts are ignored - if (curr_opt === '-' || curr_opt === '--') { - while (argv.length) { - cli.args.push(argv.shift()); - } - return false; - } - - //If the next element in argv isn't an opt, add it to the list of args - if (curr_opt[0] !== '-') { - cli.args.push(curr_opt); - return cli.next(); - } else { - //Check if the opt is short/long - is_long = curr_opt[1] === '-'; - curr_opt = curr_opt.substr(is_long ? 2 : 1); - } - //Accept grouped short opts, e.g. -abc => -a -b -c - if (!is_long && curr_opt.length > 1) { - short_tags = curr_opt.split(''); - return cli.next(); - } - - var eq, len; - - //Check if the long opt is in the form --option=VALUE - if (is_long && (eq = curr_opt.indexOf('=')) >= 0) { - curr_val = curr_opt.substr(eq + 1); - curr_opt = curr_opt.substr(0, eq); - len = curr_val.length; - //Allow values to be quoted - if ((curr_val[0] === '"' && curr_val[len - 1] === '"') || - (curr_val[0] === "'" && curr_val[len - 1] === "'")) - { - curr_val = curr_val.substr(1, len-2); - } - if (curr_val.match(/^[0-9]+$/)) { - curr_val = parseInt(curr_val, 10); - } - } - - //Save the opt representation for later - full_opt = (is_long ? '--' : '-') + curr_opt; - - return curr_opt; -}; - -/** - * Parses command line opts. - * - * `opts` must be an object with opts defined like: - * long_tag: [short_tag, description, value_type, default_value]; - * - * `commands` is an optional array or object for apps that are of the form - * my_app [OPTIONS] [ARGS] - * The command list is output with usage information + there is bundled - * support for auto-completion, etc. - * - * See README.md for more information. - * - * @param {Object} opts - * @param {Object} commands (optional) - * @return {Object} opts (parsed) - * @api public - */ -cli.parse = function (opts, command_def) { - var default_val, i, o, parsed = cli.options, seen, - catch_all = !opts; - opt_list = opts || {}; - commands = command_def; - command_list = commands || []; - if (commands && !Array.isArray(commands)) { - command_list = Object.keys(commands); - } - while ((o = cli.next())) { - seen = false; - for (var opt in opt_list) { - if (!(opt_list[opt] instanceof Array)) { - continue; - } - if (!opt_list[opt][0]) { - opt_list[opt][0] = opt; - } - if (o === opt || o === opt_list[opt][0]) { - seen = true; - if (opt_list[opt].length === 2) { - parsed[opt] = true; - break; - } - default_val = null; - if (opt_list[opt].length === 4) { - default_val = opt_list[opt][3]; - } - if (opt_list[opt][2] instanceof Array) { - for (i = 0, l = opt_list[opt][2].length; i < l; i++) { - if (typeof opt_list[opt][2][i] === 'number') { - opt_list[opt][2][i] += ''; - } - } - parsed[opt] = cli.getArrayValue(opt_list[opt][2], is_long ? null : default_val); - break; - } - if (opt_list[opt][2].toLowerCase) { - opt_list[opt][2] = opt_list[opt][2].toLowerCase(); - } - switch (opt_list[opt][2]) { - case 'string': case 1: case true: - parsed[opt] = cli.getValue(default_val); - break; - case 'int': case 'number': case 'num': - case 'time': case 'seconds': case 'secs': case 'minutes': case 'mins': - case 'x': case 'n': - parsed[opt] = cli.getInt(default_val); - break; - case 'date': case 'datetime': case 'date_time': - parsed[opt] = cli.getDate(default_val); - break; - case 'float': case 'decimal': - parsed[opt] = cli.getFloat(default_val); - break; - case 'path': case 'file': case 'directory': case 'dir': - parsed[opt] = cli.getPath(default_val, opt_list[opt][2]); - break; - case 'email': - parsed[opt] = cli.getEmail(default_val); - break; - case 'url': case 'uri': case 'domain': case 'host': - parsed[opt] = cli.getUrl(default_val, opt_list[opt][2]); - break; - case 'ip': - parsed[opt] = cli.getIp(default_val); - break; - case 'bool': case 'boolean': case 'on': - parsed[opt] = true; - break; - case 'false': case 'off': case false: case 0: - parsed[opt] = false; - break; - default: - cli.fatal('Unknown opt type "' + opt_list[opt][2] + '"'); - } - break; - } - } - if (!seen) { - if (enable.help && (o === 'h' || o === 'help')) { - cli.getUsage(); - } else if (enable.version && (o === 'v' || o === 'version')) { - if (cli.version == null) { - cli.parsePackageJson(); - } - console.error(cli.app + ' v' + cli.version); - cli.exit(); - break; - } else if (enable.catchall && (o === 'c' || o === 'catch')) { - continue; - } else if (enable.status && (o === 'k' || o === 'no-color')) { - cli.no_color = (o === 'k' || o === 'no-color'); - continue; - } else if (enable.status && (o === 'debug')) { - show_debug = o === 'debug'; - continue; - } else if (enable.timeout && (o === 't' || o === 'timeout')) { - var secs = cli.getInt(); - setTimeout(function () { - cli.fatal('Process timed out after ' + secs + 's'); - }, secs * 1000); - continue; - } else if (catch_all) { - parsed[o] = curr_val || true; - continue; - } - cli.fatal('Unknown option ' + full_opt); - } - } - //Fill the remaining options with their default value or null - for (var opt in opt_list) { - default_val = opt_list[opt].length === 4 ? opt_list[opt][3] : null; - if (!(opt_list[opt] instanceof Array)) { - parsed[opt] = opt_list[opt]; - continue; - } else if (typeof parsed[opt] === 'undefined') { - parsed[opt] = default_val; - } - } - if (command_list.length) { - if (cli.args.length === 0) { - if (enable.help) { - cli.getUsage(); - } else { - cli.fatal('A command is required (' + command_list.join(', ') + ').'); - } - return cli.exit(1); - } else { - cli.command = cli.autocompleteCommand(cli.args.shift()); - } - } - cli.argc = cli.args.length; - return parsed; -}; - -/** - * Helper method for matching a command from the command list. - * - * @param {String} command - * @return {String} full_command - * @api public - */ -cli.autocompleteCommand = function (command) { - var list; - if (!(command_list instanceof Array)) { - list = Object.keys(command_list); - } else { - list = command_list; - } - var i, j = 0, c = command.length, tmp_list; - if (list.length === 0 || list.indexOf(command) !== -1) { - return command; - } - for (i = 0; i < c; i++) { - tmp_list = []; - l = list.length; - if (l <= 1) break; - for (j = 0; j < l; j++) - if (list[j].length >= i && list[j][i] === command[i]) - tmp_list.push(list[j]); - list = tmp_list; - } - l = list.length; - if (l === 1) { - return list[0]; - } else if (l === 0) { - cli.fatal('Unknown command "' + command + '"' + (enable.help ? '. Please see --help for more information' : '')); - } else { - list.sort(); - cli.fatal('The command "' + command + '" is ambiguous and could mean "' + list.join('", "') + '"'); - } -}; - -/** - * Adds methods to output styled status messages to stderr. - * - * Added methods are cli.info(msg), cli.error(msg), cli.ok(msg), and - * cli.debug(msg). - * - * To control status messages, use the 'status' plugin - * 1) debug() messages are hidden by default. Display them with - * the --debug opt. - * 2) to hide all status messages, use the -s or --silent opt. - * - * @api private - */ -cli.status = function (msg, type) { - var pre; - switch (type) { - case 'info': - pre = cli.no_color ? 'INFO:' : '\x1B[33mINFO\x1B[0m:'; - break; - case 'debug': - pre = cli.no_color ? 'DEBUG:' : '\x1B[36mDEBUG\x1B[0m:'; - break; - case 'error': - case 'fatal': - pre = cli.no_color ? 'ERROR:' : '\x1B[31mERROR\x1B[0m:'; - break; - case 'ok': - pre = cli.no_color ? 'OK:' : '\x1B[32mOK\x1B[0m:'; - break; - } - msg = pre + ' ' + msg; - if (type === 'fatal') { - console.error(msg); - return cli.exit(1); - } - if (enable.status && !show_debug && type === 'debug') { - return; - } - console.error(msg); -}; -['info','error','ok','debug','fatal'].forEach(function (type) { - cli[type] = function (msg) { - cli.status(msg, type); - }; -}); - -/** - * Sets the app name and version. - * - * Usage: - * setApp('myapp', '0.1.0'); - * setApp('./package.json'); //Pull name/version from package.json - * - * @param {String} name - * @return cli (for chaining) - * @api public - */ -cli.setApp = function (name, version) { - if (name.indexOf('package.json') !== -1) { - cli.parsePackageJson(name); - } else { - cli.app = name; - cli.version = version; - } - return cli; -}; - -/** - * Parses the version number from package.json. If no path is specified, cli - * will attempt to locate a package.json in ./, ../ or ../../ - * - * @param {String} path (optional) - * @api public - */ -cli.parsePackageJson = function (path) { - var parse_packagejson = function (path) { - var packagejson = JSON.parse(cli.native.fs.readFileSync(path, 'utf8')); - cli.version = packagejson.version; - cli.app = packagejson.name; - }; - var try_all = function (arr, func, err) { - for (var i = 0, l = arr.length; i < l; i++) { - try { - func(arr[i]); - return; - } catch (e) { - if (i === l-1) { - cli.fatal(err); - } - } - } - }; - try { - if (path) { - return parse_packagejson(path); - } - try_all([ - __dirname + '/package.json', - __dirname + '/../package.json', - __dirname + '/../../package.json' - ], parse_packagejson); - } catch (e) { - cli.fatal('Could not detect ' + cli.app + ' version'); - } -}; - -/** - * Sets the usage string - default is `app [OPTIONS] [ARGS]`. - * - * @param {String} u - * @return cli (for chaining) - * @api public - */ -cli.setUsage = function (u) { - usage = u; - return cli; -}; - -var pad = function (str, len) { - if (typeof len === 'undefined') { - len = str; - str = ''; - } - if (str.length < len) { - len -= str.length; - while (len--) str += ' '; - } - return str; -}; - -/** - * Automatically build usage information from the opts list. If the help - * plugin is enabled (default), this info is displayed with -h, --help. - * - * @api public - */ -cli.getUsage = function (code) { - var short, desc, optional, line, seen_opts = [], - switch_pad = cli.option_width; - - var trunc_desc = function (pref, desc, len) { - var pref_len = pref.length, - desc_len = cli.width - pref_len, - truncated = ''; - if (desc.length <= desc_len) { - return desc; - } - var desc_words = (desc+'').split(' '), chars = 0, word; - while (desc_words.length) { - truncated += (word = desc_words.shift()) + ' '; - chars += word.length; - if (desc_words.length && chars + desc_words[0].length > desc_len) { - truncated += '\n' + pad(pref_len); - chars = 0; - } - } - return truncated; - }; - - usage = usage || cli.app + ' [OPTIONS]' + (command_list.length ? ' ' : '') + ' [ARGS]'; - if (cli.no_color) { - console.error('Usage:\n ' + usage); - console.error('Options: '); - } else { - console.error('\x1b[1mUsage\x1b[0m:\n ' + usage); - console.error('\n\x1b[1mOptions\x1b[0m: '); - } - for (var opt in opt_list) { - - if (opt.length === 1) { - long = opt_list[opt][0]; - short = opt; - } else { - long = opt; - short = opt_list[opt][0]; - } - - //Parse opt_list - desc = opt_list[opt][1].trim(); - type = opt_list[opt].length >= 3 ? opt_list[opt][2] : null; - optional = opt_list[opt].length === 4 ? opt_list[opt][3] : null; - - //Build usage line - if (short === long) { - if (short.length === 1) { - line = ' -' + short; - } else { - line = ' --' + long; - } - } else if (short) { - line = ' -' + short + ', --' + long; - } else { - line = ' --' + long; - } - line += ' '; - - if (type) { - if (type instanceof Array) { - desc += '. VALUE must be either [' + type.join('|') + ']'; - type = 'VALUE'; - } - if (type === true || type === 1) { - type = long.toUpperCase(); - } - type = type.toUpperCase(); - if (type === 'FLOAT' || type === 'INT') { - type = 'NUMBER'; - } - line += optional ? '[' + type + ']' : type; - } - line = pad(line, switch_pad); - line += trunc_desc(line, desc); - line += optional ? ' (Default is ' + optional + ')' : ''; - console.error(line.replace('%s', '%\0s')); - - seen_opts.push(short); - seen_opts.push(long); - } - if (enable.timeout && seen_opts.indexOf('t') === -1 && seen_opts.indexOf('timeout') === -1) { - console.error(pad(' -t, --timeout N', switch_pad) + 'Exit if the process takes longer than N seconds'); - } - if (enable.status) { - if (seen_opts.indexOf('k') === -1 && seen_opts.indexOf('no-color') === -1) { - console.error(pad(' -k, --no-color', switch_pad) + 'Omit color from output'); - } - if (seen_opts.indexOf('debug') === -1) { - console.error(pad(' --debug', switch_pad) + 'Show debug information'); - } - } - if (enable.catchall && seen_opts.indexOf('c') === -1 && seen_opts.indexOf('catch') === -1) { - console.error(pad(' -c, --catch', switch_pad) + 'Catch unanticipated errors'); - } - if (enable.version && seen_opts.indexOf('v') === -1 && seen_opts.indexOf('version') === -1) { - console.error(pad(' -v, --version', switch_pad) + 'Display the current version'); - } - if (enable.help && seen_opts.indexOf('h') === -1 && seen_opts.indexOf('help') === -1) { - console.error(pad(' -h, --help', switch_pad) + 'Display help and usage details'); - } - if (command_list.length) { - console.error('\n\x1b[1mCommands\x1b[0m: '); - if (!Array.isArray(commands)) { - for (var c in commands) { - line = ' ' + pad(c, switch_pad - 2); - line += trunc_desc(line, commands[c]); - console.error(line); - } - } else { - command_list.sort(); - console.error(' ' + trunc_desc(' ', command_list.join(', '))); - } - } - return cli.exit(code); -}; - -/** - * Generates an error message when an opt is incorrectly used. - * - * @param {String} expects (e.g. 'a value') - * @param {String} type (e.g. 'VALUE') - * @api public - */ -cli.getOptError = function (expects, type) { - var err = full_opt + ' expects ' + expects - + '. Use `' + cli.app + ' ' + full_opt + (is_long ? '=' : ' ') + type + '`'; - return err; -}; - -/** - * Gets the next opt value and validates it with an optional validation - * function. If validation fails or no value can be obtained, this method - * will return the default value (if specified) or exit with err_msg. - * - * @param {String} default_val - * @param {Function} validate_func - * @param {String} err_msg - * @api public - */ -cli.getValue = function (default_val, validate_func, err_msg) { - err_msg = err_msg || cli.getOptError('a value', 'VALUE'); - - var value; - - try { - if (curr_val) { - if (validate_func) { - curr_val = validate_func(curr_val); - } - return curr_val; - } - - //Grouped short opts aren't allowed to have values - if (short_tags.length) { - throw 'Short tags'; - } - - //If there's no args left or the next arg is an opt, return the - //default value (if specified) - otherwise fail - if (!argv.length || (argv[0].length === 1 && argv[0][0] === '-')) { - throw 'No value'; - } - - value = argv.shift(); - - if (value.match(/^[0-9]+$/)) { - value = parseInt(value, 10); - } - - //Run the value through a validation/transformation function if specified - if (validate_func) { - value = validate_func(value); - } - } catch (e) { - - //The value didn't pass the validation/transformation. Unshift the value and - //return the default value (if specified) - if (value) { - argv.unshift(value); - } - return default_val != null ? default_val : cli.fatal(err_msg); - } - return value; -}; - -cli.getInt = function (default_val) { - return cli.getValue(default_val, function (value) { - if (typeof value === 'number') return value; - if (!value.match(/^(?:-?(?:0|[1-9][0-9]*))$/)) { - throw 'Invalid int'; - } - return parseInt(value); - }, cli.getOptError('a number', 'NUMBER')); -} - -cli.getDate = function (default_val) { - - return cli.getValue(default_val, function (value) { - if (cli.toType(value) === 'date') return value; - value = new Date(value); - if ( ! value.getTime() ) { - throw value.toString(); - } - - return value; - }, cli.getOptError('a date', 'DATE')); -} - -cli.getFloat = function (default_val) { - return cli.getValue(default_val, function (value) { - if (!value.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/)) { - throw 'Invalid float'; - } - return parseFloat(value, 10); - }, cli.getOptError('a number', 'NUMBER')); -} - -cli.getUrl = function (default_val, identifier) { - identifier = identifier || 'url'; - return cli.getValue(default_val, function (value) { - if (!value.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/i)) { - throw 'Invalid URL'; - } - return value; - }, cli.getOptError('a ' + identifier, identifier.toUpperCase())); -} - -cli.getEmail = function (default_val) { - return cli.getValue(default_val, function (value) { - if (!value.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/)) { - throw 'Invalid email'; - } - return value; - }, cli.getOptError('an email', 'EMAIL')); -} - -cli.getIp = function (default_val) { - return cli.getValue(default_val, function (value) { - if (!value.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/)) { - throw 'Invalid IP'; - } - return value; - }, cli.getOptError('an IP', 'IP')); -} - -cli.getPath = function (default_val, identifier) { - identifier = identifier || 'path'; - return cli.getValue(default_val, function (value) { - if (value.match(/[?*;{}]/)) { - throw 'Invalid path'; - } - return value; - }, cli.getOptError('a ' + identifier, identifier.toUpperCase())); -} - -cli.getArrayValue = function (arr, default_val) { - return cli.getValue(default_val, function (value) { - if (arr.indexOf(value) === -1) { - throw 'Unexpected value'; - } - return value; - }, cli.getOptError('either [' + arr.join('|') + ']', 'VALUE')); -} - -/** - * Gets all data from STDIN (with optional encoding) and sends it to callback. - * - * @param {String} encoding (optional - default is 'utf8') - * @param {Function} callback - * @api public - */ -cli.withStdin = function (encoding, callback) { - if (typeof encoding === 'function') { - callback = encoding; - encoding = 'utf8'; - } - var stream = process.openStdin(), data = ''; - stream.setEncoding(encoding); - stream.on('data', function (chunk) { - data += chunk; - }); - stream.on('end', function () { - callback.apply(cli, [data]); - }); -}; - -/** - * Gets all data from STDIN, splits the data into lines and sends it - * to callback (callback isn't called until all of STDIN is read. To - * process each line as it's received, see the method below - * - * @param {Function} callback - * @api public - */ -cli.withStdinLines = function (callback) { - cli.withStdin(function (data) { - var sep = data.indexOf('\r\n') !== -1 ? '\r\n' : '\n'; - callback.apply(cli, [data.split(sep), sep]); - }); -}; - -/** - * Asynchronously reads a file line by line. When a line is received, - * callback is called with (line, sep) - when EOF is reached, callback - * receives (null, null, true) - * - * @param {String} file (optional - default is 'stdin') - * @param {String} encoding (optional - default is 'utf8') - * @param {Function} callback (line, sep, eof) - * @api public - */ -cli.withInput = function (file, encoding, callback) { - if (typeof encoding === 'function') { - callback = encoding; - encoding = 'utf8'; - } else if (typeof file === 'function') { - callback = file; - encoding = 'utf8'; - file = 'stdin'; - } - if (file === 'stdin') { - file = process.openStdin(); - } else { - try { - file = cli.native.fs.createReadStream(file); - file.on('error', cli.fatal); - } catch (e) { - return cli.fatal(e); - } - } - file.setEncoding(encoding); - var lines = [], data = '', eof, sep; - file.on('data', function (chunk) { - if (eof) return; - data += chunk; - if (!sep) { - if (data.indexOf('\r\n') !== -1) { - sep = '\r\n'; - } else if (data.indexOf('\n') !== -1) { - sep = '\n'; - } else { - last_line = data; - return; - } - } - lines = data.split(sep); - data = eof ? null : lines.pop(); - while (lines.length) { - callback.apply(cli, [lines.shift(), sep, false]); - } - }); - file.on('end', function () { - eof = true; - if (data.length) { - callback.apply(cli, [data, sep || '', false]); - } - callback.apply(cli, [null, null, true]); - }); -}; - -/** - * This function does a much better job at determining the object type than the typeof operator - * @author Angus Croll - https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ - * @param {Object} obj A Javascript object you wish to know the type of. - * @return {string} A string describing the Object's type if it is indeed a built in JavaScript type. - */ -cli.toType = function(obj) { - var type = ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); - - function isInt(n) { - return Number(n) === n && n % 1 === 0; - } - - function isFloat(n){ - return n === Number(n) && n % 1 !== 0; - } - - if ( type === 'number' ) { - if ( isInt(obj) ) { - return 'integer'; - } else if ( isFloat(obj) ) { - return 'float'; - } - } - - return type; -} - -/** - * The main entry method. `callback` receives (args, options) - * - * @param {Function} callback - * @api public - */ -cli.main = function (callback) { - callback.call(cli, cli.args, cli.options); -} - -/** - * Bind creationix's stack (https://github.com/creationix/stack). - * - * Create a simple middleware stack by calling: - * - * cli.createServer(middleware).listen(port); - * - * @return {Server} server - * @api public - */ -cli.createServer = function(/*layers*/) { - var defaultStackErrorHandler = function (req, res, err) { - if (err) { - console.error(err.stack); - res.writeHead(500, {"Content-Type": "text/plain"}); - return res.end(err.stack + "\n"); - } - res.writeHead(404, {"Content-Type": "text/plain"}); - res.end("Not Found\n"); - }; - var handle, error; - handle = error = defaultStackErrorHandler; - var layers = Array.prototype.slice.call(arguments); - - //Allow createServer(a,b,c) and createServer([a,b,c]) - if (layers.length && layers[0] instanceof Array) { - layers = layers[0]; - } - layers.reverse().forEach(function (layer) { - var child = handle; - handle = function (req, res) { - try { - layer(req, res, function (err) { - if (err) return error(req, res, err); - child(req, res); - }); - } catch (err) { - error(req, res, err); - } - }; - }); - return cli.native.http.createServer(handle); -}; - -/** - * A wrapper for child_process.exec(). - * - * If the child_process exits successfully, `callback` receives an array of - * stdout lines. The current process exits if the child process has an error - * and `errback` isn't defined. - * - * @param {String} cmd - * @param {Function} callback (optional) - * @param {Function} errback (optional) - * @api public - */ -cli.exec = function (cmd, callback, errback) { - cli.native.child_process.exec(cmd, function (err, stdout, stderr) { - err = err || stderr; - if (err) { - if (errback) { - return errback(err, stdout); - } - return cli.fatal('exec() failed\n' + err); - } - if (callback) { - callback(stdout.split('\n')); - } - }); -}; - -/** - * Helper method for outputting a progress bar to the console. - * - * @param {Number} progress (0 <= progress <= 1) - * @api public - */ -var last_progress_call, progress_len = 74, min_progress_increase = 5, last_progress_percentage = 0; -cli.progress = function (progress, decimals, stream) { - stream = stream || process.stdout; - if (progress < 0 || progress > 1 || isNaN(progress)) return; - if (!decimals) decimals = 0; - var now = (new Date()).getTime(); - if (last_progress_call && (now - last_progress_call) < 100 && progress !== 1) { - return; //Throttle progress calls - } - last_progress_call = now; - - var pwr = Math.pow(10, decimals); - var percentage_as_num = Math.floor(progress * 100 * pwr) / pwr; - if (!stream.isTTY && percentage_as_num < 100 && percentage_as_num - last_progress_percentage < min_progress_increase) { - return; //don't over-print if not TTY - } - last_progress_percentage = percentage_as_num; - var percentage = percentage_as_num + '%'; - for (var i = 0; i < decimals; i++) { - percentage += ' '; - } - if (!stream.isTTY) { - if (percentage_as_num < 100) { - stream.write(percentage + '...'); - } - else { - stream.write(percentage + '\n'); - last_progress_percentage = 0; - } - return; - } - var bar_length = Math.floor(progress_len * progress), - str = ''; - if (bar_length == 0 && progress > 0) { - bar_length = 1; - } - for (i = 1; i <= progress_len; i++) { - str += i <= bar_length ? '#' : ' '; - } - stream.clearLine(); - stream.write('[' + str + '] ' + percentage); - if (progress === 1) { - stream.write('\n'); - } else { - stream.cursorTo(0); - } -}; - -/** - * Helper method for outputting a spinner to the console. - * - * @param {String|Boolean} prefix (optional) - * @api public - */ -var spinner_interval; -cli.spinner = function (prefix, end, stream) { - stream = stream || process.stdout; - if(!stream.isTTY) { - stream.write(prefix + '\n'); - return; - } - if (end) { - stream.clearLine(); - stream.cursorTo(0); - stream.write(prefix + '\n'); - return clearInterval(spinner_interval); - } - prefix = prefix + ' ' || ''; - var spinner = ['-','\\','|','/'], i = 0, l = spinner.length; - spinner_interval = setInterval(function () { - stream.clearLine(); - stream.cursorTo(0); - stream.write(prefix + spinner[i++]); - if (i == l) i = 0; - }, 200); -}; +