diff --git a/lib/console/logger.rb b/lib/console/logger.rb index db0b36b..174b9ed 100644 --- a/lib/console/logger.rb +++ b/lib/console/logger.rb @@ -60,7 +60,7 @@ def initialize(output, **options) def progress(subject, total, **options) options[:severity] ||= :info - Progress.new(subject, total, **options) + Progress.new(subject, total, logger: self, **options) end end end diff --git a/lib/console/progress.rb b/lib/console/progress.rb index 1baf271..d76da06 100644 --- a/lib/console/progress.rb +++ b/lib/console/progress.rb @@ -18,10 +18,12 @@ def self.now # # @parameter subject [Object] The subject of the progress indicator. # @parameter total [Integer] The total number of steps. + # @parameter logger [Console::Logger] The logger to use for output. # @parameter minimum_output_duration [Numeric] The minimum duration between outputs. # @parameter options [Hash] Additional options to customize the output. - def initialize(subject, total = 0, minimum_output_duration: 0.1, **options) + def initialize(subject, total = 0, logger: Console, minimum_output_duration: 0.1, **options) @subject = subject + @logger = logger @options = options @start_time = Clock.now @@ -55,6 +57,8 @@ def duration # @returns [Rational] The ratio of steps completed to total steps. def ratio + return Rational(0, 1) if @total.zero? + Rational(@current.to_f, @total.to_f) end @@ -99,7 +103,7 @@ def increment(amount = 1) @current += amount if output? - Console.call(@subject, self.to_s, event: self.to_hash, **@options) + @logger.call(@subject, self.to_s, event: self.to_hash, **@options) @last_output_time = Clock.now end @@ -113,7 +117,7 @@ def increment(amount = 1) def resize(total) @total = total - Console.call(@subject, self.to_s, event: self.to_hash, **@options) + @logger.call(@subject, self.to_s, event: self.to_hash, **@options) @last_output_time = Clock.now return self @@ -125,7 +129,7 @@ def resize(total) # @parameter **options [Hash] Additional options to log. # @parameter &block [Proc] An optional block used to generate the log message. def mark(*arguments, **options, &block) - Console.call(@subject, *arguments, **options, **@options, &block) + @logger.call(@subject, *arguments, **options, **@options, &block) end # @returns [String] A human-readable representation of the progress indicator. diff --git a/lib/console/terminal/formatter/progress.rb b/lib/console/terminal/formatter/progress.rb index 5f244c1..76a5708 100644 --- a/lib/console/terminal/formatter/progress.rb +++ b/lib/console/terminal/formatter/progress.rb @@ -41,12 +41,10 @@ def initialize(terminal) def format(event, stream, verbose: false, width: 80) current = event[:current].to_f total = event[:total].to_f - value = current / total + value = total.zero? ? 0.0 : current / total - # Clamp value to 1.0 to avoid rendering issues: - if value > 1.0 - value = 1.0 - end + # Clamp value to avoid rendering issues: + value = value.clamp(0.0, 1.0) stream.puts "#{@terminal[:progress_bar]}#{self.bar(value, width-10)}#{@terminal.reset} #{sprintf('%6.2f', value * 100)}%" end diff --git a/releases.md b/releases.md index d101d11..48da161 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Fix `Console::Progress` to emit through the logger that created it and safely handle zero totals. + ## v1.37.0 - Show compound units in elapsed time display. diff --git a/test/console/terminal/formatter/progress.rb b/test/console/terminal/formatter/progress.rb index 1c80261..e0daea0 100644 --- a/test/console/terminal/formatter/progress.rb +++ b/test/console/terminal/formatter/progress.rb @@ -36,4 +36,16 @@ expect(buffer.string).to be =~ /100.00%/ end + + it "clamps negative progress to zero percent" do + formatter.format({current: -1, total: 1}, buffer, width: 20) + + expect(buffer.string).to be =~ /0.00%/ + end + + it "formats a zero total as zero percent" do + formatter.format({current: 0, total: 0}, buffer, width: 20) + + expect(buffer.string).to be =~ /0.00%/ + end end diff --git a/test/progress.rb b/test/progress.rb index 733c852..5e1e2ac 100644 --- a/test/progress.rb +++ b/test/progress.rb @@ -30,6 +30,48 @@ end end + with "a specific logger" do + let(:capture) {Console::Capture.new} + let(:logger) {Console::Logger.new(capture)} + let(:progress) {logger.progress("My Measurement", 1)} + + it "emits progress through that logger" do + progress.increment + + expect(capture.last).to have_keys( + severity: be == :info, + subject: be == "My Measurement", + event: have_keys(type: be == :progress), + ) + expect(console_capture).to be(:empty?) + end + + it "emits resizing through that logger" do + progress.resize(2) + + expect(capture.last).to have_keys( + severity: be == :info, + subject: be == "My Measurement", + event: have_keys( + type: be == :progress, + total: be == 2, + ), + ) + expect(console_capture).to be(:empty?) + end + + it "emits marks through that logger" do + progress.mark("Hello World!") + + expect(capture.last).to have_keys( + severity: be == :info, + subject: be == "My Measurement", + arguments: be == ["Hello World!"], + ) + expect(console_capture).to be(:empty?) + end + end + with "#resize" do it "can resize the progress bar total" do progress.resize(200) @@ -77,4 +119,12 @@ ) end end + + with "a zero total" do + let(:progress) {Console::Progress.new("My Measurement")} + + it "has a zero ratio" do + expect(progress.ratio).to be == 0.0 + end + end end