powerbar-1.0.17/0000755000004100000410000000000012647255272013465 5ustar www-datawww-datapowerbar-1.0.17/Rakefile0000644000004100000410000000012012647255272015123 0ustar www-datawww-datarequire "bundler/gem_tasks" task :default do sh "ruby bin/powerbar-demo" end powerbar-1.0.17/bin/0000755000004100000410000000000012647255272014235 5ustar www-datawww-datapowerbar-1.0.17/bin/powerbar-demo0000755000004100000410000001555412647255272016740 0ustar www-datawww-data#!/usr/bin/env ruby require 'rubygems' require 'powerbar' require 'logger' # DEMO 1 def demo_1 total = 100000 step = 1000 p = PowerBar.new # This is how you alter the settings. # (see the source-code for all available settings) p.settings.tty.finite.show_eta = true (0..total).step(step).each do |i| p.show({:msg => 'DEMO 1 - Ten seconds of progress', :done => i, :total => total}) sleep 0.1 end # close(true) will set the bar to 100% before closing. # use this when you are sure your operation has finished but # for some reason 'done' may still be smaller than 'total'. # # this is not necessary in this example, and usually a bad idea, # but demonstrated here so you know it exists. p.close(true) end # DEMO 2 def demo_2 total = 100000 step = 1000 text = " "*30 + "Need to change text in mid-progress? No Problemo!" + " "*5 + "As seen in: DEMO 2 - Scrolling madness" p = PowerBar.new j = 0 (0..total).step(step).each do |i| woah = sprintf('%-26s', text[j%text.length..j%text.length+25]) p.show({:msg => woah, :done => i, :total => total}) j += 1 sleep 0.1 end p.close end # DEMO 3 def demo_3 total = 100000 step = 1000 text = "DEMO 3 - Don't sweat your message width too much, we can squeeze it some" p = PowerBar.new j = 0 (0..total).step(step).each do |i| woah = text[0..j] p.show({:msg => woah, :done => i, :total => total}) j += 1 sleep 0.1 end p.close end # DEMO 4 def demo_4 total = 100000 step = 1000 text = "\e[0mDEMO 4 - Colors!" interstitial = [ "Oh, btw, know what?\n", "You may use PowerBar#print to print messages while the bar is running.\n", "Awesome, right?\n" ] p = PowerBar.new p.settings.tty.finite.template.main = \ "${} ${ }\e[0m${/s} \e[33;1m${%} " + "\e[36;1m${}\e[31;1m${ ETA: }" p.settings.tty.finite.template.padchar = "\e[30;1m#{p.settings.tty.finite.template.padchar}" p.settings.tty.finite.template.barchar = "\e[34;1m#{p.settings.tty.finite.template.barchar}" p.settings.tty.finite.template.exit = "\e[?25h\e[0m" # clean up after us p.settings.tty.finite.template.close = "\e[?25h\e[0m\n" # clean up after us p.settings.tty.finite.output = Proc.new{ |s| # The default output-function truncates our # string to enable the "squeezing" as seen in the # previous demo. This doesn't mix so well with ANSI-colors, # thus if you want to use colors you'll have to make the # output-function a little more naive. Like this: $stderr.print s } j = 0 (0..total).step(step).each do |i| p.show({:msg => text, :done => i, :total => total}) j += 1 if 0 == j % 25 p.print interstitial.shift end sleep 0.1 end p.close end # DEMO 5 def demo_5 total = 100000 step = 1000 text = "DEMO 5 - When total is :unknown then we only display what we know" p = PowerBar.new j = 0 (0..total).step(step).each do |i| p.show({:msg => text, :done => i, :total => :unknown}) j += 1 sleep 0.1 end p.close end # DEMO 6 def demo_6 total = 100000 step = 1000 text = "DEMO 6 - Still :unknown, now with a different template" p = PowerBar.new p.settings.tty.infinite.template.main = \ '${} > ${Rate: /s, }elapsed: ${}' j = 0 (0..total).step(step).each do |i| p.show({:msg => text, :done => i, :total => :unknown}) j += 1 sleep 0.1 end p.close end # DEMO 7 def demo_7 total = 100000 step = 1000 text = "DEMO 7 - Forcing notty mode" p = PowerBar.new p.settings.force_mode = :notty j = 0 (0..total).step(step).each do |i| p.show({:msg => text, :done => i, :total => total}) j += 1 if j == 60 p.print "Hint: Of course PowerBar#print also works when in :notty-mode\n" end sleep 0.1 end p.close end # DEMO 8 def demo_8 total = 100000 step = 1000 log = Logger.new(STDOUT) text = "DEMO 8 - Forcing notty mode and using a logger" p = PowerBar.new p.settings.force_mode = :notty p.settings.notty.finite.output = Proc.new { |s| log.debug(s.chomp) unless s == '' } j = 0 (0..total).step(step).each do |i| p.show({:msg => text, :done => i, :total => total}) j += 1 if j == 60 p.print "Hint: Yes, PowerBar#print also works here. ;)\n" end sleep 0.1 end p.close end # DEMO 9 def demo_9 total = 100000 step = 1000 puts "DEMO 9 - No output by PowerBar." puts "Here we use it to collect stats silently, and then we roll our own output:\n-" p = PowerBar.new j = 0 (0..total).step(step).each do |i| # update() does not output anything, it only updates (who would've guessed!) p.update({:done => i, :total => total}) j += 1 if 0 == j % 50 puts "elapsed is: #{p.elapsed}, humanized elapsed is: #{p.h_elapsed}" puts "percent is: #{p.percent}, humanized percent is: #{p.h_percent}" puts "eta is: #{p.eta}, humanized eta is: #{p.h_eta}" puts "done is: #{p.done}, humanized done is: #{p.h_done}" puts "total is: #{p.total}, humanized done is: #{p.h_total}" puts "total is: #{p.total}, humanized done is: #{p.h_total}" puts "bar is: #{p.bar}" puts "-" end sleep 0.1 end #p.close # no need to close this one end # DEMO 10 def demo_10 total = 100000 step = 1000 text = " " * 30 + "DEMO 10 - OMGWTFBBQ!" p = PowerBar.new p.settings.tty.finite.template.padchar = "\e[30;1m#{p.settings.tty.finite.template.padchar}" p.settings.tty.finite.template.barchar = "\e[34;1m#{p.settings.tty.finite.template.barchar}" p.settings.tty.finite.output = Proc.new{ |s| $stderr.print s } j = 0 total = 300000000 step = 1000000 spin = ')|(|' spun = ',.oO^`^Oo' (0..total).step(step).each do |i| p.send(:state).scope = nil # omghax, don't try this at home! wtf_range = (j % text.length)..((j % text.length) + 25) wtf = sprintf('%-26s', text[wtf_range]) wtf.gsub!(/./) { |char| rand(2).zero? ? char.downcase : char.upcase } p.show( :msg => wtf, :done => i, :total => total, :settings => { :tty => { :finite => { :template => { :barchar => "\e[36;1m#{spin[j%spin.length..j%spin.length]}", :padchar => "\e[0;34m#{spun[j%spun.length..j%spun.length]}", :main => "\e[#{rand(1)};3#{rand(7)}m${}\e[0m "+ "\e[0m${ }\e[0m${/s} "+ "\e[33;1m${%} " + "\e[36;1m${}\e[31;1m${ ETA: }", :exit => "\e[?25h\e[0m", :close => "\e[?25h\e[0m\n" } } } } ) j += 1 sleep 0.1 end p.wipe puts "Thanks for watching! ;-)" end begin method("demo_#{ARGV[0]}".to_sym).call rescue (1..10).each do |i| method("demo_#{i}".to_sym).call puts "---" sleep 1 end end powerbar-1.0.17/Gemfile0000644000004100000410000000015112647255272014755 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in powerbar.gemspec gemspec gem "rake" powerbar-1.0.17/README.MD0000644000004100000410000000572512647255272014655 0ustar www-datawww-data# PowerBar This is PowerBar - The last progressbar-library you'll ever need. ## Features * Detects when stdout is not a terminal and automatically falls back to logging * Does not clutter your log-files with ansi-codes! * If your CLI-app can run interactively and non-interactively (e.g. cronjob) you will automatically get reasonable progress-output in both modes. * By default prints to stderr but can call any output-method of your choice (e.g. your favorite Logger). * Fully customizable; all output is template-driven. * All output is optional. You may set PowerBar to silently collect progress information (percentage-done, throughput, ETA, etc.) and then use the computed values elsewhere in your app. * All state can be updated at any time. For example: If you're monitoring a multi-part operation then you can change the status-message of a running PowerBar to reflect the current state. ## Demo ![screencast](https://github.com/busyloop/powerbar/raw/master/ass/screencast.gif?raw=true) ## Installation `gem install powerbar` ## Getting Started Watch the demo that was installed along with the gem: `powerbar-demo` Then look at the [source-code](https://github.com/busyloop/powerbar/blob/master/bin/powerbar-demo) of the demo. Pretty much all use-cases are covered in there, including templates and how to hook in your own logger. ## Example (for the impatient) ``` #!/usr/bin/env ruby require 'powerbar' total = 100000 step = 1000 p = PowerBar.new # Override some defaults to demonstrate how the settings work p.settings.tty.finite.template.barchar = '*' p.settings.tty.finite.template.padchar = '.' # Dummy loop to simulate some progress (0..total).step(step).each do |i| p.show(:msg => 'DEMO 1 - Ten seconds of progress', :done => i, :total => total) sleep 0.1 end p.close ``` ## Documentation? Use the [source](https://github.com/busyloop/powerbar/blob/master/lib/powerbar.rb), Luke! ## License (MIT) Copyright (C) 2011 by moe@busyloop.net Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. powerbar-1.0.17/lib/0000755000004100000410000000000012647255272014233 5ustar www-datawww-datapowerbar-1.0.17/lib/powerbar.rb0000644000004100000410000003042012647255272016400 0ustar www-datawww-data# # Copyright (C) 2011 by moe@busyloop.net # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # require 'powerbar/version' require 'hashie/mash' # # This is PowerBar - The last progressbar-library you'll ever need. # class PowerBar STRIP_ANSI = Regexp.compile '\e\[(\d+)(;\d+)?(;\d+)?[m|K]', nil RUBY18 = RUBY_VERSION[0..2] == "1.8" def initialize(opts={}) @@exit_hooked = false @state = Hashie::Mash.new( { :time_last_show => Time.at(0), # <- don't mess with us :time_last_update => Time.at(0), # <- unless you know :time_start => nil, # <- what you're doing! :time_now => nil, # <- :msg => 'PowerBar!', :done => 0, :total => :unknown, :settings => { :rate_sample_max_interval => 10, # See PowerBar::Rate :rate_sample_window => 6, # See PowerBar::Rate :force_mode => nil, # set to :tty or :notty to force either mode :kilo => 1024, # Change this to 1000 when measuring network traffic or such. :tty => { # <== Settings when stdout is a tty :finite => { # <== Settings for a finite progress bar (when total != :unknown) # The :output Proc is called to draw on the screen --------------------. :output => Proc.new{ |s| $stderr.print s[0..terminal_width()-1] }, # <-' :interval => 0.1, # Minimum interval between screen refreshes (in seconds) :show_eta => true, # Set to false if you want to hide the ETA without changing the template :template => { # <== template for a finite progress bar on a tty :pre => "\e[1G\e[?25l", # printed before the progress-bar # # :main is the progressbar template # # The following tokens are available: # msg, bar, rate, percent, elapsed, eta, done, total # # Tokens may be used like so: # ${} # OR: # ${surrounding text} # # The surrounding text is only rendered when # evaluates to something other than nil. :main => '${}: ${[] }${/s }${% }${}${, ETA: }', :post => '', # printed after the progressbar :wipe => "\e[0m\e[1G\e[K", # printed when 'wipe' is called :close => "\e[?25h\n", # printed when 'close' is called :exit => "\e[?25h", # printed if the process exits unexpectedly :barchar => RUBY18 ? '#' : "\u2588", # fill-char for the progress-bar :padchar => RUBY18 ? '.' : "\u2022" # padding-char for the progress-bar }, }, :infinite => { # <== Settings for an infinite progress "bar" (when total is :unknown) :output => Proc.new{ |s| $stderr.print s[0..terminal_width()-1] }, :interval => 0.1, :show_eta => false, :template => { :pre => "\e[1G\e[?25l", :main => "${}: ${ }${/s }${}", :post => "\e[K", :wipe => "\e[0m\e[1G\e[K", :close => "\e[?25h\n", :exit => "\e[?25h", :barchar => RUBY18 ? '#' : "\u2588", :padchar => RUBY18 ? '.' : "\u2022" }, } }, :notty => { # <== Settings when stdout is not a tty :finite => { # You may want to hook in your favorite Logger-Library here. ---. :output => Proc.new{ |s| $stderr.print s }, # <----------------' :interval => 1, :show_eta => true, :line_width => 78, # Maximum output line width :template => { :pre => '', :main => "${}: ${}/${}, ${%}${, /s}${, elapsed: }${, ETA: }\n", :post => '', :wipe => '', :close => nil, :exit => nil, :barchar => "#", :padchar => "." }, }, :infinite => { :output => Proc.new{ |s| $stderr.print s }, :interval => 1, :show_eta => false, :line_width => 78, :template => { :pre => "", :main => "${}: ${ }${/s }${}\n", :post => "", :wipe => "", :close => nil, :exit => nil, :barchar => "#", :padchar => "." }, } } } }.merge(opts) ) end # settings-hash def settings @state.settings end # settings under current scope (e.g. tty.infinite) def scope scope_hash = [settings.force_mode,state.total].hash return @state.scope unless @state.scope.nil? or scope_hash != @state.scope_hash state.scope_at = [ settings.force_mode || ($stdout.isatty ? :tty : :notty), :unknown == state.total ? :infinite : :finite ] state.scope = state.settings state.scope_at.each do |s| begin state.scope = state.scope[s] rescue NoMethodError raise StandardError, "Invalid configuration: #{state.scope_at.join('.')} "+ "(Can't resolve: #{state.scope_at[state.scope_at.index(s)-1]})" end end state.scope_hash = scope_hash state.scope end # Hook at_exit to ensure cleanup if we get interrupted def hook_exit return if @@exit_hooked if scope.template.exit at_exit do exit! end end @@exit_hooked = true end # Print the close-template and defuse the exit-hook. # Be a good citizen, always close your PowerBars! def close(fill=false) show( { :done => fill && !state.total.is_a?(Symbol) ? state.total : state.done, :tty => { :finite => { :show_eta => false }, :infinite => { :show_eta => false }, }, :notty => { :finite => { :show_eta => false }, :infinite => { :show_eta => false }, }, }, true) scope.output.call(scope.template.close) unless scope.template.close.nil? state.closed = true end # Remove progress-bar, print a message def print(s) wipe scope.output.call(s) end # Update state (and settings) without printing anything. def update(opts={}) state.merge!(opts) state.time_start ||= Time.now state.time_now = Time.now @rate ||= PowerBar::Rate.new(state.time_now, state.settings.rate_sample_window, state.settings.rate_sample_max_interval) @rate.append(state.time_now, state.done) end # Output the PowerBar. # Returns true if bar was shown, false otherwise. def show(opts={}, force=false) return false if scope.interval > Time.now - state.time_last_show and force == false update(opts) hook_exit state.time_last_show = Time.now state.closed = false scope.output.call(scope.template.pre) scope.output.call(render) scope.output.call(scope.template.post) true end # Render the PowerBar and return as a string. def render(opts={}) update(opts) render_template end # Remove the PowerBar from the screen. def wipe scope.output.call(scope.template.wipe) end # Render the actual bar-portion of the PowerBar. # The length of the bar is determined from the template. # Returns nil if the bar-length would be == 0. def bar return nil if state.total.is_a? Symbol skel = render_template(:main, [:bar]) lwid = state.scope_at[0] == :tty ? terminal_width() : scope.line_width barlen = [lwid - skel.gsub(STRIP_ANSI, '').length, 0].max fill = [0,[(state.done.to_f/state.total*barlen).to_i,barlen].min].max thebar = scope.template.barchar * fill + scope.template.padchar * [barlen - fill,0].max thebar.length == 0 ? nil : thebar end def h_bar bar end def msg state.msg end def h_msg msg end def eta (state.total - state.done) / rate end # returns nil when eta is < 1 second def h_eta return nil unless scope.show_eta 1 < eta ? humanize_interval(eta) : nil end def elapsed (state.time_now - state.time_start).to_f end def h_elapsed humanize_interval(elapsed) end def percent return 0.0 if state.total.is_a? Symbol state.done.to_f/state.total*100 end def h_percent sprintf "%d", percent end def rate @rate.avg end def h_rate humanize_quantity(round(rate, 1)) end def total state.total end def h_total humanize_quantity(state.total) end def done state.done end def h_done humanize_quantity(state.done) end def terminal_width if /solaris/ =~ RUBY_PLATFORM && (`stty` =~ /\brows = (\d+).*\bcolumns = (\d+)/) w, r = [$2, $1] else w, r = `stty size 2>/dev/null`.split.reverse end w = `tput cols` unless w w = w.to_i if w w end private def state @state end def round(number, digit) if number.zero? or number.nan? or number.infinite? number else factor = 10.0 ** digit (number * factor).round / factor end end # Cap'n Hook def exit! return if state.closed scope.output.call(scope.template.exit) unless scope.template.exit.nil? end def render_template(tplid=:main, skip=[]) tpl = scope.template[tplid] skip.each do |s| tpl = tpl.gsub(/\$\{([^<]*)<#{s}>([^}]*)\}/, '\1\2') end tpl.gsub(/\$\{[^\}]+\}/) do |var| sub = nil r = var.gsub(/<[^>]+>/) do |t| t = t[1..-2] begin sub = send "h_#{t}" rescue NoMethodError raise NameError, "Invalid token '#{t}' in template '#{tplid}'" end end sub.nil? ? '' : r[2..-2] end end HQ_UNITS = %w(b k M G T).freeze def humanize_quantity(number, format='%n%u') return nil if number.nil? return nil if number.is_a? Float and (number.nan? or number.infinite?) kilo = settings.kilo return number if number.to_i < kilo max_exp = HQ_UNITS.size - 1 number = Float(number) exponent = (Math.log(number) / Math.log(kilo)).to_i exponent = max_exp if exponent > max_exp number /= kilo ** exponent unit = HQ_UNITS[exponent] return format.gsub(/%n/, round(number, 1).to_s).gsub(/%u/, unit) end def humanize_interval(s) return nil if s.nil? or s.infinite? sprintf("%02d:%02d:%02d", s / 3600, s / 60 % 60, s % 60) end class Rate < Array attr_reader :last_sample_at def initialize(at, window, max_interval=10, interval_step_up=0.1) super([]) @last_sample_at = at @sample_interval = 0 @sample_interval_step_up = interval_step_up @sample_interval_max = max_interval @counter = 0 @window = window end def append(at, v) return if @sample_interval > at - @last_sample_at @sample_interval += @sample_interval_step_up if @sample_interval < @sample_interval_max rate = (v - @counter) / (at - @last_sample_at).to_f return if rate.nan? @last_sample_at = at @counter = v self << rate shift while @window < length self end def sum inject(:+).to_f end def avg sum / size end end end powerbar-1.0.17/lib/powerbar/0000755000004100000410000000000012647255272016054 5ustar www-datawww-datapowerbar-1.0.17/lib/powerbar/version.rb0000644000004100000410000000005012647255272020061 0ustar www-datawww-dataclass Powerbar VERSION = "1.0.17" end powerbar-1.0.17/metadata.yml0000644000004100000410000000264612647255272016000 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: powerbar version: !ruby/object:Gem::Version version: 1.0.17 platform: ruby authors: - Moe autorequire: bindir: bin cert_chain: [] date: 2016-01-17 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: hashie requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.1.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.1.0 description: The last progressbar-library you'll ever need email: - moe@busyloop.net executables: - powerbar-demo extensions: [] extra_rdoc_files: [] files: - ".gitignore" - Gemfile - README.MD - Rakefile - bin/powerbar-demo - lib/powerbar.rb - lib/powerbar/version.rb - powerbar.gemspec homepage: https://github.com/busyloop/powerbar licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.5 signing_key: specification_version: 4 summary: The last progressbar-library you'll ever need test_files: [] powerbar-1.0.17/.gitignore0000644000004100000410000000005112647255272015451 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* .yardoc powerbar-1.0.17/powerbar.gemspec0000644000004100000410000000143412647255272016655 0ustar www-datawww-data# -*- encoding: utf-8 -*- $LOAD_PATH.push File.expand_path("../lib", __FILE__) require "powerbar/version" Gem::Specification.new do |s| s.name = "powerbar" s.version = Powerbar::VERSION s.authors = ["Moe"] s.email = ["moe@busyloop.net"] s.homepage = "https://github.com/busyloop/powerbar" s.summary = %q{The last progressbar-library you'll ever need} s.description = %q{The last progressbar-library you'll ever need} s.license = "MIT" s.add_dependency "hashie", ">= 1.1.0" s.files = `git ls-files`.split("\n").reject {|e| e.start_with? 'ass/'} s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] end