A flexible and extensible progress bar for terminal applications.
TTY::ProgressBar provides independent progress bar component for TTY toolkit.
Add this line to your application's Gemfile:
gem "tty-progressbar"
And then execute:
$ bundle
Or install it yourself as:
$ gem install tty-progressbar
TTY::ProgressBar requires only a format string with :bar
token and total number of steps to completion:
bar = TTY::ProgressBar.new("downloading [:bar]", total: 30)
Once initialized, use advance method to indicated progress:
30.times do
sleep(0.1)
bar.advance # by default increases by 1
end
This would produce the following animation in your terminal:
# downloading [======================= ]
You can further change a progress bar behaviour and display by changing configuration options and using many predefined tokens and bar formats.
When you don't know the total yet, you can set it to nil
to switch to indeterminate progress:
# downloading [ <=> ]
Use TTY::ProgressBar::Multi to display multiple parallel progress bars.
Declare a top level bar and then register child bars:
bars = TTY::ProgressBar::Multi.new("main [:bar] :percent")
bar1 = bars.register("one [:bar] :percent", total: 15)
bar2 = bars.register("two [:bar] :percent", total: 15)
Then progress the child bars in parallel:
bars.start # starts all registered bars timers
th1 = Thread.new { 15.times { sleep(0.1); bar1.advance } }
th2 = Thread.new { 15.times { sleep(0.1); bar2.advance } }
[th1, th2].each { |t| t.join }
A possible terminal output may look like this:
# ┌ main [=============== ] 50%
# ├── one [===== ] 34%
# └── two [========== ] 67%
Once you have TTY::ProgressBar instance, you can progress the display by calling advance
method. By default, it will increase by 1
but you can pass any number of steps, for instance, a number of bytes for a downloaded file:
bar.advance(1024)
You can also pass negative steps if you wish to backtrack the progress:
bar.advance(-1)
Note: If a progress bar has already finished then any negative steps will not set it back to desired value.
To simplify progressing over an enumerable you can use iterate
which as a first argument accepts an Enumerable
and as a second the amount to progress the bar with.
First, create a progress bar without a total which will be automatically updated for you once iteration starts:
bar = TTY::ProgressBar.new("[:bar]")
Then, either directly iterate over a collection by yielding values to a block:
bar.iterate(30.times) { |v| ... }
Or return an Enumerator
:
progress = bar.iterate(30.times)
# => #<Enumerator: #<Enumerator::Generator:0x...:each>
By default, progress bar is advanced by 1
but you can change it by passing second argument:
bar.iterate(30.times, 5)
One particularly useful application of iterate
are Ruby infamous lazy enumerators, or slowly advancing enumerations, representing complex processes.
For example, an Enumerator
that downloads content from a remote server chunk at a time:
downloader = Enumerator.new do |y|
start = 0
loop do
yield(download_from_server(start, CHUNK_SIZE))
raise StopIteration if download_finished?
start += CHUNK_SIZE
end
end
Would be used with progress bar with the total size matching the content size like so:
bar = TTY::ProgressBar.new("[:bar]", total: content_size)
# you need to provide the total for the iterate to avoid calling enumerator.count
response = bar.iterate(downloader, CHUNK_SIZE).to_a.join
This would result in progress bar advancing after each chunk up until all content has been downloaded, returning the result of the download in response
variable.
Please run slow_process example to see this in action.
A progress doesn't have to start from zero. You can set it to a given value using current=
method:
bar.current = 50
Note: If a progress bar has already finished then setting current value will not have any effect.
In order to update overall completion of a progress bar as an exact percentage use the ratio=
method. The method accepts values between 0
and 1
inclusive. For example, a ratio of 0.5
will attempt to set the progress bar halfway:
bar.ratio = 0.5
You can set how many terminal columns will the :bar
actually span excluding any other tokens and/or text.
For example, if you need the bar to be always 20 columns wide do:
bar.width = 20
Or with configuration options:
bar = TTY::ProgressBar.new("[:bar]", width: 20)
By default the timer for internal time estimation is started automatically when the advance
method is called. However, if you require control on when the progression timer is started use start
call:
bar.start # => sets timer and draws initial progress bar
Once a progress bar has been started, you can change its configuration option(s) by calling update
:
bar.update(complete: "+", frequency: 10)
In order to immediately stop and finish progress of a bar call finish
. This will finish drawing the progress by advancing it to 100% and returning to a new line.
bar.finish
In order to immediately stop a bar in the current position and thus prevent any further progress use stop
:
bar.stop
A running progress bar can be paused at the current position using pause
method:
bar.pause
A paused progress bar will stop accumulating any time measurements like elapsed time. It also won't return to a new line, so a progress animation can be smoothly resumed.
In order to reset currently running or finished progress bar to its original configuration and initial position use reset
like so:
bar.reset
After resetting a progress bar, if you wish to draw and start a bar and its timers use start
call.
When a bar is stopped or paused, you can continue its progression using the resume
method.
bar.resume
A resumed progression will continue accumulating the total elapsed time without including time intervals for pausing or stopping.
During progression you can check whether a bar is finished or not by calling complete?
. The bar will only return true
if the progression finished successfully, otherwise false
will be returned.
bar.complete? # => false
To check whether a progress bar is paused or not use paused?
:
bar.paused? # => true
To check whether a progress bar is stopped or not use stopped?
:
bar.stopped? # => true
You can make a progress bar indeterminate by setting :total
to nil
. In this state, a progress bar animation is displayed to show unbounded task. You can check whether the progress bar is indeterminate with the indeterminate?
method:
bar.indeterminate? # => false
If you want to change a progress bar's current width, use resize
and pass in a new desired length. However, if you don't provide any width the resize
will use terminal current width as its base for scaling.
bar.resize # determine terminal width and scale accordingly
bar.resize(50) # will resize bar proportionately from this point onwards
To handle automatic resizing you can trap :WINCH
signal:
trap(:WINCH) { bar.resize }
A progress bar fires events when it is progressing, paused, stopped or finished. You can register to listen for these events using the on
message.
Every time an advance
is called the :progress
event gets fired which you can listen for inside a block. A first yielded argument is the actual amount of progress:
bar.on(:progress) { |amount| ... }
When a progress bar finishes and completes then the :done
event is fired. You can listen for this event:
bar.on(:done) { ... }
Alternatively, when a progress bar gets stopped the :stopped
event is fired. You can listen for this event:
bar.on(:stopped) { ... }
Anytime a progress bar is paused the :paused
event will be fired. To listen for this event do:
bar.on(:paused) { ... }
There are number of configuration options that can be provided:
=
.=
.<=>
.:classic
.stderr
.0
.1 sec
.false
.false
.false
.All the above options can be passed in as hash options or block parameters:
bar = TTY::ProgressBar.new("[:bar]") do |config|
config.total = 30
config.frequency = 10
config.clear = true
end
The progress bar's configuration can also be changed at runtime with configure
:
bar.configure do |config|
config.total = 100 # takes precedence over the original value
config.frequency = 20
end
Or with the update method:
bar.update(total: 100, frequency: 20)
The :total
option determines the final value at which the progress bar fills up and stops.
TTY::ProgressBar.new("[:bar]", total: 30)
Setting :total
to nil
or leaving it out will cause the progress bar to switch to indeterminate mode. Instead of showing completeness for a task, it will render animation like <=>
that moves left and right:
# [ <=> ]
The indeterminate mode is useful to show time-consuming and unbounded task.
Run examples/indeterminate to see indeterminate progress animation in action.
The progress bar width defaults to the total value and is capped at the maximum terminal width minus all the labels. If you want to enforce the bar to have a specific length use the :width
option:
TTY::ProgressBar.new("[:bar]", width: 30)
By default, the =
character is used to mark progression but this can be changed with :complete
option:
TTY::ProgressBar.new("[:bar]", complete: "x")
Then the output could look like this:
# [xxxxxxxx ]
By default no characters are shown to mark the remaining progress in the :classic
bar format. Other bar styles often have incomplete character. You can change this with :incomplete
option:
TTY::ProgressBar.new("[:bar]", incomplete: "_")
A possible output may look like this:
# [======_________]
If you prefer for the animated bar to display a specific character for a head of progression then use :head
option:
TTY::ProgressBar.new("[:bar]", head: ">")
This could result in output like this:
# [=======> ]
By default, a progress bar shows indeterminate progress using <=>
characters:
# [ <=> ]
Other bar formats use different characters.
You can change this with the :unknown
option:
TTY::ProgressBar.new("[:bar]", unknown: "<?>")
This may result in the following output:
# [ <?> ]
There are number of preconfigured bar formats you can choose from.
Name | Determinate | Indeterminate |
---|---|---|
:arrow |
▸▸▸▸▸▹▹▹▹▹ |
◂▸ |
:asterisk |
✱✱✱✱✱✳✳✳✳✳ |
✳✱✳ |
:blade |
▰▰▰▰▰▱▱▱▱▱ |
▱▰▱ |
:block |
█████░░░░░ |
█ |
:box |
■■■■■□□□□□ |
□■□ |
:bracket |
❭❭❭❭❭❭❭❭❭❭ |
❬=❭ |
:burger |
≡≡≡≡≡≡≡≡≡≡ |
<≡> |
:button |
⦿⦿⦿⦿⦿⦾⦾⦾⦾⦾ |
⦾⦿⦾ |
:chevron |
›››››››››› |
‹=› |
:circle |
●●●●●○○○○○ |
○●○ |
:classic |
========== |
<=> |
:crate |
▣▣▣▣▣⬚⬚⬚⬚⬚ |
⬚▣⬚ |
:diamond |
♦♦♦♦♦♢♢♢♢♢ |
♢♦♢ |
:dot |
・・・・・・・・・・ |
・・・ |
:heart |
♥♥♥♥♥♡♡♡♡♡ |
♡♥♡ |
:rectangle |
▮▮▮▮▮▯▯▯▯▯ |
▯▮▯ |
:square |
▪▪▪▪▪▫▫▫▫▫ |
▫▪▫ |
:star |
★★★★★☆☆☆☆☆ |
☆★☆ |
:track |
▬▬▬▬▬═════ |
═▬═ |
:tread |
❱❱❱❱❱❱❱❱❱❱ |
❰=❱ |
:triangle |
▶▶▶▶▶▷▷▷▷▷ |
◀▶ |
:wave |
~~~~~_____ |
<~> |
For example, you can specify :box
format with the :bar_format
option:
TTY::ProgressBar.new("[:bar]", bar_format: :box)
This will result in output like this:
# [■■■■■□□□□□□□□□□]
You can overwrite :complete
, :incomplete
, :head
and :unknown
characters:
TTY::ProgressBar.new("[:bar]", bar_format: :box, incomplete: " ", unknown: "?")
This will display the following when total is given:
# [■■■■■ ]
And for the unknown progress the ?
character will move from left to right:
# [ ? ]
A progress bar only outputs to a console. When the output is, for example, redirected to a file or a pipe, the progress bar doesn't get printed. This is so, for example, your error logs do not overflow with progress bar output.
You can change where console output is streamed with :output
option:
bar = TTY::ProgressBar.new(output: $stdout)
The output stream defaults to stderr
.
Each time the advance
is called it causes the progress bar to repaint. In cases when there is a huge number of updates per second, you may need to limit the rendering process by using the frequency
option.
The frequency
option accepts integer
representing number of Hz
units, for instance, frequency of 2 will mean that the progress will be updated maximum 2 times per second.
TTY::ProgressBar.new("[:bar]", total: 30, frequency: 10) # 10 Hz
Every time advance
method is called, a time sample is taken for speed measurement. By default, all the samples are grouped in second intervals to provide a rate of speed. You can change this by passing the interval
option.
The interval
option is an integer
that represents the number of seconds, for example, interval of 60
would mean that speed is measured per 1 minute.
TTY::ProgressBar.new(":rate/minute", total: 100, interval: 60) # 1 minute
TTY::ProgressBar.new(":rate/hour", total: 100, interval: 3600) # 1 hour
By default the cursor is visible during progress bar rendering. If you wish to hide it, you can do so with the :hide_cursor
option.
Please note that hiding cursor changes user's terminal and you need to ensure that the cursor is made visible after your code finishes. This means also handling premature interrupt signals and other unpredictable events.
One solution is to wrap your progress rendering inside the begin
and ensure
like so:
progress = TTY::ProgressBar.new("[:bar]", hide_cursor: true)
begin
# logic to advance progress bar
ensure
progress.stop # or progress.finish
# both methods will ensure that cursor is made visible again
end
By default, when a progress bar finishes it returns to a new line leaving the last progress output behind.
If you prefer to erase a progress bar when it is finished use :clear
option:
TTY::ProgressBar.new("[:bar]", clear: true)
When a progress bar finishes and its animation includes :head character, the character will remain in the output:
# [=============>]
To replace a head character when a progress bar is finished use :clear_head
option:
TTY::ProgressBar.new("[:bar]", clear_head: true)
This will result in the following output:
# [==============]
Every TTY::ProgressBar instance requires a format string, which apart from regular characters accepts special tokens to display dynamic information. For instance, a format to measure download progress could be:
"downloading [:bar] :elapsed :percent"
These are the tokens that are currently supported:
:bar
the progress bar:current
the current progress number:current_byte
the current progress in bytes:total
the total progress number:total_byte
the total progress in bytes:percent
the completion percentage:elapsed
the elapsed time in seconds:eta
the estimated time to completion in seconds:eta_time
the estimated time of day at completion:rate
the current rate of progression per second:byte_rate
the current rate of progression in bytes per second:mean_rate
the averaged rate of progression per second:mean_byte
the averaged rate of progression in bytes per secondIn the indeterminate mode, the progress bar displays -
for tokens that cannot be calculated like :total
, :total_byte
, :percent
and :eta
. The following format:
"[:bar] :current/:total :total_byte :percent ET::elapsed ETA::eta :rate/s"
Will result in:
# [ <=> ] 23/- -B -% ET: 1s ETA:--s 18.01/s
If the provided tokens do not meet your needs, you can write your own formatter and instrument formatting pipeline to use a formatter you prefer. This option is preferred if you are going to rely on progress bar internal data such as rate
, current
etc. which will all be available on the passed in progress bar instance.
For example, let's say you want to add :time
token. First, start by creating a custom formatter class called TimeFormatter
that will dynamically update :time
token in the formatted string. In order for the TimeFormatter
to recognise the :time
token, you'll need to include the TTY::ProgressBar::Formatter
module with a regular expression matching the token like so:
class TimeFormatter
include TTY::ProgressBar::Formatter[/:time/i]
...
end
Next, add call
method that will substitute the matched token with an actual value. For example, to see the time elapsed since the start do:
class TimeFormatter
include TTY::ProgressBar::Formatter[/:time/i]
def call(value) # specify how display string is formatted
# access current progress bar instance to read start time
elapsed = (Time.now - progress.start_time).to_s
value.gsub(matcher, elapsed) # replace :time token with a value
end
end
Notice that you have access to all the configuration options inside the formatter by simply invoking them on the progress
instance.
Create TTY::ProgressBar instance using the new token:
bar = TTY::ProgressBar.new(":time", total: 30)
Then add TimeFormatter
to the pipeline like so:
bar.use TimeFormatter
Then invoke progression:
bar.advance
You can define custom tokens by passing pairs name: value
to advance
method in order to dynamically update formatted bar. This option is useful for lightweight content replacement such as titles that doesn't depend on the internal data of a progress bar. For example:
bar = TTY::ProgressBar.new("(:current) :title", total: 4)
bar.advance(title: "Hello Piotr!")
bar.advance(3, title: "Bye Piotr!")
This will output:
# (1) Hello Piotr!
# (4) Bye Piotr!
The format string as well as :complete, :head, :incomplete and :unknown configuration options can contain Unicode characters that aren't monospaced.
For example, you can specify complete bar progression character to be Unicode non-monospaced:
bar = TTY::ProgressBar.new("Unicode [:bar]", total: 30, complete: "あ")
Advancing above progress bar to completion will fit あ
characters in 30 terminal columns:
# Unicode [あああああああああああああああ]
Similarly, the formatted string can include Unicode characters:
bar = TTY::ProgressBar.new("あめかんむり[:bar]", total: 20)
A finished progress bar will also fit within allowed width:
# あめかんむり[== ]
If you want to print messages out to terminal along with the progress bar use the log
method. The messages will appear above the progress bar and will continue scrolling up as more are logged out.
bar.log("Piotrrrrr")
bar.advance
This could result in the following output:
# Piotrrrrr
# downloading [======================= ]
The multi progress bar can be created in two ways. If you simply want to group multiple progress bars together, you can create multi bar without a format string like so:
TTY::ProgressBar::Multi.new
However, if you want a top level multibar that tracks progress of all the registered progress bars then you need to provide a formatted string:
TTY::ProgressBar::Multi.new("main [:bar] :percent")
To create a TTY::ProgressBar
under the multibar use register
like so:
multibar = TTY::ProgressBar::Multi.new
bar = multibar.register("[:bar]", total: 30)
The register
call returns the newly created progress bar that can be changed using all the available progress bar API methods.
Note: Remember to specify total value for each registered progress bar, either when sending register
message or when using update
to dynamically assign the total value.
Once multi progress bar has been created you can advance each registered progress bar individually, either by executing them one after the other synchronously or by placing them in separate threads thus progressing each bar asynchronously. The multi bar handles synchronization and display of all bars as they continue their respective rendering.
For example, to display two bars asynchronously, first register them with the multi bar:
bar1 = multibar.register("one [:bar]", total: 20)
bar2 = multibar.register("two [:bar]", total: 30)
Next place the progress behaviour in separate process or thread:
th1 = Thread.new { 20.times { expensive_work(); bar1.advance } }
th2 = Thread.new { 30.times { expensive_work(); bar2.advance } }
Finally, wait for the threads to finish:
[th1, th2].each { |t| t.join }
By default the top level multi bar will be rendered as the first bar and have its timer started when one of the registered bars advances. However, if you wish to start timers and draw the top level multi bar do:
multibar.start # => sets timer and draws top level multi progress bar
In order to finish all progress bars call finish
. This will finish the top level progress bar, if it exists, and any registered progress bar still in progress.
multibar.finish
Use stop
to terminate immediately all progress bars registered with the multibar.
multibar.stop
All running progress bars can be paused at their current positions using the pause
method:
multibar.pause
When one or more registered progress bar is stopped or paused, they can be resumed all at once using the resume
method:
multibar.resume
To check if all registered progress bars have been successfully finished use complete?
multibar.complete? # => true
To check whether all progress bars are paused or not use paused?
:
multibar.paused? # => true
To check whether all progress bars are stopped or not use stopped?
:
multibar.stopped? # => true
Similar to TTY::ProgressBar
the multi bar fires events when it is progressing, stopped or finished. You can register to listen for events using the on
message.
Every time any of the registered progress bars progresses the :progress
event is fired which you can listen for:
multibar.on(:progress) { ... }
When all the registered progress bars finish and complete then the :done
event is fired. You can listen for this event:
multibar.on(:done) { ... }
When any of the progress bars gets stopped the :stopped
event is fired. You can listen for this event:
multibar.on(:stopped) { ... }
Anytime a registered progress bar pauses, a :paused
event will be fired. To listen for this event do:
multibar.on(:paused) { ... }
In addition to all configuration options you can style multi progress bar:
TTY::ProgressBar::Multi.new("[:bar]", style: {
top: ". ",
middle: "|-> ",
bottom: "|__ "
})
This section demonstrates some of the possible uses for the TTY::ProgressBar, for more please see examples folder in the source directory.
Creating a progress bar that displays in color is as simple as coloring the :complete
and :incomplete
character options. In order to help with coloring you can use pastel library like so:
require "pastel"
pastel = Pastel.new
green = pastel.on_green(" ")
red = pastel.on_red(" ")
And then pass in the colored strings as options to TTY::ProgressBar:
bar = TTY::ProgressBar.new("|:bar|",
total: 30,
complete: green,
incomplete: red
)
To see how a progress bar is reported in terminal you can do:
30.times do
sleep(0.1)
bar.advance
end
Commonly a progress bar is utilized to measure download speed per second. This can be done like so:
TTY::ProgressBar.new("[:bar] :byte_rate/s") do |config|
config.total = 300000
config.interval = 1 # => 1 sec
end
This will result in output similar to:
# downloading [======================= ] 4.12MB/s
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
Everyone interacting in the TTY::ProgressBar project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Copyright (c) 2014 Piotr Murach. See LICENSE for further details.