UNPKG

cordova-sync

Version:

Respond to file changes and build Cordova application

313 lines (275 loc) 8.05 kB
const colors = require('colors'); const util = require('util'); const tty = require('tty'); const charm = require('charm')(process); const cliSpinners = require('cli-spinners'); const cliCursor = require('cli-cursor'); const PADDING = ' '; var iterations = 0; var looper = null; var running = false; var items = {}; var defaultPattern = null; var settings = { invert: false, interval: 250, pattern: null, bottom: false }; var isatty = tty.isatty(1) && tty.isatty(2); var tty_size = { width: isatty ? process.stdout.getWindowSize ? process.stdout.getWindowSize(1)[0] : tty.getWindowSize()[1] : 75, height: isatty ? process.stdout.getWindowSize ? process.stdout.getWindowSize(1)[1] : tty.getWindowSize()[2] : 75 }; // // This is a single item (Or cell or whatever you call it) in the status display // var Item = function (options) { const defaults = { name: null, max: null, precision: 2, steps: false }; for (var attrname in defaults) { this[attrname] = options.hasOwnProperty(attrname) && options[attrname] !== null ? options[attrname] : defaults[attrname]; } if(options.custom) this.custom = options.custom.bind(this); this.val = options.count || 0; }; // // Item functions for value changes, rendering, etc // Item.prototype = { inc: function (amount) { this.val += (amount !== undefined) ? amount : 1; }, dec: function (amount) { this.val -= (amount !== undefined) ? amount : 1; }, doneStep: function (success, message) { if(!this.steps || this.count >= this.steps.length) return; charm.erase('line').erase('down'); message = message ? ` - ${message}` : ''; write(`${success ? '✔'.green : '✖'.red} ${this.render('step')}${message}\n`); this.inc(); }, render: function (style) { switch (style) { case 'step': if(!this.steps || this.count >= this.steps.length) return ''; return `${this.steps[this.count]}`; case 'custom': return this.custom ? this.custom() : ''; case 'percentage': if (!this.max) return ''; var max = typeof this.max == 'function' ? this.max() : this.max; return (100 * this.count / max).toFixed(this.precision) + '%'; case 'time': return nicetime(this.count); case 'bar': if (!this.max) return ''; var bar_len = 10; var max = typeof this.max == 'function' ? this.max() : this.max; var done = Math.round(bar_len * this.count / max); return '[' + '▒'.repeat(Math.min(bar_len, done)) + '-'.repeat(Math.max(0,bar_len - done)) + ']'; case 'default': case 'count': default: var max = typeof this.max == 'function' ? this.max() : this.max; return this.count + (max ? '/' + max : ''); } } }; // // Getter/setter for count. Auto-rendering, basically. // Object.defineProperties(Item.prototype, { count: { get: function () { return this.val; }, set: function (newValue) { this.val = newValue; } } }); // // Repeats a string, using it for the status bar instead of loops // String.prototype.repeat = function (len) { return new Array(len + 1).join(this); }; // // Render the status bar row // If stamp is true, it will console.log it instead of doing an stdout // const render = () => { iterations++; if (!running) return; var color_len = 0; for (var i = 0; i < items.length; i++) { if (items[i].color) { color_len += (items[i].color('')).length; } } var out = generateBar(); var bar = ' '.repeat(tty_size.width); if (settings.invert) { bar = bar.inverse; out = out.inverse; } var current_height = Math.ceil((out.length - color_len) / tty_size.width ); charm.position(function (x, y) { var current_row = y; // If the current cursor row was on the bar, we need to make a gap if (settings.bottom && current_row > tty_size.height - current_height) { for(var i = 0; i < current_height; i++) { // charm.delete('line', 1); charm.erase('line'); write('\n'); } y -= current_height - (tty_size.height - current_row); } charm .move(0, settings.bottom ? tty_size.height : 0) .left(tty_size.width) .write(bar); if(settings.bottom) { for(var i = 0; i < Math.max(0, current_height - 1); i++) { charm.left(tty_size.width).write(bar).up(1); } } charm .left(tty_size.width) .write(out) .position(x, y); }); }; const write = (string) => process.stdout.write(string); const generateBar = (withPattern) => { var pattern = withPattern ? withPattern : (settings.pattern ? settings.pattern : defaultPattern); return pattern.replace(/\{([a-zA-z0-9\s\.]*)\}/g, (match, id) => { var tokens = id.split('.'); var portion = ''; var color = null; var modifier = null; if(tokens.length > 1 && colors[tokens[1]]) { color = colors[tokens[1]]; modifier = tokens.length > 2 ? tokens[2] : null; } else if (tokens.length > 2 && colors[tokens[2]]) { color = colors[tokens[2]]; modifier = tokens[1]; } else if(tokens.length > 1) { modifier = tokens[1]; } switch (tokens[0]) { case 'timestamp': case 'uptime': portion = nicetime(iterations * settings.interval / 1000, true); break; case 'spinner': var spinnerType = modifier || 'dots'; portion = cliSpinners[spinnerType].frames[iterations % cliSpinners[spinnerType].frames.length]; break; default: if(items[tokens[0]]) portion = items[tokens[0]].render(modifier); break; } return color ? color(portion) : portion; }); }; // // Currently just changes the milliseconds to either a number of seconds or number of minutes // var nicetime = (ms, use_seconds) => { var seconds = (ms / (use_seconds ? 1 : 1000)).toFixed((use_seconds ? 0 : 3)); var minutes = (seconds / 60).toFixed(3); var time = (minutes < 2) ? seconds : minutes; return time + (minutes < 2 ? 's' : 'm'); }; process.on('exit', function () { if(running) stamp(); }); exports.addItem = (name, options) => { if(!name || typeof name !== 'string') return null; options = options || {}; options.name = name; var item = new Item(options); items[name] = item; rebuildPattern(); return items[name]; }; var rebuildPattern = () => { defaultPattern = Object.keys(items).reduce((memo, item) => { return `${memo}${PADDING}${item}: {${item}}${PADDING}|`; }, `Status @ {uptime}${PADDING}|`); }; exports.removeItem = (item) => { if(typeof item === 'string') { delete items[item]; } else if(item instanceof Item) { delete items[item.name]; } rebuildPattern(); } exports.removeAll = () => { items = {}; rebuildPattern(); } exports.toString = () => generateBar(); exports.clear = () => charm.erase('line').erase('down'); exports.console = function () { var methods = {}; ['log', 'info', 'error', 'warn'].forEach(m => { methods[m] = function () { if(m !== 'log' || running) exports.clear(); console[m].apply(this, arguments); if(running) render(); } }); return methods; }; // // Turns it on, will start rendering on interval now // exports.start = (opts) => { iterations = 0; settings = Object.assign(settings, opts) running = true; if(!settings.bottom) cliCursor.hide(); render(); looper = setInterval(render, settings.interval); }; exports.setPattern = (pattern) => settings.pattern = pattern; exports.stop = () => { running = false; clearTimeout(looper); cliCursor.show(); // charm.end(); }; // // Stamps the current status to the console // var stamp = exports.stamp = (withPattern) => { charm.erase('line').erase('down'); return console.log(generateBar(withPattern)); } // // Gets the total number of cells in the bar // exports.cellCount = () => Object.keys(items).length;