Class: TTY::ProgressBar

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
MonitorMixin
Defined in:
lib/tty/progressbar.rb,
lib/tty/progressbar/meter.rb,
lib/tty/progressbar/multi.rb,
lib/tty/progressbar/timer.rb,
lib/tty/progressbar/formats.rb,
lib/tty/progressbar/version.rb,
lib/tty/progressbar/pipeline.rb,
lib/tty/progressbar/converter.rb,
lib/tty/progressbar/formatter.rb,
lib/tty/progressbar/formatters.rb,
lib/tty/progressbar/configuration.rb,
lib/tty/progressbar/formatter/bar.rb,
lib/tty/progressbar/formatter/rate.rb,
lib/tty/progressbar/formatter/total.rb,
lib/tty/progressbar/formatter/current.rb,
lib/tty/progressbar/formatter/elapsed.rb,
lib/tty/progressbar/formatter/percent.rb,
lib/tty/progressbar/formatter/byte_rate.rb,
lib/tty/progressbar/formatter/estimated.rb,
lib/tty/progressbar/formatter/mean_byte.rb,
lib/tty/progressbar/formatter/mean_rate.rb,
lib/tty/progressbar/formatter/total_byte.rb,
lib/tty/progressbar/formatter/current_byte.rb,
lib/tty/progressbar/formatter/estimated_time.rb

Overview

Used for creating terminal progress bar

Defined Under Namespace

Modules: Converter, Formats Classes: BarFormatter, ByteFormatter, ByteRateFormatter, Configuration, CurrentFormatter, ElapsedFormatter, EstimatedFormatter, EstimatedTimeFormatter, Formatter, Formatters, MeanByteFormatter, MeanRateFormatter, Meter, Multi, PercentFormatter, Pipeline, RateFormatter, Timer, TotalByteFormatter, TotalFormatter

Constant Summary collapse

ECMA_CSI =
"\e["
NEWLINE =
"\n"
CURSOR_LOCK =
Monitor.new
VERSION =
"0.18.2"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(format, options = {}) {|@configuration| ... } ⇒ ProgressBar

Create progress bar

Examples:

bar = TTY::Progressbar.new
bar.configure do |config|
  config.total = 20
end

Parameters:

  • format (String)

    the tokenized string that displays the output

  • options (Hash) (defaults to: {})
  • option (Hash)

    a customizable set of options

Options Hash (options):

  • :total (Numeric)

    the total number of steps to completion

  • :width (Numeric)

    the maximum width for the progress bar except all formatting tokens

  • :incomplete (String)

    the incomplete character in progress animation

  • :head (String)

    the head character, defaults to complete

  • :unknown (String)

    the unknown character for indeterminate progress animation

  • :bar_format (Boolean)

    the preconfigured bar format name, defaults to :classic

  • :output (Object)

    the object that responds to print call, defaults to stderr

  • :frequency (Number)

    the frequency with which to display a progress bar per second

  • :interval (Number)

    the time interval for sampling of speed measurement, defaults to 1 second

  • :hide_cursor (Boolean)

    whether or not to hide the cursor, defaults to false

  • :clear (Boolean)

    whether or not to clear the progress line, defaults to false

  • :clear_head (Boolean)

    whether or not to replace head character with complete, defaults to false

Yields:

  • (@configuration)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/tty/progressbar.rb', line 106

def initialize(format, options = {})
  super()
  @format = format
  if format.is_a?(Hash)
    raise ArgumentError, "Expected bar formatting string, " \
                         "got `#{format}` instead."
  end
  @configuration = TTY::ProgressBar::Configuration.new(options)
  yield @configuration if block_given?

  @formatters = TTY::ProgressBar::Formatters.new
  @meter = TTY::ProgressBar::Meter.new(interval)
  @timer = TTY::ProgressBar::Timer.new
  @callbacks = Hash.new { |h, k| h[k] = [] }

  @formatters.load(self)
  reset

  @first_render = true
  @multibar = nil
  @row = nil
end

Instance Attribute Details

#currentObject



33
34
35
# File 'lib/tty/progressbar.rb', line 33

