Skip to content

class Athena::Console::Helper::ProgressBar
inherits Reference #

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:

  • %current% - The current step
  • %max% - The maximum number of steps (or zero if there is not one)
  • %bar% - The progress bar itself
  • %percent% - The percentage of completion (not available if no max is defined)
  • %elapsed% - The time elapsed since the start of the progress bar
  • %remaining% - The remaining time to complete the task (not available if no max is defined)
  • %estimated% - The estimated time to complete the task (not available if no max is defined)
  • %memory% - The current memory usage
  • %message% - Used to display arbitrary messages, more on this later

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.seconds
end

Which would ultimately look something like:

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

Constructors#

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

Class methods#

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

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

.placeholder_formatter(name : String) : ACON::Helper::ProgressBar::PlaceholderFormatter | Nil#

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

.set_format_definition(name : String, format : String) : Nil#

Registers the format globally with the provided name.

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

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

Methods#

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

Advanced the progress bar by the provided number of steps.

#bar_character : String#

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

#bar_character=(bar_character : String | Nil)#

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

#bar_offset : Int32#

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

#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%

#bar_width=(size : Int32) : Nil#

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

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

#display : Nil#

Displays the progress bar's current state.

#empty_bar_character : String#

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

#empty_bar_character=(empty_bar_character : String)#

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

#estimated : Float64#

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

#finish#

Finishes the progress output, making it 100% complete.

#format=(format : String)#

Sets the format string used to determine how to display the progress bar. See Custom Formats for more information.

#format=(format : ACON::Helper::ProgressBar::Format)#

Sets what built in format to use. See Built-in Formats for more information.

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

#max_steps : Int32#

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

#max_steps=(max : Int32) : Nil#

Sets the maximum possible steps to the provided max.

#maximum_seconds_between_redraws=(maximum_seconds_between_redraws : Float64)#

Sets the maximum amount of time between redraws.

See Controlling Rendering for more information.

#message(name : String = "message") : String | Nil#

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

#minimum_seconds_between_redraws=(minimum_seconds_between_redraws : Float64)#

Sets the minimum amount of time between redraws.

See Controlling Rendering for more information.

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

#placeholder_formatter(name : String) : ACON::Helper::ProgressBar::PlaceholderFormatter | Nil#

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

#progress : Int32#

Returns the current step of the progress bar

#progress=(step : Int32) : Nil#

Explicitly sets the current step number of the progress bar.

ameba:disable Metrics/CyclomaticComplexity

#progress_character : String#

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

#progress_character=(progress_character : String)#

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

#progress_percent : Float64#

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

#redraw_frequency=(steps : Int32 | Nil) : Nil#

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

See Controlling Rendering for more information.

#remaining : Float64#

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

#set_message(message : String, name : String = "message") : Nil#

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

#set_placeholder_formatter(name : String, &block : self, ACON::Output::Interface -> String) : Nil#

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

#set_placeholder_formatter(name : String, callable : ACON::Helper::ProgressBar::PlaceholderFormatter) : Nil#

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

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

#start_time : Time#

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

#step_width : Int32#

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

#step_width? : Int32 | ::Nil#

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