class Athena::Console::Helper::ProgressBar

Overview

When executing longer-running commands, it can be helpful to show progress information that updates as the command runs:

Progress Bar

TIP: Consider using ACON::Style::Athena to display a progress bar.

The ProgressBar helper can be used to progress information to any ACON::Output::Interface:

# Create a new progress bar with 50 required units for completion.
progress_bar = ACON::Helper::ProgressBar.new output, 50

# Start and display the progress bar.
progress_bar.start

50.times do
  # Do work

  # Advance the progress bar by 1 unit.
  progress_bar.advance

  # Or advance by more than a single unit.
  # progress_bar.advance 3
end

# Ensure progress bar is at 100%.
progress_bar.finish

A progress bar can also be created without a required number of units, in which case it will just act as a throbber. However, #max_steps= can be called at any point to either set, or increase the required number of units. E.g. if its only known after performing some calculations, or additional work is needed such that the original value is not invalid.

TIP: Consider using an ACON::Helper::ProgressIndicator instead of a progress bar for this use case.

Be sure to call #finish when the task completes to ensure the progress bar is refreshed with a 100% completion.

NOTE By default the progress bar will write its output to STDERR, however this can be customized by using an ACON::Output::IO explicitly.

If the progress information is stored within an Enumerable type, the #iterate method can be used to start, advance, and finish the progress bar automatically, yielding each item in the collection:

bar = ACON::Helper::ProgressBar.new output
arr = [1, 2, 3]

bar.iterate(arr) do |item|
  # Do something
end

Which would output:

0/2 [>---------------------------]   0%
1/2 [==============>-------------]  50%
2/2 [============================] 100%

NOTE Iterator types are also supported, but need the max value provided explicitly via the second argument to #iterate if known.

Progressing

While the #advance method can be used to move the progress bar ahead by a specific number of steps, the current step can be set explicitly via #progress=.

It is also possible to start the progress bar at a specific step, which is useful when resuming some long-standing task:

# Create a 100 unit progress bar.
progress_bar = ACON::Helper::ProgressBar.new output, 100

# Display the progress bar starting at already 25% complete.
progress_bar.start at: 25

TIP: The progress can also be regressed (stepped backwards) by providing #advance a negative value.

Controlling Rendering

If available, ANCI Escape Codes are used to handle the rendering of the progress bar, otherwise updates are added as new lines. #minimum_seconds_between_redraws= can be used to prevent the output being flooded. #redraw_frequency= can be used to to redraw every N iterations. By default, redraw frequency is 100ms or 10% of your #max_steps.

Customizing

Built-in Formats

The progress bar comes with a few built-in formats based on the ACON::Output::Verbosity the command was executed with:

# Verbosity::NORMAL (CLI with no verbosity flag)
 0/3 [>---------------------------]   0%
 1/3 [=========>------------------]  33%
 3/3 [============================] 100%

# Verbosity::VERBOSE (-v)
 0/3 [>---------------------------]   0%  1 sec
 1/3 [=========>------------------]  33%  1 sec
 3/3 [============================] 100%  1 sec

# Verbosity::VERY_VERBOSE (-vv)
 0/3 [>---------------------------]   0%  1 sec/1 sec
 1/3 [=========>------------------]  33%  1 sec/1 sec
 3/3 [============================] 100%  1 sec/1 sec

# Verbosity::DEBUG (-vvv)
 0/3 [>---------------------------]   0%  1 sec/1 sec  1kiB
 1/3 [=========>------------------]  33%  1 sec/1 sec  1kiB
 3/3 [============================] 100%  1 sec/1 sec  1kiB

NOTE If a command called with ACON::Output::Verbosity::QUIET, the progress bar will not be displayed.

The format may also be set explicitly in code via:

# If the progress bar has a maximum number of steps.
bar.format = :very_verbose

# Without a maximum
bar.format = :very_verbose_nomax

Custom Formats

While the built-in formats are sufficient for most use cases, custom ones may also be defined:

bar.format = "%bar%"

Which would set the format to only display the progress bar itself:

>---------------------------
=========>------------------
============================

A progress bar format is a string that contains specific placeholders (a name enclosed with the % character); the placeholders are replaced based on the current progress of the bar. The built-in placeholders include:

For example, the format string for ACON::Helper::ProgressBar::Format::NORMAL is " %current% [%bar%] %elapsed:6s%". Individual placeholders can have their formatting tweaked by anything that sprintf supports by separating the name of the placeholder with a :. The part after the colon will be passed to sprintf.

If a format should be used across an entire application, they can be registered globally via .set_format_definition:

ACON::Helper::ProgressBar.set_format_definition "minimal", "Progress: %percent%%"

bar = ACON::Helper::ProgressBar.new output, 3
bar.format = "minimal"

Which would output:

Progress: 0%
Progress: 33%
Progress: 100%

