tty-progressbar 0.11.0 → 0.12.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +302 -57
- data/examples/failure.rb +14 -0
- data/examples/iterator.rb +7 -0
- data/examples/multi/main_bar.rb +17 -0
- data/examples/multi/simple.rb +15 -0
- data/examples/threaded.rb +16 -0
- data/lib/tty-progressbar.rb +3 -2
- data/lib/tty/progressbar.rb +221 -58
- data/lib/tty/progressbar/configuration.rb +4 -0
- data/lib/tty/progressbar/converter.rb +2 -1
- data/lib/tty/progressbar/formatter.rb +2 -1
- data/lib/tty/progressbar/formatter/bar.rb +3 -1
- data/lib/tty/progressbar/formatter/byte_rate.rb +2 -1
- data/lib/tty/progressbar/formatter/current.rb +2 -1
- data/lib/tty/progressbar/formatter/current_byte.rb +2 -1
- data/lib/tty/progressbar/formatter/elapsed.rb +2 -1
- data/lib/tty/progressbar/formatter/estimated.rb +2 -1
- data/lib/tty/progressbar/formatter/mean_byte.rb +2 -1
- data/lib/tty/progressbar/formatter/mean_rate.rb +2 -1
- data/lib/tty/progressbar/formatter/percent.rb +2 -1
- data/lib/tty/progressbar/formatter/rate.rb +2 -1
- data/lib/tty/progressbar/formatter/total.rb +2 -1
- data/lib/tty/progressbar/formatter/total_byte.rb +2 -1
- data/lib/tty/progressbar/meter.rb +2 -1
- data/lib/tty/progressbar/multi.rb +221 -0
- data/lib/tty/progressbar/version.rb +1 -1
- data/spec/unit/events_spec.rb +35 -0
- data/spec/unit/formatter/elapsed_spec.rb +0 -1
- data/spec/unit/head_spec.rb +34 -0
- data/spec/unit/inspect_spec.rb +1 -1
- data/spec/unit/iterate_spec.rb +48 -0
- data/spec/unit/multi/advance_spec.rb +139 -0
- data/spec/unit/multi/events_spec.rb +64 -0
- data/spec/unit/multi/finish_spec.rb +19 -0
- data/spec/unit/multi/line_inset_spec.rb +67 -0
- data/spec/unit/multi/register_spec.rb +36 -0
- data/spec/unit/multi/stop_spec.rb +17 -0
- data/spec/unit/new_spec.rb +6 -0
- data/spec/unit/reset_spec.rb +0 -2
- data/spec/unit/stop_spec.rb +21 -0
- data/spec/unit/update_spec.rb +24 -0
- data/tty-progressbar.gemspec +1 -0
- metadata +44 -2
data/examples/failure.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tty-progressbar'
|
4
|
+
|
5
|
+
bars = TTY::ProgressBar::Multi.new("main [:bar] :percent")
|
6
|
+
|
7
|
+
bar1 = bars.register "foo [:bar] :percent", total: 20
|
8
|
+
bar2 = bars.register "bar [:bar] :percent", total: 30
|
9
|
+
bar3 = bars.register "baz [:bar] :percent", total: 10
|
10
|
+
|
11
|
+
bars.start
|
12
|
+
|
13
|
+
th1 = Thread.new { 20.times { sleep(0.2); bar1.advance } }
|
14
|
+
th2 = Thread.new { 30.times { sleep(0.1); bar2.advance } }
|
15
|
+
th3 = Thread.new { 10.times { sleep(0.3); bar3.advance } }
|
16
|
+
|
17
|
+
[th1, th2, th3].each(&:join)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tty-progressbar'
|
4
|
+
|
5
|
+
bars = TTY::ProgressBar::Multi.new
|
6
|
+
|
7
|
+
bar1 = bars.register "foo [:bar] :percent", total: 20
|
8
|
+
bar2 = bars.register "bar [:bar] :percent", total: 30
|
9
|
+
bar3 = bars.register "baz [:bar] :percent", total: 10
|
10
|
+
|
11
|
+
th1 = Thread.new { 20.times { sleep(0.2); bar1.advance } }
|
12
|
+
th2 = Thread.new { 30.times { sleep(0.1); bar2.advance } }
|
13
|
+
th3 = Thread.new { 10.times { sleep(0.3); bar3.advance } }
|
14
|
+
|
15
|
+
[th1, th2, th3].each(&:join)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tty-progressbar'
|
4
|
+
|
5
|
+
threads = []
|
6
|
+
|
7
|
+
bar = TTY::ProgressBar.new("[:bar] :percent", total: 30)
|
8
|
+
|
9
|
+
threads << Thread.new {
|
10
|
+
15.times { sleep(0.1); bar.update(complete: '-', head: '-'); bar.advance(); }
|
11
|
+
}
|
12
|
+
threads << Thread.new {
|
13
|
+
15.times { sleep(0.1); bar.update(complete: '+', head: '+'); bar.advance(); }
|
14
|
+
}
|
15
|
+
|
16
|
+
threads.map(&:join)
|
data/lib/tty-progressbar.rb
CHANGED
data/lib/tty/progressbar.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'io/console'
|
4
5
|
require 'forwardable'
|
6
|
+
require 'monitor'
|
7
|
+
require 'tty-cursor'
|
5
8
|
require 'tty-screen'
|
6
9
|
|
7
10
|
require_relative 'progressbar/configuration'
|
@@ -15,24 +18,28 @@ module TTY
|
|
15
18
|
# @api public
|
16
19
|
class ProgressBar
|
17
20
|
extend Forwardable
|
21
|
+
include MonitorMixin
|
18
22
|
|
19
23
|
ECMA_ESC = "\e".freeze
|
20
24
|
ECMA_CSI = "\e[".freeze
|
21
|
-
ECMA_CHA = 'G'.freeze
|
22
25
|
ECMA_CLR = 'K'.freeze
|
23
26
|
|
24
27
|
DEC_RST = 'l'.freeze
|
25
28
|
DEC_SET = 'h'.freeze
|
26
29
|
DEC_TCEM = '?25'.freeze
|
27
30
|
|
31
|
+
CURSOR_LOCK = Monitor.new
|
32
|
+
|
28
33
|
attr_reader :format
|
29
34
|
|
30
35
|
attr_reader :current
|
31
36
|
|
32
37
|
attr_reader :start_at
|
33
38
|
|
39
|
+
attr_reader :row
|
40
|
+
|
34
41
|
def_delegators :@configuration, :total, :width, :no_width,
|
35
|
-
:complete, :incomplete, :hide_cursor, :clear,
|
42
|
+
:complete, :incomplete, :head, :hide_cursor, :clear,
|
36
43
|
:output, :frequency, :interval, :width=
|
37
44
|
|
38
45
|
def_delegators :@meter, :rate, :mean_rate
|
@@ -65,33 +72,63 @@ module TTY
|
|
65
72
|
#
|
66
73
|
# @api public
|
67
74
|
def initialize(format, options = {})
|
68
|
-
|
75
|
+
super()
|
76
|
+
@format = format
|
77
|
+
if format.is_a?(Hash)
|
78
|
+
raise ArgumentError, "Expected bar formatting string, " \
|
79
|
+
"got `#{format}` instead."
|
80
|
+
end
|
69
81
|
@configuration = TTY::ProgressBar::Configuration.new(options)
|
70
82
|
yield @configuration if block_given?
|
71
83
|
|
84
|
+
@formatter = TTY::ProgressBar::Formatter.new
|
85
|
+
@meter = TTY::ProgressBar::Meter.new(interval)
|
86
|
+
@callbacks = Hash.new { |h, k| h[k] = [] }
|
87
|
+
|
88
|
+
@formatter.load
|
89
|
+
reset
|
90
|
+
end
|
91
|
+
|
92
|
+
# Reset progress to default configuration
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def reset
|
72
96
|
@width = 0 if no_width
|
73
97
|
@render_period = frequency == 0 ? 0 : 1.0 / frequency
|
74
98
|
@current = 0
|
75
|
-
@readings = 0
|
76
99
|
@last_render_time = Time.now
|
77
100
|
@last_render_width = 0
|
78
101
|
@done = false
|
102
|
+
@stopped = false
|
79
103
|
@start_at = Time.now
|
80
104
|
@started = false
|
81
105
|
@tokens = {}
|
82
|
-
@
|
83
|
-
@
|
106
|
+
@multibar = nil
|
107
|
+
@row = nil
|
108
|
+
@first_render = true
|
84
109
|
|
85
|
-
@
|
110
|
+
@meter.clear
|
111
|
+
end
|
112
|
+
|
113
|
+
# Attach this bar to multi bar
|
114
|
+
#
|
115
|
+
# @param [TTY::ProgressBar::Multi] multibar
|
116
|
+
# the multibar under which this bar is registered
|
117
|
+
#
|
118
|
+
# @api private
|
119
|
+
def attach_to(multibar)
|
120
|
+
@multibar = multibar
|
86
121
|
end
|
87
122
|
|
88
123
|
# Start progression by drawing bar and setting time
|
89
124
|
#
|
90
125
|
# @api public
|
91
126
|
def start
|
92
|
-
|
93
|
-
|
94
|
-
|
127
|
+
synchronize do
|
128
|
+
@started = true
|
129
|
+
@start_at = Time.now
|
130
|
+
@meter.start
|
131
|
+
end
|
95
132
|
|
96
133
|
advance(0)
|
97
134
|
end
|
@@ -102,25 +139,66 @@ module TTY
|
|
102
139
|
#
|
103
140
|
# @api public
|
104
141
|
def advance(progress = 1, tokens = {})
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
142
|
+
return if done?
|
143
|
+
|
144
|
+
synchronize do
|
145
|
+
if progress.respond_to?(:to_hash)
|
146
|
+
tokens, progress = progress, 1
|
147
|
+
end
|
148
|
+
@start_at = Time.now if @current.zero? && !@started
|
149
|
+
@current += progress
|
150
|
+
@tokens = tokens
|
151
|
+
@meter.sample(Time.now, progress)
|
152
|
+
|
153
|
+
if !no_width && @current >= total
|
154
|
+
finish && return
|
155
|
+
end
|
156
|
+
|
157
|
+
now = Time.now
|
158
|
+
return if (now - @last_render_time) < @render_period
|
159
|
+
render
|
160
|
+
emit(:progress)
|
110
161
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
162
|
+
end
|
163
|
+
|
164
|
+
# Iterate over collection either yielding computation to block
|
165
|
+
# or provided Enumerator.
|
166
|
+
#
|
167
|
+
# @example
|
168
|
+
# bar.iterate(30.times) { ... }
|
169
|
+
#
|
170
|
+
# @param [Enumerable] collection
|
171
|
+
# the collection to iterate over
|
172
|
+
#
|
173
|
+
# @param [Integer] progress
|
174
|
+
# the amount to move progress bar by
|
175
|
+
#
|
176
|
+
# @return [Enumerator]
|
177
|
+
#
|
178
|
+
# @api public
|
179
|
+
def iterate(collection, progress = 1, &block)
|
180
|
+
update(total: collection.count)
|
181
|
+
prog = Enumerator.new do |iter|
|
182
|
+
collection.each do |elem|
|
183
|
+
advance(progress)
|
184
|
+
iter.yield(elem)
|
185
|
+
end
|
119
186
|
end
|
187
|
+
block_given? ? prog.each(&block) : prog
|
188
|
+
end
|
120
189
|
|
121
|
-
|
122
|
-
|
123
|
-
|
190
|
+
# Update configuration options for this bar
|
191
|
+
#
|
192
|
+
# @param [Hash[Symbol]] options
|
193
|
+
# the configuration options to update
|
194
|
+
#
|
195
|
+
# @api public
|
196
|
+
def update(options = {})
|
197
|
+
synchronize do
|
198
|
+
options.each do |name, val|
|
199
|
+
@configuration.public_send("#{name}=", val)
|
200
|
+
end
|
201
|
+
end
|
124
202
|
end
|
125
203
|
|
126
204
|
# Advance the progress bar to the updated value
|
@@ -152,15 +230,17 @@ module TTY
|
|
152
230
|
#
|
153
231
|
# @api public
|
154
232
|
def ratio
|
155
|
-
|
156
|
-
|
233
|
+
synchronize do
|
234
|
+
proportion = total > 0 ? (@current.to_f / total) : 0
|
235
|
+
[[proportion, 0].max, 1].min
|
236
|
+
end
|
157
237
|
end
|
158
238
|
|
159
239
|
# Render progress to the output
|
160
240
|
#
|
161
241
|
# @api private
|
162
242
|
def render
|
163
|
-
return if
|
243
|
+
return if done?
|
164
244
|
if hide_cursor && @last_render_width == 0 && !(@current >= total)
|
165
245
|
write(ECMA_CSI + DEC_TCEM + DEC_RST)
|
166
246
|
end
|
@@ -175,15 +255,43 @@ module TTY
|
|
175
255
|
@last_render_width = formatted.length
|
176
256
|
end
|
177
257
|
|
258
|
+
# Move cursor to a row of the current bar if the bar is rendered
|
259
|
+
# under a multibar. Otherwise, do not move and yield on current row.
|
260
|
+
#
|
261
|
+
# @api private
|
262
|
+
def move_to_row
|
263
|
+
if @multibar
|
264
|
+
CURSOR_LOCK.synchronize do
|
265
|
+
if @first_render
|
266
|
+
@row = @multibar.next_row
|
267
|
+
yield if block_given?
|
268
|
+
output.print "\n"
|
269
|
+
@first_render = false
|
270
|
+
else
|
271
|
+
lines_up = (@multibar.rows + 1) - @row
|
272
|
+
output.print TTY::Cursor.save
|
273
|
+
output.print TTY::Cursor.up(lines_up)
|
274
|
+
yield if block_given?
|
275
|
+
output.print TTY::Cursor.restore
|
276
|
+
end
|
277
|
+
end
|
278
|
+
else
|
279
|
+
yield if block_given?
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
178
283
|
# Write out to the output
|
179
284
|
#
|
180
285
|
# @param [String] data
|
181
286
|
#
|
182
287
|
# @api private
|
183
288
|
def write(data, clear_first = false)
|
184
|
-
|
185
|
-
|
186
|
-
|
289
|
+
move_to_row do
|
290
|
+
output.print(TTY::Cursor.column(1)) if clear_first
|
291
|
+
characters_in = @multibar.line_inset(self) if @multibar
|
292
|
+
output.print("#{characters_in}#{data}")
|
293
|
+
output.flush
|
294
|
+
end
|
187
295
|
end
|
188
296
|
|
189
297
|
# Resize progress bar with new configuration
|
@@ -193,10 +301,12 @@ module TTY
|
|
193
301
|
#
|
194
302
|
# @api public
|
195
303
|
def resize(new_width = nil)
|
196
|
-
return if
|
197
|
-
|
198
|
-
|
199
|
-
|
304
|
+
return if done?
|
305
|
+
synchronize do
|
306
|
+
clear_line
|
307
|
+
if new_width
|
308
|
+
self.width = new_width
|
309
|
+
end
|
200
310
|
end
|
201
311
|
end
|
202
312
|
|
@@ -208,31 +318,38 @@ module TTY
|
|
208
318
|
if hide_cursor && @last_render_width != 0
|
209
319
|
write(ECMA_CSI + DEC_TCEM + DEC_SET, false)
|
210
320
|
end
|
211
|
-
return if
|
321
|
+
return if done?
|
212
322
|
@current = total unless no_width
|
213
323
|
render
|
214
324
|
clear ? clear_line : write("\n", false)
|
325
|
+
ensure
|
215
326
|
@meter.clear
|
216
327
|
@done = true
|
328
|
+
emit(:done)
|
217
329
|
end
|
218
330
|
|
219
|
-
#
|
331
|
+
# Stop and cancel the progress at the current position
|
220
332
|
#
|
221
333
|
# @api public
|
222
|
-
def
|
223
|
-
|
334
|
+
def stop
|
335
|
+
# reenable cursor if it is turned off
|
336
|
+
if hide_cursor && @last_render_width != 0
|
337
|
+
write(ECMA_CSI + DEC_TCEM + DEC_SET, false)
|
338
|
+
end
|
339
|
+
return if done?
|
340
|
+
render
|
341
|
+
clear ? clear_line : write("\n", false)
|
342
|
+
ensure
|
343
|
+
@meter.clear
|
344
|
+
@stopped = true
|
345
|
+
emit(:stopped)
|
224
346
|
end
|
225
347
|
|
226
|
-
#
|
348
|
+
# Clear current line
|
227
349
|
#
|
228
350
|
# @api public
|
229
|
-
def
|
230
|
-
|
231
|
-
@readings = 0
|
232
|
-
@done = false
|
233
|
-
@meter.clear
|
234
|
-
|
235
|
-
advance(0) # rerender with new configuration
|
351
|
+
def clear_line
|
352
|
+
output.print(ECMA_CSI + '0m' + ECMA_CSI + '1000D' + ECMA_CSI + ECMA_CLR)
|
236
353
|
end
|
237
354
|
|
238
355
|
# Check if progress is finised
|
@@ -245,6 +362,39 @@ module TTY
|
|
245
362
|
@done
|
246
363
|
end
|
247
364
|
|
365
|
+
# Check if progress is stopped
|
366
|
+
#
|
367
|
+
# @return [Boolean]
|
368
|
+
#
|
369
|
+
# @api public
|
370
|
+
def stopped?
|
371
|
+
@stopped
|
372
|
+
end
|
373
|
+
|
374
|
+
# Check if progress is finished or stopped
|
375
|
+
#
|
376
|
+
# @return [Boolean]
|
377
|
+
#
|
378
|
+
# @api public
|
379
|
+
def done?
|
380
|
+
@done || @stopped
|
381
|
+
end
|
382
|
+
|
383
|
+
# Register callback with this bar
|
384
|
+
#
|
385
|
+
# @param [Symbol] name
|
386
|
+
# the name for the event to listen for, e.i. :complete
|
387
|
+
#
|
388
|
+
# @return [self]
|
389
|
+
#
|
390
|
+
# @api public
|
391
|
+
def on(name, &callback)
|
392
|
+
synchronize do
|
393
|
+
@callbacks[name] << callback
|
394
|
+
end
|
395
|
+
self
|
396
|
+
end
|
397
|
+
|
248
398
|
# Log message above the current progress bar
|
249
399
|
#
|
250
400
|
# @param [String] message
|
@@ -253,7 +403,7 @@ module TTY
|
|
253
403
|
# @api public
|
254
404
|
def log(message)
|
255
405
|
sanitized_message = message.gsub(/\r|\n/, ' ')
|
256
|
-
if
|
406
|
+
if done?
|
257
407
|
write(sanitized_message + "\n", false)
|
258
408
|
return
|
259
409
|
end
|
@@ -287,14 +437,15 @@ module TTY
|
|
287
437
|
#
|
288
438
|
# @api public
|
289
439
|
def inspect
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
440
|
+
"#<#{self.class.name} " \
|
441
|
+
"@format=\"#{format}\", " \
|
442
|
+
"@current=\"#{@current}\", " \
|
443
|
+
"@total=\"#{total}\", " \
|
444
|
+
"@width=\"#{width}\", " \
|
445
|
+
"@complete=\"#{complete}\", " \
|
446
|
+
"@head=\"#{head}\", " \
|
447
|
+
"@incomplete=\"#{incomplete}\", " \
|
448
|
+
"@interval=\"#{interval}\">"
|
298
449
|
end
|
299
450
|
|
300
451
|
private
|
@@ -309,5 +460,17 @@ module TTY
|
|
309
460
|
end
|
310
461
|
message
|
311
462
|
end
|
463
|
+
|
464
|
+
# Emit callback by name
|
465
|
+
#
|
466
|
+
# @param [Symbol]
|
467
|
+
# the event name
|
468
|
+
#
|
469
|
+
# @api private
|
470
|
+
def emit(name, *args)
|
471
|
+
@callbacks[name].each do |callback|
|
472
|
+
callback.call(*args)
|
473
|
+
end
|
474
|
+
end
|
312
475
|
end # ProgressBar
|
313
476
|
end # TTY
|