lib/progress.rb in progress-2.4.0 vs lib/progress.rb in progress-3.0.0

- old
+ new

@@ -1,5 +1,7 @@ +# encoding: UTF-8 + require 'singleton' require 'thread' # ==== Procedural example # Progress.start('Test', 1000) @@ -33,260 +35,270 @@ # end # end class Progress include Singleton - attr_accessor :title, :current, :total, :note - attr_reader :current_step - def initialize(title, total) - if title.is_a?(Numeric) && total.nil? - title, total = nil, title - elsif total.nil? - total = 1 + attr_reader :total + attr_reader :current + attr_reader :title + attr_accessor :note + def initialize(total, title) + if !total.kind_of?(Numeric) && (title.nil? || title.kind_of?(Numeric)) + total, title = title, total end - @title = title + total = total && total != 0 ? Float(total) : 1.0 + + @total = total @current = 0.0 - @total = total == 0.0 ? 1.0 : Float(total) + @title = title end - def step_if_blank - if current == 0.0 && total == 1.0 - self.current = 1.0 - end + def to_f(inner) + inner = 1.0 if inner > 1.0 + inner *= @step if @step + (current + inner) / total end - def to_f(inner) - inner = [inner, 1.0].min - if current_step - inner *= current_step + def step(step, note) + if !step.kind_of?(Numeric) + step, note = nil, step end - (current + inner) / total + step = 1 if step.nil? + + @step = step + @note = note + ret = yield if block_given? + Thread.exclusive do + @current += step + end + ret end - def step(steps) - @current_step = steps - yield - ensure - @current_step = nil + def set(new_current, note) + @step = new_current - @current + @note = note + ret = yield if block_given? + Thread.exclusive do + @current = new_current + end + ret end + @lock = Mutex.new class << self + # start progress indication - def start(title = nil, total = nil) - if levels.empty? - @started_at = Time.now - @eta = nil - @semaphore = Mutex.new - start_beeper + def start(total = nil, title = nil) + lock do + if running? + unless @started_in == Thread.current + warn 'Can\'t start inner progress in different thread' + if block_given? + return yield + else + return + end + end + else + @started_in = Thread.current + @eta = Eta.new + start_beeper + end + @levels ||= [] + @levels.push new(total, title) end - levels << new(title, total) - print_message true + print_message :force => true if block_given? begin yield ensure stop end end end - # step current progress by `num / den` - def step(num = 1, den = 1, &block) - if levels.last - set(levels.last.current + Float(num) / den, &block) + # step current progress + def step(step = nil, note = nil, &block) + if running? + ret = @levels.last.step(step, note, &block) + print_message + ret elsif block block.call end end - # set current progress to `value` - def set(value, &block) - if levels.last - ret = if block - levels.last.step(value - levels.last.current, &block) - end - if levels.last - levels.last.current = Float(value) - end + # set value of current progress + def set(new_current, note = nil, &block) + if running? + ret = @levels.last.set(new_current, note, &block) print_message - self.note = nil ret elsif block block.call end end # stop progress def stop - if levels.last - if levels.last.step_if_blank || levels.length == 1 - print_message true - set_title nil - end - levels.pop - if levels.empty? + if running? + if @levels.length == 1 + print_message :force => true, :finish => true stop_beeper - io.puts end + @levels.pop end end - # check in block of showing progress + # check if progress was started def running? - !levels.empty? + @levels && !@levels.empty? end # set note - def note=(s) - if levels.last - levels.last.note = s + def note=(note) + if running? + @levels.last.note = note end end - # output progress as lines (not trying to stay on line) - # Progress.lines = true - attr_writer :lines - - # force highlight - # Progress.highlight = true - attr_writer :highlight - - private - - def levels - @levels ||= [] + # stay on one line + def stay_on_line? + @stay_on_line.nil? ? io_tty? : @stay_on_line end - def io - @io || $stderr + # explicitly set staying on one line [true/false/nil] + def stay_on_line=(value) + @stay_on_line = true && value end - def io_tty? - io.tty? || ENV['PROGRESS_TTY'] - end - - def lines? - @lines.nil? ? !io_tty? : @lines - end - + # highlight output using control characters def highlight? @highlight.nil? ? io_tty? : @highlight end - def time_to_print? - if !@previous || @previous < Time.now - 0.3 - @previous = Time.now - true - end + # explicitly set highlighting [true/false/nil] + def highlight=(value) + @highlight = true && value end - def eta(completed) - now = Time.now - if now > @started_at && completed > 0 - current_eta = @started_at + (now - @started_at) / completed - @eta = @eta ? @eta + (current_eta - @eta) * (1 + completed) * 0.5 : current_eta - seconds = @eta - now - if seconds > 0 - left = case seconds - when 0...60 - '%.0fs' % seconds - when 60...3600 - '%.1fm' % (seconds / 60) - when 3600...86400 - '%.1fh' % (seconds / 3600) - else - '%.1fd' % (seconds / 86400) - end - eta_string = " (ETA: #{left})" - end - end + # show progerss in terminal title + def set_terminal_title? + @set_terminal_title.nil? ? io_tty? : @set_terminal_title end - def set_title(title) - if io_tty? - io.print "\e]0;#{title}\a" - end + # explicitly set showing progress in terminal title [true/false/nil] + def set_terminal_title=(value) + @set_terminal_title = true && value end - def lock(force) - if force ? @semaphore.lock : @semaphore.try_lock + private + + def lock(force = true) + if force ? @lock.lock : @lock.try_lock begin yield ensure - @semaphore.unlock + @lock.unlock end end end + def io + @io || $stderr + end + + def io_tty? + io.tty? || ENV['PROGRESS_TTY'] + end + def start_beeper - @beeper = Thread.new do - loop do - sleep 10 - print_message unless Thread.current[:skip] - end + @beeper = Beeper.new(10) do + print_message end end def stop_beeper - @beeper.kill - @beeper = nil + @beeper.stop if @beeper end def restart_beeper - if @beeper - @beeper[:skip] = true - @beeper.run - @beeper[:skip] = false - end + @beeper.restart if @beeper end - def print_message(force = false) + def time_to_print? + !@next_time_to_print || @next_time_to_print <= Time.now + end + + def eta(current) + @eta.left(current) + end + + def elapsed + @eta.elapsed + end + + def print_message(options = {}) + force = options[:force] lock force do - restart_beeper if force || time_to_print? - inner = 0 - parts, parts_cl = [], [] - levels.reverse.each do |level| - inner = current = level.to_f(inner) - value = current.zero? ? '......' : "#{'%5.1f' % (current * 100.0)}%" + @next_time_to_print = Time.now + 0.3 + restart_beeper - title = level.title ? "#{level.title}: " : nil - if !highlight? || value == '100.0%' - parts << "#{title}#{value}" + current = 0 + parts = [] + title_parts = [] + @levels.reverse.each do |level| + current = level.to_f(current) + + percent = current == 0 ? '......' : "#{'%5.1f' % (current * 100.0)}%" + title = level.title && "#{level.title}: " + if !highlight? || percent == '100.0%' + parts << "#{title}#{percent}" else - parts << "#{title}\e[1m#{value}\e[0m" + parts << "#{title}\e[1m#{percent}\e[0m" end - parts_cl << "#{title}#{value}" + title_parts << "#{title}#{percent}" end - eta_string = eta(inner) - message = "#{parts.reverse * ' > '}#{eta_string}" - message_cl = "#{parts_cl.reverse * ' > '}#{eta_string}" + timing = if options[:finish] + " (elapsed: #{elapsed})" + elsif eta_ = eta(current) + " (ETA: #{eta_})" + end - if note = levels.last && levels.last.note + message = "#{parts.reverse * ' > '}#{timing}" + text_message = "#{title_parts.reverse * ' > '}#{timing}" + + if note = running? && @levels.last.note message << " - #{note}" - message_cl << " - #{note}" + text_message << " - #{note}" end - if lines? - io.puts message - else - io << message << "\e[K\r" - end + message = "\r#{message}\e[K" if stay_on_line? + message << "\n" if !stay_on_line? || options[:finish] + io << message - set_title message_cl + if set_terminal_title? + title = options[:finish] ? nil : text_message.to_s.gsub("\a", '␇') + io << "\e]0;#{title}\a" + end end end end + end end +require 'progress/beeper' +require 'progress/eta' + require 'progress/enumerable' require 'progress/integer' -require 'progress/active_record' +require 'progress/active_record' if defined?(ActiveRecord::Base) module Kernel - def Progress(title = nil, total = nil, &block) - Progress.start(title, total, &block) + def Progress(*args, &block) + Progress.start(*args, &block) end private :Progress end