def current
  @current
end

#formatObject



31
32
33
# File 'lib/tty/progressbar.rb', line 31

def format
  @format
end

#rowObject (readonly)



35
36
37
# File 'lib/tty/progressbar.rb', line 35

def row
  @row
end

Class Method Details

.display_columns(value) ⇒ Integer

Determine the monospace display width of a string

Parameters:

  • value (String)

    the value to determine width of

Returns:

  • (Integer)


62
63
64
# File 'lib/tty/progressbar.rb', line 62

def self.display_columns(value)
  Unicode::DisplayWidth.of(Strings::ANSI.sanitize(value))
end

.max_columnsInteger

Determine terminal width

Returns:

  • (Integer)


50
51
52
# File 'lib/tty/progressbar.rb', line 50

def self.max_columns
  TTY::Screen.width
end

Instance Method Details

#advance(progress = 1, tokens = {}) ⇒ Object

Advance the progress bar

Parameters:

  • progress (Object|Number) (defaults to: 1)


205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/tty/progressbar.rb', line 205

def advance(progress = 1, tokens = {})
  return if done?

  synchronize do
    emit(:progress, progress)
    if progress.respond_to?(:to_hash)
      tokens, progress = progress, 1
    end
    @timer.start
    @current += progress
    # When progress is unknown increase by 2% up to max 200%, after
    # that reset back to 0%
    @unknown += 2 if indeterminate?
    @unknown = 0 if @unknown > 199
    @tokens = tokens
    @meter.sample(Time.now, progress)

    if !indeterminate? && @current >= total
      finish && return
    end

    return if (Time.now - @last_render_time) < @render_period

    render
  end
end

#attach_to(multibar) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Attach this bar to multi bar

Parameters:



170
171
172
# File 'lib/tty/progressbar.rb', line 170

def attach_to(multibar)
  @multibar = multibar
end

#clear_lineObject

Clear current line



484
485
486
# File 'lib/tty/progressbar.rb', line 484

def clear_line
  output.print("#{ECMA_CSI}0m#{TTY::Cursor.clear_line}")
end

#complete?Boolean

Check if progress is finished

Returns:

  • (Boolean)

    true when progress finished, false otherwise



494
495
496
# File 'lib/tty/progressbar.rb', line 494

def complete?
  @done
end

#configure {|@configuration| ... } ⇒ Object

Access instance configuration

Yields:

  • (@configuration)


151
152
153
# File 'lib/tty/progressbar.rb', line 151

def configure
  yield @configuration
end

#done?Boolean

Check if progress is finished, stopped or paused

Returns:

  • (Boolean)


521
522
523
# File 'lib/tty/progressbar.rb', line 521

def done?
  @done || @stopped || @paused
end

#finishObject

End the progress



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/tty/progressbar.rb', line 419

def finish
  return if done?

  @current = total unless indeterminate?
  render
  clear ? clear_line : write(NEWLINE, false)
ensure
  @meter.clear
  @done = true
  @timer.stop

  # reenable cursor if it is turned off
  if hide_cursor && @last_render_width != 0
    write(TTY::Cursor.show, false)
  end

  emit(:done)
end

#indeterminate?Boolean

Check if progress can be determined or not

Returns:

  • (Boolean)


160
161
162
# File 'lib/tty/progressbar.rb', line 160

def indeterminate?
  total.nil?
end

#inspectString

Inspect bar properties

Returns:

  • (String)


572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/tty/progressbar.rb', line 572

def inspect
  "#<#{self.class.name} " \
  "@format=\"#{@format}\", " \
  "@current=\"#{@current}\", " \
  "@total=\"#{total}\", " \
  "@width=\"#{width}\", " \
  "@complete=\"#{complete}\", " \
  "@head=\"#{head}\", " \
  "@incomplete=\"#{incomplete}\", " \
  "@unknown=\"#{unknown}\", " \
  "@interval=\"#{interval}\">"
end

#iterate(collection, progress = 1, &block) ⇒ Enumerator

Note:

If ‘total` is set, iteration will NOT stop after this number of iterations, only when provided Enumerable is finished. It may be convenient in “unsure number of iterations” situations (like downloading in chunks, when server may eventually send more chunks than predicted), but be careful to not pass infinite enumerators without previously doing `.take(some_finite_number)` on them.

Iterate over collection either yielding computation to block or provided Enumerator. If the bar’s ‘total` was not set, it would be taken from `collection.count`, otherwise previously set `total` would be used. This allows using the progressbar with infinite, lazy, or slowly-calculated enumerators.

Examples:

bar.iterate(30.times) { ... }

Parameters:

  • collection (Enumerable)

    the collection to iterate over

  • progress (Integer) (defaults to: 1)

    the amount to move progress bar by

Returns:

  • (Enumerator)


259
260
261
262
263
264
265
266
267
268
# File 'lib/tty/progressbar.rb', line 259

def iterate(collection, progress = 1, &block)
  update(total: collection.count * progress) unless total
  progress_enum = Enumerator.new do |iter|
    collection.each do |elem|
      advance(progress)
      iter.yield(elem)
    end
  end
  block_given? ? progress_enum.each(&block) : progress_enum
end

#log(message) ⇒ Object

Log message above the current progress bar

Parameters:

  • message (String)

    the message to log out



546
547
548
549
550
551
552
553
554
555
556
# File 'lib/tty/progressbar.rb', line 546

def log(message)
  sanitized_message = message.gsub(/\r|\n/, " ")
  if done?
    write("#{sanitized_message}#{NEWLINE}", false)
    return
  end
  sanitized_message = padout(sanitized_message)

  write("#{sanitized_message}#{NEWLINE}", true)
  render
end

#move_to_rowObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Move cursor to a row of the current bar if the bar is rendered under a multibar. Otherwise, do not move and yield on current row.



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/tty/progressbar.rb', line 362

def move_to_row
  if @multibar
    CURSOR_LOCK.synchronize do
      if @first_render
        @row = @multibar.next_row
        yield if block_given?
        output.print NEWLINE
        @first_render = false
      else
        lines_up = (@multibar.rows + 1) - @row
        output.print TTY::Cursor.save
        output.print TTY::Cursor.up(lines_up)
        yield if block_given?
        output.print TTY::Cursor.restore
      end
    end
  else
    yield if block_given?
  end
end

#on(name, &callback) ⇒ self

Register callback with this bar

Parameters:

  • name (Symbol)

    the name for the event to listen for, e.i. :complete

Returns:

  • (self)


533
534
535
536
537
538
# File 'lib/tty/progressbar.rb', line 533

def on(name, &callback)
  synchronize do
    @callbacks[name] << callback
  end
  self
end

#pauseObject

Pause the progress at the current position



473
474
475
476
477
478
479
# File 'lib/tty/progressbar.rb', line 473

def pause
  synchronize do
    @paused = true
    @timer.stop
    emit(:paused)
  end
end

#paused?Boolean

Check if progress is paused

Returns:

  • (Boolean)


512
513
514
# File 'lib/tty/progressbar.rb', line 512

def paused?
  @paused
end

#ratioFloat

Ratio of completed over total steps

When the total is unknown the progress ratio oscillates by going up from 0 to 1 and then down from 1 to 0 and up again to infinity.

Returns:

  • (Float)


318
319
320
321
322
323
324
325
326
327
# File 'lib/tty/progressbar.rb', line 318

def ratio
  synchronize do
    proportion = if total
                   total > 0 ? (@current.to_f / total) : 0
                 else
                   (@unknown > 100 ? 200 - @unknown : @unknown).to_f / 100
                 end
    [[proportion, 0].max, 1].min
  end
end

#ratio=(value) ⇒ Object

Advance the progress bar to an exact ratio. The target value is set to the closest available value.

Parameters:

  • value (Float)

    the ratio between 0 and 1 inclusive



304
305
306
307
# File 'lib/tty/progressbar.rb', line 304

def ratio=(value)
  target = (value * total).floor
  advance(target - @current)
end

#renderObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Render progress to the output



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/tty/progressbar.rb', line 332

