diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..2f6b644 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,136 @@ +declare namespace dargs { + interface Options { + /** + Keys or regex of keys to exclude. Takes precedence over `includes`. + */ + excludes?: ReadonlyArray; + + /** + Keys or regex of keys to include. + */ + includes?: ReadonlyArray; + + /** + Maps keys in `input` to an aliased name. Matching keys are converted to arguments with a single dash (`-`) in front of the aliased key and the value in a separate array item. Keys are still affected by `includes` and `excludes`. + */ + aliases?: {[key: string]: string}; + + /** + Setting this to `false` makes it return the key and value as separate array items instead of using a `=` separator in one item. This can be useful for tools that doesn't support `--foo=bar` style flags. + + @default true + + @example + ``` + console.log(dargs({foo: 'bar'}, {useEquals: false})); + // [ + // '--foo', 'bar' + // ] + ``` + */ + useEquals?: boolean; + + /** + Exclude `false` values. Can be useful when dealing with strict argument parsers that throw on unknown arguments like `--no-foo`. + + @default false + */ + ignoreFalse?: boolean; + + /** + By default, camelCased keys will be hyphenated. Enabling this will bypass the conversion process. + + @default false + + @example + ``` + console.log(dargs({fooBar: 'baz'})); + //=> ['--foo-bar', 'baz'] + + console.log(dargs({fooBar: 'baz'}, {allowCamelCase: true})); + //=> ['--fooBar', 'baz'] + ``` + */ + allowCamelCase?: boolean; + } +} + +/** +Reverse [`minimist`](https://github.com/substack/minimist). Convert an object of options into an array of command-line arguments. + +@param input - Object to convert to command-line arguments. + +@example +``` +import dargs = require('dargs'); + +const input = { + _: ['some', 'option'], // Values in '_' will be appended to the end of the generated argument list + '--': ['separated', 'option'], // Values in '--' will be put at the very end of the argument list after the escape option (`--`) + foo: 'bar', + hello: true, // Results in only the key being used + cake: false, // Prepends `no-` before the key + camelCase: 5, // CamelCase is slugged to `camel-case` + multiple: ['value', 'value2'], // Converted to multiple arguments + pieKind: 'cherry', + sad: ':(' +}; + +const excludes = ['sad', /.*Kind$/]; // Excludes and includes accept regular expressions +const includes = ['camelCase', 'multiple', 'sad', /^pie.+/]; +const aliases = {file: 'f'}; + +console.log(dargs(input, {excludes})); +// [ +// '--foo=bar', +// '--hello', +// '--no-cake', +// '--camel-case=5', +// '--multiple=value', +// '--multiple=value2', +// 'some', +// 'option', +// '--', +// 'separated', +// 'option' +// ] + +console.log(dargs(input, {excludes, includes})); +// [ +// '--camel-case=5', +// '--multiple=value', +// '--multiple=value2' +// ] + + +console.log(dargs(input, {includes})); +// [ +// '--camel-case=5', +// '--multiple=value', +// '--multiple=value2', +// '--pie-kind=cherry', +// '--sad=:(' +// ] + + +console.log(dargs({ + foo: 'bar', + hello: true, + file: 'baz' +}, {aliases})); +// [ +// '--foo=bar', +// '--hello', +// '-f', 'baz' +// ] +``` +*/ +declare function dargs( + input: { + '--'?: string[]; + _?: string[]; + } & {[key: string]: string | boolean | number | string[]}, + options?: dargs.Options +): string[]; + +export = dargs; diff --git a/index.js b/index.js index 733e94c..0eab8f9 100644 --- a/index.js +++ b/index.js @@ -1,92 +1,101 @@ 'use strict'; -const match = (array, value) => array.some(x => x instanceof RegExp ? x.test(value) : x === value); +const match = (array, value) => + array.some(x => (x instanceof RegExp ? x.test(value) : x === value)); -module.exports = (input, opts) => { +const dargs = (input, options) => { const args = []; let extraArgs = []; let separatedArgs = []; - opts = Object.assign({ + options = Object.assign({ useEquals: true - }, opts); + }, options); - const makeArg = (key, val) => { - key = '--' + (opts.allowCamelCase ? key : key.replace(/[A-Z]/g, '-$&').toLowerCase()); + const makeArg = (key, value) => { + key = + '--' + + (options.allowCamelCase ? + key : + key.replace(/[A-Z]/g, '-$&').toLowerCase()); - if (opts.useEquals) { - args.push(key + (val ? `=${val}` : '')); + if (options.useEquals) { + args.push(key + (value ? `=${value}` : '')); } else { args.push(key); - if (val) { - args.push(val); + if (value) { + args.push(value); } } }; - const makeAliasArg = (key, val) => { + const makeAliasArg = (key, value) => { args.push(`-${key}`); - if (val) { - args.push(val); + if (value) { + args.push(value); } }; // TODO: Use Object.entries() when targeting Node.js 8 for (let key of Object.keys(input)) { - const val = input[key]; + const value = input[key]; let pushArg = makeArg; - if (Array.isArray(opts.excludes) && match(opts.excludes, key)) { + if (Array.isArray(options.excludes) && match(options.excludes, key)) { continue; } - if (Array.isArray(opts.includes) && !match(opts.includes, key)) { + if (Array.isArray(options.includes) && !match(options.includes, key)) { continue; } - if (typeof opts.aliases === 'object' && opts.aliases[key]) { - key = opts.aliases[key]; + if (typeof options.aliases === 'object' && options.aliases[key]) { + key = options.aliases[key]; pushArg = makeAliasArg; } if (key === '--') { - if (!Array.isArray(val)) { - throw new TypeError(`Expected key \`--\` to be Array, got ${typeof val}`); + if (!Array.isArray(value)) { + throw new TypeError( + `Expected key \`--\` to be Array, got ${typeof value}` + ); } - separatedArgs = val; + separatedArgs = value; continue; } if (key === '_') { - if (!Array.isArray(val)) { - throw new TypeError(`Expected key \`_\` to be Array, got ${typeof val}`); + if (!Array.isArray(value)) { + throw new TypeError( + `Expected key \`_\` to be Array, got ${typeof value}` + ); } - extraArgs = val; + extraArgs = value; continue; } - if (val === true) { + if (value === true) { pushArg(key, ''); } - if (val === false && !opts.ignoreFalse) { + if (value === false && !options.ignoreFalse) { pushArg(`no-${key}`); } - if (typeof val === 'string') { - pushArg(key, val); + if (typeof value === 'string') { + pushArg(key, value); } - if (typeof val === 'number' && !Number.isNaN(val)) { - pushArg(key, String(val)); + if (typeof value === 'number' && !Number.isNaN(value)) { + pushArg(key, String(value)); } - if (Array.isArray(val)) { - for (const arrayValue of val) { + if (Array.isArray(value)) { + for (const arrayValue of value) { pushArg(key, arrayValue); } } @@ -106,3 +115,5 @@ module.exports = (input, opts) => { return args; }; + +module.exports = dargs; diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..c2e1d50 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,27 @@ +import {expectType, expectError} from 'tsd'; +import dargs = require('.'); + +const input = { + _: ['some', 'option'], + foo: 'bar', + hello: true, + cake: false, + camelCase: 5, + multiple: ['value', 'value2'], + pieKind: 'cherry', + sad: ':(' +}; + +const excludes = ['sad', /.*Kind$/]; +const includes = ['camelCase', 'multiple', 'sad', /^pie.*/]; +const aliases = {file: 'f'}; + +expectType(dargs(input, {excludes})); +expectType(dargs(input, {includes})); +expectType(dargs(input, {aliases})); +expectType(dargs(input, {useEquals: false})); +expectType(dargs(input, {ignoreFalse: true})); +expectType(dargs(input, {allowCamelCase: true})); + +expectError(dargs({_: 'foo'})); +expectError(dargs({'--': 'foo'})); diff --git a/package.json b/package.json index cbf427d..a467be8 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,11 @@ "node": ">=6" }, "scripts": { - "test": "xo && ava" + "test": "xo && ava && tsd" }, "files": [ - "index.js" + "index.js", + "index.d.ts" ], "keywords": [ "reverse", @@ -42,7 +43,8 @@ "argv" ], "devDependencies": { - "ava": "*", - "xo": "*" + "ava": "^1.4.1", + "tsd": "^0.7.2", + "xo": "^0.24.0" } } diff --git a/readme.md b/readme.md index 00b50af..16ee4eb 100644 --- a/readme.md +++ b/readme.md @@ -19,6 +19,7 @@ const dargs = require('dargs'); const input = { _: ['some', 'option'], // Values in '_' will be appended to the end of the generated argument list + '--': ['separated', 'option'], // Values in '--' will be put at the very end of the argument list after the escape option (`--`) foo: 'bar', hello: true, // Results in only the key being used cake: false, // Prepends `no-` before the key @@ -42,6 +43,9 @@ console.log(dargs(input, {excludes})); '--multiple=value', '--multiple=value2', 'some', + 'option', + '--', + 'separated', 'option' ] */ diff --git a/test.js b/test.js index 752d7a8..95b5ded 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ import test from 'ava'; -import m from '.'; +import dargs from '.'; const fixture = { _: ['some', 'option'], @@ -17,7 +17,7 @@ const fixture = { }; test('convert options to cli flags', t => { - t.deepEqual(m(fixture), [ + t.deepEqual(dargs(fixture), [ '--a=foo', '--b', '--no-c', @@ -38,25 +38,31 @@ test('convert options to cli flags', t => { }); test('raises a TypeError if \'_\' value is not an Array', t => { - t.throws(m.bind(m, {a: 'foo', _: 'baz'}), TypeError); + t.throws(dargs.bind(dargs, {a: 'foo', _: 'baz'}), TypeError); }); test('raises a TypeError if \'--\' value is not an Array', t => { - t.throws(m.bind(m, {a: 'foo', '--': 'baz'}), TypeError); + t.throws(dargs.bind(dargs, {a: 'foo', '--': 'baz'}), TypeError); }); test('useEquals options', t => { - t.deepEqual(m(fixture, { + t.deepEqual(dargs(fixture, { useEquals: false }), [ - '--a', 'foo', + '--a', + 'foo', '--b', '--no-c', - '--d', '5', - '--e', 'foo', - '--e', 'bar', - '--h', 'with a space', - '--i', 'let\'s try quotes', + '--d', + '5', + '--e', + 'foo', + '--e', + 'bar', + '--h', + 'with a space', + '--i', + 'let\'s try quotes', '--camel-case-camel', 'some', 'option', @@ -69,7 +75,7 @@ test('useEquals options', t => { }); test('exclude options', t => { - t.deepEqual(m(fixture, {excludes: ['b', /^e$/, 'h', 'i']}), [ + t.deepEqual(dargs(fixture, {excludes: ['b', /^e$/, 'h', 'i']}), [ '--a=foo', '--no-c', '--d=5', @@ -85,7 +91,7 @@ test('exclude options', t => { }); test('includes options', t => { - t.deepEqual(m(fixture, {includes: ['a', 'c', 'd', 'e', /^camelCase.*/]}), [ + t.deepEqual(dargs(fixture, {includes: ['a', 'c', 'd', 'e', /^camelCase.*/]}), [ '--a=foo', '--no-c', '--d=5', @@ -96,7 +102,7 @@ test('includes options', t => { }); test('excludes and includes options', t => { - t.deepEqual(m(fixture, { + t.deepEqual(dargs(fixture, { excludes: ['a', 'd'], includes: ['a', 'c', /^[de]$/, 'camelCaseCamel'] }), [ @@ -108,24 +114,26 @@ test('excludes and includes options', t => { }); test('option to ignore false values', t => { - t.deepEqual(m({foo: false}, {ignoreFalse: true}), []); + t.deepEqual(dargs({foo: false}, {ignoreFalse: true}), []); }); test('aliases option', t => { - t.deepEqual(m({a: 'foo', file: 'test'}, { + t.deepEqual(dargs({a: 'foo', file: 'test'}, { aliases: {file: 'f'} }), [ '--a=foo', - '-f', 'test' + '-f', + 'test' ]); }); test('includes and aliases options', t => { - t.deepEqual(m(fixture, { + t.deepEqual(dargs(fixture, { includes: ['a', 'c', 'd', 'e', 'camelCaseCamel'], aliases: {a: 'a'} }), [ - '-a', 'foo', + '-a', + 'foo', '--no-c', '--d=5', '--e=foo', @@ -135,7 +143,7 @@ test('includes and aliases options', t => { }); test('camelCase option', t => { - t.deepEqual(m(fixture, { + t.deepEqual(dargs(fixture, { allowCamelCase: true }), [ '--a=foo',