import ora from 'ora'
import lodash from 'lodash'
import aneka from 'aneka'
const { defaultsDeep } = aneka
const { isPlainObject } = lodash
/**
* @typedef TPrintOptions
* @property {boolean} [showDatetime=false] - Show actual date & time
* @property {boolean} [showCounter=false] - Show as counter
* @property {boolean} [silent] - Suppress any messages. Defaults to the one set in {@tutorial config}
* @property {Object} [ora] - {@link https://github.com/sindresorhus/ora#api|Ora's options} object
* @see {@link Print}
*/
/**
* Universal print engine, supports text translation using {@link App#t|app's built-in translation}.
*
* Features many methods to display things on screen/console using {@link https://github.com/sindresorhus/ora|ora}
* based spinner.
*
* @class
*/
class Print {
/**
* @param {Plugin} plugin - Plugin instance
* @param {TPrintOptions} [options={}] - Options object
*/
constructor (plugin, options = {}) {
/**
* Options object
* @type {TPrintOptions}
*/
this.options = options
/**
* Attached plugin
* @type {Plugin}
*/
this.plugin = plugin
/**
* The app instance
* @type {App}
*/
this.app = plugin.app
if (this.app.applet) {
if (this.app.bajo.config.counter) this.options.showCounter = true
if (this.app.bajo.config.datetime) this.options.showDatetime = true
}
/**
* Time when instance is created
* @type {Object}
* @see {@link https://day.js.org|dayjs} object
*/
this.startTime = this.app.lib.dayjs()
/**
* ora instance
* @see {@link https://github.com/sindresorhus/ora|ora}
*/
this.ora = ora(this.options.ora)
this.setOpts()
}
/**
* Setting spinner options; override the one passed at constructor
*
* @method
* @param {any[]} [args=[]] - Array of options. If the last argument is an object, it will be used to override ora options
*/
setOpts = (args = []) => {
const { silent } = this.app.bajo.config
let opts = {}
if (isPlainObject(args.slice(-1)[0])) opts = args.pop()
this.options.silent = !!(silent || this.options.silent)
this.options = defaultsDeep(opts, this.options)
}
/**
* Translate, prefixed with counter and/or datetime etc
*
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora's options
* @returns {string}
*/
buildText = (text, ...args) => {
text = this.plugin.t(text, ...args)
this.setOpts(args)
const prefixes = []
if (this.options.showDatetime) prefixes.push('[' + this.app.lib.dayjs().toISOString() + ']')
if (this.options.showCounter) prefixes.push('[' + this.getElapsed() + ']')
// if (prefixes.length > 0) this.ora.prefixText = this.ora.prefixText + prefixes.join(' ')
if (prefixes.length > 0) text = prefixes.join(' ') + ' ' + text
return text
}
/**
* Set spinner's text
*
* @method
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora's options
* @returns {Print} Return the instance itself, usefull for method chaining
*/
setText = (text, ...args) => {
text = this.buildText(text, ...args)
this.ora.text = text
return this
}
/**
* Get elapsed time since instance is created
*
* @method
* @param {string} [unit=hms] - Unit's time. Put 'hms' (default) to get hour, minute, second format or of any format supported by {@link https://day.js.org/docs/en/display/difference|dayjs}
* @returns {string} Elapsed time since start
* @see {@link https://day.js.org/docs/en/display/difference|dayjs duration format}
*/
getElapsed = (unit = 'hms') => {
const u = unit === 'hms' ? 'second' : unit
const elapsed = this.app.lib.dayjs().diff(this.startTime, u)
return unit === 'hms' ? this.app.lib.aneka.secToHms(elapsed) : elapsed
}
/**
* Start the spinner
*
* @method
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora's options
* @returns {Print} Return the instance itself, usefull for method chaining
*/
start = (text, ...args) => {
this.setOpts(args)
this.setText(text, ...args)
this.ora.start()
return this
}
/**
* Stop the spinner
*
* @method
* @returns {Print} Return the instance itself, usefull for method chaining
*/
stop = () => {
this.ora.stop()
return this
}
/**
* Print success message, prefixed with a check icon
*
* @method
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
* @returns {Print} Return the instance itself, usefull for method chaining
*/
succeed = (text, ...args) => {
this.setText(text, ...args)
this.ora.succeed()
return this
}
/**
* Print failed message, prefixed with a cross icon
*
* @method
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
* @returns {Print} Return the instance itself, usefull for method chaining
*/
fail = (text, ...args) => {
this.setText(text, ...args)
this.ora.fail()
return this
}
/**
* Print warning message, prefixed with a warn icon
*
* @method
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
* @returns {Print} Return the instance itself, usefull for method chaining
*/
warn = (text, ...args) => {
this.setText(text, ...args)
this.ora.warn()
return this
}
/**
* Print information message, prefixed with an info icon
*
* @method
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
* @returns {Print} Return the instance itself, usefull for method chaining
*/
info = (text, ...args) => {
this.setText(text, ...args)
this.ora.info()
return this
}
/**
* Clear spinner text
*
* @method
* @returns {Print} Return the instance itself, usefull for method chaining
*/
clear = () => {
this.ora.clear()
return this
}
/**
* Force render spinner
*
* @method
* @returns {Print} Return the instance itself, usefull for method chaining
*/
render = () => {
this.ora.render()
return this
}
/**
* Print failed message, prefixed with a cross icon and exit
*
* @method
* @param {string} text - Text to use
* @param {...any} [args] - Any variable to interpolate text. If the last argument is an object, it will be used to override ora options
*/
fatal = (text, ...args) => {
if (text instanceof Error) {
text = text.message
args = []
}
this.setText(text, ...args)
this.ora.fail()
if (text instanceof Error && this.app.bajo.config.log.level === 'trace') console.error(text)
this.app.exit()
}
/**
* Create a new print instance
*
* @method
* @param {TPrintOptions} [options] - Options object. If not provided, defaults to the current options
* @returns {Print} Return new print instance
*/
spinner = (options) => {
const spin = new Print(this.plugin, options ?? this.options)
spin.startTime = this.startTime.clone()
return spin
}
}
export default Print