easy-cli-framework
Version:
A framework for building CLI applications that are robust and easy to maintain. Supports theming, configuration files, interactive prompts, and more.
254 lines (250 loc) • 9.93 kB
JavaScript
var chalk = require('chalk');
var index = require('./logger/index.js');
var themedTable = require('./themed-table.js');
var themedSpinner = require('./themed-spinner.js');
var simpleProgress = require('./progress/simple-progress.js');
require('cli-progress');
var progressWithStatus = require('./progress/progress-with-status.js');
/** @packageDocumentation Support for theming for command line applications, it includes support for verbosity, themed text display, spinners, and progress bars. */
/**
* A theme for the CLI, that allows for customizing the look and feel of the CLI, generating logs, tables, spinners, and progress bars.
*
* @class EasyCLITheme
*
* @property {number} verbosity The verbosity level of the theme
* @property {Record<NamedDisplayOptions, StringDisplayOptions>} namedDisplayOptions The named display options for the theme
* @property {EasyCLITheme} setVerbosity Sets the verbosity level of the theme
* @property {EasyCLITheme} setNamedDisplayOption Sets the named display options for the theme
* @property {EasyCLILogger} getLogger Gets a logger with the theme
* @property {ThemedTable} getTable Gets a table with the theme
* @property {ThemedSpinner} getSpinner Gets a spinner with the theme
* @property {ThemedSimpleProgressBar} getSimpleProgressBar Gets a simple progress bar with the theme
* @property {ThemedStatusProgressBar} getStatusProgressBar Gets a status progress bar with the theme
*
* @example
* ```typescript
* const theme = new EasyCLITheme(
* 0, // Set the verbosity level to 0
* {
* log: { color: '#F5F5F5' }, // Update the log color
* error: { color: '#FF5555', bold: true }, // Update the error color and make it bold
* custom: { color: '#55FF55' }, // Add a custom named display option
* }
* );
* const logger = theme.getLogger();
* logger.log('Hello, world!');
* ```
*/
class EasyCLITheme {
/**
* Creates an instance of EasyCLITheme.
*
* @param {number} [verbosity=0] The verbosity level of the theme
* @param {Record<NamedDisplayOptions, StringDisplayOptions>} [namedDisplayOptions] The named display options for the theme
*/
constructor(verbosity = 0, namedDisplayOptions) {
this.verbosity = 0;
this.namedDisplayOptions = {
log: {},
error: { color: '#FF5555', bold: true },
warn: { color: '#FFFF55' },
info: { color: '#F5F5F5' },
success: { color: '#55FF55' },
default: {},
};
this.verbosity = verbosity;
if (namedDisplayOptions) {
this.namedDisplayOptions = {
...this.namedDisplayOptions,
...namedDisplayOptions,
};
}
}
/**
* An internal method to merge display options, allowing for multiple display options to be combined
* ie. ['info', { bold: true }] => { color: '#F5F5F5', bold: true }
*
* @param {DisplayOptions} options The display options to merge
*
* @returns {StringDisplayOptions} A single display options object
*/
mergeDisplayOptions(options) {
var _a, _b, _c;
// If it's a string, we can just return the named display options
if (typeof options === 'string') {
return {
...this.namedDisplayOptions.default,
...((_b = (_a = this.namedDisplayOptions) === null || _a === undefined ? undefined : _a[options]) !== null && _b !== undefined ? _b : {}),
};
}
// If it's an object, we can just return the object
if (!Array.isArray(options)) {
return {
...this.namedDisplayOptions.default,
...options,
};
}
// Otherwise combine them in reverse order
return [(_c = this.namedDisplayOptions.default) !== null && _c !== undefined ? _c : {}, ...options.reverse()]
.map(option => {
var _a, _b;
return typeof option === 'string'
? (_b = (_a = this.namedDisplayOptions) === null || _a === undefined ? undefined : _a[option]) !== null && _b !== undefined ? _b : {}
: option;
})
.reduce((acc, option) => ({ ...acc, ...option }), {});
}
/**
* Formats a string with the display options
*
* @param {string} string The string to format
* @param {DisplayOptions} options The display options to use
*
* @returns {string} The formatted string
*
* @example
* ```typescript
* const theme = new EasyCLITheme();
* const formatted = theme.formattedString('Hello, world!', ['info', { bold: true }]);
* console.log(formatted);
* ```
*/
formattedString(string, options) {
let formatter = chalk;
let formatOptions = this.mergeDisplayOptions(options);
if (formatOptions.bold)
formatter = formatter.bold;
if (formatOptions.italic)
formatter = formatter.italic;
if (formatOptions.underline)
formatter = formatter.underline;
if (formatOptions.strikethrough)
formatter = formatter.strikethrough;
if (formatOptions.color)
formatter = formatter.hex(formatOptions.color);
if (formatOptions.bgColor)
formatter = formatter.bgHex(formatOptions.bgColor);
return formatter(string);
}
/**
* Sets the verbosity level of the theme
*
* @param {number} verbosity The verbosity level to set
*
* @returns {EasyCLITheme} The theme with the verbosity level, useful for optional chaining
*/
setVerbosity(verbosity) {
this.verbosity = verbosity;
return this;
}
/**
* Sets a named display options for the theme
*
* @param {NamedDisplayOptions} name The name of the display options
* @param {StringDisplayOptions} options The display options to set
*
* @returns {EasyCLITheme} The theme with the named display options set for use with optional chaining.
*/
setNamedDisplayOption(name, options) {
this.namedDisplayOptions[name] = options;
return this;
}
/**
* Gets a logger instance with the theme.
*
* @returns {EasyCLILogger} The logger with the theme
*/
getLogger() {
return new index.EasyCLILogger({ theme: this, verbosity: this.verbosity }); // TODO: Considering generating a singleton logger
}
/**
* Gets a themed table using this theme
* @template TItem The datatype for the items in the table.
*
* @param {ThemedTableColumn<TItem>[]} [columns=[]] The columns for the table
* @param {number} [totalWidth=120] The total width of the table
*
* @returns {ThemedTable<TItem>} The themed table instance
*
* @example
* ```typescript
* const theme = new EasyCLITheme();
* const table = theme.getTable([
* { name: 'Name', data: item => item.name },
* { name: 'Age', data: item => item.age },
* ]);
*
* table.render([
* { name: 'Alice', age: 25 },
* { name: 'Bob', age: 30 },
* ]);
* ```
*/
getTable(columns = [], totalWidth = 120) {
return new themedTable.ThemedTable({ theme: this, columns, totalWidth }); // TODO: Add verbosity and other options
}
/**
* Gets a spinner using this theme
*
* @param {DisplayOptions} [format='default'] The format for the spinner
*
* @returns {ThemedSpinner} The themed spinner instance
*
* @example
* ```typescript
* const theme = new EasyCLITheme();
* const spinner = theme.getSpinner('default');
* spinner.start('Loading...');
* ```
*/
getSpinner(format = 'default') {
return new themedSpinner.ThemedSpinner(this, format); // TODO: Add other options
}
/**
* Gets a simple progress bar using this theme
*
* @param {string} name The name of the progress bar
* @param {DisplayOptions} [format='default'] The format for the progress bar
* @param {ThemedSimpleProgressBarOptions} [options={}] The options for the progress bar
*
* @returns {ThemedSimpleProgressBar} The themed simple progress bar instance
* @example
* ```typescript
* const theme = new EasyCLITheme();
* const progressBar = theme.getSimpleProgressBar('progress', 'default', {
* showCurrentRecord: true,
* currentRecordDisplayOptions: 'info',
* });
* ```
*/
getSimpleProgressBar(name, format = 'default', options = {}) {
return new simpleProgress.ThemedSimpleProgressBar(this, name, format, options); // TODO: Add Other Options
}
/**
* Gets a status progress bar using this theme
*
* @param {string} name The name of the progress bar
* @param {DisplayOptions} [format='default'] The format for the progress bar
* @param {ThemedStatusProgressBarOptions} [options={}] The options for the progress bar
*
* @returns {ThemedStatusProgressBar} The themed status progress bar instance
* @example
* ```typescript
* const theme = new EasyCLITheme();
* const progressBar = theme.getStatusProgressBar('Task', 'Task in progress', {
* showCurrentRecord: true,
* });
* ```
*/
getStatusProgressBar(name, format = 'default', options = {}) {
return new progressWithStatus.ThemedStatusProgressBar(this, name, format, options); // TODO: Add Other Options
}
}
exports.EasyCLILogger = index.EasyCLILogger;
exports.EasyCLILoggerResponse = index.EasyCLILoggerResponse;
exports.ThemedTable = themedTable.ThemedTable;
exports.ThemedSpinner = themedSpinner.ThemedSpinner;
exports.ThemedSimpleProgressBar = simpleProgress.ThemedSimpleProgressBar;
exports.ThemedStatusProgressBar = progressWithStatus.ThemedStatusProgressBar;
exports.EasyCLITheme = EasyCLITheme;
;