Skip to content

Instantly share code, notes, and snippets.

@alaindet
Last active April 1, 2022 14:07
Show Gist options
  • Save alaindet/d6c68848656dcabd63da4c9153e6ef26 to your computer and use it in GitHub Desktop.
Save alaindet/d6c68848656dcabd63da4c9153e6ef26 to your computer and use it in GitHub Desktop.
CLImb - CLI parser in JavaScript
// node ./test1.js --bool1 -2 --foo aa --bar=bb -f aaa -abc arg1 arg2
const climb = require('./climb');
const input = climb(process.argv, {
bool1: {
longName: 'bool1',
},
bool2: {
longName: 'bool2',
shortName: '2',
},
foo: {
longName: 'foo',
shortName: 'f',
withArgs: true,
},
bar: {
longName: 'bar',
shortName: 'b',
withArgs: true,
},
a: {
shortName: 'a',
withArgs: false,
},
b: {
shortName: 'b',
withArgs: false,
},
c: {
shortName: 'c',
withArgs: false,
},
});
console.log(input);
/*
{
args: [
'arg1',
'arg2'
],
options: {
bool1: true,
bool2: true,
foo: 'aaa',
bar: 'bb',
a: true,
b: true,
c: true
}
}
*/
// node ./myscript.js --bool1 -2 --foo aa --bar=bb -f aaa -abc arg1 arg2
module.exports = (argv, optionsSchema) => {
const [bin, script, ...rawItems] = argv;
const options = {};
const parsed = {
args: [],
options: {},
};
for (const optionName in optionsSchema) {
const option = {
name: optionName,
longName: optionsSchema[optionName]?.longName ?? null,
shortName: optionsSchema[optionName]?.shortName ?? null,
withArgs: optionsSchema[optionName]?.withArgs ?? false,
};
if (option?.longName) {
options[option.longName] = option;
}
if (option?.shortName) {
options[option.shortName] = option;
}
}
// Split multi-flags before processing
// Ex.: -ab => -a -b
const items = [];
const isMultiFlag = /^\-[a-zA-Z0-9]{2,}$/;
for (const item of rawItems) {
if (!item.match(isMultiFlag)) {
items.push(item);
continue;
}
item.slice(1).split('').forEach(flag => items.push(`-${flag}`));
}
for (let i = 0, len = items.length; i < len; i++) {
const item = items[i];
const isOption = item.startsWith('-');
if (isOption) {
const isLongOption = item.startsWith('--');
let optName = isLongOption ? item.slice(2) : item.slice(1);
let value;
const isEqualsFormat = optName.indexOf('=') !== -1;
// --foo=bar
if (isEqualsFormat) {
const [newName, newValue] = optName.split('=');
optName = newName;
value = newValue;
}
const opt = options[optName];
if (!opt) continue;
// No args, keep going
if (!opt.withArgs) {
parsed.options[opt.name] = true;
continue;
}
// Is it in "--foo=bar" or "-f=bar" format?
if (isEqualsFormat) {
parsed.options[opt.name] = value;
continue;
}
// Maybe "--foo bar" or "-f bar" format?
if (!items[i + 1]?.startsWith('-')) {
parsed.options[opt.name] = items[i + 1];
i += 1; // Skip next item!
continue;
}
// No arg found, sorry
parsed.options[opt.name] = undefined;
continue;
}
// Argument
parsed.args.push(item);
}
return parsed;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment