import camelCase from 'lodash.camelcase'; /** * Takes any input and guarantees an array back. * * - converts array-like objects (e.g. `arguments`) to a real array * - converts `undefined` to an empty array * - converts any another other, singular value (including `null`) into an array containing that value * - ignores input which is already an array * * @module array-back * @example * > const arrayify = require('array-back') * * > arrayify(undefined) * [] * * > arrayify(null) * [ null ] * * > arrayify(0) * [ 0 ] * * > arrayify([ 1, 2 ]) * [ 1, 2 ] * * > function f(){ return arrayify(arguments); } * > f(1,2,3) * [ 1, 2, 3 ] */ function isObject (input) { return typeof input === 'object' && input !== null } function isArrayLike (input) { return isObject(input) && typeof input.length === 'number' } /** * @param {*} - the input value to convert to an array * @returns {Array} * @alias module:array-back */ function arrayify (input) { if (Array.isArray(input)) { return input } else { if (input === undefined) { return [] } else if (isArrayLike(input)) { return Array.prototype.slice.call(input) } else { return [ input ] } } } /** * Takes any input and guarantees an array back. * * - converts array-like objects (e.g. `arguments`) to a real array * - converts `undefined` to an empty array * - converts any another other, singular value (including `null`) into an array containing that value * - ignores input which is already an array * * @module array-back * @example * > const arrayify = require('array-back') * * > arrayify(undefined) * [] * * > arrayify(null) * [ null ] * * > arrayify(0) * [ 0 ] * * > arrayify([ 1, 2 ]) * [ 1, 2 ] * * > function f(){ return arrayify(arguments); } * > f(1,2,3) * [ 1, 2, 3 ] */ function isObject$1 (input) { return typeof input === 'object' && input !== null } function isArrayLike$1 (input) { return isObject$1(input) && typeof input.length === 'number' } /** * @param {*} - the input value to convert to an array * @returns {Array} * @alias module:array-back */ function arrayify$1 (input) { if (Array.isArray(input)) { return input } else { if (input === undefined) { return [] } else if (isArrayLike$1(input)) { return Array.prototype.slice.call(input) } else { return [ input ] } } } /** * Find and either replace or remove items in an array. * * @module find-replace * @example * > const findReplace = require('find-replace') * > const numbers = [ 1, 2, 3] * * > findReplace(numbers, n => n === 2, 'two') * [ 1, 'two', 3 ] * * > findReplace(numbers, n => n === 2, [ 'two', 'zwei' ]) * [ 1, [ 'two', 'zwei' ], 3 ] * * > findReplace(numbers, n => n === 2, 'two', 'zwei') * [ 1, 'two', 'zwei', 3 ] * * > findReplace(numbers, n => n === 2) // no replacement, so remove * [ 1, 3 ] */ /** * @param {array} - The input array * @param {testFn} - A predicate function which, if returning `true` causes the current item to be operated on. * @param [replaceWith] {...any} - If specified, found values will be replaced with these values, else removed. * @returns {array} * @alias module:find-replace */ function findReplace (array, testFn) { const found = []; const replaceWiths = arrayify$1(arguments); replaceWiths.splice(0, 2); arrayify$1(array).forEach((value, index) => { let expanded = []; replaceWiths.forEach(replaceWith => { if (typeof replaceWith === 'function') { expanded = expanded.concat(replaceWith(value)); } else { expanded.push(replaceWith); } }); if (testFn(value)) { found.push({ index: index, replaceWithValue: expanded }); } }); found.reverse().forEach(item => { const spliceArgs = [ item.index, 1 ].concat(item.replaceWithValue); array.splice.apply(array, spliceArgs); }); return array } /** * Some useful tools for working with `process.argv`. * * @module argv-tools * @typicalName argvTools * @example * const argvTools = require('argv-tools') */ /** * Regular expressions for matching option formats. * @static */ const re = { short: /^-([^\d-])$/, long: /^--(\S+)/, combinedShort: /^-[^\d-]{2,}$/, optEquals: /^(--\S+?)=(.*)/ }; /** * Array subclass encapsulating common operations on `process.argv`. * @static */ class ArgvArray extends Array { /** * Clears the array has loads the supplied input. * @param {string[]} argv - The argv list to load. Defaults to `process.argv`. */ load (argv) { this.clear(); if (argv && argv !== process.argv) { argv = arrayify(argv); } else { /* if no argv supplied, assume we are parsing process.argv */ argv = process.argv.slice(0); const deleteCount = process.execArgv.some(isExecArg) ? 1 : 2; argv.splice(0, deleteCount); } argv.forEach(arg => this.push(String(arg))); } /** * Clear the array. */ clear () { this.length = 0; } /** * expand ``--option=value` style args. */ expandOptionEqualsNotation () { if (this.some(arg => re.optEquals.test(arg))) { const expandedArgs = []; this.forEach(arg => { const matches = arg.match(re.optEquals); if (matches) { expandedArgs.push(matches[1], matches[2]); } else { expandedArgs.push(arg); } }); this.clear(); this.load(expandedArgs); } } /** * expand getopt-style combinedShort options. */ expandGetoptNotation () { if (this.hasCombinedShortOptions()) { findReplace(this, re.combinedShort, expandCombinedShortArg); } } /** * Returns true if the array contains combined short options (e.g. `-ab`). * @returns {boolean} */ hasCombinedShortOptions () { return this.some(arg => re.combinedShort.test(arg)) } static from (argv) { const result = new this(); result.load(argv); return result } } /** * Expand a combined short option. * @param {string} - the string to expand, e.g. `-ab` * @returns {string[]} * @static */ function expandCombinedShortArg (arg) { /* remove initial hypen */ arg = arg.slice(1); return arg.split('').map(letter => '-' + letter) } /** * Returns true if the supplied arg matches `--option=value` notation. * @param {string} - the arg to test, e.g. `--one=something` * @returns {boolean} * @static */ function isOptionEqualsNotation (arg) { return re.optEquals.test(arg) } /** * Returns true if the supplied arg is in either long (`--one`) or short (`-o`) format. * @param {string} - the arg to test, e.g. `--one` * @returns {boolean} * @static */ function isOption (arg) { return (re.short.test(arg) || re.long.test(arg)) && !re.optEquals.test(arg) } /** * Returns true if the supplied arg is in long (`--one`) format. * @param {string} - the arg to test, e.g. `--one` * @returns {boolean} * @static */ function isLongOption (arg) { return re.long.test(arg) && !isOptionEqualsNotation(arg) } /** * Returns the name from a long, short or `--options=value` arg. * @param {string} - the arg to inspect, e.g. `--one` * @returns {string} * @static */ function getOptionName (arg) { if (re.short.test(arg)) { return arg.match(re.short)[1] } else if (isLongOption(arg)) { return arg.match(re.long)[1] } else if (isOptionEqualsNotation(arg)) { return arg.match(re.optEquals)[1].replace(/^--/, '') } else { return null } } function isValue (arg) { return !(isOption(arg) || re.combinedShort.test(arg) || re.optEquals.test(arg)) } function isExecArg (arg) { return ['--eval', '-e'].indexOf(arg) > -1 || arg.startsWith('--eval=') } /** * For type-checking Javascript values. * @module typical * @typicalname t * @example * const t = require('typical') */ /** * Returns true if input is a number * @param {*} - the input to test * @returns {boolean} * @static * @example * > t.isNumber(0) * true * > t.isNumber(1) * true * > t.isNumber(1.1) * true * > t.isNumber(0xff) * true * > t.isNumber(0644) * true * > t.isNumber(6.2e5) * true * > t.isNumber(NaN) * false * > t.isNumber(Infinity) * false */ function isNumber (n) { return !isNaN(parseFloat(n)) && isFinite(n) } /** * A plain object is a simple object literal, it is not an instance of a class. Returns true if the input `typeof` is `object` and directly decends from `Object`. * * @param {*} - the input to test * @returns {boolean} * @static * @example * > t.isPlainObject({ something: 'one' }) * true * > t.isPlainObject(new Date()) * false * > t.isPlainObject([ 0, 1 ]) * false * > t.isPlainObject(/test/) * false * > t.isPlainObject(1) * false * > t.isPlainObject('one') * false * > t.isPlainObject(null) * false * > t.isPlainObject((function * () {})()) * false * > t.isPlainObject(function * () {}) * false */ function isPlainObject (input) { return input !== null && typeof input === 'object' && input.constructor === Object } /** * An array-like value has all the properties of an array, but is not an array instance. Examples in the `arguments` object. Returns true if the input value is an object, not null and has a `length` property with a numeric value. * * @param {*} - the input to test * @returns {boolean} * @static * @example * function sum(x, y){ * console.log(t.isArrayLike(arguments)) * // prints `true` * } */ function isArrayLike$2 (input) { return isObject$2(input) && typeof input.length === 'number' } /** * returns true if the typeof input is `'object'`, but not null! * @param {*} - the input to test * @returns {boolean} * @static */ function isObject$2 (input) { return typeof input === 'object' && input !== null } /** * Returns true if the input value is defined * @param {*} - the input to test * @returns {boolean} * @static */ function isDefined (input) { return typeof input !== 'undefined' } /** * Returns true if the input value is a string * @param {*} - the input to test * @returns {boolean} * @static */ function isString (input) { return typeof input === 'string' } /** * Returns true if the input value is a boolean * @param {*} - the input to test * @returns {boolean} * @static */ function isBoolean (input) { return typeof input === 'boolean' } /** * Returns true if the input value is a function * @param {*} - the input to test * @returns {boolean} * @static */ function isFunction (input) { return typeof input === 'function' } /** * Returns true if the input value is an es2015 `class`. * @param {*} - the input to test * @returns {boolean} * @static */ function isClass (input) { if (isFunction(input)) { return /^class /.test(Function.prototype.toString.call(input)) } else { return false } } /** * Returns true if the input is a string, number, symbol, boolean, null or undefined value. * @param {*} - the input to test * @returns {boolean} * @static */ function isPrimitive (input) { if (input === null) return true switch (typeof input) { case 'string': case 'number': case 'symbol': case 'undefined': case 'boolean': return true default: return false } } /** * Returns true if the input is a Promise. * @param {*} - the input to test * @returns {boolean} * @static */ function isPromise (input) { if (input) { const isPromise = isDefined(Promise) && input instanceof Promise; const isThenable = input.then && typeof input.then === 'function'; return !!(isPromise || isThenable) } else { return false } } /** * Returns true if the input is an iterable (`Map`, `Set`, `Array`, Generator etc.). * @param {*} - the input to test * @returns {boolean} * @static * @example * > t.isIterable('string') * true * > t.isIterable(new Map()) * true * > t.isIterable([]) * true * > t.isIterable((function * () {})()) * true * > t.isIterable(Promise.resolve()) * false * > t.isIterable(Promise) * false * > t.isIterable(true) * false * > t.isIterable({}) * false * > t.isIterable(0) * false * > t.isIterable(1.1) * false * > t.isIterable(NaN) * false * > t.isIterable(Infinity) * false * > t.isIterable(function () {}) * false * > t.isIterable(Date) * false * > t.isIterable() * false * > t.isIterable({ then: function () {} }) * false */ function isIterable (input) { if (input === null || !isDefined(input)) { return false } else { return ( typeof input[Symbol.iterator] === 'function' || typeof input[Symbol.asyncIterator] === 'function' ) } } var t = { isNumber, isString, isBoolean, isPlainObject, isArrayLike: isArrayLike$2, isObject: isObject$2, isDefined, isFunction, isClass, isPrimitive, isPromise, isIterable }; /** * @module option-definition */ /** * Describes a command-line option. Additionally, if generating a usage guide with [command-line-usage](https://github.com/75lb/command-line-usage) you could optionally add `description` and `typeLabel` properties to each definition. * * @alias module:option-definition * @typicalname option */ class OptionDefinition { constructor (definition) { /** * The only required definition property is `name`, so the simplest working example is * ```js * const optionDefinitions = [ * { name: 'file' }, * { name: 'depth' } * ] * ``` * * Where a `type` property is not specified it will default to `String`. * * | # | argv input | commandLineArgs() output | * | --- | -------------------- | ------------ | * | 1 | `--file` | `{ file: null }` | * | 2 | `--file lib.js` | `{ file: 'lib.js' }` | * | 3 | `--depth 2` | `{ depth: '2' }` | * * Unicode option names and aliases are valid, for example: * ```js * const optionDefinitions = [ * { name: 'один' }, * { name: '两' }, * { name: 'три', alias: 'т' } * ] * ``` * @type {string} */ this.name = definition.name; /** * The `type` value is a setter function (you receive the output from this), enabling you to be specific about the type and value received. * * The most common values used are `String` (the default), `Number` and `Boolean` but you can use a custom function, for example: * * ```js * const fs = require('fs') * * class FileDetails { * constructor (filename) { * this.filename = filename * this.exists = fs.existsSync(filename) * } * } * * const cli = commandLineArgs([ * { name: 'file', type: filename => new FileDetails(filename) }, * { name: 'depth', type: Number } * ]) * ``` * * | # | argv input | commandLineArgs() output | * | --- | ----------------- | ------------ | * | 1 | `--file asdf.txt` | `{ file: { filename: 'asdf.txt', exists: false } }` | * * The `--depth` option expects a `Number`. If no value was set, you will receive `null`. * * | # | argv input | commandLineArgs() output | * | --- | ----------------- | ------------ | * | 2 | `--depth` | `{ depth: null }` | * | 3 | `--depth 2` | `{ depth: 2 }` | * * @type {function} * @default String */ this.type = definition.type || String; /** * getopt-style short option names. Can be any single character (unicode included) except a digit or hyphen. * * ```js * const optionDefinitions = [ * { name: 'hot', alias: 'h', type: Boolean }, * { name: 'discount', alias: 'd', type: Boolean }, * { name: 'courses', alias: 'c' , type: Number } * ] * ``` * * | # | argv input | commandLineArgs() output | * | --- | ------------ | ------------ | * | 1 | `-hcd` | `{ hot: true, courses: null, discount: true }` | * | 2 | `-hdc 3` | `{ hot: true, discount: true, courses: 3 }` | * * @type {string} */ this.alias = definition.alias; /** * Set this flag if the option takes a list of values. You will receive an array of values, each passed through the `type` function (if specified). * * ```js * const optionDefinitions = [ * { name: 'files', type: String, multiple: true } * ] * ``` * * Note, examples 1 and 3 below demonstrate "greedy" parsing which can be disabled by using `lazyMultiple`. * * | # | argv input | commandLineArgs() output | * | --- | ------------ | ------------ | * | 1 | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` | * | 2 | `--files one.js --files two.js` | `{ files: [ 'one.js', 'two.js' ] }` | * | 3 | `--files *` | `{ files: [ 'one.js', 'two.js' ] }` | * * @type {boolean} */ this.multiple = definition.multiple; /** * Identical to `multiple` but with greedy parsing disabled. * * ```js * const optionDefinitions = [ * { name: 'files', lazyMultiple: true }, * { name: 'verbose', alias: 'v', type: Boolean, lazyMultiple: true } * ] * ``` * * | # | argv input | commandLineArgs() output | * | --- | ------------ | ------------ | * | 1 | `--files one.js --files two.js` | `{ files: [ 'one.js', 'two.js' ] }` | * | 2 | `-vvv` | `{ verbose: [ true, true, true ] }` | * * @type {boolean} */ this.lazyMultiple = definition.lazyMultiple; /** * Any values unaccounted for by an option definition will be set on the `defaultOption`. This flag is typically set on the most commonly-used option to make for more concise usage (i.e. `$ example *.js` instead of `$ example --files *.js`). * * ```js * const optionDefinitions = [ * { name: 'files', multiple: true, defaultOption: true } * ] * ``` * * | # | argv input | commandLineArgs() output | * | --- | ------------ | ------------ | * | 1 | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` | * | 2 | `one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` | * | 3 | `*` | `{ files: [ 'one.js', 'two.js' ] }` | * * @type {boolean} */ this.defaultOption = definition.defaultOption; /** * An initial value for the option. * * ```js * const optionDefinitions = [ * { name: 'files', multiple: true, defaultValue: [ 'one.js' ] }, * { name: 'max', type: Number, defaultValue: 3 } * ] * ``` * * | # | argv input | commandLineArgs() output | * | --- | ------------ | ------------ | * | 1 | | `{ files: [ 'one.js' ], max: 3 }` | * | 2 | `--files two.js` | `{ files: [ 'two.js' ], max: 3 }` | * | 3 | `--max 4` | `{ files: [ 'one.js' ], max: 4 }` | * * @type {*} */ this.defaultValue = definition.defaultValue; /** * When your app has a large amount of options it makes sense to organise them in groups. * * There are two automatic groups: `_all` (contains all options) and `_none` (contains options without a `group` specified in their definition). * * ```js * const optionDefinitions = [ * { name: 'verbose', group: 'standard' }, * { name: 'help', group: [ 'standard', 'main' ] }, * { name: 'compress', group: [ 'server', 'main' ] }, * { name: 'static', group: 'server' }, * { name: 'debug' } * ] * ``` * *
# | Command Line | commandLineArgs() output | *
---|---|---|
1 | --verbose |
|
*
2 | --debug |
|
*
3 | --verbose --debug --compress |
|
*
4 | --compress |
|
*