2 var hasUnicode = require("has-unicode")
3 var ansi = require("ansi")
5 center: require("lodash.pad"),
6 left: require("lodash.padend"),
7 right: require("lodash.padstart")
9 var defaultStream = process.stderr
11 return process.stderr.isTTY
13 function getWritableTTYColumns() {
14 // Writing to the final column wraps the line
15 // We have to use stdout here, because Node's magic SIGWINCH handler only
16 // updates process.stdout, not process.stderr
17 return process.stdout.columns - 1
20 var ProgressBar = module.exports = function (options, cursor) {
21 if (! options) options = {}
22 if (! cursor && options.write) {
27 cursor = ansi(defaultStream)
31 this.theme = options.theme || (hasUnicode() ? ProgressBar.unicode : ProgressBar.ascii)
32 this.template = options.template || [
33 {type: "name", separated: true, length: 25},
34 {type: "spinner", separated: true},
36 {type: "completionbar"},
39 this.updatefreq = options.maxUpdateFrequency == null ? 50 : options.maxUpdateFrequency
41 this.lastCompleted = 0
43 this.last = new Date(0)
46 this._handleSizeChange = function () {
47 if (!self.showing) return
52 ProgressBar.prototype = {}
54 ProgressBar.unicode = {
72 ProgressBar.prototype.setTheme = function(theme) {
76 ProgressBar.prototype.setTemplate = function(template) {
77 this.template = template
80 ProgressBar.prototype._enableResizeEvents = function() {
81 process.stdout.on('resize', this._handleSizeChange)
84 ProgressBar.prototype._disableResizeEvents = function() {
85 process.stdout.removeListener('resize', this._handleSizeChange)
88 ProgressBar.prototype.disable = function() {
93 ProgressBar.prototype.enable = function() {
98 ProgressBar.prototype.hide = function() {
100 if (this.disabled) return
102 if (this.showing) this.cursor.up(1)
103 this.cursor.horizontalAbsolute(0).eraseLine()
107 var repeat = function (str, count) {
109 for (var ii=0; ii<count; ++ii) out += str
113 ProgressBar.prototype.pulse = function(name) {
115 if (! this.showing) return
116 if (this.disabled) return
118 var baseName = this.lastName
121 ? baseName + " " + this.theme.subsection + " " + name
125 this.lastName = baseName
128 ProgressBar.prototype.show = function(name, completed) {
129 name = this.lastName = name || this.lastName
130 completed = this.lastCompleted = completed || this.lastCompleted
133 if (this.disabled) return
134 if (! this.spun && ! completed) return
135 if (this.tryAgain) return
138 if (this.showing && new Date() - this.last < this.updatefreq) {
139 this.tryAgain = setTimeout(function () {
141 if (self.disabled) return
142 if (! self.spun && ! completed) return
144 }, this.updatefreq - (new Date() - this.last))
157 self.last = new Date()
159 var statusline = self.renderTemplate(self.theme, self.template, values)
161 if (self.showing) self.cursor.up(1)
164 .horizontalAbsolute(0)
165 .write(statusline.substr(0, getWritableTTYColumns()) + "\n")
172 ProgressBar.prototype.renderTemplate = function (theme, template, values) {
173 values.startgroup = theme.startgroup
174 values.endgroup = theme.endgroup
175 values.spinner = values.spinner
176 ? theme.spinner.substr(values.spinner % theme.spinner.length,1)
179 var output = {prebar: "", postbar: ""}
180 var status = "prebar"
182 template.forEach(function(T) {
183 if (typeof T === "string") {
187 if (T.type === "completionbar") {
191 if (!values.hasOwnProperty(T.type)) throw new Error("Unknown template value '"+T.type+"'")
192 var value = self.renderValue(T, values[T.type])
193 if (value === "") return
194 var sofar = output[status].length
195 var lastChar = sofar ? output[status][sofar-1] : null
196 if (T.separated && sofar && lastChar !== " ") {
197 output[status] += " "
199 output[status] += value
200 if (T.separated) output[status] += " "
204 if (status === "postbar") {
205 var nonBarLen = output.prebar.length + output.postbar.length
207 var barLen = getWritableTTYColumns() - nonBarLen
208 var sofar = Math.round(barLen * Math.max(0,Math.min(1,values.completed||0)))
209 var rest = barLen - sofar
210 bar = repeat(theme.complete, sofar)
211 + repeat(theme.incomplete, rest)
214 return output.prebar + bar + output.postbar
216 ProgressBar.prototype.renderValue = function (template, value) {
217 if (value == null || value === "") return ""
218 var maxLength = template.maxLength || template.length
219 var minLength = template.minLength || template.length
220 var alignWith = align[template.align] || align.left
221 // if (maxLength) value = value.substr(-1 * maxLength)
222 if (maxLength) value = value.substr(0, maxLength)
223 if (minLength) value = alignWith(value, minLength)