1'use strict' 2var Plumbing = require('./plumbing.js') 3var hasUnicode = require('has-unicode') 4var hasColor = require('./has-color.js') 5var onExit = require('signal-exit').onExit 6var defaultThemes = require('./themes') 7var setInterval = require('./set-interval.js') 8var process = require('./process.js') 9var setImmediate = require('./set-immediate') 10 11module.exports = Gauge 12 13function callWith (obj, method) { 14 return function () { 15 return method.call(obj) 16 } 17} 18 19function Gauge (arg1, arg2) { 20 var options, writeTo 21 if (arg1 && arg1.write) { 22 writeTo = arg1 23 options = arg2 || {} 24 } else if (arg2 && arg2.write) { 25 writeTo = arg2 26 options = arg1 || {} 27 } else { 28 writeTo = process.stderr 29 options = arg1 || arg2 || {} 30 } 31 32 this._status = { 33 spun: 0, 34 section: '', 35 subsection: '', 36 } 37 this._paused = false // are we paused for back pressure? 38 this._disabled = true // are all progress bar updates disabled? 39 this._showing = false // do we WANT the progress bar on screen 40 this._onScreen = false // IS the progress bar on screen 41 this._needsRedraw = false // should we print something at next tick? 42 this._hideCursor = options.hideCursor == null ? true : options.hideCursor 43 this._fixedFramerate = options.fixedFramerate == null 44 ? !(/^v0\.8\./.test(process.version)) 45 : options.fixedFramerate 46 this._lastUpdateAt = null 47 this._updateInterval = options.updateInterval == null ? 50 : options.updateInterval 48 49 this._themes = options.themes || defaultThemes 50 this._theme = options.theme 51 var theme = this._computeTheme(options.theme) 52 var template = options.template || [ 53 { type: 'progressbar', length: 20 }, 54 { type: 'activityIndicator', kerning: 1, length: 1 }, 55 { type: 'section', kerning: 1, default: '' }, 56 { type: 'subsection', kerning: 1, default: '' }, 57 ] 58 this.setWriteTo(writeTo, options.tty) 59 var PlumbingClass = options.Plumbing || Plumbing 60 this._gauge = new PlumbingClass(theme, template, this.getWidth()) 61 62 this._$$doRedraw = callWith(this, this._doRedraw) 63 this._$$handleSizeChange = callWith(this, this._handleSizeChange) 64 65 this._cleanupOnExit = options.cleanupOnExit == null || options.cleanupOnExit 66 this._removeOnExit = null 67 68 if (options.enabled || (options.enabled == null && this._tty && this._tty.isTTY)) { 69 this.enable() 70 } else { 71 this.disable() 72 } 73} 74Gauge.prototype = {} 75 76Gauge.prototype.isEnabled = function () { 77 return !this._disabled 78} 79 80Gauge.prototype.setTemplate = function (template) { 81 this._gauge.setTemplate(template) 82 if (this._showing) { 83 this._requestRedraw() 84 } 85} 86 87Gauge.prototype._computeTheme = function (theme) { 88 if (!theme) { 89 theme = {} 90 } 91 if (typeof theme === 'string') { 92 theme = this._themes.getTheme(theme) 93 } else if ( 94 Object.keys(theme).length === 0 || theme.hasUnicode != null || theme.hasColor != null 95 ) { 96 var useUnicode = theme.hasUnicode == null ? hasUnicode() : theme.hasUnicode 97 var useColor = theme.hasColor == null ? hasColor : theme.hasColor 98 theme = this._themes.getDefault({ 99 hasUnicode: useUnicode, 100 hasColor: useColor, 101 platform: theme.platform, 102 }) 103 } 104 return theme 105} 106 107Gauge.prototype.setThemeset = function (themes) { 108 this._themes = themes 109 this.setTheme(this._theme) 110} 111 112Gauge.prototype.setTheme = function (theme) { 113 this._gauge.setTheme(this._computeTheme(theme)) 114 if (this._showing) { 115 this._requestRedraw() 116 } 117 this._theme = theme 118} 119 120Gauge.prototype._requestRedraw = function () { 121 this._needsRedraw = true 122 if (!this._fixedFramerate) { 123 this._doRedraw() 124 } 125} 126 127Gauge.prototype.getWidth = function () { 128 return ((this._tty && this._tty.columns) || 80) - 1 129} 130 131Gauge.prototype.setWriteTo = function (writeTo, tty) { 132 var enabled = !this._disabled 133 if (enabled) { 134 this.disable() 135 } 136 this._writeTo = writeTo 137 this._tty = tty || 138 (writeTo === process.stderr && process.stdout.isTTY && process.stdout) || 139 (writeTo.isTTY && writeTo) || 140 this._tty 141 if (this._gauge) { 142 this._gauge.setWidth(this.getWidth()) 143 } 144 if (enabled) { 145 this.enable() 146 } 147} 148 149Gauge.prototype.enable = function () { 150 if (!this._disabled) { 151 return 152 } 153 this._disabled = false 154 if (this._tty) { 155 this._enableEvents() 156 } 157 if (this._showing) { 158 this.show() 159 } 160} 161 162Gauge.prototype.disable = function () { 163 if (this._disabled) { 164 return 165 } 166 if (this._showing) { 167 this._lastUpdateAt = null 168 this._showing = false 169 this._doRedraw() 170 this._showing = true 171 } 172 this._disabled = true 173 if (this._tty) { 174 this._disableEvents() 175 } 176} 177 178Gauge.prototype._enableEvents = function () { 179 if (this._cleanupOnExit) { 180 this._removeOnExit = onExit(callWith(this, this.disable)) 181 } 182 this._tty.on('resize', this._$$handleSizeChange) 183 if (this._fixedFramerate) { 184 this.redrawTracker = setInterval(this._$$doRedraw, this._updateInterval) 185 if (this.redrawTracker.unref) { 186 this.redrawTracker.unref() 187 } 188 } 189} 190 191Gauge.prototype._disableEvents = function () { 192 this._tty.removeListener('resize', this._$$handleSizeChange) 193 if (this._fixedFramerate) { 194 clearInterval(this.redrawTracker) 195 } 196 if (this._removeOnExit) { 197 this._removeOnExit() 198 } 199} 200 201Gauge.prototype.hide = function (cb) { 202 if (this._disabled) { 203 return cb && process.nextTick(cb) 204 } 205 if (!this._showing) { 206 return cb && process.nextTick(cb) 207 } 208 this._showing = false 209 this._doRedraw() 210 cb && setImmediate(cb) 211} 212 213Gauge.prototype.show = function (section, completed) { 214 this._showing = true 215 if (typeof section === 'string') { 216 this._status.section = section 217 } else if (typeof section === 'object') { 218 var sectionKeys = Object.keys(section) 219 for (var ii = 0; ii < sectionKeys.length; ++ii) { 220 var key = sectionKeys[ii] 221 this._status[key] = section[key] 222 } 223 } 224 if (completed != null) { 225 this._status.completed = completed 226 } 227 if (this._disabled) { 228 return 229 } 230 this._requestRedraw() 231} 232 233Gauge.prototype.pulse = function (subsection) { 234 this._status.subsection = subsection || '' 235 this._status.spun++ 236 if (this._disabled) { 237 return 238 } 239 if (!this._showing) { 240 return 241 } 242 this._requestRedraw() 243} 244 245Gauge.prototype._handleSizeChange = function () { 246 this._gauge.setWidth(this._tty.columns - 1) 247 this._requestRedraw() 248} 249 250Gauge.prototype._doRedraw = function () { 251 if (this._disabled || this._paused) { 252 return 253 } 254 if (!this._fixedFramerate) { 255 var now = Date.now() 256 if (this._lastUpdateAt && now - this._lastUpdateAt < this._updateInterval) { 257 return 258 } 259 this._lastUpdateAt = now 260 } 261 if (!this._showing && this._onScreen) { 262 this._onScreen = false 263 var result = this._gauge.hide() 264 if (this._hideCursor) { 265 result += this._gauge.showCursor() 266 } 267 return this._writeTo.write(result) 268 } 269 if (!this._showing && !this._onScreen) { 270 return 271 } 272 if (this._showing && !this._onScreen) { 273 this._onScreen = true 274 this._needsRedraw = true 275 if (this._hideCursor) { 276 this._writeTo.write(this._gauge.hideCursor()) 277 } 278 } 279 if (!this._needsRedraw) { 280 return 281 } 282 if (!this._writeTo.write(this._gauge.show(this._status))) { 283 this._paused = true 284 this._writeTo.on('drain', callWith(this, function () { 285 this._paused = false 286 this._doRedraw() 287 })) 288 } 289} 290