Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add isMultiple option for flags #143

Merged
merged 20 commits into from May 5, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
node_modules
yarn.lock
.vscode
ulken marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 4 additions & 1 deletion index.d.ts
Expand Up @@ -7,6 +7,7 @@ declare namespace meow {
readonly type?: Type;
readonly alias?: string;
readonly default?: Default;
readonly multiple?: boolean;
}

type StringFlag = Flag<'string', string>;
Expand All @@ -24,14 +25,16 @@ declare namespace meow {
- `type`: Type of value. (Possible values: `string` `boolean` `number`)
- `alias`: Usually used to define a short flag alias.
- `default`: Default value when the flag is not specified.
- `multiple`: Indicates a flag can be set multiple times. Returns an array. (Default: false)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What returns an array? It's not clear enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point. Maybe drop the word return altogether?

How about Values are turned into an array (in order?)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with the shorter form


@example
```
flags: {
unicorn: {
type: 'string',
alias: 'u',
default: 'rainbow'
default: ['rainbow', 'cat'],
multiple: true
}
}
```
Expand Down
46 changes: 34 additions & 12 deletions index.js
Expand Up @@ -9,6 +9,7 @@ const redent = require('redent');
const readPkgUp = require('read-pkg-up');
const hardRejection = require('hard-rejection');
const normalizePackageData = require('normalize-package-data');
const arrify = require('arrify');

// Prevent caching of this module so module.parent is always accurate
delete require.cache[__filename];
Expand All @@ -26,6 +27,7 @@ const meow = (helpText, options) => {
normalize: false
}).packageJson || {},
argv: process.argv.slice(2),
flags: {},
inferType: false,
input: 'string',
help: helpText,
Expand All @@ -40,16 +42,27 @@ const meow = (helpText, options) => {
hardRejection();
}

const minimistFlags = options.flags && typeof options.booleanDefault !== 'undefined' ? Object.keys(options.flags).reduce(
(flags, flag) => {
if (flags[flag].type === 'boolean' && !Object.prototype.hasOwnProperty.call(flags[flag], 'default')) {
flags[flag].default = options.booleanDefault;
}
const minimistFlags = Object.entries(options.flags).reduce((flags, [flagKey, flagValue]) => {
const flag = {...flagValue};
const {booleanDefault} = options;

return flags;
},
options.flags
) : options.flags;
if (
typeof booleanDefault !== 'undefined' &&
flag.type === 'boolean' &&
!Object.prototype.hasOwnProperty.call(flag, 'default')
) {
flag.default = flag.multiple ? [booleanDefault] : booleanDefault;
}

if (flag.multiple) {
flag.type = 'array';
delete flag.multiple;
}

flags[flagKey] = flag;

return flags;
}, {});

let minimistOptions = {
arguments: options.input,
Expand All @@ -71,6 +84,13 @@ const meow = (helpText, options) => {
};
}

if (minimistOptions.array !== undefined) {
minimistOptions.array = arrify(minimistOptions.array).map(flagKey => ({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a short code comments what's happening here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better yet: Poke @vadimdemedes to give his input on supporting this in minimist-options: minimist-options | Support boolean and number arrays. Then there wouldn't be any need for a comment (or code!) at all...

Your input would be highly valuable as well!

key: flagKey,
[options.flags[flagKey].type || 'string']: true
}));
}

ulken marked this conversation as resolved.
Show resolved Hide resolved
const {pkg} = options;
const argv = yargs(options.argv, minimistOptions);
let help = redent(trimNewlines((options.help || '').replace(/\t+\n*$/, '')), 2);
Expand Down Expand Up @@ -112,10 +132,12 @@ const meow = (helpText, options) => {
const flags = camelcaseKeys(argv, {exclude: ['--', /^\w$/]});
const unnormalizedFlags = {...flags};

if (options.flags !== undefined) {
for (const flagValue of Object.values(options.flags)) {
delete flags[flagValue.alias];
for (const [flagKey, flagValue] of Object.entries(options.flags).filter(([key]) => key !== '--')) {
if (!flagValue.multiple && Array.isArray(flags[flagKey])) {
throw new Error(`Only one value allowed for --${flagKey}.`);
ulken marked this conversation as resolved.
Show resolved Hide resolved
}

delete flags[flagValue.alias];
}

return {
Expand Down
4 changes: 2 additions & 2 deletions index.test-d.ts
Expand Up @@ -35,8 +35,8 @@ const result = meow('Help text', {
foo: {type: 'boolean', alias: 'f'},
'foo-bar': {type: 'number'},
bar: {type: 'string', default: ''}
}}
);
}
});

