UNPKG

cli-kit

Version:

Everything you need to create awesome command line interfaces

153 lines (137 loc) 4.1 kB
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 }; } }