tty-progressbar 0.14.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +98 -21
  3. data/LICENSE.txt +1 -1
  4. data/README.md +511 -119
  5. data/lib/tty-progressbar.rb +2 -2
  6. data/lib/tty/progressbar.rb +216 -87
  7. data/lib/tty/progressbar/configuration.rb +124 -16
  8. data/lib/tty/progressbar/converter.rb +16 -19
  9. data/lib/tty/progressbar/formats.rb +120 -0
  10. data/lib/tty/progressbar/formatter.rb +33 -38
  11. data/lib/tty/progressbar/formatter/bar.rb +87 -29
  12. data/lib/tty/progressbar/formatter/byte_rate.rb +6 -20
  13. data/lib/tty/progressbar/formatter/current.rb +4 -19
  14. data/lib/tty/progressbar/formatter/current_byte.rb +9 -17
  15. data/lib/tty/progressbar/formatter/elapsed.rb +9 -18
  16. data/lib/tty/progressbar/formatter/estimated.rb +14 -18
  17. data/lib/tty/progressbar/formatter/estimated_time.rb +37 -0
  18. data/lib/tty/progressbar/formatter/mean_byte.rb +6 -20
  19. data/lib/tty/progressbar/formatter/mean_rate.rb +6 -20
  20. data/lib/tty/progressbar/formatter/percent.rb +10 -16
  21. data/lib/tty/progressbar/formatter/rate.rb +5 -19
  22. data/lib/tty/progressbar/formatter/total.rb +10 -16
  23. data/lib/tty/progressbar/formatter/total_byte.rb +14 -18
  24. data/lib/tty/progressbar/formatters.rb +53 -0
  25. data/lib/tty/progressbar/meter.rb +2 -2
  26. data/lib/tty/progressbar/multi.rb +69 -23
  27. data/lib/tty/progressbar/pipeline.rb +13 -6
  28. data/lib/tty/progressbar/timer.rb +89 -0
  29. data/lib/tty/progressbar/version.rb +3 -1
  30. metadata +65 -151
  31. data/.codeclimate.yml +0 -11
  32. data/.gitignore +0 -14
  33. data/.rspec +0 -3
  34. data/.travis.yml +0 -25
  35. data/CODE_OF_CONDUCT.md +0 -74
  36. data/Gemfile +0 -14
  37. data/Rakefile +0 -8
  38. data/appveyor.yml +0 -21
  39. data/examples/color.rb +0 -18
  40. data/examples/failure.rb +0 -12
  41. data/examples/iterator.rb +0 -5
  42. data/examples/lazy.rb +0 -6
  43. data/examples/multi/main_bar.rb +0 -13
  44. data/examples/multi/simple.rb +0 -13
  45. data/examples/simple.rb +0 -7
  46. data/examples/slow_process.rb +0 -29
  47. data/examples/speed.rb +0 -11
  48. data/examples/threaded.rb +0 -14
  49. data/examples/tokens.rb +0 -12
  50. data/spec/spec_helper.rb +0 -50
  51. data/spec/unit/advance_spec.rb +0 -25
  52. data/spec/unit/clear_spec.rb +0 -17
  53. data/spec/unit/complete_spec.rb +0 -16
  54. data/spec/unit/converter/to_bytes_spec.rb +0 -47
  55. data/spec/unit/converter/to_seconds_spec.rb +0 -15
  56. data/spec/unit/converter/to_time_spec.rb +0 -19
  57. data/spec/unit/custom_formatter_spec.rb +0 -26
  58. data/spec/unit/custom_token_spec.rb +0 -14
  59. data/spec/unit/events_spec.rb +0 -33
  60. data/spec/unit/finish_spec.rb +0 -15
  61. data/spec/unit/formatter/bar_spec.rb +0 -16
  62. data/spec/unit/formatter/byte_rate_spec.rb +0 -32
  63. data/spec/unit/formatter/current_byte_spec.rb +0 -16
  64. data/spec/unit/formatter/current_spec.rb +0 -14
  65. data/spec/unit/formatter/elapsed_spec.rb +0 -58
  66. data/spec/unit/formatter/estimated_spec.rb +0 -27
  67. data/spec/unit/formatter/mean_byte_spec.rb +0 -32
  68. data/spec/unit/formatter/mean_rate_spec.rb +0 -31
  69. data/spec/unit/formatter/percent_spec.rb +0 -16
  70. data/spec/unit/formatter/rate_spec.rb +0 -31
  71. data/spec/unit/formatter/total_byte_spec.rb +0 -16
  72. data/spec/unit/formatter/total_spec.rb +0 -16
  73. data/spec/unit/frequency_spec.rb +0 -27
  74. data/spec/unit/head_spec.rb +0 -32
  75. data/spec/unit/hide_cursor_spec.rb +0 -27
  76. data/spec/unit/inspect_spec.rb +0 -11
  77. data/spec/unit/iterate_spec.rb +0 -79
  78. data/spec/unit/log_spec.rb +0 -29
  79. data/spec/unit/meter_spec.rb +0 -70
  80. data/spec/unit/multi/advance_spec.rb +0 -123
  81. data/spec/unit/multi/events_spec.rb +0 -115
  82. data/spec/unit/multi/finish_spec.rb +0 -41
  83. data/spec/unit/multi/line_inset_spec.rb +0 -65
  84. data/spec/unit/multi/register_spec.rb +0 -35
  85. data/spec/unit/multi/stop_spec.rb +0 -15
  86. data/spec/unit/new_spec.rb +0 -66
  87. data/spec/unit/pipeline_spec.rb +0 -19
  88. data/spec/unit/ratio_spec.rb +0 -31
  89. data/spec/unit/reset_spec.rb +0 -31
  90. data/spec/unit/resize_spec.rb +0 -35
  91. data/spec/unit/set_current_spec.rb +0 -43
  92. data/spec/unit/start_spec.rb +0 -14
  93. data/spec/unit/stop_spec.rb +0 -19
  94. data/spec/unit/update_spec.rb +0 -22
  95. data/spec/unit/width_spec.rb +0 -21
  96. data/tasks/console.rake +0 -9
  97. data/tasks/coverage.rake +0 -9
  98. data/tasks/spec.rake +0 -27
  99. data/tty-progressbar.gemspec +0 -30
