[Service] Integrate DeviceHome and SignalingServer
[platform/framework/web/wrtjs.git] / device_home / node_modules / cliui / index.js
1 var stringWidth = require('string-width')
2 var stripAnsi = require('strip-ansi')
3 var wrap = require('wrap-ansi')
4 var align = {
5   right: alignRight,
6   center: alignCenter
7 }
8 var top = 0
9 var right = 1
10 var bottom = 2
11 var left = 3
12
13 function UI (opts) {
14   this.width = opts.width
15   this.wrap = opts.wrap
16   this.rows = []
17 }
18
19 UI.prototype.span = function () {
20   var cols = this.div.apply(this, arguments)
21   cols.span = true
22 }
23
24 UI.prototype.resetOutput = function () {
25   this.rows = []
26 }
27
28 UI.prototype.div = function () {
29   if (arguments.length === 0) this.div('')
30   if (this.wrap && this._shouldApplyLayoutDSL.apply(this, arguments)) {
31     return this._applyLayoutDSL(arguments[0])
32   }
33
34   var cols = []
35
36   for (var i = 0, arg; (arg = arguments[i]) !== undefined; i++) {
37     if (typeof arg === 'string') cols.push(this._colFromString(arg))
38     else cols.push(arg)
39   }
40
41   this.rows.push(cols)
42   return cols
43 }
44
45 UI.prototype._shouldApplyLayoutDSL = function () {
46   return arguments.length === 1 && typeof arguments[0] === 'string' &&
47     /[\t\n]/.test(arguments[0])
48 }
49
50 UI.prototype._applyLayoutDSL = function (str) {
51   var _this = this
52   var rows = str.split('\n')
53   var leftColumnWidth = 0
54
55   // simple heuristic for layout, make sure the
56   // second column lines up along the left-hand.
57   // don't allow the first column to take up more
58   // than 50% of the screen.
59   rows.forEach(function (row) {
60     var columns = row.split('\t')
61     if (columns.length > 1 && stringWidth(columns[0]) > leftColumnWidth) {
62       leftColumnWidth = Math.min(
63         Math.floor(_this.width * 0.5),
64         stringWidth(columns[0])
65       )
66     }
67   })
68
69   // generate a table:
70   //  replacing ' ' with padding calculations.
71   //  using the algorithmically generated width.
72   rows.forEach(function (row) {
73     var columns = row.split('\t')
74     _this.div.apply(_this, columns.map(function (r, i) {
75       return {
76         text: r.trim(),
77         padding: _this._measurePadding(r),
78         width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined
79       }
80     }))
81   })
82
83   return this.rows[this.rows.length - 1]
84 }
85
86 UI.prototype._colFromString = function (str) {
87   return {
88     text: str,
89     padding: this._measurePadding(str)
90   }
91 }
92
93 UI.prototype._measurePadding = function (str) {
94   // measure padding without ansi escape codes
95   var noAnsi = stripAnsi(str)
96   return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length]
97 }
98
99 UI.prototype.toString = function () {
100   var _this = this
101   var lines = []
102
103   _this.rows.forEach(function (row, i) {
104     _this.rowToString(row, lines)
105   })
106
107   // don't display any lines with the
108   // hidden flag set.
109   lines = lines.filter(function (line) {
110     return !line.hidden
111   })
112
113   return lines.map(function (line) {
114     return line.text
115   }).join('\n')
116 }
117
118 UI.prototype.rowToString = function (row, lines) {
119   var _this = this
120   var padding
121   var rrows = this._rasterize(row)
122   var str = ''
123   var ts
124   var width
125   var wrapWidth
126
127   rrows.forEach(function (rrow, r) {
128     str = ''
129     rrow.forEach(function (col, c) {
130       ts = '' // temporary string used during alignment/padding.
131       width = row[c].width // the width with padding.
132       wrapWidth = _this._negatePadding(row[c]) // the width without padding.
133
134       ts += col
135
136       for (var i = 0; i < wrapWidth - stringWidth(col); i++) {
137         ts += ' '
138       }
139
140       // align the string within its column.
141       if (row[c].align && row[c].align !== 'left' && _this.wrap) {
142         ts = align[row[c].align](ts, wrapWidth)
143         if (stringWidth(ts) < wrapWidth) ts += new Array(width - stringWidth(ts)).join(' ')
144       }
145
146       // apply border and padding to string.
147       padding = row[c].padding || [0, 0, 0, 0]
148       if (padding[left]) str += new Array(padding[left] + 1).join(' ')
149       str += addBorder(row[c], ts, '| ')
150       str += ts
151       str += addBorder(row[c], ts, ' |')
152       if (padding[right]) str += new Array(padding[right] + 1).join(' ')
153
154       // if prior row is span, try to render the
155       // current row on the prior line.
156       if (r === 0 && lines.length > 0) {
157         str = _this._renderInline(str, lines[lines.length - 1])
158       }
159     })
160
161     // remove trailing whitespace.
162     lines.push({
163       text: str.replace(/ +$/, ''),
164       span: row.span
165     })
166   })
167
168   return lines
169 }
170
171 function addBorder (col, ts, style) {
172   if (col.border) {
173     if (/[.']-+[.']/.test(ts)) return ''
174     else if (ts.trim().length) return style
175     else return '  '
176   }
177   return ''
178 }
179
180 // if the full 'source' can render in
181 // the target line, do so.
182 UI.prototype._renderInline = function (source, previousLine) {
183   var leadingWhitespace = source.match(/^ */)[0].length
184   var target = previousLine.text
185   var targetTextWidth = stringWidth(target.trimRight())
186
187   if (!previousLine.span) return source
188
189   // if we're not applying wrapping logic,
190   // just always append to the span.
191   if (!this.wrap) {
192     previousLine.hidden = true
193     return target + source
194   }
195
196   if (leadingWhitespace < targetTextWidth) return source
197
198   previousLine.hidden = true
199
200   return target.trimRight() + new Array(leadingWhitespace - targetTextWidth + 1).join(' ') + source.trimLeft()
201 }
202
203 UI.prototype._rasterize = function (row) {
204   var _this = this
205   var i
206   var rrow
207   var rrows = []
208   var widths = this._columnWidths(row)
209   var wrapped
210
211   // word wrap all columns, and create
212   // a data-structure that is easy to rasterize.
213   row.forEach(function (col, c) {
214     // leave room for left and right padding.
215     col.width = widths[c]
216     if (_this.wrap) wrapped = wrap(col.text, _this._negatePadding(col), { hard: true }).split('\n')
217     else wrapped = col.text.split('\n')
218
219     if (col.border) {
220       wrapped.unshift('.' + new Array(_this._negatePadding(col) + 3).join('-') + '.')
221       wrapped.push("'" + new Array(_this._negatePadding(col) + 3).join('-') + "'")
222     }
223
224     // add top and bottom padding.
225     if (col.padding) {
226       for (i = 0; i < (col.padding[top] || 0); i++) wrapped.unshift('')
227       for (i = 0; i < (col.padding[bottom] || 0); i++) wrapped.push('')
228     }
229
230     wrapped.forEach(function (str, r) {
231       if (!rrows[r]) rrows.push([])
232
233       rrow = rrows[r]
234
235       for (var i = 0; i < c; i++) {
236         if (rrow[i] === undefined) rrow.push('')
237       }
238       rrow.push(str)
239     })
240   })
241
242   return rrows
243 }
244
245 UI.prototype._negatePadding = function (col) {
246   var wrapWidth = col.width
247   if (col.padding) wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0)
248   if (col.border) wrapWidth -= 4
249   return wrapWidth
250 }
251
252 UI.prototype._columnWidths = function (row) {
253   var _this = this
254   var widths = []
255   var unset = row.length
256   var unsetWidth
257   var remainingWidth = this.width
258
259   // column widths can be set in config.
260   row.forEach(function (col, i) {
261     if (col.width) {
262       unset--
263       widths[i] = col.width
264       remainingWidth -= col.width
265     } else {
266       widths[i] = undefined
267     }
268   })
269
270   // any unset widths should be calculated.
271   if (unset) unsetWidth = Math.floor(remainingWidth / unset)
272   widths.forEach(function (w, i) {
273     if (!_this.wrap) widths[i] = row[i].width || stringWidth(row[i].text)
274     else if (w === undefined) widths[i] = Math.max(unsetWidth, _minWidth(row[i]))
275   })
276
277   return widths
278 }
279
280 // calculates the minimum width of
281 // a column, based on padding preferences.
282 function _minWidth (col) {
283   var padding = col.padding || []
284   var minWidth = 1 + (padding[left] || 0) + (padding[right] || 0)
285   if (col.border) minWidth += 4
286   return minWidth
287 }
288
289 function getWindowWidth () {
290   if (typeof process === 'object' && process.stdout && process.stdout.columns) return process.stdout.columns
291 }
292
293 function alignRight (str, width) {
294   str = str.trim()
295   var padding = ''
296   var strWidth = stringWidth(str)
297
298   if (strWidth < width) {
299     padding = new Array(width - strWidth + 1).join(' ')
300   }
301
302   return padding + str
303 }
304
305 function alignCenter (str, width) {
306   str = str.trim()
307   var padding = ''
308   var strWidth = stringWidth(str.trim())
309
310   if (strWidth < width) {
311     padding = new Array(parseInt((width - strWidth) / 2, 10) + 1).join(' ')
312   }
313
314   return padding + str
315 }
316
317 module.exports = function (opts) {
318   opts = opts || {}
319
320   return new UI({
321     width: (opts || {}).width || getWindowWidth() || 80,
322     wrap: typeof opts.wrap === 'boolean' ? opts.wrap : true
323   })
324 }