lib/progressbar.rb in ruby-progressbar-0.0.10 vs lib/progressbar.rb in ruby-progressbar-0.11.0
- old
+ new
@@ -19,29 +19,43 @@
@terminal_width = 80
@bar_mark = "o"
@current = 0
@previous = 0
@finished_p = false
- @start_time = Time.now
+ @start_time = time_now
@previous_time = @start_time
- @title_width = 14
- @format = "%-#{@title_width}s %3d%% %s %s"
@format_arguments = [:title, :percentage, :bar, :stat]
+ @smoothing = 0.9
+ @running_average = 0
clear
show
end
attr_reader :title
attr_reader :current
attr_reader :total
attr_accessor :start_time
attr_writer :bar_mark
+ attr_writer :title_width
+ def title_width
+ @title_width ||= 14
+ end
+
+ def format
+ @format || "%-#{title_width}s %3d%% %s %s"
+ end
+
+ # Exponential smoothing helps keep jitter out of the time-remaining estimate.
+ # The value may be anything from 0.0 to 1.0. Contrary to intuition, LOWER
+ # values make the average smoother, and 1.0 is equivalent to no smoothing
+ # whatsoever (the classic behavior). Default value is 0.9.
+ attr_accessor :smoothing
+
private
def fmt_bar
- bar_width = do_percentage * @terminal_width / 100
- sprintf("|%s%s|",
- @bar_mark * bar_width,
+ sprintf("|%s%s|",
+ @bar_mark * bar_width,
" " * (@terminal_width - bar_width))
end
def fmt_percentage
do_percentage
@@ -50,21 +64,25 @@
def fmt_stat
if @finished_p then elapsed else eta end
end
def fmt_stat_for_file_transfer
- if @finished_p then
+ if @finished_p then
sprintf("%s %s %s", bytes, transfer_rate, elapsed)
- else
+ else
sprintf("%s %s %s", bytes, transfer_rate, eta)
end
end
def fmt_title
- @title[0,(@title_width - 1)] + ":"
+ @title[0,(title_width - 1)] + ":"
end
+ def bar_width
+ do_percentage * @terminal_width / 100
+ end
+
def convert_bytes (bytes)
if bytes < 1024
sprintf("%6dB", bytes)
elsif bytes < 1024 * 1000 # 1000kb
sprintf("%5.1fKB", bytes.to_f / 1024)
@@ -74,42 +92,42 @@
sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
end
end
def transfer_rate
- bytes_per_second = @current.to_f / (Time.now - @start_time)
+ bytes_per_second = @current.to_f / (time_now - @start_time)
sprintf("%s/s", convert_bytes(bytes_per_second))
end
def bytes
convert_bytes(@current)
end
def format_time (t)
- t = t.to_i
- sec = t % 60
- min = (t / 60) % 60
- hour = t / 3600
- sprintf("%02d:%02d:%02d", hour, min, sec);
+ if t < 0 or t.infinite? or t.nan? # "not a number"
+ '--:--:--'
+ else
+ t = t.to_i
+ sec = t % 60
+ min = (t / 60) % 60
+ hour = t / 3600
+ sprintf("%02d:%02d:%02d", hour, min, sec);
+ end
end
# ETA stands for Estimated Time of Arrival.
def eta
- if @current == 0
- "ETA: --:--:--"
- else
- elapsed = Time.now - @start_time
- eta = elapsed * @total / @current - elapsed;
- sprintf("ETA: %s", format_time(eta))
- end
+ elapsed = time_now - @start_time
+ eta = elapsed * @total / @running_average - elapsed;
+ sprintf("ETA: %s", format_time(eta))
end
def elapsed
- elapsed = Time.now - @start_time
+ elapsed = time_now - @start_time
sprintf("Time: %s", format_time(elapsed))
end
-
+
def eol
if @finished_p then "\n" else "\r" end
end
def do_percentage
@@ -136,55 +154,95 @@
default_width
end
end
def show
- arguments = @format_arguments.map {|method|
+ tty? ? show_tty : show_no_tty
+ @previous_time = time_now
+ end
+
+ # Print output to a tty device.
+ def show_tty
+ arguments = @format_arguments.map {|method|
method = sprintf("fmt_%s", method)
send(method)
}
- line = sprintf(@format, *arguments)
+ line = sprintf(format, *arguments)
width = get_width
- if line.length == width - 1
- @out.print(line + eol)
- @out.flush
+ if line.length == width - 1
+ @out.write(line + eol)
elsif line.length >= width
@terminal_width = [@terminal_width - (line.length - width + 1), 0].max
- if @terminal_width == 0 then @out.print(line + eol) else show end
+ if @terminal_width == 0 then @out.write(line + eol) else show end
else # line.length < width - 1
@terminal_width += width - line.length + 1
show
end
- @previous_time = Time.now
end
+ # Print output to a non-terminal device, such as a log file.
+ # The terminal width is set to 80 columns.
+ def show_no_tty
+ @out.print("| " + elapsed + eol) and return if finished?
+
+ # Draw title the first time
+ if @last_bar_width.nil?
+ @last_bar_width = 0
+ @terminal_width = @terminal_width - fmt_title.size - elapsed.size - 4
+ @out.print(fmt_title + " |")
+ else
+ bar_width_change = bar_width - @last_bar_width
+ if bar_width_change > 0
+ @out.print(@bar_mark * bar_width_change)
+ @last_bar_width = bar_width
+ end
+ end
+ end
+
def show_if_needed
if @total.zero?
cur_percentage = 100
prev_percentage = 0
else
cur_percentage = (@current * 100 / @total).to_i
prev_percentage = (@previous * 100 / @total).to_i
end
# Use "!=" instead of ">" to support negative changes
- if cur_percentage != prev_percentage ||
- Time.now - @previous_time >= 1 || @finished_p
+ if cur_percentage != prev_percentage ||
+ time_now - @previous_time >= 1 || @finished_p
show
end
end
+ def time_now
+ # Ignore Timecop time mocking
+ if Time.respond_to?(:now_without_mock_time)
+ Time.now_without_mock_time
+ # Ignore Delorean time mocking
+ elsif Time.respond_to?(:now_without_delorean)
+ Time.now_without_delorean
+ else
+ Time.now
+ end
+ end
+
+ def tty?
+ @out.tty?
+ end
+
public
def clear
+ return unless tty?
@out.print "\r"
@out.print(" " * (get_width - 1))
@out.print "\r"
end
def finish
- @current = @total
+ @current = @previous = @running_average = @total
@finished_p = true
show
end
def finished?
@@ -207,22 +265,25 @@
@finished_p = true
show
end
def inc (step = 1)
- @current += step
- @current = @total if @current > @total
- show_if_needed
- @previous = @current
+ set(@current + step)
end
def set (count)
- if count < 0 || count > @total
- raise "invalid count: #{count} (total: #{@total})"
- end
- @current = count
+ # Constrain input to 0 <= count <= 100
+ @current = [ [count, @total].min, 0 ].max
+
+ # Update the exponentially-smoothed average
+ @running_average = @previous * @smoothing +
+ @running_average * (1.0 - @smoothing)
+
+ # If this makes the percentage change by a tick or more, show it
show_if_needed
+
+ # Update for the next iteration
@previous = @current
end
def inspect
"#<ProgressBar:#{@current}/#{@total}>"
@@ -232,6 +293,5 @@
class ReversedProgressBar < ProgressBar
def do_percentage
100 - super
end
end
-