tty-progressbar 0.14.0 → 0.18.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.
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