expectType<string[]>(result.input);
expectType<PackageJson>(result.pkg);
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -41,6 +41,7 @@
],
"dependencies": {
"@types/minimist": "^1.2.0",
"arrify": "^2.0.1",
"camelcase-keys": "^6.1.1",
"decamelize-keys": "^1.1.0",
"hard-rejection": "^2.0.0",
Expand Down
4 changes: 3 additions & 1 deletion readme.md
Expand Up @@ -97,6 +97,7 @@ The key is the flag name and the value is an object with any of:
- `type`: Type of value. (Possible values: `string` `boolean` `number`)
- `alias`: Usually used to define a short flag alias.
- `default`: Default value when the flag is not specified.
- `multiple`: Indicates a flag can be set multiple times. Returns an array. (Default: false)

Example:

Expand All @@ -105,7 +106,8 @@ flags: {
unicorn: {
type: 'string',
alias: 'u',
default: 'rainbow'
default: ['rainbow', 'cat'],
multiple: true
}
}
```
Expand Down
143 changes: 143 additions & 0 deletions test.js
Expand Up @@ -307,3 +307,146 @@ test('supports `number` flag type - throws on incorrect default value', t => {
});
});
});

test('multiple flag set once returns array', t => {
t.deepEqual(meow({
argv: ['--foo=bar'],
flags: {
foo: {
type: 'string',
multiple: true
}
}
}).flags, {
foo: ['bar']
});
});

test('multiple flag set multiple times', t => {
t.deepEqual(meow({
argv: ['--foo=bar', '--foo=baz'],
flags: {
foo: {
type: 'string',
multiple: true
}
}
}).flags, {
foo: ['bar', 'baz']
});
});

test('multiple flag with space separated values', t => {
t.deepEqual(meow({
argv: ['--foo', 'bar', 'baz'],
flags: {
foo: {
type: 'string',
multiple: true
}
}
}).flags, {
foo: ['bar', 'baz']
});
});

test('single flag set more than once => throws', t => {
t.throws(() => {
meow({
argv: ['--foo=bar', '--foo=baz'],
flags: {
foo: {
type: 'string'
}
}
});
}, {message: 'Only one value allowed for --foo.'});
});

test('multiple boolean flag', t => {
t.deepEqual(meow({
argv: ['--foo', '--foo=false'],
flags: {
foo: {
type: 'boolean',
multiple: true
}
}
}).flags, {
foo: [true, false]
});
});

test('multiple boolean flag is false by default', t => {
t.deepEqual(meow({
argv: [],
flags: {
foo: {
type: 'boolean',
multiple: true
}
}
}).flags, {
foo: [false]
});
});

test('multiple flag with `booleanDefault: undefined` => filter out unset boolean args', t => {
t.deepEqual(meow({
argv: ['--foo'],
booleanDefault: undefined,
flags: {
foo: {
type: 'boolean',
multiple: true
},
bar: {
type: 'boolean',
multiple: true
}
}
}).flags, {
foo: [true]
});
});

test('multiple number flag', t => {
t.deepEqual(meow({
argv: ['--foo=1.3', '--foo=-1'],
flags: {
foo: {
type: 'number',
multiple: true
}
}
}).flags, {
foo: [1.3, -1]
});
});

test('multiple flag default values', t => {
t.deepEqual(meow({
argv: [],
flags: {
string: {
type: 'string',
multiple: true,
default: ['foo']
},
boolean: {
type: 'boolean',
multiple: true,
default: [true]
},
number: {
type: 'number',
multiple: true,
default: [0.5]
}
}
}).flags, {
string: ['foo'],
boolean: [true],
number: [0.5]
});
});