UNPKG

cli-kit

Version:

Everything you need to create awesome command line interfaces

142 lines (125 loc) 4.11 kB
import E from '../lib/errors.js'; import Extension from './extension.js'; import { declareCLIKitClass } from '../lib/util.js'; /** * Stores a map of `Extension` instances that have been registered for a context. * * @extends {Map} */ export default class ExtensionMap extends Map { /** * Declares the class name. * * @access public */ constructor() { super(); declareCLIKitClass(this, 'ExtensionMap'); } /** * Adds an extension to the map. * * @param {Object|String|Extension|ExtensionMap|Array.<String|Extension>} ext - An object of * extension names to extension paths or instances, an extension path, an `Extension` instance, * or an array of those types. An extension path may be a directory containing a Node.js * module, a path to a `.js` file, or the name of a executable. May also be an `ExtensionMap` * instance. * @param {String} [name] - The extension name used for the context name. If not set, it will * attempt to find a `package.json` with a `cli-kit.name` value. * @param {Boolean} [clone] - When `true` and `ext` is an `Extension` or `ExtensionMap`, it * will clone the `Extension` instead of set by reference. * @returns {Array.<Extension>} * @access public * * @example * .add({ foo: '/path/to/ext', bar: new Extension() }) * .add('/path/to/ext'); * .add('/path/to/ext', 'foo'); * .add(new Extension()) * .add(new ExtensionMap()) * .add([ '/path/to/ext', new Extension() ]) */ add(ext, name, clone) { if (!ext) { throw E.INVALID_ARGUMENT('Expected extension to be an extension instance or a path', { name: 'ext', scope: 'ExtensionMap.add', value: ext }); } if (name && typeof name !== 'string') { throw E.INVALID_ARGUMENT('Expected extension name to be a string', { name: 'name', scope: 'ExtensionMap.add', value: name }); } const results = []; const extensions = typeof ext === 'object' ? ( ext.clikit instanceof Set && ext.clikit.has('Extension') ? [ ext ] : ext.clikit instanceof Set && ext.clikit.has('ExtensionMap') ? ext.entries() : Object.entries(ext) ) : Array.isArray(ext) ? ext : [ ext ]; // at this point, we have an array of `Strings` (paths), Array [name,ext], and Extension instances for (let it of extensions) { let ext = null; if (!clone && it instanceof Extension) { ext = it; } else if (Array.isArray(it)) { // [name,ext] const [ name, pathOrExt ] = it; ext = pathOrExt instanceof Extension ? pathOrExt : new Extension(pathOrExt, { name }); } else if (it && (typeof it === 'string' || (typeof it === 'object' && it.clikit instanceof Set && it.clikit.has('Extension')))) { // path or params ext = new Extension(it, { name }); } if (ext) { for (const ctxName of Object.keys(ext.exports)) { this.set(ctxName, ext); } results.push(ext); } else { throw E.INVALID_ARGUMENT(`Invalid extension "${it}", expected a valid path or an object`, { name: 'extension', scope: 'ExtensionMap.add', value: it }); } } return results; } /** * Returns the number of extensions. * * @returns {Number} * @access public */ get count() { return this.size; } /** * Generates an object containing the extensions for the help screen. * * @returns {Promise<Object>} * @access public */ async generateHelp() { const entries = []; for (const ctxName of Array.from(this.keys())) { const { aliases, clikitHelp, desc, hidden, name } = this.get(ctxName).exports[ctxName]; if (!hidden && !clikitHelp) { const labels = new Set([ name ]); for (const [ alias, display ] of Object.entries(aliases)) { if (display === 'visible') { labels.add(alias); } } entries.push({ name, desc, label: Array.from(labels).sort((a, b) => { return a.length === b.length ? a.localeCompare(b) : a.length - b.length; }).join(', '), aliases: aliases ? Object.keys(aliases) : null }); } } entries.sort((a, b) => a.label.localeCompare(b.label)); return { count: entries.length, entries }; } }