Skip to content

Instantly share code, notes, and snippets.

@tjklemz
Created August 11, 2020 21:06
Show Gist options
  • Save tjklemz/21f245637115a9f17690e5fe6b75c585 to your computer and use it in GitHub Desktop.
Save tjklemz/21f245637115a9f17690e5fe6b75c585 to your computer and use it in GitHub Desktop.
Partout: Simple command line parser for Node
/*
partout([
'artifact',
{ name: 'output', defaultValue: '.' },
{ name: 'platform', flag: true, parse: true }
], '-p darwin app'.split(' '))
*/
const identity = (value) => value;
const getFlag = (option) => {
const { name, parse, flag, toggle, defaultValue } = option;
const short = typeof flag === 'string' ? flag : name.charAt(0);
return { name, short, parse: parse && typeof parse === 'boolean' ? identity : (parse || identity), toggle, defaultValue };
};
const getPositional = (positional) => {
const name = typeof positional === 'string' ? positional : positional.name;
const parse = typeof positional === 'string' ? identity : positional.parse;
const defaultValue = typeof positional === 'string' ? undefined : positional.defaultValue;
return { name, parse: parse && typeof parse === 'boolean' ? identity : (parse || identity), defaultValue };
};
const parseArgs = async (options = [], args = process.argv.slice(2)) => {
const flags = options.filter(option => typeof option === 'object' && option.flag);
const positional = options.filter(option => typeof option === 'string' || (typeof option === 'object' && !option.flag));
const parsed = {};
let j = 0;
for (let i = 0; i < args.length; ++i) {
const flag = flags.find((flag) => {
const { name, short } = getFlag(flag);
return `-${short}` === args[i] || `--${name}` === args[i];
});
if (flag) {
const { name, parse, toggle } = getFlag(flag);
if (!toggle) {
i += 1;
parsed[name] = await parse(args[i]);
} else {
parsed[name] = true;
}
} else if (positional[j]) {
const { name, parse } = getPositional(positional[j]);
parsed[name] = await parse(args[i]);
j += 1;
}
}
for (const flag of flags) {
const { name, parse, toggle, defaultValue } = getFlag(flag);
if (!(name in parsed)) {
parsed[name] = await parse(toggle && typeof defaultValue === 'undefined' ? false : defaultValue);
}
}
for (; j < positional.length; ++j) {
const { name, parse, defaultValue } = getPositional(positional[j]);
parsed[name] = await parse(defaultValue);
}
return parsed;
};
module.exports = parseArgs;
const test = require('ava');
const parseArgs = require('./partout');
test('basic options', async (t) => {
const args = '-p darwin app'.split(' ');
const result = await parseArgs([
'artifact',
{ name: 'output', defaultValue: '.' },
{ name: 'platform', flag: true, parse: true }
], args);
t.like(result, {
artifact: 'app',
output: '.',
platform: 'darwin',
});
});
const parsePlatform = platform => {
if (!['darwin', 'win32'].includes(platform)) {
throw new Error(`Platform must be "darwin" or "win32". Got: ${platform}`)
}
return platform;
};
const parseOutput = async output => {
await new Promise(resolve => setTimeout(resolve, 5));
if (output !== '.' && !output.endsWith('/out')) {
throw new Error(`Output is not valid. Got: ${output}`);
}
return output.split('/');
};
test('parsing success', async (t) => {
const args = '-p darwin app ./my/out'.split(' ');
const result = await parseArgs([
'artifact',
{ name: 'output', defaultValue: '.', parse: parseOutput },
{ name: 'platform', flag: true, parse: parsePlatform },
], args);
t.like(result, {
artifact: 'app',
output: ['.', 'my', 'out'],
platform: 'darwin',
});
});
test('parsing fail', async (t) => {
const args = '-p mac app ./my/out'.split(' ');
await t.throwsAsync(async () => {
await parseArgs([
'artifact',
{ name: 'output', defaultValue: '.', parse: parseOutput },
{ name: 'platform', flag: true, parse: parsePlatform },
], args);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment