gauge-v2 1.0.0

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