class TTY::ProgressBar

Used for creating terminal progress bar

@api public

Constants

CURSOR_LOCK
ECMA_CSI
NEWLINE
VERSION

Attributes

current[R]
format[RW]
row[R]

Public Class Methods

display_columns(value) click to toggle source

Determine the monospace display width of a string

@param [String] value

the value to determine width of

@return [Integer]

@api public

# File lib/tty/progressbar.rb, line 62
def self.display_columns(value)
  Unicode::DisplayWidth.of(Strings::ANSI.sanitize(value))
end
max_columns() click to toggle source

Determine terminal width

@return [Integer]

@api public

# File lib/tty/progressbar.rb, line 50
def self.max_columns
  TTY::Screen.width
end
new(format, options = {}) { |configuration| ... } click to toggle source

Create progress bar

@example

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

@param [String] format

the tokenized string that displays the output

@param [Hash] options @option options [Numeric] :total

the total number of steps to completion

@option options [Numeric] :width

the maximum width for the progress bar except all formatting tokens

@option option [String] :complete

the complete character in progress animation

@option options [String] :incomplete

the incomplete character in progress animation

@option options [String] :head

the head character, defaults to complete

@option options [String] :unknown

the unknown character for indeterminate progress animation

@option options [Boolean] :bar_format

the preconfigured bar format name, defaults to :classic

@option options [Object] :output

the object that responds to print call, defaults to stderr

@option options [Number] :frequency

the frequency with which to display a progress bar per second

@option options [Number] :interval

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

@option options [Boolean] :hide_cursor

whether or not to hide the cursor, defaults to false

@option options [Boolean] :clear

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

@option options [Boolean] :clear_head

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

@api public

Calls superclass method
# 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

Public Instance Methods

advance(progress = 1, tokens = {}) click to toggle source

Advance the progress bar

@param [Object|Number] progress

@api public

# 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) click to toggle source

Attach this bar to multi bar

@param [TTY::ProgressBar::Multi] multibar

the multibar under which this bar is registered

@api private

# File lib/tty/progressbar.rb, line 170
def attach_to(multibar)
  @multibar = multibar
end
clear_line() click to toggle source

Clear current line

@api public

# File lib/tty/progressbar.rb, line 484
def clear_line
  output.print("#{ECMA_CSI}0m#{TTY::Cursor.clear_line}")
end
complete?() click to toggle source

Check if progress is finished

@return [Boolean]

true when progress finished, false otherwise

@api public

# File lib/tty/progressbar.rb, line 494
def complete?
  @done
end
configure() { |configuration| ... } click to toggle source

Access instance configuration

@api public

# File lib/tty/progressbar.rb, line 151
def configure
  yield @configuration
end
current=(value) click to toggle source

Advance the progress bar to the updated value

@param [Number] value

the desired value to updated to

@api public

# File lib/tty/progressbar.rb, line 292
def current=(value)
  value = [0, [value, total].min].max
  advance(value - @current)
end
done?() click to toggle source

Check if progress is finished, stopped or paused

@return [Boolean]

@api public

# File lib/tty/progressbar.rb, line 521
def done?
  @done || @stopped || @paused
end
finish() click to toggle source

End the progress

@api public

# 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?() click to toggle source

Check if progress can be determined or not

@return [Boolean]

@api public

# File lib/tty/progressbar.rb, line 160
def indeterminate?
  total.nil?
end
inspect() click to toggle source

Inspect bar properties

@return [String]

@api public

# 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) click to toggle source

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.

@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.

@example

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

@param [Enumerable] collection

the collection to iterate over

@param [Integer] progress

the amount to move progress bar by

@return [Enumerator]

@api public

# 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) click to toggle source

Log message above the current progress bar

@param [String] message

the message to log out

@api public

# 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_row() { || ... } click to toggle source

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.

@api private

# 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) click to toggle source

Register callback with this bar

@param [Symbol] name

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

@return [self]

@api public

# File lib/tty/progressbar.rb, line 533
def on(name, &callback)
  synchronize do
    @callbacks[name] << callback
  end
  self
end
pause() click to toggle source

Pause the progress at the current position

@api public

# File lib/tty/progressbar.rb, line 473
def pause
  synchronize do
    @paused = true
    @timer.stop
    emit(:paused)
  end
end
paused?() click to toggle source

Check if progress is paused

@return [Boolean]

@api public

# File lib/tty/progressbar.rb, line 512
def paused?
  @paused
end
ratio() click to toggle source

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.

@return [Float]

@api public

# 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) click to toggle source

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

@param [Float] value

the ratio between 0 and 1 inclusive

@api public

# File lib/tty/progressbar.rb, line 304
def ratio=(value)
  target = (value * total).floor
  advance(target - @current)
end
render() click to toggle source

Render progress to the output

@api private

# 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
reset() click to toggle source

Reset progress to default configuration

@api public

# 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) click to toggle source

Resize progress bar with new configuration

@param [Integer] new_width

the new width for the bar display

@api public

# 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
resume() click to toggle source

Resume rendering when bar is done, stopped or paused

@api public

# File lib/tty/progressbar.rb, line 441
def resume
  synchronize do
    @done = false
    @stopped = false
    @paused = false
  end
end
start() click to toggle source

Start progression by drawing bar and setting time

@api public

# File lib/tty/progressbar.rb, line 191
def start
  synchronize do
    @timer.start
    @meter.start
  end

  advance(0)
end
stop() click to toggle source

Stop and cancel the progress at the current position

@api public

# 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?() click to toggle source

Check if progress is stopped

@return [Boolean]

@api public

# File lib/tty/progressbar.rb, line 503
def stopped?
  @stopped
end
to_s() click to toggle source

Show bar format

@return [String]

@api public

# File lib/tty/progressbar.rb, line 563
def to_s
  @format.to_s
end
update(options = {}) click to toggle source

Update configuration options for this bar

@param [Hash] options

the configuration options to update

@api public

# 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) click to toggle source

Use custom token formatter

@param [Object] formatter_class

the formatter class to add to formatting pipeline

@api public

# 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) click to toggle source

Write out to the output

@param [String] data

@api private

# 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

Private Instance Methods

emit(name, *args) click to toggle source

Emit callback by name

@param [Symbol]

the event name

@api private

# File lib/tty/progressbar.rb, line 606
def emit(name, *args)
  @callbacks[name].each do |callback|
    callback.(*args)
  end
end
padout(message) click to toggle source

Pad message out with spaces

@api private

# File lib/tty/progressbar.rb, line 590
def padout(message)
  message_length = self.class.display_columns(message)

  if @last_render_width > message_length
    remaining_width = @last_render_width - message_length
    message += " " * remaining_width
  end
  message
end
tty?() click to toggle source

Check if IO is attached to a terminal

return [Boolean]

@api public

# File lib/tty/progressbar.rb, line 617
def tty?
  output.respond_to?(:tty?) && output.tty?
end