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.3"

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



489
490
491
# File 'lib/tty/progressbar.rb', line 489

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



499
500
501
# File 'lib/tty/progressbar.rb', line 499

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)


526
527
528
# File 'lib/tty/progressbar.rb', line 526

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

#finishObject

End the progress



424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/tty/progressbar.rb', line 424

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)


577
578
579
580
581
582
583
584
585
586
587
588
# File 'lib/tty/progressbar.rb', line 577

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



551
552
553
554
555
556
557
558
559
560
561
# File 'lib/tty/progressbar.rb', line 551

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.



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/tty/progressbar.rb', line 367

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)


538
539
540
541
542
543
# File 'lib/tty/progressbar.rb', line 538

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

#pauseObject

Pause the progress at the current position



478
479
480
481
482
483
484
# File 'lib/tty/progressbar.rb', line 478

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

#paused?Boolean

Check if progress is paused

Returns:

  • (Boolean)


517
518
519
# File 'lib/tty/progressbar.rb', line 517

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)


323
324
325
326
327
328
329
330
331
332
# File 'lib/tty/progressbar.rb', line 323

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



309
310
311
312
# File 'lib/tty/progressbar.rb', line 309

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



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/tty/progressbar.rb', line 337

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



410
411
412
413
414
415
416
417
418
419
# File 'lib/tty/progressbar.rb', line 410

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



446
447
448
449
450
451
452
# File 'lib/tty/progressbar.rb', line 446

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



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/tty/progressbar.rb', line 457

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)


508
509
510
# File 'lib/tty/progressbar.rb', line 508

def stopped?
  @stopped
end

#to_sString

Show bar format

Returns:

  • (String)


568
569
570
# File 'lib/tty/progressbar.rb', line 568

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)


393
394
395
396
397
398
399
400
401
402
# File 'lib/tty/progressbar.rb', line 393

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