@@ -1,2 +1,2 @@
1
- require_relative 'tty/progressbar'
2
- require_relative 'tty/progressbar/multi'
1
+ require_relative "tty/progressbar"
2
+ require_relative "tty/progressbar/multi"
@@ -1,15 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'io/console'
4
- require 'forwardable'
5
- require 'monitor'
6
- require 'tty-cursor'
7
- require 'tty-screen'
8
-
9
- require_relative 'progressbar/configuration'
10
- require_relative 'progressbar/formatter'
11
- require_relative 'progressbar/meter'
12
- require_relative 'progressbar/version'
3
+ require "io/console"
4
+ require "forwardable"
5
+ require "monitor"
6
+ require "tty-cursor"
7
+ require "tty-screen"
8
+ require "strings-ansi"
9
+ require "unicode/display_width"
10
+
11
+ require_relative "progressbar/timer"
12
+ require_relative "progressbar/configuration"
13
+ require_relative "progressbar/formatters"
14
+ require_relative "progressbar/meter"
15
+ require_relative "progressbar/version"
13
16
 
14
17
  module TTY
15
18
  # Used for creating terminal progress bar
@@ -19,28 +22,55 @@ module TTY
19
22
  extend Forwardable
20
23
  include MonitorMixin
21
24
 
22
- ECMA_CSI = "\e[".freeze
25
+ ECMA_CSI = "\e["
26
+
27
+ NEWLINE = "\n"
23
28
 
24
29
  CURSOR_LOCK = Monitor.new
25
30
 
26
- attr_reader :format
31
+ attr_accessor :format
27
32
 
28
33
  attr_reader :current
29
34
 
30
- attr_reader :start_at
31
-
32
35
  attr_reader :row
33
36
 
34
- def_delegators :@configuration, :total, :width, :no_width,
35
- :complete, :incomplete, :head, :hide_cursor, :clear,
36
- :output, :frequency, :interval, :width=
37
+ def_delegators :@configuration, :total, :width, :complete, :incomplete,
38
+ :head, :clear_head, :hide_cursor, :clear, :output,
39
+ :frequency, :interval, :inset, :width=, :unknown, :bar_format
37
40
 
38
41
  def_delegators :@meter, :rate, :mean_rate
39
42
 
40
- def_delegator :@formatter, :use
43
+ def_delegators :@timer, :elapsed_time, :start_time
44
+
45
+ # Determine terminal width
46
+ #
47
+ # @return [Integer]
48
+ #
49
+ # @api public
50
+ def self.max_columns
51
+ TTY::Screen.width
52
+ end
53
+
54
+ # Determine the monospace display width of a string
55
+ #
56
+ # @param [String] value
57
+ # the value to determine width of
58
+ #
59
+ # @return [Integer]
60
+ #
61
+ # @api public
62
+ def self.display_columns(value)
63
+ Unicode::DisplayWidth.of(Strings::ANSI.sanitize(value))
64
+ end
41
65
 
42
66
  # Create progress bar
43
67
  #
68
+ # @example
69
+ # bar = TTY::Progressbar.new
70
+ # bar.configure do |config|
71
+ # config.total = 20
72
+ # end
73
+ #
44
74
  # @param [String] format
45
75
  # the tokenized string that displays the output
46
76
  #
@@ -48,20 +78,29 @@ module TTY
48
78
  # @option options [Numeric] :total
49
79
  # the total number of steps to completion
50
80
  # @option options [Numeric] :width
51
- # the maximum width for the bars display including
52
- # all formatting options
53
- # @option options [Boolean] :no_width
54
- # true when progression is unknown defaulting to false
55
- # @option options [Boolean] :clear
56
- # whether or not to clear the progress line
57
- # @option options [Boolean] :hide_cursor
58
- # display or hide cursor
81
+ # the maximum width for the progress bar except all formatting tokens
82
+ # @option option [String] :complete
83
+ # the complete character in progress animation
84
+ # @option options [String] :incomplete
85
+ # the incomplete character in progress animation
86
+ # @option options [String] :head
87
+ # the head character, defaults to complete
88
+ # @option options [String] :unknown
89
+ # the unknown character for indeterminate progress animation
90
+ # @option options [Boolean] :bar_format
91
+ # the preconfigured bar format name, defaults to :classic
59
92
  # @option options [Object] :output
60
- # the object that responds to print call defaulting to stderr
93
+ # the object that responds to print call, defaults to stderr
61
94
  # @option options [Number] :frequency
62
- # the frequency with which to display bars
95
+ # the frequency with which to display a progress bar per second
63
96
  # @option options [Number] :interval
64
- # the period for sampling of speed measurement
97
+ # the time interval for sampling of speed measurement, defaults to 1 second
98
+ # @option options [Boolean] :hide_cursor
99
+ # whether or not to hide the cursor, defaults to false
100
+ # @option options [Boolean] :clear
101
+ # whether or not to clear the progress line, defaults to false
102
+ # @option options [Boolean] :clear_head
103
+ # whether or not to replace head character with complete, defaults to false
65
104
  #
66
105
  # @api public
67
106
  def initialize(format, options = {})
@@ -74,33 +113,52 @@ module TTY
74
113
  @configuration = TTY::ProgressBar::Configuration.new(options)
75
114
  yield @configuration if block_given?
76
115
 
77
- @formatter = TTY::ProgressBar::Formatter.new
78
- @meter = TTY::ProgressBar::Meter.new(interval)
116
+ @formatters = TTY::ProgressBar::Formatters.new
117
+ @meter = TTY::ProgressBar::Meter.new(interval)
118
+ @timer = TTY::ProgressBar::Timer.new
79
119
  @callbacks = Hash.new { |h, k| h[k] = [] }
80
120
 
81
- @formatter.load
121
+ @formatters.load(self)
82
122
  reset
123
+
124
+ @first_render = true
125
+ @multibar = nil
126
+ @row = nil
83
127
  end
84
128
 
85
129
  # Reset progress to default configuration
86
130
  #
87
131
  # @api public
88
132
  def reset
89
- @width = 0 if no_width
133
+ @width = 0 if indeterminate?
90
134
  @render_period = frequency == 0 ? 0 : 1.0 / frequency
91
135
  @current = 0
136
+ @unknown = 0
92
137
  @last_render_time = Time.now
93
138
  @last_render_width = 0
94
139
  @done = false
95
140
  @stopped = false
96
- @start_at = Time.now
97
- @started = false
141
+ @paused = false
98
142
  @tokens = {}
99
- @multibar = nil
100
- @row = nil
101
- @first_render = true
102
143
 
103
144
  @meter.clear
145
+ @timer.reset
146
+ end
147
+
148
+ # Access instance configuration
149
+ #
150
+ # @api public
151
+ def configure
152
+ yield @configuration
153
+ end
154
+
155
+ # Check if progress can be determined or not
156
+ #
157
+ # @return [Boolean]
158
+ #
159
+ # @api public
160
+ def indeterminate?
161
+ total.nil?
104
162
  end
105
163
 
106
164
  # Attach this bar to multi bar
@@ -113,13 +171,26 @@ module TTY
113
171
  @multibar = multibar
114
172
  end
115
173
 
174
+ # Use custom token formatter
175
+ #
176
+ # @param [Object] formatter_class
177
+ # the formatter class to add to formatting pipeline
178
+ #
179
+ # @api public
180
+ def use(formatter_class)
181
+ unless formatter_class.is_a?(Class)
182
+ raise ArgumentError, "Formatter needs to be a class"
183
+ end
184
+
185
+ @formatters.use(formatter_class.new(self))
186
+ end
187
+
116
188
  # Start progression by drawing bar and setting time
117
189
  #
118
190
  # @api public
119
191
  def start
120
192
  synchronize do
121
- @started = true
122
- @start_at = Time.now
193
+ @timer.start
123
194
  @meter.start
124
195
  end
125
196
 
@@ -135,21 +206,25 @@ module TTY
135
206
  return if done?
136
207
 
137
208
  synchronize do
138
- emit(:progress)
209
+ emit(:progress, progress)
139
210
  if progress.respond_to?(:to_hash)
140
211
  tokens, progress = progress, 1
141
212
  end
142
- @start_at = Time.now if @current.zero? && !@started
143
- @current += progress
144
- @tokens = tokens
213
+ @timer.start
214
+ @current += progress
215
+ # When progress is unknown increase by 2% up to max 200%, after
216
+ # that reset back to 0%
217
+ @unknown += 2 if indeterminate?
218
+ @unknown = 0 if @unknown > 199
219
+ @tokens = tokens
145
220
  @meter.sample(Time.now, progress)
146
221
 
147
- if !no_width && @current >= total
222
+ if !indeterminate? && @current >= total
148
223
  finish && return
149
224
  end
150
225
 
151
- now = Time.now
152
- return if (now - @last_render_time) < @render_period
226
+ return if (Time.now - @last_render_time) < @render_period
227
+
153
228
  render
154
229
  end
155
230
  end
@@ -166,7 +241,7 @@ module TTY
166
241
  # be convenient in "unsure number of iterations" situations
167
242
  # (like downloading in chunks, when server may eventually send
168
243
  # more chunks than predicted), but be careful to not pass infinite
169
- # enumerators without previosly doing `.take(some_finite_number)`
244
+ # enumerators without previously doing `.take(some_finite_number)`
170
245
  # on them.
171
246
  #
172
247
  # @example
@@ -201,7 +276,9 @@ module TTY
201
276
  def update(options = {})
202
277
  synchronize do
203
278
  options.each do |name, val|
204
- @configuration.public_send("#{name}=", val)
279
+ if @configuration.respond_to?("#{name}=")
280
+ @configuration.public_send("#{name}=", val)
281
+ end
205
282
  end
206
283
  end
207
284
  end
@@ -231,12 +308,20 @@ module TTY
231
308
 
232
309
  # Ratio of completed over total steps
233
310
  #
311
+ # When the total is unknown the progress ratio oscillates
312
+ # by going up from 0 to 1 and then down from 1 to 0 and
313
+ # up again to infinity.
314
+ #
234
315
  # @return [Float]
235
316
  #
236
317
  # @api public
237
318
  def ratio
238
319
  synchronize do
239
- proportion = total > 0 ? (@current.to_f / total) : 0
320
+ proportion = if total
321
+ total > 0 ? (@current.to_f / total) : 0
322
+ else
323
+ (@unknown > 100 ? 200 - @unknown : @unknown).to_f / 100
324
+ end
240
325
  [[proportion, 0].max, 1].min
241
326
  end
242
327
  end
@@ -246,18 +331,28 @@ module TTY
246
331
  # @api private
247
332
  def render
248
333
  return if done?
249
- if hide_cursor && @last_render_width == 0 && !(@current >= total)
334
+
335
+ if hide_cursor && @last_render_width == 0 &&
336
+ (indeterminate? || @current < total)
250
337
  write(TTY::Cursor.hide)
251
338
  end
252
339
 
253
- formatted = @formatter.decorate(self, @format)
340
+ if @multibar
341
+ characters_in = @multibar.line_inset(self)
342
+ update(inset: self.class.display_columns(characters_in))
343
+ end
344
+
345
+ formatted = @formatters.decorate(@format)
254
346
  @tokens.each do |token, val|
255
347
  formatted = formatted.gsub(":#{token}", val)
256
348
  end
257
- write(formatted, true)
349
+
350
+ padded = padout(formatted)
351
+
352
+ write(padded, true)
258
353
 
259
354
  @last_render_time = Time.now
260
- @last_render_width = formatted.length
355
+ @last_render_width = self.class.display_columns(formatted)
261
356
  end
262
357
 
263
358
  # Move cursor to a row of the current bar if the bar is rendered
@@ -270,7 +365,7 @@ module TTY
270
365
  if @first_render
271
366
  @row = @multibar.next_row
272
367
  yield if block_given?
273
- output.print "\n"
368
+ output.print NEWLINE
274
369
  @first_render = false
275
370
  else
276
371
  lines_up = (@multibar.rows + 1) - @row
@@ -309,6 +404,7 @@ module TTY
309
404
  # @api public
310
405
  def resize(new_width = nil)
311
406
  return if done?
407
+
312
408
  synchronize do
313
409
  clear_line
314
410
  if new_width
@@ -321,45 +417,75 @@ module TTY
321
417
  #
322
418
  # @api public
323
419
  def finish
324
- # reenable cursor if it is turned off
325
- if hide_cursor && @last_render_width != 0
326
- write(TTY::Cursor.show, false)
327
- end
328
420
  return if done?
329
- @current = total unless no_width
421
+
422
+ @current = total unless indeterminate?
330
423
  render
331
- clear ? clear_line : write("\n", false)
424
+ clear ? clear_line : write(NEWLINE, false)
332
425
  ensure
333
426
  @meter.clear
334
427
  @done = true
428
+ @timer.stop
429
+
430
+ # reenable cursor if it is turned off
431
+ if hide_cursor && @last_render_width != 0
432
+ write(TTY::Cursor.show, false)
433
+ end
434
+
335
435
  emit(:done)
336
436
  end
337
437
 
438
+ # Resume rendering when bar is done, stopped or paused
439
+ #
440
+ # @api public
441
+ def resume
442
+ synchronize do
443
+ @done = false
444
+ @stopped = false
445
+ @paused = false
446
+ end
447
+ end
448
+
338
449
  # Stop and cancel the progress at the current position
339
450
  #
340
451
  # @api public
341
452
  def stop
342
- # reenable cursor if it is turned off
343
- if hide_cursor && @last_render_width != 0
344
- write(TTY::Cursor.show, false)
345
- end
346
453
  return if done?
454
+
347
455
  render
348
- clear ? clear_line : write("\n", false)
456
+ clear ? clear_line : write(NEWLINE, false)
349
457
  ensure
350
458
  @meter.clear
351
459
  @stopped = true
460
+ @timer.stop
461
+
462
+ # reenable cursor if it is turned off
463
+ if hide_cursor && @last_render_width != 0
464
+ write(TTY::Cursor.show, false)
465
+ end
466
+
352
467
  emit(:stopped)
353
468
  end
354
469
 
470
+ # Pause the progress at the current position
471
+ #
472
+ # @api public
473
+ def pause
474
+ synchronize do
475
+ @paused = true
476
+ @timer.stop
477
+ emit(:paused)
478
+ end
479
+ end
480
+
355
481
  # Clear current line
356
482
  #
357
483
  # @api public
358
484
  def clear_line
359
- output.print(ECMA_CSI + '0m' + TTY::Cursor.clear_line)
485
+ output.print("#{ECMA_CSI}0m#{TTY::Cursor.clear_line}")
360
486
  end
361
487
 
362
- # Check if progress is finised
488
+ # Check if progress is finished
363
489
  #
364
490
  # @return [Boolean]
365
491
  # true when progress finished, false otherwise
@@ -378,13 +504,22 @@ module TTY
378
504
  @stopped
379
505
  end
380
506
 
381
- # Check if progress is finished or stopped
507
+ # Check if progress is paused
508
+ #
509
+ # @return [Boolean]
510
+ #
511
+ # @api public
512
+ def paused?
513
+ @paused
514
+ end
515
+
516
+ # Check if progress is finished, stopped or paused
382
517
  #
383
518
  # @return [Boolean]
384
519
  #
385
520
  # @api public
386
521
  def done?
387
- @done || @stopped
522
+ @done || @stopped || @paused
388
523
  end
389
524
 
390
525
  # Register callback with this bar
@@ -409,26 +544,17 @@ module TTY
409
544
  #
410
545
  # @api public
411
546
  def log(message)
412
- sanitized_message = message.gsub(/\r|\n/, ' ')
547
+ sanitized_message = message.gsub(/\r|\n/, " ")
413
548
  if done?
414
- write(sanitized_message + "\n", false)
549
+ write("#{sanitized_message}#{NEWLINE}", false)
415
550
  return
416
551
  end
417
552
  sanitized_message = padout(sanitized_message)
418
553
 
419
- write(sanitized_message + "\n", true)
554
+ write("#{sanitized_message}#{NEWLINE}", true)
420
555
  render
421
556
  end
422
557
 
423
- # Determine terminal width
424
- #
425
- # @return [Integer]
426
- #
427
- # @api public
428
- def max_columns
429
- TTY::Screen.width
430
- end
431
-
432
558
  # Show bar format
433
559
  #
434
560
  # @return [String]
@@ -445,13 +571,14 @@ module TTY
445
571
  # @api public
446
572
  def inspect
447
573
  "#<#{self.class.name} " \
448
- "@format=\"#{format}\", " \
574
+ "@format=\"#{@format}\", " \
449
575
  "@current=\"#{@current}\", " \
450
576
  "@total=\"#{total}\", " \
451
577
  "@width=\"#{width}\", " \
452
578
  "@complete=\"#{complete}\", " \
453
579
  "@head=\"#{head}\", " \
454
580
  "@incomplete=\"#{incomplete}\", " \
581
+ "@unknown=\"#{unknown}\", " \
455
582
  "@interval=\"#{interval}\">"
456
583
  end
457
584
 
@@ -461,9 +588,11 @@ module TTY
461
588
  #
462
589
  # @api private
463
590
  def padout(message)
464
- if @last_render_width > message.length
465
- remaining_width = @last_render_width - message.length
466
- message += ' ' * remaining_width
591
+ message_length = self.class.display_columns(message)
592
+
593
+ if @last_render_width > message_length
594
+ remaining_width = @last_render_width - message_length
595
+ message += " " * remaining_width
467
596
  end
468
597
  message
469
598
  end
@@ -476,7 +605,7 @@ module TTY
476
605
  # @api private
477
606
  def emit(name, *args)
478
607
  @callbacks[name].each do |callback|
479
- callback.call(*args)
608
+ callback.(*args)
480
609
  end
481
610
  end
482
611