args
Advanced tools
Comparing version 2.3.0 to 2.4.0
461
index.js
@@ -1,13 +0,12 @@ | ||
'use strict' | ||
'use strict'; | ||
// Native | ||
const path = require('path') | ||
const spawn = require('child_process').spawn | ||
const path = require('path'); | ||
const spawn = require('child_process').spawn; | ||
// Packages | ||
const parser = require('minimist') | ||
const pkginfo = require('pkginfo') | ||
const loudRejection = require('loud-rejection') | ||
const camelcase = require('camelcase') | ||
const chalk = require('chalk') | ||
const parser = require('minimist'); | ||
const pkginfo = require('pkginfo'); | ||
const camelcase = require('camelcase'); | ||
const chalk = require('chalk'); | ||
@@ -19,4 +18,5 @@ class Args { | ||
options: [], | ||
commands: [] | ||
} | ||
commands: [], | ||
examples: [] | ||
}; | ||
@@ -29,33 +29,60 @@ // Configuration defaults | ||
value: null, | ||
name: null | ||
name: null, | ||
mainColor: 'yellow', | ||
subColor: 'dim' | ||
}; | ||
this.printMainColor = chalk; | ||
this.printSubColor = chalk; | ||
} | ||
example(usage, description) { | ||
if (typeof usage !== 'string' || typeof description !== 'string') { | ||
throw new Error( | ||
'Usage for adding an Example: args.example("usage", "description")' | ||
); | ||
} | ||
this.details.examples.push({ usage, description }); | ||
// Make unhandled promise rejections fail loudly instead of the default silent fail | ||
loudRejection() | ||
return this; | ||
} | ||
examples(list) { | ||
if (list.constructor !== Array) { | ||
throw new Error('Item passed to .examples is not an array'); | ||
} | ||
for (const item of list) { | ||
const usage = item.usage || false; | ||
const description = item.description || false; | ||
this.example(usage, description); | ||
} | ||
return this; | ||
} | ||
options(list) { | ||
if (list.constructor !== Array) { | ||
throw new Error('Item passed to .options is not an array') | ||
throw new Error('Item passed to .options is not an array'); | ||
} | ||
for (const item of list) { | ||
const preset = item.defaultValue || false | ||
const init = item.init || false | ||
const preset = item.defaultValue || false; | ||
const init = item.init || false; | ||
this.option(item.name, item.description, preset, init) | ||
this.option(item.name, item.description, preset, init); | ||
} | ||
return this | ||
return this; | ||
} | ||
option(name, description, defaultValue, init) { | ||
let usage = [] | ||
let usage = []; | ||
const assignShort = (name, options, short) => { | ||
if (options.find(flagName => flagName.usage[0] === short)) { | ||
short = name.charAt(0).toUpperCase() | ||
short = name.charAt(0).toUpperCase(); | ||
} | ||
return [short, name] | ||
} | ||
return [short, name]; | ||
}; | ||
@@ -66,9 +93,9 @@ // If name is an array, pick the values | ||
case String: | ||
usage = assignShort(name, this.details.options, name.charAt(0)) | ||
break | ||
usage = assignShort(name, this.details.options, name.charAt(0)); | ||
break; | ||
case Array: | ||
usage = usage.concat(name) | ||
break | ||
usage = usage.concat(name); | ||
break; | ||
default: | ||
throw new Error('Invalid name for option') | ||
throw new Error('Invalid name for option'); | ||
} | ||
@@ -78,3 +105,3 @@ | ||
if (usage.length > 0 && usage[0].length > 1) { | ||
throw new Error('Short version of option is longer than 1 char') | ||
throw new Error('Short version of option is longer than 1 char'); | ||
} | ||
@@ -86,18 +113,18 @@ | ||
description | ||
} | ||
}; | ||
let defaultIsWrong | ||
let defaultIsWrong; | ||
switch (defaultValue) { | ||
case false: | ||
defaultIsWrong = true | ||
break | ||
defaultIsWrong = true; | ||
break; | ||
case null: | ||
defaultIsWrong = true | ||
break | ||
defaultIsWrong = true; | ||
break; | ||
case undefined: | ||
defaultIsWrong = true | ||
break | ||
defaultIsWrong = true; | ||
break; | ||
default: | ||
defaultIsWrong = false | ||
defaultIsWrong = false; | ||
} | ||
@@ -107,11 +134,13 @@ | ||
if (!defaultIsWrong) { | ||
const initFunction = typeof init === 'function' | ||
optionDetails.init = initFunction ? init : this.handleType(defaultValue)[1] | ||
const initFunction = typeof init === 'function'; | ||
optionDetails.init = initFunction | ||
? init | ||
: this.handleType(defaultValue)[1]; | ||
} | ||
// Register option to global scope | ||
this.details.options.push(optionDetails) | ||
this.details.options.push(optionDetails); | ||
// Allow chaining of .option() | ||
return this | ||
return this; | ||
} | ||
@@ -121,7 +150,7 @@ | ||
if (Array.isArray(init)) { | ||
aliases = init | ||
init = undefined | ||
aliases = init; | ||
init = undefined; | ||
} | ||
if (aliases && Array.isArray(aliases)) { | ||
usage = [].concat([usage], aliases) | ||
usage = [].concat([usage], aliases); | ||
} | ||
@@ -134,13 +163,13 @@ | ||
init: typeof init === 'function' ? init : false | ||
}) | ||
}); | ||
// Allow chaining of .command() | ||
return this | ||
return this; | ||
} | ||
handleType(value) { | ||
let type = value | ||
let type = value; | ||
if (typeof value !== 'function') { | ||
type = value.constructor | ||
type = value.constructor; | ||
} | ||
@@ -152,10 +181,10 @@ | ||
case String: | ||
return ['[value]'] | ||
return ['[value]']; | ||
case Array: | ||
return ['<list>'] | ||
return ['<list>']; | ||
case Number: | ||
case parseInt: | ||
return ['<n>', parseInt] | ||
return ['<n>', parseInt]; | ||
default: | ||
return [''] | ||
return ['']; | ||
} | ||
@@ -165,10 +194,10 @@ } | ||
readOption(option) { | ||
let value = false | ||
const contents = {} | ||
let value = false; | ||
const contents = {}; | ||
// If option has been used, get its value | ||
for (const name of option.usage) { | ||
const fromArgs = this.raw[name] | ||
const fromArgs = this.raw[name]; | ||
if (typeof fromArgs !== 'undefined') { | ||
value = fromArgs | ||
value = fromArgs; | ||
} | ||
@@ -179,9 +208,12 @@ } | ||
for (let name of option.usage) { | ||
let propVal = value | ||
let propVal = value; | ||
if (typeof option.defaultValue !== 'undefined' && typeof propVal !== typeof option.defaultValue) { | ||
propVal = option.defaultValue | ||
if ( | ||
typeof option.defaultValue !== 'undefined' && | ||
typeof propVal !== typeof option.defaultValue | ||
) { | ||
propVal = option.defaultValue; | ||
} | ||
let condition = true | ||
let condition = true; | ||
@@ -191,3 +223,3 @@ if (option.init) { | ||
if (option.init === toString) { | ||
condition = propVal.constructor === Number | ||
condition = propVal.constructor === Number; | ||
} | ||
@@ -197,3 +229,3 @@ | ||
// Pass it through the initializer | ||
propVal = option.init(propVal) | ||
propVal = option.init(propVal); | ||
} | ||
@@ -204,19 +236,19 @@ } | ||
if (name.length > 1) { | ||
name = camelcase(name) | ||
name = camelcase(name); | ||
} | ||
// Add option to list | ||
contents[name] = propVal | ||
contents[name] = propVal; | ||
} | ||
return contents | ||
return contents; | ||
} | ||
getOptions() { | ||
const options = {} | ||
const args = {} | ||
const options = {}; | ||
const args = {}; | ||
// Copy over the arguments | ||
Object.assign(args, this.raw) | ||
delete args._ | ||
Object.assign(args, this.raw); | ||
delete args._; | ||
@@ -226,6 +258,6 @@ // Set option defaults | ||
if (typeof option.defaultValue === 'undefined') { | ||
continue | ||
continue; | ||
} | ||
Object.assign(options, this.readOption(option)) | ||
Object.assign(options, this.readOption(option)); | ||
} | ||
@@ -236,41 +268,62 @@ | ||
if (!{}.hasOwnProperty.call(args, option)) { | ||
continue | ||
continue; | ||
} | ||
const related = this.isDefined(option, 'options') | ||
const related = this.isDefined(option, 'options'); | ||
if (related) { | ||
const details = this.readOption(related) | ||
Object.assign(options, details) | ||
const details = this.readOption(related); | ||
Object.assign(options, details); | ||
} | ||
} | ||
return options | ||
return options; | ||
} | ||
generateExamples() { | ||
const examples = this.details.examples; | ||
const parts = []; | ||
for (const item in examples) { | ||
if (!{}.hasOwnProperty.call(examples, item)) { | ||
continue; | ||
} | ||
const usage = this.printSubColor('$ ' + examples[item].usage); | ||
const description = this.printMainColor( | ||
'- ' + examples[item].description | ||
); | ||
parts.push(` ${description}\n\n ${usage}\n\n`); | ||
} | ||
return parts; | ||
} | ||
generateDetails(kind) { | ||
// Get all properties of kind from global scope | ||
const items = this.details[kind] | ||
const parts = [] | ||
const isCmd = kind === 'commands' | ||
const items = this.details[kind]; | ||
const parts = []; | ||
const isCmd = kind === 'commands'; | ||
// Sort items alphabetically | ||
items.sort((a, b) => { | ||
const first = isCmd ? a.usage : a.usage[1] | ||
const second = isCmd ? b.usage : b.usage[1] | ||
const first = isCmd ? a.usage : a.usage[1]; | ||
const second = isCmd ? b.usage : b.usage[1]; | ||
switch (true) { | ||
case (first < second): return -1 | ||
case (first > second): return 1 | ||
default: return 0 | ||
case first < second: | ||
return -1; | ||
case first > second: | ||
return 1; | ||
default: | ||
return 0; | ||
} | ||
}) | ||
}); | ||
for (const item in items) { | ||
if (!{}.hasOwnProperty.call(items, item)) { | ||
continue | ||
continue; | ||
} | ||
let usage = items[item].usage | ||
let initial = items[item].defaultValue | ||
let usage = items[item].usage; | ||
let initial = items[item].defaultValue; | ||
@@ -280,12 +333,14 @@ // If usage is an array, show its contents | ||
if (isCmd) { | ||
usage = usage.join(', ') | ||
usage = usage.join(', '); | ||
} else { | ||
const isVersion = usage.indexOf('v') | ||
usage = `-${usage[0]}, --${usage[1]}` | ||
const isVersion = usage.indexOf('v'); | ||
usage = `-${usage[0]}, --${usage[1]}`; | ||
if (!initial) { | ||
initial = items[item].init | ||
initial = items[item].init; | ||
} | ||
usage += (initial && isVersion === -1) ? ' ' + this.handleType(initial)[0] : '' | ||
usage += initial && isVersion === -1 | ||
? ' ' + this.handleType(initial)[0] | ||
: ''; | ||
} | ||
@@ -295,3 +350,3 @@ } | ||
// Overwrite usage with readable syntax | ||
items[item].usage = usage | ||
items[item].usage = usage; | ||
} | ||
@@ -302,13 +357,13 @@ | ||
const longest = items.slice().sort((a, b) => { | ||
return b.usage.length - a.usage.length | ||
})[0].usage.length | ||
return b.usage.length - a.usage.length; | ||
})[0].usage.length; | ||
for (const item of items) { | ||
let usage = item.usage | ||
let description = item.description | ||
const defaultValue = item.defaultValue | ||
const difference = longest - usage.length | ||
let usage = item.usage; | ||
let description = item.description; | ||
const defaultValue = item.defaultValue; | ||
const difference = longest - usage.length; | ||
// Compensate the difference to longest property with spaces | ||
usage += ' '.repeat(difference) | ||
usage += ' '.repeat(difference); | ||
@@ -318,11 +373,16 @@ // Add some space around it as well | ||
if (typeof defaultValue === 'boolean') { | ||
description += ` (${defaultValue ? 'enabled' : 'disabled'} by default)` | ||
description += ` (${defaultValue ? 'enabled' : 'disabled'} by default)`; | ||
} else { | ||
description += ` (defaults to ${JSON.stringify(defaultValue)})` | ||
description += ` (defaults to ${JSON.stringify(defaultValue)})`; | ||
} | ||
} | ||
parts.push(' ' + chalk.yellow(usage) + ' ' + chalk.dim(description)) | ||
parts.push( | ||
' ' + | ||
this.printMainColor(usage) + | ||
' ' + | ||
this.printSubColor(description) | ||
); | ||
} | ||
return parts | ||
return parts; | ||
} | ||
@@ -333,3 +393,3 @@ | ||
if (details.usage === 'help' && !this.config.help) { | ||
details.init = false | ||
details.init = false; | ||
} | ||
@@ -339,17 +399,19 @@ | ||
if (details.init) { | ||
const sub = [].concat(this.sub) | ||
sub.shift() | ||
const sub = [].concat(this.sub); | ||
sub.shift(); | ||
return details.init.bind(this)(details.usage, sub, options) | ||
return details.init.bind(this)(details.usage, sub, options); | ||
} | ||
// Generate full name of binary | ||
const full = this.binary + '-' + (Array.isArray(details.usage) ? details.usage[0] : details.usage) | ||
const full = this.binary + | ||
'-' + | ||
(Array.isArray(details.usage) ? details.usage[0] : details.usage); | ||
const args = process.argv | ||
let i = 0 | ||
const args = process.argv; | ||
let i = 0; | ||
while (i < 3) { | ||
args.shift() | ||
i++ | ||
args.shift(); | ||
i++; | ||
} | ||
@@ -360,19 +422,19 @@ | ||
stdio: 'inherit' | ||
}) | ||
}); | ||
// Throw an error if something fails within that binary | ||
this.child.on('error', err => { | ||
throw err | ||
}) | ||
throw err; | ||
}); | ||
this.child.on('exit', (code, signal) => { | ||
process.on('exit', () => { | ||
this.child = null | ||
this.child = null; | ||
if (signal) { | ||
process.kill(process.pid, signal) | ||
process.kill(process.pid, signal); | ||
} else { | ||
process.exit(code) | ||
process.exit(code); | ||
} | ||
}) | ||
}) | ||
}); | ||
}); | ||
@@ -382,25 +444,27 @@ // proxy SIGINT to child process | ||
if (this.child) { | ||
this.child.kill('SIGINT') | ||
this.child.kill('SIGTERM') // if that didn't work, we're probably in an infinite loop, so make it die | ||
this.child.kill('SIGINT'); | ||
this.child.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die | ||
} | ||
}) | ||
}); | ||
} | ||
checkVersion() { | ||
const parent = module.parent | ||
const parent = module.parent; | ||
// Load parent module | ||
pkginfo(parent) | ||
pkginfo(parent); | ||
// And get its version propery | ||
const version = parent.exports.version | ||
const version = parent.exports.version; | ||
if (version) { | ||
// If it exists, register it as a default option | ||
this.option('version', 'Output the version number') | ||
this.option('version', 'Output the version number'); | ||
// And immediately output it if used in command line | ||
if (this.raw.v || this.raw.version) { | ||
console.log(version) | ||
process.exit() | ||
console.log(version); | ||
// eslint-disable-next-line unicorn/no-process-exit | ||
process.exit(); | ||
} | ||
@@ -412,15 +476,15 @@ } | ||
// Get all items of kind | ||
const children = this.details[list] | ||
const children = this.details[list]; | ||
// Check if a child matches the requested name | ||
for (const child of children) { | ||
const usage = child.usage | ||
const type = usage.constructor | ||
const usage = child.usage; | ||
const type = usage.constructor; | ||
if (type === Array && usage.indexOf(name) > -1) { | ||
return child | ||
return child; | ||
} | ||
if (type === String && usage === name) { | ||
return child | ||
return child; | ||
} | ||
@@ -430,3 +494,3 @@ } | ||
// If nothing matches, item is not defined | ||
return false | ||
return false; | ||
} | ||
@@ -436,36 +500,60 @@ | ||
// Override default option values | ||
Object.assign(this.config, options) | ||
Object.assign(this.config, options); | ||
if (Array.isArray(this.config.mainColor)) { | ||
for (const item in this.config.mainColor) { | ||
if (!{}.hasOwnProperty.call(this.config.mainColor, item)) { | ||
continue; | ||
} | ||
// chain all colors to our print method | ||
this.printMainColor = this.printMainColor[this.config.mainColor[item]]; | ||
} | ||
} else { | ||
this.printMainColor = this.printMainColor[this.config.mainColor]; | ||
} | ||
if (Array.isArray(this.config.subColor)) { | ||
for (const item in this.config.subColor) { | ||
if (!{}.hasOwnProperty.call(this.config.subColor, item)) { | ||
continue; | ||
} | ||
// chain all colors to our print method | ||
this.printSubColor = this.printSubColor[this.config.subColor[item]]; | ||
} | ||
} else { | ||
this.printSubColor = this.printSubColor[this.config.subColor]; | ||
} | ||
if (this.config.help) { | ||
// Register default options and commands | ||
this.option('help', 'Output usage information') | ||
this.command('help', 'Display help', this.showHelp) | ||
this.option('help', 'Output usage information'); | ||
this.command('help', 'Display help', this.showHelp); | ||
} | ||
// Parse arguments using minimist | ||
this.raw = parser(argv.slice(1), this.config.minimist) | ||
this.binary = path.basename(this.raw._[0]) | ||
this.raw = parser(argv.slice(1), this.config.minimist); | ||
this.binary = path.basename(this.raw._[0]); | ||
// If default version is allowed, check for it | ||
if (this.config.version) { | ||
this.checkVersion() | ||
this.checkVersion(); | ||
} | ||
const subCommand = this.raw._[1] | ||
const helpTriggered = this.raw.h || this.raw.help | ||
const subCommand = this.raw._[1]; | ||
const helpTriggered = this.raw.h || this.raw.help; | ||
const args = {} | ||
const defined = this.isDefined(subCommand, 'commands') | ||
const optionList = this.getOptions() | ||
const args = {}; | ||
const defined = this.isDefined(subCommand, 'commands'); | ||
const optionList = this.getOptions(); | ||
Object.assign(args, this.raw) | ||
args._.shift() | ||
Object.assign(args, this.raw); | ||
args._.shift(); | ||
// Export sub arguments of command | ||
this.sub = args._ | ||
this.sub = args._; | ||
// If sub command is defined, run it | ||
if (defined) { | ||
this.runCommand(defined, optionList) | ||
return {} | ||
this.runCommand(defined, optionList); | ||
return {}; | ||
} | ||
@@ -476,77 +564,82 @@ | ||
if (this.config.help && helpTriggered) { | ||
this.showHelp() | ||
this.showHelp(); | ||
} | ||
// Hand back list of options | ||
return optionList | ||
return optionList; | ||
} | ||
showHelp() { | ||
const name = this.config.name || this.binary.replace('-', ' ') | ||
const firstBig = word => word.charAt(0).toUpperCase() + word.substr(1) | ||
const name = this.config.name || this.binary.replace('-', ' '); | ||
const firstBig = word => word.charAt(0).toUpperCase() + word.substr(1); | ||
const parts = [] | ||
const parts = []; | ||
const groups = { | ||
commands: true, | ||
options: true | ||
} | ||
options: true, | ||
examples: true | ||
}; | ||
for (const group in groups) { | ||
if (this.details[group].length > 0) { | ||
continue | ||
continue; | ||
} | ||
groups[group] = false | ||
groups[group] = false; | ||
} | ||
const optionHandle = groups.options ? ' [options]' : '' | ||
const cmdHandle = groups.commands ? ' [command]' : '' | ||
const value = typeof this.config.value === 'string' ? ' ' + this.config.value : '' | ||
const optionHandle = groups.options ? '[options] ' : ''; | ||
const cmdHandle = groups.commands ? '[command]' : ''; | ||
const value = typeof this.config.value === 'string' | ||
? ' ' + this.config.value | ||
: ''; | ||
parts.push([ | ||
'', | ||
'Usage: ' + chalk.yellow(name) + chalk.dim(optionHandle + cmdHandle + value), | ||
`Usage: ${this.printMainColor(name)} ${this.printSubColor(optionHandle + cmdHandle + value)}`, | ||
'' | ||
]) | ||
]); | ||
for (const group in groups) { | ||
if (!groups[group]) { | ||
continue | ||
continue; | ||
} | ||
parts.push([ | ||
'', | ||
firstBig(group) + ':', | ||
'', | ||
'' | ||
]) | ||
parts.push(['', firstBig(group) + ':', '', '']); | ||
parts.push(this.generateDetails(group)) | ||
parts.push(['', '']) | ||
if (group === 'examples') { | ||
parts.push(this.generateExamples()); | ||
} else { | ||
parts.push(this.generateDetails(group)); | ||
} | ||
parts.push(['', '']); | ||
} | ||
let output = '' | ||
let output = ''; | ||
// And finally, merge and output them | ||
for (const part of parts) { | ||
output += part.join('\n ') | ||
output += part.join('\n '); | ||
} | ||
if (!groups.commands && !groups.options) { | ||
output = 'No sub commands or options available' | ||
output = 'No sub commands or options available'; | ||
} | ||
const usageFilter = this.config.usageFilter | ||
const usageFilter = this.config.usageFilter; | ||
// If filter is available, pass usage information through | ||
if (typeof usageFilter === 'function') { | ||
output = usageFilter(output) || output | ||
output = usageFilter(output) || output; | ||
} | ||
console.log(output) | ||
process.exit() | ||
console.log(output); | ||
// eslint-disable-next-line unicorn/no-process-exit | ||
process.exit(); | ||
} | ||
} | ||
module.exports = exports.default = new Args() | ||
module.exports = (exports.default = new Args()); |
{ | ||
"name": "args", | ||
"version": "2.3.0", | ||
"version": "2.4.0", | ||
"description": "Minimal toolkit for building CLIs", | ||
@@ -9,7 +9,9 @@ "files": [ | ||
"scripts": { | ||
"test": "xo && ava" | ||
"precommit": "lint-staged", | ||
"test": "npm run lint && ava", | ||
"lint": "xo" | ||
}, | ||
"repository": "leo/args", | ||
"engines": { | ||
"node": ">= 4.0.0" | ||
"node": ">= 6.6.0" | ||
}, | ||
@@ -26,12 +28,11 @@ "keywords": [ | ||
"xo": { | ||
"esnext": true, | ||
"space": true, | ||
"semicolon": false, | ||
"rules": { | ||
"unicorn/no-process-exit": 0, | ||
"max-lines": 0, | ||
"ava/no-ignored-test-files": 0, | ||
"import/no-unresolved": 0 | ||
} | ||
"extends": "prettier" | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"npm run lint", | ||
"prettier --single-quote --write", | ||
"git add" | ||
] | ||
}, | ||
"author": "leo", | ||
@@ -44,4 +45,8 @@ "license": "MIT", | ||
"devDependencies": { | ||
"ava": "0.18.0", | ||
"ava": "0.18.2", | ||
"eslint-config-prettier": "1.5.0", | ||
"execa": "0.6.0", | ||
"husky": "0.13.3", | ||
"lint-staged": "3.4.0", | ||
"prettier": "0.22.0", | ||
"xo": "0.17.0" | ||
@@ -52,3 +57,2 @@ }, | ||
"chalk": "1.1.3", | ||
"loud-rejection": "1.3.0", | ||
"minimist": "1.2.0", | ||
@@ -55,0 +59,0 @@ "pkginfo": "0.4.0" |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
26994
4
499
202
7
2
- Removedloud-rejection@1.3.0
- Removedarray-find-index@1.0.2(transitive)
- Removedloud-rejection@1.3.0(transitive)
- Removedsignal-exit@2.1.2(transitive)