Skip to content

Commit

Permalink
Add indeterminate progress bar display when no total is given (ref #43)
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrmurach committed Sep 17, 2020
1 parent 5a3da80 commit 77e6972
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 19 deletions.
44 changes: 33 additions & 11 deletions lib/tty/progressbar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class ProgressBar

attr_reader :row

def_delegators :@configuration, :total, :width, :no_width, :complete,
:incomplete, :head, :clear_head, :hide_cursor, :clear,
:output, :frequency, :interval, :inset, :width=
def_delegators :@configuration, :total, :width, :complete, :incomplete,
:head, :clear_head, :hide_cursor, :clear, :output,
:frequency, :interval, :inset, :width=, :unknown

def_delegators :@meter, :rate, :mean_rate

Expand Down Expand Up @@ -77,7 +77,7 @@ def self.display_columns(value)
# @option options [Numeric] :width
# the maximum width for the bars display including
# all formatting options
# @option options [Boolean] :no_width
# @option options [Boolean] :indeterminate
# true when progression is unknown defaulting to false
# @option options [Boolean] :clear
# whether or not to clear the progress line
Expand Down Expand Up @@ -117,9 +117,10 @@ def initialize(format, options = {})
#
# @api public
def reset
@width = 0 if no_width
@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
Expand All @@ -131,6 +132,15 @@ def reset
@meter.clear
end

# Check if progress can be determinted or not
#
# @return [Boolean]
#
# @api public
def indeterminate?
total.nil?
end

# Attach this bar to multi bar
#
# @param [TTY::ProgressBar::Multi] multibar
Expand Down Expand Up @@ -180,12 +190,16 @@ def advance(progress = 1, tokens = {})
if progress.respond_to?(:to_hash)
tokens, progress = progress, 1
end
@start_at = Time.now if @current.zero? && !@started
@current += progress
@tokens = tokens
@start_at = Time.now if @current.zero? && !@started
@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 !no_width && @current >= total
if !indeterminate? && @current >= total
finish && return
end

Expand Down Expand Up @@ -274,12 +288,20 @@ def ratio=(value)

# 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
def ratio
synchronize do
proportion = total > 0 ? (@current.to_f / total) : 0
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
Expand Down Expand Up @@ -373,7 +395,7 @@ def resize(new_width = nil)
# @api public
def finish
return if done?
@current = total unless no_width
@current = total unless indeterminate?
render
clear ? clear_line : write("\n", false)
ensure
Expand Down
6 changes: 3 additions & 3 deletions lib/tty/progressbar/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ class Configuration

attr_accessor :width

attr_accessor :no_width

attr_accessor :incomplete

attr_accessor :complete

attr_accessor :unknown

attr_accessor :head

attr_accessor :clear_head
Expand All @@ -33,9 +33,9 @@ class Configuration
def initialize(options)
self.total = options[:total] if options[:total]
@width = options.fetch(:width) { total }
@no_width = options.fetch(:no_width) { false }
@incomplete = options.fetch(:incomplete) { " " }
@complete = options.fetch(:complete) { "=" }
@unknown = options.fetch(:unknown) { "<=>" }
@head = options.fetch(:head) { @complete || "=" }
@clear_head = options.fetch(:clear_head) { false }
@hide_cursor = options.fetch(:hide_cursor) { false }
Expand Down
19 changes: 18 additions & 1 deletion lib/tty/progressbar/formatter/bar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,24 @@ def format(value)
available_space = [0, ProgressBar.max_columns -
ProgressBar.display_columns(without_bar) -
@progress.inset].max
width = [@progress.width, available_space].min
width = [@progress.width.to_i, available_space].min

# When we don't know the total progress, use either user
# defined width or rely on terminal width detection
if @progress.indeterminate?
buffer = []
width = available_space if width.zero?
width -= @progress.unknown.size
complete = (width * @progress.ratio).round
incomplete = width - complete

buffer << " " * complete
buffer << @progress.unknown
buffer << " " * incomplete

return value.gsub(MATCHER, buffer.join)
end

complete_bar_length = (width * @progress.ratio).round
complete_char_length = ProgressBar.display_columns(@progress.complete)
incomplete_char_length = ProgressBar.display_columns(@progress.incomplete)
Expand Down
15 changes: 15 additions & 0 deletions spec/unit/formatter/bar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@
].join)
end

it "animates unknown progress without total" do
progress = TTY::ProgressBar.new("[:bar]", output: output, total: nil, width: 5)
2.times { progress.advance }
progress.update(total: 5)
3.times { progress.advance }
output.rewind
expect(output.read).to eq([
"\e[1G[<=> ]",
"\e[1G[<=> ]",
"\e[1G[=== ]",
"\e[1G[==== ]",
"\e[1G[=====]\n"
].join)
end

it "animates colors correctly" do
red = "\e[31m \e[0m"
green = "\e[32m \e[0m"
Expand Down
28 changes: 28 additions & 0 deletions spec/unit/indeterminate_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
RSpec.describe TTY::ProgressBar, "indeterminate" do
let(:output) { StringIO.new("", "w+") }

it "animates indeterminate progress" do
progress = described_class.new("[:bar]", output: output, width: 10)
104.times { progress.advance }

output.rewind
expect(output.read).to eq([
"\e[1G[<=> ]" * 3,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 8,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=>]" * 7,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 8,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 7,
"\e[1G[ <=> ]" * 7,
"\e[1G[<=> ]" * 7,
"\e[1G[ <=> ]"
].join)
end
end
2 changes: 1 addition & 1 deletion spec/unit/pipeline_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
subject(:pipeline) { described_class.new }

it "decorates tokenized string with pipeline formatters" do
progress_bar = double(current: '3', total: '10')
progress_bar = double(current: '3', total: '10', indeterminate?: false)
pipeline.use TTY::ProgressBar::CurrentFormatter.new(progress_bar)
pipeline.use TTY::ProgressBar::TotalFormatter.new(progress_bar)
tokenized = "[:current/:total]"
Expand Down
4 changes: 1 addition & 3 deletions spec/unit/render_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

it "pads out longer previous lines" do
progress = TTY::ProgressBar.new ":current_byte" do |config|
config.no_width = true
config.output = output
config.total = 1_048_577
config.output = output
end

progress.advance(1)
Expand Down

0 comments on commit 77e6972

Please sign in to comment.