def render
  return if done?

  if hide_cursor && @last_render_width == 0 &&
     (indeterminate? || @current < total)
    write(TTY::Cursor.hide)
  end

  if @multibar
    characters_in = @multibar.line_inset(self)
    update(inset: self.class.display_columns(characters_in))
  end

  formatted = @formatters.decorate(@format)
  @tokens.each do |token, val|
    formatted = formatted.gsub(":#{token}", val)
  end

  padded = padout(formatted)

  write(padded, true)

  @last_render_time  = Time.now
  @last_render_width = self.class.display_columns(formatted)
end

#resetObject

Reset progress to default configuration



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/tty/progressbar.rb', line 132

def reset
  @width             = 0 if indeterminate?
  @render_period     = frequency == 0 ? 0 : 1.0 / frequency
  @current           = 0
  @unknown           = 0
  @last_render_time  = Time.now
  @last_render_width = 0
  @done              = false
  @stopped           = false
  @paused            = false
  @tokens            = {}

  @meter.clear
  @timer.reset
end

#resize(new_width = nil) ⇒ Object

Resize progress bar with new configuration

Parameters:

  • new_width (Integer) (defaults to: nil)

    the new width for the bar display



405
406
407
408
409
410
411
412
413
414
# File 'lib/tty/progressbar.rb', line 405

def resize(new_width = nil)
  return if done?

  synchronize do
    clear_line
    if new_width
      self.width = new_width
    end
  end
end

#resumeObject

Resume rendering when bar is done, stopped or paused



441
442
443
444
445
446
447
# File 'lib/tty/progressbar.rb', line 441

def resume
  synchronize do
    @done = false
    @stopped = false
    @paused = false
  end
end

#startObject

Start progression by drawing bar and setting time



191
192
193
194
195
196
197
198
# File 'lib/tty/progressbar.rb', line 191

def start
  synchronize do
    @timer.start
    @meter.start
  end

  advance(0)
end

#stopObject

Stop and cancel the progress at the current position



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/tty/progressbar.rb', line 452

def stop
  return if done?

  render
  clear ? clear_line : write(NEWLINE, false)
ensure
  @meter.clear
  @stopped = true
  @timer.stop

  # reenable cursor if it is turned off
  if hide_cursor && @last_render_width != 0
    write(TTY::Cursor.show, false)
  end

  emit(:stopped)
end

#stopped?Boolean

Check if progress is stopped

Returns:

  • (Boolean)


503
504
505
# File 'lib/tty/progressbar.rb', line 503

def stopped?
  @stopped
end

#to_sString

Show bar format

Returns:

  • (String)


563
564
565
# File 'lib/tty/progressbar.rb', line 563

def to_s
  @format.to_s
end

#update(options = {}) ⇒ Object

Update configuration options for this bar

Parameters:

  • options (Hash[Symbol]) (defaults to: {})

    the configuration options to update



276
277
278
279
280
281
282
283
284
# File 'lib/tty/progressbar.rb', line 276

def update(options = {})
  synchronize do
    options.each do |name, val|
      if @configuration.respond_to?("#{name}=")
        @configuration.public_send("#{name}=", val)
      end
    end
  end
end

#use(formatter_class) ⇒ Object

Use custom token formatter

Parameters:

  • formatter_class (Object)

    the formatter class to add to formatting pipeline



180
181
182
183
184
185
186
# File 'lib/tty/progressbar.rb', line 180

def use(formatter_class)
  unless formatter_class.is_a?(Class)
    raise ArgumentError, "Formatter needs to be a class"
  end

  @formatters.use(formatter_class.new(self))
end

#write(data, clear_first = false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Write out to the output

Parameters:

  • data (String)


388
389
390
391
392
393
394
395
396
397
# File 'lib/tty/progressbar.rb', line 388

def write(data, clear_first = false)
  return unless tty? # write only to terminal

  move_to_row do
    output.print(TTY::Cursor.column(1)) if clear_first
    characters_in = @multibar.line_inset(self) if @multibar
    output.print("#{characters_in}#{data}")
    output.flush
  end
end