TIP: It is almost always better to override the built-in formats in order to automatically vary the display based on the verbosity the command is being ran with.

When creating a custom format, be sure to also define a _nomax variant if it is using a placeholder that is only available if #max_steps is defined.

ACON::Helper::ProgressBar.set_format_definition "minimal", "%current%/%remaining%"
ACON::Helper::ProgressBar.set_format_definition "minimal_nomax", "%current%"

bar = ACON::Helper::ProgressBar.new output, 3
bar.format = "minimal"

The format will automatically be set to minimal_nomax if the bar does not have a maximum number of steps.

TIP: A format can contain any valid ANSI codes, or any ACON::Formatter::OutputStyleInterface markup.

TIP: A format may also span multiple lines, which can be useful to also display contextual information (like the first example).

Bar Settings

The bar placeholder is a bit special in that all of the characters used to display it can be customized:

# The Finished part of the bar.
bar.bar_character = "<comment>=</comment>"

# The unfinished part of the bar.
bar.empty_bar_character = " "

# The progress character.
bar.progress_character = "|"

# The width of the bar.
bar.bar_width = 50

Custom Placeholders

Just like the format, custom placeholders may also be defined. This can be useful to have a common way of displaying some sort of application specific information between multiple progress bars:

ACON::Helper::ProgressBar.set_placeholder_formatter "remaining_steps" do |bar|
  "#{bar.max_steps - bar.progress}"
end

From here it could then be used in a format string as %remaining_steps% just like any other placeholder. .set_placeholder_formatter registers the format globally, while #set_placeholder_formatter would set it on a specific progress bar.

Custom Messages

While there is a built-in #message placeholder that can be set via #set_message, none of the built-in formats include it. As such, before displaying these messages, a custom format needs to be defined:

bar = ACON::Helper::ProgressBar.new output, 100
bar.format = " %current%/%max% -- %message%"

bar.set_message "Start"
bar.start # 0/100 -- Start

bar.set_message "Task is in progress..."
bar.advance # 1/100 -- Task is in progress...

#set_message also allows or an optional second argument, which can be used to have multiple independent messages within the same format string:

files.each do |file_name|
  bar.set_message "Importing files..."
  bar.set_message file_name, "filename"
  bar.advance # => 2/100 -- Importing files... (foo/bar.txt)
end

Multiple Progress Bars

When using ACON::Output::Sections, multiple progress bars can be displayed at the same time and updated independently:

output = output.as ACON::Output::ConsoleOutputInterface

section1 = output.section
section2 = output.section

bar1 = ACON::Helper::ProgressBar.new section1
bar2 = ACON::Helper::ProgressBar.new section2

bar1.start 100
bar2.start 100

100.times do |idx|
  bar1.advance
  bar2.advance(4) if idx.divisible_by? 2

  sleep 0.05
end

Which would ultimately look something like:

34/100 [=========>------------------]  34%
68/100 [===================>--------]  68%

Defined in:

helper/progress_bar.cr

Constructors

Class Method Summary

Instance Method Summary

Constructor Detail

def self.new(output : ACON::Output::Interface, max : Int32 | Nil = nil, minimum_seconds_between_redraws : Float64 = 0.04, clock : ACLK::Interface = ACLK::Monotonic.new) #

[View source]

Class Method Detail

def self.format_definition(name : String) : String | Nil #

Returns the global format string for the provided name if it exists, otherwise nil.


[View source]
def self.placeholder_formatter(name : String) : ACON::Helper::ProgressBar::PlaceholderFormatter | Nil #

Returns the global formatter for the provided name if it exists, otherwise nil.


[View source]
def self.set_format_definition(name : String, format : String) : Nil #

Registers the format globally with the provided name.


[View source]
def self.set_placeholder_formatter(name : String, &block : self, ACON::Output::Interface -> String) : Nil #

Registers a custom placeholder with the provided name with the block being the formatter.


[View source]
def self.set_placeholder_formatter(name : String, callable : ACON::Helper::ProgressBar::PlaceholderFormatter) : Nil #

Registers a custom placeholder with the provided name, using the provided callable as the formatter.


[View source]

Instance Method Detail

def advance(by step : Int32 = 1) : Nil #

Advanced the progress bar by the provided number of steps.


[View source]
def bar_character : String #

Returns the character to use for the finished part of the bar.


[View source]
def bar_character=(bar_character : String | Nil) #

Explicitly sets the character to use for the finished part of the bar.


[View source]
def bar_offset : Int32 #

Returns the amount of #bar_character representing the current #progress.


[View source]
def bar_width : Int32 #

Returns the width of the progress bar in pixels.

bar1 = ...
bar1.bar_width = 50
bar1.start 10

bar2 = ...
bar2.bar_width = 10
bar2.start 20

bar1.finish
bar2.finish
10/10 [==================================================] 100%
20/20 [==========] 100%

[View source]
def bar_width=(size : Int32) : Nil #

