cli-kit
Version:
Everything you need to create awesome command line interfaces
153 lines (137 loc) • 4.1 kB
JavaScript
import E from '../lib/errors.js';
import Option from './option.js';
import { declareCLIKitClass } from '../lib/util.js';
/**
* Stores a map of `Option` instances that have been registered for a context.
*
* @extends {Map}
*/
export default class OptionMap extends Map {
/**
* An internal counter of options that have been added.
*
* @type {Number}
*/
count = 0;
/**
* Declares the class name.
*
* @access public
*/
constructor() {
super();
declareCLIKitClass(this, 'OptionList');
}
/**
* Adds a option to the list.
*
* @param {String|Object|Option|OptionMap|Array<Object|Option|String>} format - An option
* format, an object of format to option descriptions, `Option` constructor params or `Option`
* instances, an `Option` instance, an `OptionMap` instance, or an array of `Option`
* constructor params and `Option` instances grouped by `String` labels.
* @param {Object|Option|String} [params] - When `format` is a format string, then this
* argument is either `Option` constructor parameters, an `Option` instance, or an option
* description.
* @returns {Array.<Option>}
* @access public
*/
add(format, params) {
if (!format) {
throw E.INVALID_ARGUMENT('Invalid option format or option', { name: 'format', scope: 'OptionMap.add', value: format });
}
const results = [];
let lastGroup = '';
let options = [];
if (Array.isArray(format)) {
options = format;
} else if (typeof format === 'object' && format.clikit instanceof Set && (format.clikit.has('OptionList') || format.clikit.has('OptionMap'))) {
for (const [ group, opts ] of format.entries()) {
if (group && group !== lastGroup) {
options.push(group);
lastGroup = group;
}
options.push.apply(options, opts);
}
} else if (typeof format === 'object' && (format instanceof Option || !(format.clikit instanceof Set) || !format.clikit.has('Option'))) {
options.push(format);
} else {
options.push(new Option(format, params));
}
// reset group
lastGroup = '';
const add = opt => {
let opts = this.get(lastGroup);
if (!opts) {
this.set(lastGroup, opts = []);
}
opts.push(opt);
results.push(opt);
this.count++;
};
// at this point we have a unified array of stuff
for (const it of options) {
if (typeof it === 'string') {
lastGroup = it;
continue;
}
if (typeof it !== 'object') {
throw E.INVALID_ARGUMENT(`Expected option to be an object: ${it && it.name || it}`, { name: 'option', scope: 'OptionMap.add', value: it });
}
if (it instanceof Option) {
add(it);
} else if (it.clikit instanceof Set) {
add(new Option(it));
} else {
// it is a format-params object
for (const [ format, params ] of Object.entries(it)) {
add(params instanceof Option ? params : new Option(format, params));
}
}
}
return results;
}
/**
* Generates an object containing the options for the help screen.
*
* @returns {Promise<Object>}
* @access public
*/
async generateHelp() {
let count = 0;
const groups = {};
const sortFn = (a, b) => {
return a.order < b.order ? -1 : a.order > b.order ? 1 : a.long.localeCompare(b.long);
};
for (const [ groupName, options ] of this.entries()) {
const group = groups[groupName] = [];
for (const opt of options.sort(sortFn)) {
if (!opt.hidden) {
const label = `${opt.short ? `-${opt.short}, ` : ''}` +
`--${opt.negate ? 'no-' : ''}${opt.long}` +
`${opt.isFlag ? '' : ` ${opt.required ? '<' : '['}${opt.hint || 'value'}${opt.required ? '>' : ']'}`}`;
group.push({
aliases: Object.keys(opt.aliases).filter(a => opt.aliases[a]),
datatype: opt.datatype,
default: opt.default,
desc: opt.desc,
hint: opt.hint,
isFlag: opt.isFlag,
label,
long: opt.long,
max: opt.max,
min: opt.min,
name: opt.name,
negate: opt.negate,
required: opt.required,
short: opt.short
});
count++;
}
}
}
return {
count,
groups
};
}
}