Built motion from commit 10af8726.|2.6.34
[motion2.git] / legacy-libs / npmlog / log.js
1 'use strict'
2 var Progress = require('are-we-there-yet')
3 var Gauge = require('gauge')
4 var EE = require('events').EventEmitter
5 var log = exports = module.exports = new EE()
6 var util = require('util')
7
8 var setBlocking = require('set-blocking')
9 var consoleControl = require('console-control-strings')
10
11 setBlocking(true)
12 var stream = process.stderr
13 Object.defineProperty(log, 'stream', {
14   set: function (newStream) {
15     stream = newStream
16     if (this.gauge) this.gauge.setWriteTo(stream, stream)
17   },
18   get: function () {
19     return stream
20   }
21 })
22
23 // by default, decide based on tty-ness.
24 var colorEnabled
25 log.useColor = function () {
26   return colorEnabled != null ? colorEnabled : stream.isTTY
27 }
28
29 log.enableColor = function () {
30   colorEnabled = true
31   this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
32 }
33 log.disableColor = function () {
34   colorEnabled = false
35   this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
36 }
37
38 // default level
39 log.level = 'info'
40
41 log.gauge = new Gauge(stream, {
42   enabled: false, // no progress bars unless asked
43   theme: {hasColor: log.useColor()},
44   template: [
45     {type: 'progressbar', length: 20},
46     {type: 'activityIndicator', kerning: 1, length: 1},
47     {type: 'section', default: ''},
48     ':',
49     {type: 'logline', kerning: 1, default: ''}
50   ]
51 })
52
53 log.tracker = new Progress.TrackerGroup()
54
55 // we track this separately as we may need to temporarily disable the
56 // display of the status bar for our own loggy purposes.
57 log.progressEnabled = log.gauge.isEnabled()
58
59 var unicodeEnabled
60
61 log.enableUnicode = function () {
62   unicodeEnabled = true
63   this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
64 }
65
66 log.disableUnicode = function () {
67   unicodeEnabled = false
68   this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
69 }
70
71 log.setGaugeThemeset = function (themes) {
72   this.gauge.setThemeset(themes)
73 }
74
75 log.setGaugeTemplate = function (template) {
76   this.gauge.setTemplate(template)
77 }
78
79 log.enableProgress = function () {
80   if (this.progressEnabled) return
81   this.progressEnabled = true
82   this.tracker.on('change', this.showProgress)
83   if (this._pause) return
84   this.gauge.enable()
85 }
86
87 log.disableProgress = function () {
88   if (!this.progressEnabled) return
89   this.progressEnabled = false
90   this.tracker.removeListener('change', this.showProgress)
91   this.gauge.disable()
92 }
93
94 var trackerConstructors = ['newGroup', 'newItem', 'newStream']
95
96 var mixinLog = function (tracker) {
97   // mixin the public methods from log into the tracker
98   // (except: conflicts and one's we handle specially)
99   Object.keys(log).forEach(function (P) {
100     if (P[0] === '_') return
101     if (trackerConstructors.filter(function (C) { return C === P }).length) return
102     if (tracker[P]) return
103     if (typeof log[P] !== 'function') return
104     var func = log[P]
105     tracker[P] = function () {
106       return func.apply(log, arguments)
107     }
108   })
109   // if the new tracker is a group, make sure any subtrackers get
110   // mixed in too
111   if (tracker instanceof Progress.TrackerGroup) {
112     trackerConstructors.forEach(function (C) {
113       var func = tracker[C]
114       tracker[C] = function () { return mixinLog(func.apply(tracker, arguments)) }
115     })
116   }
117   return tracker
118 }
119
120 // Add tracker constructors to the top level log object
121 trackerConstructors.forEach(function (C) {
122   log[C] = function () { return mixinLog(this.tracker[C].apply(this.tracker, arguments)) }
123 })
124
125 log.clearProgress = function (cb) {
126   if (!this.progressEnabled) return cb && process.nextTick(cb)
127   this.gauge.hide(cb)
128 }
129
130 log.showProgress = function (name, completed) {
131   if (!this.progressEnabled) return
132   var values = {}
133   if (name) values.section = name
134   var last = log.record[log.record.length - 1]
135   if (last) {
136     values.subsection = last.prefix
137     var disp = log.disp[last.level] || last.level
138     var logline = this._format(disp, log.style[last.level])
139     if (last.prefix) logline += ' ' + this._format(last.prefix, this.prefixStyle)
140     logline += ' ' + last.message.split(/\r?\n/)[0]
141     values.logline = logline
142   }
143   values.completed = completed || this.tracker.completed()
144   this.gauge.show(values)
145 }.bind(log) // bind for use in tracker's on-change listener
146
147 // temporarily stop emitting, but don't drop
148 log.pause = function () {
149   this._paused = true
150   if (this.progressEnabled) this.gauge.disable()
151 }
152
153 log.resume = function () {
154   if (!this._paused) return
155   this._paused = false
156
157   var b = this._buffer
158   this._buffer = []
159   b.forEach(function (m) {
160     this.emitLog(m)
161   }, this)
162   if (this.progressEnabled) this.gauge.enable()
163 }
164
165 log._buffer = []
166
167 var id = 0
168 log.record = []
169 log.maxRecordSize = 10000
170 log.log = function (lvl, prefix, message) {
171   var l = this.levels[lvl]
172   if (l === undefined) {
173     return this.emit('error', new Error(util.format(
174       'Undefined log level: %j', lvl)))
175   }
176
177   var a = new Array(arguments.length - 2)
178   var stack = null
179   for (var i = 2; i < arguments.length; i++) {
180     var arg = a[i - 2] = arguments[i]
181
182     // resolve stack traces to a plain string.
183     if (typeof arg === 'object' && arg &&
184         (arg instanceof Error) && arg.stack) {
185
186       Object.defineProperty(arg, 'stack', {
187         value: stack = arg.stack + '',
188         enumerable: true,
189         writable: true
190       })
191     }
192   }
193   if (stack) a.unshift(stack + '\n')
194   message = util.format.apply(util, a)
195
196   var m = { id: id++,
197             level: lvl,
198             prefix: String(prefix || ''),
199             message: message,
200             messageRaw: a }
201
202   this.emit('log', m)
203   this.emit('log.' + lvl, m)
204   if (m.prefix) this.emit(m.prefix, m)
205
206   this.record.push(m)
207   var mrs = this.maxRecordSize
208   var n = this.record.length - mrs
209   if (n > mrs / 10) {
210     var newSize = Math.floor(mrs * 0.9)
211     this.record = this.record.slice(-1 * newSize)
212   }
213
214   this.emitLog(m)
215 }.bind(log)
216
217 log.emitLog = function (m) {
218   if (this._paused) {
219     this._buffer.push(m)
220     return
221   }
222   if (this.progressEnabled) this.gauge.pulse(m.prefix)
223   var l = this.levels[m.level]
224   if (l === undefined) return
225   if (l < this.levels[this.level]) return
226   if (l > 0 && !isFinite(l)) return
227
228   // If 'disp' is null or undefined, use the lvl as a default
229   // Allows: '', 0 as valid disp
230   var disp = log.disp[m.level] != null ? log.disp[m.level] : m.level
231   this.clearProgress()
232   m.message.split(/\r?\n/).forEach(function (line) {
233     if (this.heading) {
234       this.write(this.heading, this.headingStyle)
235       this.write(' ')
236     }
237     this.write(disp, log.style[m.level])
238     var p = m.prefix || ''
239     if (p) this.write(' ')
240     this.write(p, this.prefixStyle)
241     this.write(' ' + line + '\n')
242   }, this)
243   this.showProgress()
244 }
245
246 log._format = function (msg, style) {
247   if (!stream) return
248
249   var output = ''
250   if (this.useColor()) {
251     style = style || {}
252     var settings = []
253     if (style.fg) settings.push(style.fg)
254     if (style.bg) settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1))
255     if (style.bold) settings.push('bold')
256     if (style.underline) settings.push('underline')
257     if (style.inverse) settings.push('inverse')
258     if (settings.length) output += consoleControl.color(settings)
259     if (style.beep) output += consoleControl.beep()
260   }
261   output += msg
262   if (this.useColor()) {
263     output += consoleControl.color('reset')
264   }
265   return output
266 }
267
268 log.write = function (msg, style) {
269   if (!stream) return
270
271   stream.write(this._format(msg, style))
272 }
273
274 log.addLevel = function (lvl, n, style, disp) {
275   // If 'disp' is null or undefined, use the lvl as a default
276   if (disp == null) disp = lvl
277   this.levels[lvl] = n
278   this.style[lvl] = style
279   if (!this[lvl]) {
280     this[lvl] = function () {
281       var a = new Array(arguments.length + 1)
282       a[0] = lvl
283       for (var i = 0; i < arguments.length; i++) {
284         a[i + 1] = arguments[i]
285       }
286       return this.log.apply(this, a)
287     }.bind(this)
288   }
289   this.disp[lvl] = disp
290 }
291
292 log.prefixStyle = { fg: 'magenta' }
293 log.headingStyle = { fg: 'white', bg: 'black' }
294
295 log.style = {}
296 log.levels = {}
297 log.disp = {}
298 log.addLevel('silly', -Infinity, { inverse: true }, 'sill')
299 log.addLevel('verbose', 1000, { fg: 'blue', bg: 'black' }, 'verb')
300 log.addLevel('info', 2000, { fg: 'green' })
301 log.addLevel('timing', 2500, { fg: 'green', bg: 'black' })
302 log.addLevel('http', 3000, { fg: 'green', bg: 'black' })
303 log.addLevel('notice', 3500, { fg: 'blue', bg: 'black' })
304 log.addLevel('warn', 4000, { fg: 'black', bg: 'yellow' }, 'WARN')
305 log.addLevel('error', 5000, { fg: 'red', bg: 'black' }, 'ERR!')
306 log.addLevel('silent', Infinity)
307
308 // allow 'error' prefix
309 log.on('error', function () {})