Sets the width of the bar in pixels to the provided size. See #bar_width.


[View source]
def clear : Nil #

Clears the progress bar from the output. Can be used in conjunction with #display to allow outputting something while a progress bar is running. Call #clear, write the content, then call #display to show the progress bar again.

NOTE Requires that #overwrite= be set to true.


[View source]
def display : Nil #

Displays the progress bar's current state.


[View source]
def empty_bar_character : String #

Represents the character used for the unfinished part of the bar.


[View source]
def empty_bar_character=(empty_bar_character : String) #

Represents the character used for the unfinished part of the bar.


[View source]
def estimated : Float64 #

Returns an estimated amount of time in seconds until the progress bar is completed.


[View source]
def finish #

Finishes the progress output, making it 100% complete.


[View source]
def format=(format : String) #

Sets the format string used to determine how to display the progress bar. See [Custom Formats][Athena::Console::Helper::ProgressBar--custom-formats] for more information.


[View source]
def format=(format : ACON::Helper::ProgressBar::Format) #

Sets what built in format to use. See [Built-in Formats][Athena::Console::Helper::ProgressBar--built-in-formats] for more information.


[View source]
def iterate(enumerable : Enumerable(T), max : Int32 | Nil = nil, & : T -> Nil) : Nil forall T #

Start, advance, and finish the progress bar automatically, yielding each item in the provided enumerable.

bar = ACON::Helper::ProgressBar.new output
arr = [1, 2, 3]

bar.iterate(arr) do |item|
  # Do something
end

Which would output:

0/2 [>---------------------------]   0%
1/2 [==============>-------------]  50%
2/2 [============================] 100%

NOTE Iterator types are also supported, but need the max value provided explicitly via the second argument to #iterate if known.


[View source]
def max_steps : Int32 #

Returns the maximum number of possible steps, or 0 if it is unknown.


[View source]
def max_steps=(max : Int32) : Nil #

Sets the maximum possible steps to the provided max.


[View source]
def maximum_seconds_between_redraws=(maximum_seconds_between_redraws : Float64) #

Sets the maximum amount of time between redraws.

See [Controlling Rendering][Athena::Console::Helper::ProgressBar--controlling-rendering] for more information.


[View source]
def message(name : String = "message") : String | Nil #

Returns the message associated with the provided name if defined, otherwise nil.


[View source]
def minimum_seconds_between_redraws=(minimum_seconds_between_redraws : Float64) #

Sets the minimum amount of time between redraws.

See [Controlling Rendering][Athena::Console::Helper::ProgressBar--controlling-rendering] for more information.


[View source]
def overwrite=(overwrite : Bool) #

Sets if the progress bar should overwrite the progress bar. Set to false in order to print the progress bar on a new line for each update.


[View source]
def placeholder_formatter(name : String) : ACON::Helper::ProgressBar::PlaceholderFormatter | Nil #

Returns the amount of time in seconds until the progress bar is completed.


[View source]
def progress : Int32 #

Returns the current step of the progress bar


[View source]
def progress=(step : Int32) : Nil #

Explicitly sets the current step number of the progress bar.

ameba:disable Metrics/CyclomaticComplexity


[View source]
def progress_character : String #

Represents the character used for the current progress of the bar.


[View source]
def progress_character=(progress_character : String) #

Represents the character used for the current progress of the bar.


[View source]
def progress_percent : Float64 #

Returns the a percent of progress of #progress versus #max_steps. Returns zero if there is no max defined.


[View source]
def redraw_frequency=(steps : Int32 | Nil) : Nil #

Redraw the progress bar every after advancing the provided amount of steps.

See [Controlling Rendering][Athena::Console::Helper::ProgressBar--controlling-rendering] for more information.


[View source]
def remaining : Float64 #

Returns an estimated total amount of time in seconds needed for the progress bar to complete.


[View source]
def set_message(message : String, name : String = "message") : Nil #

Sets the message with the provided name to that of the provided message.


[View source]
def set_placeholder_formatter(name : String, &block : self, ACON::Output::Interface -> String) : Nil #

Same as .set_placeholder_formatter, but scoped to this particular progress bar.


[View source]
def set_placeholder_formatter(name : String, callable : ACON::Helper::ProgressBar::PlaceholderFormatter) : Nil #

Same as .set_placeholder_formatter, but scoped to this particular progress bar.


[View source]
def start(max : Int32 | Nil = nil, at start_at : Int32 = 0) : Nil #

Starts the progress bar.

Optionally sets the maximum number of steps to max, or nil to leave unchanged. Optionally starts the progress bar at the provided step.


[View source]
def start_time : Time #

Returns the time the progress bar was started as a Unix epoch.


[View source]
def step_width : Int32 #

Returns the width in pixels that the current #progress takes up when displayed.


[View source]
def step_width? : Int32 | Nil #

Returns the width in pixels that the current #progress takes up when displayed.


[View source]