class TTY::ProgressBar
Used for creating terminal progress bar
@api public
Constants
- CURSOR_LOCK
- ECMA_CSI
- NEWLINE
- VERSION
Attributes
Public Class Methods
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
Determine terminal width
@return [Integer]
@api public
# File lib/tty/progressbar.rb, line 50 def self.max_columns TTY::Screen.width end
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
# 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 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 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 current line
@api public
# File lib/tty/progressbar.rb, line 484 def clear_line output.print("#{ECMA_CSI}0m#{TTY::Cursor.clear_line}") end
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
Access instance configuration
@api public
# File lib/tty/progressbar.rb, line 151 def configure yield @configuration end
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
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
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
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 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 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 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 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
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 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
Check if progress is paused
@return [Boolean]
@api public
# File lib/tty/progressbar.rb, line 512 def paused? @paused end
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
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 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 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 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 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 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 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
Check if progress is stopped
@return [Boolean]
@api public
# File lib/tty/progressbar.rb, line 503 def stopped? @stopped end
Show bar format
@return [String]
@api public
# File lib/tty/progressbar.rb, line 563 def to_s @format.to_s end
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 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 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 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
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
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