├── .github ├── FUNDING.yml └── workflows │ ├── danger.yml │ └── rspec.yml ├── .gitignore ├── .reek.yml ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── Changelog.mkd ├── Dangerfile ├── Gemfile ├── LICENSE ├── README.mkd ├── Rakefile ├── examples ├── enumerable.rb ├── printing_messages.rb └── simple.rb ├── lib ├── progress_bar.rb └── progress_bar │ ├── core_ext │ └── enumerable_with_progress.rb │ ├── version.rb │ └── with_progress.rb ├── profile ├── shell_every_update ├── shell_every_update.gif ├── shell_once └── shell_once.gif ├── progress_bar.gemspec └── spec ├── arguments_spec.rb ├── bar_spec.rb ├── counter_spec.rb ├── elapsed_spec.rb ├── eta_spec.rb ├── percentage_spec.rb ├── print_spec.rb ├── progress_bar_spec.rb ├── rate_spec.rb ├── spec_helper.rb └── with_progress_spec.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: paul 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/danger.yml: -------------------------------------------------------------------------------- 1 | name: Run Danger on PRs 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | 7 | jobs: 8 | danger: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: ruby/setup-ruby@v1 13 | with: 14 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 15 | - run: bundle exec danger 16 | env: 17 | DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/rspec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | "on": [pull_request] 4 | jobs: 5 | rspec: 6 | name: RSpec 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | ruby: 11 | - "2.7" 12 | - "3.0" 13 | - "3.1" 14 | - "3.2" 15 | - "3.3" 16 | - "3.4" 17 | - head 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby }} 23 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 24 | - name: RSpec 25 | run: | 26 | bundle exec rspec 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | 6 | # Rubocop inherited configs 7 | .rubocop-http* 8 | 9 | # Editor junk 10 | .DS_Store 11 | .vscode 12 | .idea 13 | 14 | -------------------------------------------------------------------------------- /.reek.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - bin 4 | 5 | detectors: 6 | UtilityFunction: 7 | public_methods_only: true 8 | 9 | TooManyStatements: 10 | exclude: 11 | - initialize 12 | max_statements: 8 13 | 14 | RepeatedConditional: 15 | max_ifs: 4 16 | 17 | LongParameterList: 18 | exclude: 19 | - initialize 20 | 21 | # This one just makes sure the Class/Module has a comment. Dumb. 22 | IrresponsibleModule: 23 | enabled: false 24 | 25 | # Transaction result blocks are 3-deep 26 | NestedIterators: 27 | max_allowed_nesting: 3 28 | 29 | UncommunicativeVariableName: 30 | accept: 31 | - i # array index 32 | - c # config 33 | - k # key 34 | - v # value 35 | - h # hash initializer (Hash.new { |h,k| h[k] = Hash.new }) 36 | - "_" 37 | UncommunicativeModuleName: 38 | accept: 39 | - Auth0 40 | 41 | # AS::Subscriber objects tend to rely heavily on `event` and `payload`, so its 42 | # hard to avoid "Feature Envy", but is perfectly readable. 43 | FeatureEnvy: 44 | enabled: false 45 | 46 | directories: 47 | "spec/support": 48 | UtilityFunction: 49 | enabled: false 50 | 51 | # vi:syntax=yaml 52 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format 2 | documentation 3 | --color 4 | --backtrace 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | inherit_from: 3 | - https://relaxed.ruby.style/rubocop.yml 4 | 5 | require: 6 | - rubocop-rspec 7 | 8 | AllCops: 9 | DisplayCopNames: true 10 | DisplayStyleGuide: true 11 | NewCops: enable 12 | TargetRubyVersion: 2.6 13 | 14 | Exclude: 15 | - "vendor/**/*" 16 | - "spec/fixtures/**/*" 17 | - "bin/**/*" 18 | - "script/**/*" 19 | - "tmp/**/*" 20 | 21 | Layout/HashAlignment: 22 | EnforcedHashRocketStyle: table 23 | EnforcedColonStyle: table 24 | Lint/AmbiguousBlockAssociation: 25 | Exclude: 26 | - "spec/**/*" # `expect { }.to change { }` is fine 27 | Lint/ShadowingOuterLocalVariable: 28 | # Shadowing outer local variables with block parameters is often useful to 29 | # not reinvent a new name for the same thing, it highlights the relation 30 | # between the outer variable and the parameter. The cases where it's actually 31 | # confusing are rare, and usually bad for other reasons already, for example 32 | # because the method is too long. 33 | Enabled: false 34 | Metrics/BlockLength: 35 | Exclude: 36 | - Gemfile 37 | - Guardfile 38 | - shared_context 39 | - feature 40 | AllowedMethods: 41 | - configure 42 | - context 43 | - define 44 | - describe 45 | - factory 46 | - it 47 | - namespace 48 | - specify 49 | - task 50 | - shared_examples_for 51 | - shared_context 52 | - feature 53 | - define_type 54 | Layout/LineLength: 55 | Enabled: true 56 | Max: 120 57 | Metrics/ClassLength: 58 | Exclude: 59 | - "spec/**/*_spec.rb" 60 | Naming/RescuedExceptionsVariableName: 61 | PreferredName: ex 62 | Naming/FileName: 63 | Enabled: false 64 | Naming/MethodParameterName: 65 | Enabled: false 66 | Style/EmptyLiteral: 67 | Enabled: false 68 | Style/FormatStringToken: 69 | Enabled: false 70 | Style/FrozenStringLiteralComment: 71 | Enabled: true 72 | Style/HashSyntax: 73 | Exclude: 74 | - lib/tasks/**/*.rake 75 | Style/NumericLiterals: 76 | Enabled: false 77 | Style/StringConcatenation: 78 | Enabled: false 79 | Style/StringLiterals: 80 | Enabled: true 81 | EnforcedStyle: double_quotes 82 | Style/SymbolArray: 83 | MinSize: 4 84 | 85 | # Rspec 86 | RSpec/ContextWording: 87 | Enabled: false 88 | RSpec/DescribeClass: 89 | Enabled: false 90 | RSpec/DescribedClass: 91 | Enabled: false 92 | RSpec/ExampleLength: 93 | Max: 10 94 | RSpec/ExampleWording: 95 | Enabled: false 96 | RSpec/ExpectChange: 97 | EnforcedStyle: block 98 | RSpec/ImplicitExpect: 99 | Enabled: false 100 | RSpec/LeadingSubject: 101 | Enabled: false 102 | RSpec/MultipleExpectations: 103 | Max: 4 104 | RSpec/NestedGroups: 105 | Max: 4 106 | RSpec/NotToNot: 107 | Enabled: false 108 | RSpec/ExpectInHook: 109 | Enabled: false 110 | RSpec/LetSetup: 111 | Enabled: false 112 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: rake spec 2 | rvm: 3 | - 2.2 4 | - 2.3 5 | - 2.4.0-preview3 6 | - ruby-head 7 | 8 | matrix: 9 | allow_failures: 10 | - rvm: 11 | - 2.4.0-preview3 12 | - ruby-head 13 | -------------------------------------------------------------------------------- /Changelog.mkd: -------------------------------------------------------------------------------- 1 | # 1.3.4 2 | 3 | - Updated dependencies (#63) 4 | - Relaxed highline gem requirement to allow 3.x (#62, Thanks to @dgholz) 5 | 6 | # 1.3.3 7 | 8 | * Fixed use of unqualified ::Time that was conflicting with another 9 | ProgressBar gem [#58](https://github.com/paul/progress_bar/pull/58). 10 | Thanks to [harryloewen](https://github.com/harryloewen) for the 11 | [report](https://github.com/paul/progress_bar/issues/57) 12 | 13 | # 1.3.2 14 | 15 | * Added `ProgressBar#puts`, to be able to print text to the output without 16 | interfering with the rendering of the bar output. 17 | [#56](https://github.com/paul/progress_bar/pull/56) Thanks to 18 | [TRex22](https://github.com/TRex22) for the suggestion in 19 | [#44](https://github.com/paul/progress_bar/pull/44). 20 | 21 | # 1.3.1 22 | 23 | * Added support for passing bar options to `Enumerable#with_progress` 24 | extension. 25 | 26 | # 1.3.0 27 | 28 | * Relaxed highline gem requirement to allow highline 2.0 as well as 1.6+ 29 | 30 | 31 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # vi: ft=ruby 4 | 5 | github.dismiss_out_of_range_messages 6 | rubocop.lint inline_comment: true 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # A sample Gemfile 4 | source "https://rubygems.org" 5 | 6 | gemspec 7 | 8 | group :development, :test do 9 | gem "rake" 10 | gem "reek" 11 | gem "rspec" 12 | gem "rubocop" 13 | gem "rubocop-rspec" 14 | gem "timecop" 15 | 16 | gem "danger-rubocop", "~> 0.13.0" 17 | end 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2011 Paul Sadauskas 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # ProgressBar 2 | 3 | [![Build Status](https://github.com/paul/progress_bar/workflows/Test/badge.svg)](https://github.com/paul/progress_bar/actions)[![Gem Version](https://badge.fury.io/rb/progress_bar.svg)](http://badge.fury.io/rb/progress_bar) 4 | 5 | *ProgressBar* is a simple Ruby library for displaying progress of 6 | long-running tasks on the console. It is intended to be as simple to use 7 | as possible. 8 | 9 | **NOTE:** This project isn't dead! It's just feature complete, and I don't want 10 | to keep adding things to it. If you find bugs, please open an Issue, or even 11 | better, a Pull Request, and I'll take a look. We at ProgressBar know you have 12 | lots of progress bar alternatives, and we thank you for using ProgressBar! 13 | 14 | 15 | # Installation 16 | 17 | gem install progress_bar 18 | 19 | # Examples 20 | 21 | ## The Easy Way 22 | 23 | ```ruby 24 | require 'progress_bar' 25 | 26 | bar = ProgressBar.new 27 | 28 | 100.times do 29 | sleep 0.1 30 | bar.increment! 31 | end 32 | ``` 33 | 34 | Produces output like: 35 | 36 | [####################################### ] [ 59.00%] [00:06] 37 | 38 | *Note: It may not be exactly like this. I might have changed the default 39 | meters between now and when I wrote this readme, and forgotten to update 40 | it.* 41 | 42 | ## Setting the Max 43 | 44 | Usually, the defaults should be fine, the only thing you'll need to 45 | tweak is the max. 46 | 47 | ```ruby 48 | bar = ProgressBar.new(1000) 49 | ``` 50 | 51 | ## Larger Steps 52 | 53 | If you want to process several things, and update less often, you can 54 | pass a number to `#increment!` 55 | 56 | ```ruby 57 | bar.increment! 42 58 | ``` 59 | 60 | ## Printing additional output 61 | 62 | Sometimes you want to print some additional messages in the output, but since the ProgressBar uses terminal control characters to replace the text on the same line on every update, the output looks funny: 63 | 64 | [####################################### ] [ 59.00%] [00:06] 65 | Hello! 66 | [######################################### ] [ 60.00%] [00:05] 67 | 68 | To prevent this, you can use `ProgressBar#puts` so ProgressBar knows you want to print something, and it'll clear the bar before printing, then resume printing on the next line: 69 | 70 | ```ruby 71 | 100.times do |i| 72 | sleep 0.1 73 | bar.puts "Halfway there!" if i == 50 74 | bar.increment! 75 | end 76 | ``` 77 | 78 | Produces output like: 79 | 80 | Halfway there! 81 | [##################################] [100/100] [100%] [00:10] [00:00] [ 9.98/s] 82 | 83 | Try it out in `examples/printing_messages.rb` to see how it looks. 84 | 85 | ## Picking the meters 86 | 87 | By default, ProgressBar will use all available meters (this will 88 | probably change). To select which meters you want, and in which order, 89 | pass them to the constructor: 90 | 91 | ```ruby 92 | bar = ProgressBar.new(100, :bar, :rate, :eta) 93 | ``` 94 | 95 | 96 | ### Available Meters 97 | 98 | * `:bar` -- The bar itself, fills empty space with "#"s. Ex: `[### 99 | ]`. 100 | * `:counter` -- Number of items complete, over the max. Ex: `[ 20/100]` 101 | * `:percentage` -- Percentage of items in the maximum. Ex: `[ 42%]` 102 | * `:elapsed` -- Time elapsed (since the ProgressBar was initialized. 103 | Ex: `[00:42]` 104 | * `:eta` -- Estimated Time remaining. Given the rate that items are 105 | completed, a guess at how long the rest will take. Ex: `[01:30]` 106 | * `:rate` -- The rate at which items are being completed. Ex: `[ 107 | 42.42/s]` 108 | 109 | Run the tests to see examples of all the formats, with different values 110 | and maximums. 111 | ``` 112 | gem install --development progress_bar 113 | rspec spec/*_spec.rb 114 | ``` 115 | 116 | ## Using ProgressBar on Enumerable-alikes. 117 | 118 | If you do a lot of progresses, you can shorten your way with this: 119 | 120 | ```ruby 121 | class Array 122 | include ProgressBar::WithProgress 123 | end 124 | 125 | [1,2,3].each_with_progress{do_something} 126 | 127 | # or any other Enumerable's methods: 128 | 129 | (1..1000).to_a.with_progress.select{|i| (i % 2).zero?} 130 | ``` 131 | 132 | You can include `ProgressBar::WithProgress` in any class, having methods 133 | `#count` and `#each`, like some DB datasets and so on. 134 | 135 | If you are using progress_bar regularly on plain arrays, you may want to 136 | do: 137 | 138 | ```ruby 139 | require 'progress_bar/core_ext/enumerable_with_progress' 140 | 141 | # it adds each_with_progress/with_progress to Array/Hash/Range 142 | 143 | (1..400).with_progress.select{|i| (i % 2).zero?} 144 | ``` 145 | 146 | If you want to display only specific meters you can do it like so: 147 | 148 | ```ruby 149 | (1..400).with_progress(:bar, :elapsed).select{|i| (i % 2).zero?} 150 | ``` 151 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler" 4 | Bundler::GemHelper.install_tasks 5 | 6 | require "rspec/core/rake_task" 7 | RSpec::Core::RakeTask.new(:spec) 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /examples/enumerable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/progress_bar/core_ext/enumerable_with_progress" 4 | 5 | (20...34).with_progress.select{ |i| sleep 0.1; (i % 2).zero? } 6 | -------------------------------------------------------------------------------- /examples/printing_messages.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/progress_bar" 4 | 5 | bar = ProgressBar.new 6 | 7 | 100.times do |i| 8 | sleep 0.1 9 | bar.puts("Some long text\nthat contains newlines") if i == 10 10 | bar.puts("Halfway there!") if i == 50 11 | bar.puts("Almost done!") if i == 90 12 | bar.increment! 13 | end 14 | 15 | __END__ 16 | 17 | $ ruby examples/printing_messages.rb 18 | Some long text 19 | that contains newlines 20 | Halfway there! 21 | Almost done! 22 | [##################################] [100/100] [100%] [00:10] [00:00] [ 9.98/s] 23 | -------------------------------------------------------------------------------- /examples/simple.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/progress_bar" 4 | 5 | bar = ProgressBar.new 6 | 7 | 100.times do 8 | sleep 0.1 9 | bar.increment! 10 | end 11 | -------------------------------------------------------------------------------- /lib/progress_bar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "options" 4 | require "highline" 5 | 6 | class ProgressBar 7 | Error = Class.new(StandardError) 8 | ArgumentError = Class.new(Error) 9 | 10 | attr_accessor :count, :max, :meters 11 | 12 | def initialize(*args) 13 | @count = 0 14 | @max = 100 15 | @meters = [:bar, :counter, :percentage, :elapsed, :eta, :rate] 16 | 17 | @max = args.shift if args.first.is_a? Numeric 18 | raise ArgumentError, "Max must be a positive integer" unless @max >= 0 19 | 20 | @meters = args unless args.empty? 21 | 22 | @last_write = ::Time.at(0) 23 | @start = ::Time.now 24 | 25 | @hl = HighLine.new 26 | @terminal_width = 80 27 | @last_width_adjustment = ::Time.at(0) 28 | end 29 | 30 | def increment!(count = 1) 31 | @count += count 32 | now = ::Time.now 33 | return unless (now - @last_write) > 0.2 || @count >= max 34 | 35 | write 36 | @last_write = now 37 | end 38 | 39 | def puts(text) 40 | clear! 41 | $stderr.write(text) 42 | $stderr.puts 43 | write 44 | end 45 | 46 | def write 47 | print "\r" + to_s 48 | end 49 | 50 | def remaining 51 | max - count 52 | end 53 | 54 | def ratio 55 | [count.to_f / max, 1.0].min # never go above 1, even if count > max 56 | end 57 | 58 | def percentage 59 | ratio * 100 60 | end 61 | 62 | def elapsed 63 | ::Time.now - @start 64 | end 65 | 66 | def rate 67 | if count > 0 68 | count / elapsed 69 | else 70 | 0 71 | end 72 | end 73 | 74 | def eta 75 | if count > 0 76 | remaining / rate 77 | else 78 | 0 79 | end 80 | end 81 | 82 | def to_s 83 | self.count = max if count > max 84 | meters.inject(String.new) do |text, meter| 85 | text << "#{render(meter)} " 86 | end.strip 87 | end 88 | 89 | protected 90 | 91 | def print(str) 92 | $stderr.write str 93 | end 94 | 95 | def clear! 96 | print "\r" + (" " * terminal_width) + "\r" 97 | end 98 | 99 | def render(meter) 100 | send(:"render_#{meter}") 101 | end 102 | 103 | def width_of(meter) 104 | send(:"#{meter}_width") 105 | end 106 | 107 | def render_bar 108 | return "" if bar_width < 2 109 | 110 | progress_width = (ratio * (bar_width - 2)).floor 111 | remainder_width = bar_width - 2 - progress_width 112 | "[#{'#' * progress_width}#{' ' * remainder_width}]" 113 | end 114 | 115 | def render_counter 116 | "[%#{max_width}i/%i]" % [count, max] 117 | end 118 | 119 | def render_percentage 120 | format = (max == 100 ? "%3i" : "%6.2f") 121 | "[#{format}%%]" % percentage 122 | end 123 | 124 | def render_elapsed 125 | "[#{format_interval(elapsed)}]" 126 | end 127 | 128 | def render_eta 129 | "[#{format_interval(eta)}]" 130 | end 131 | 132 | def render_rate 133 | "[%#{max_width + 3}.2f/s]" % rate 134 | end 135 | 136 | def terminal_width 137 | # HighLine check takes a long time, so only update width every second. 138 | now = ::Time.now 139 | if now - @last_width_adjustment > 1 140 | @last_width_adjustment = now 141 | new_width = @hl.output_cols.to_i 142 | new_width = 80 if new_width < 1 143 | @terminal_width = new_width 144 | else 145 | @terminal_width 146 | end 147 | end 148 | 149 | def bar_width 150 | terminal_width - non_bar_width 151 | end 152 | 153 | def non_bar_width 154 | meters.reject { |meter| meter == :bar }.inject(0) do |width, meter| 155 | width + width_of(meter) + 1 156 | end 157 | end 158 | 159 | # [ 1/100] 160 | def counter_width 161 | (max_width * 2) + 3 162 | end 163 | 164 | def percentage_width 165 | if max == 100 # [ 24%] 166 | 6 167 | else # [ 24.00%] 168 | 9 169 | end 170 | end 171 | 172 | def elapsed_width 173 | format_interval(elapsed).length + 2 174 | end 175 | 176 | def eta_width 177 | format_interval(eta).length + 2 178 | end 179 | 180 | # [ 23.45/s] 181 | def rate_width 182 | render_rate.length 183 | end 184 | 185 | def max_width 186 | max.to_s.length 187 | end 188 | 189 | HOUR = 3600 190 | MINUTE = 60 191 | def format_interval(interval) 192 | if interval > HOUR 193 | "%02i:%02i:%02i" % [interval / HOUR, interval % HOUR / MINUTE, interval % MINUTE] 194 | else 195 | "%02i:%02i" % [interval / MINUTE, interval % MINUTE] 196 | end 197 | end 198 | end 199 | 200 | require_relative "progress_bar/with_progress" 201 | -------------------------------------------------------------------------------- /lib/progress_bar/core_ext/enumerable_with_progress.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../progress_bar" 4 | 5 | ObjectSpace.each_object(Module) do |mod| 6 | if mod <= Enumerable 7 | mod.send :include, ProgressBar::WithProgress 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/progress_bar/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ProgressBar 4 | VERSION = "1.3.4" 5 | end 6 | -------------------------------------------------------------------------------- /lib/progress_bar/with_progress.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ProgressBar 4 | module WithProgress 5 | def each_with_progress(*args, &block) 6 | bar = ProgressBar.new(count, *args) 7 | if block 8 | each{ |obj| yield(obj).tap{ bar.increment! } } 9 | else 10 | Enumerator.new{ |yielder| 11 | each do |obj| 12 | (yielder << obj).tap{ bar.increment! } 13 | end 14 | } 15 | end 16 | end 17 | 18 | alias_method :with_progress, :each_with_progress 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /profile/shell_every_update: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul/progress_bar/70eb83765033a0fe8419276a9d677bb97e45bdda/profile/shell_every_update -------------------------------------------------------------------------------- /profile/shell_every_update.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul/progress_bar/70eb83765033a0fe8419276a9d677bb97e45bdda/profile/shell_every_update.gif -------------------------------------------------------------------------------- /profile/shell_once: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul/progress_bar/70eb83765033a0fe8419276a9d677bb97e45bdda/profile/shell_once -------------------------------------------------------------------------------- /profile/shell_once.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul/progress_bar/70eb83765033a0fe8419276a9d677bb97e45bdda/profile/shell_once.gif -------------------------------------------------------------------------------- /progress_bar.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/progress_bar/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "progress_bar" 7 | s.version = ProgressBar::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Paul Sadauskas"] 10 | s.email = ["psadauskas@gmail.com"] 11 | s.homepage = "http://github.com/paul/progress_bar" 12 | s.license = "WTFPL" 13 | s.summary = "Simple Progress Bar for output to a terminal" 14 | s.description = "Give people feedback about long-running tasks without overloading them with information: Use a progress bar, like Curl or Wget!" 15 | 16 | s.required_ruby_version = ">= 2.4.0" 17 | 18 | s.add_dependency("highline", ">= 1.6") 19 | s.add_dependency("options", "~> 2.3.0") 20 | 21 | s.files = `git ls-files`.split("\n") 22 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 23 | s.require_paths = ["lib"] 24 | s.metadata["rubygems_mfa_required"] = "true" 25 | end 26 | -------------------------------------------------------------------------------- /spec/arguments_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar arguments" do 6 | let(:default_max) { 100 } 7 | let(:default_meters) { [:bar, :counter, :percentage, :elapsed, :eta, :rate] } 8 | 9 | it "should set appropriate defaults without any arguments" do 10 | bar = ProgressBar.new 11 | expect(bar.max).to eq default_max 12 | expect(bar.meters).to eq default_meters 13 | end 14 | 15 | it "should allow a single argument specifying the max" do 16 | bar = ProgressBar.new(123) 17 | expect(bar.max).to eq 123 18 | expect(bar.meters).to eq default_meters 19 | end 20 | 21 | it "should allow specifying just the meters" do 22 | bar = ProgressBar.new(:bar, :eta) 23 | expect(bar.max).to eq default_max 24 | expect(bar.meters).to eq [:bar, :eta] 25 | end 26 | 27 | it "should allow specyfing the max and meters" do 28 | bar = ProgressBar.new(123, :bar, :eta) 29 | expect(bar.max).to eq 123 30 | expect(bar.meters).to eq [:bar, :eta] 31 | end 32 | 33 | it "should raise an error when initial max is nonsense" do 34 | expect { ProgressBar.new(-1) } 35 | .to raise_error(ProgressBar::ArgumentError) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/bar_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar bar output" do 6 | let(:max) { 100 } 7 | let(:terminal_width) { 12 } 8 | 9 | let(:progress_bar) { ProgressBar.new(max, :bar) } 10 | 11 | before do 12 | allow(progress_bar).to receive(:terminal_width).and_return(terminal_width) 13 | progress_bar.count = count 14 | end 15 | 16 | subject { progress_bar.to_s } 17 | 18 | describe "at count=0" do 19 | let(:count) { 0 } 20 | 21 | it { should == "[ ]" } 22 | end 23 | 24 | describe "at count=50" do 25 | let(:count) { 50 } 26 | 27 | it { should == "[##### ]" } 28 | end 29 | 30 | describe "at count=100" do 31 | let(:count) { 100 } 32 | 33 | it { should == "[##########]" } 34 | end 35 | 36 | describe "at count=25 (non-integer divide, should round down)" do 37 | let(:count) { 25 } 38 | 39 | it { should == "[## ]" } 40 | end 41 | 42 | # https://github.com/paul/progress_bar/pull/31 43 | describe "constant bar width" do 44 | let(:max) { 17 } 45 | let(:count) { 0 } 46 | let(:terminal_width) { 80 } 47 | 48 | it "has a constant bar width for all values of max and count" do 49 | (count..max).each do |i| 50 | progress_bar.count = i 51 | expect( progress_bar.to_s.length ).to eq(80) 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/counter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar counter output" do 6 | let(:progress_bar) { ProgressBar.new(100, :counter) } 7 | 8 | subject { progress_bar.to_s } 9 | 10 | describe "at count=0" do 11 | before do 12 | progress_bar.count = 0 13 | end 14 | 15 | it { should == "[ 0/100]" } 16 | end 17 | 18 | describe "at count=50" do 19 | before do 20 | progress_bar.count = 50 21 | end 22 | 23 | it { should == "[ 50/100]" } 24 | end 25 | 26 | describe "at count=100" do 27 | before do 28 | progress_bar.count = 100 29 | end 30 | 31 | it { should == "[100/100]" } 32 | end 33 | 34 | describe "with a shorter max" do 35 | let(:progress_bar) { ProgressBar.new(42, :counter) } 36 | 37 | it { should == "[ 0/42]" } 38 | end 39 | 40 | describe "with a longer max" do 41 | let(:progress_bar) { ProgressBar.new(4242, :counter) } 42 | 43 | it { should == "[ 0/4242]" } 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/elapsed_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar elapsed output" do 6 | let(:progress_bar) { ProgressBar.new(100, :elapsed) } 7 | 8 | before do 9 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 10 | progress_bar 11 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 10) # 10 seconds later 12 | end 13 | 14 | subject { progress_bar.to_s } 15 | 16 | describe "at count=0" do 17 | before do 18 | progress_bar.count = 0 19 | end 20 | 21 | it { should == "[00:10]" } 22 | end 23 | 24 | describe "at count=50" do 25 | before do 26 | progress_bar.count = 50 27 | end 28 | 29 | it { should == "[00:10]" } 30 | end 31 | 32 | describe "at count=100" do 33 | before do 34 | progress_bar.count = 100 35 | end 36 | 37 | it { should == "[00:10]" } 38 | end 39 | 40 | describe "with times over 1 hour" do 41 | let(:progress_bar) { ProgressBar.new(42, :elapsed) } 42 | 43 | before do 44 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 45 | Timecop.freeze Time.utc(2010, 3, 10, 2, 0, 0) # 2 hours later 46 | end 47 | 48 | it { should == "[02:00:00]" } 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/eta_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar eta output" do 6 | let(:progress_bar) { ProgressBar.new(100, :eta) } 7 | 8 | before do 9 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 10 | progress_bar 11 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 10) # 10 seconds later 12 | end 13 | 14 | subject { progress_bar.to_s } 15 | 16 | describe "at count=0" do 17 | before do 18 | progress_bar.count = 0 19 | end 20 | 21 | it { should == "[00:00]" } 22 | end 23 | 24 | describe "at count=50" do 25 | before do 26 | progress_bar.count = 50 27 | end 28 | 29 | it { should == "[00:10]" } 30 | end 31 | 32 | describe "at count=100" do 33 | before do 34 | progress_bar.count = 100 35 | end 36 | 37 | it { should == "[00:00]" } 38 | end 39 | 40 | describe "with times over 1 hour" do 41 | let(:progress_bar) { ProgressBar.new(42, :eta) } 42 | 43 | before do 44 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 45 | progress_bar.count = 21 46 | Timecop.freeze Time.utc(2010, 3, 10, 2, 0, 0) # 2 hours later 47 | end 48 | 49 | it { should == "[02:00:00]" } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/percentage_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar percentage output" do 6 | let(:progress_bar) { ProgressBar.new(100, :percentage) } 7 | 8 | subject { progress_bar.to_s } 9 | 10 | describe "at count=0" do 11 | before do 12 | progress_bar.count = 0 13 | end 14 | 15 | it { should == "[ 0%]" } 16 | end 17 | 18 | describe "at count=50" do 19 | before do 20 | progress_bar.count = 50 21 | end 22 | 23 | it { should == "[ 50%]" } 24 | end 25 | 26 | describe "at count=100" do 27 | before do 28 | progress_bar.count = 100 29 | end 30 | 31 | it { should == "[100%]" } 32 | end 33 | 34 | describe "with a max that is not 100" do 35 | let(:progress_bar) { ProgressBar.new(42, :percentage) } 36 | 37 | before do 38 | progress_bar.count = 21 39 | end 40 | 41 | it { should == "[ 50.00%]" } 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/print_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe "ProgressBar print output" do 6 | let(:progress_bar) { ProgressBar.new(:counter) } 7 | 8 | before do 9 | allow(progress_bar).to receive(:terminal_width).and_return(10) 10 | end 11 | 12 | it "should replace the current bar with the text, and continue the bar on the next line" do 13 | expect { 14 | progress_bar.increment! 15 | progress_bar.puts("Hello, world!") 16 | }.to output("\r[ 1/100]" \ 17 | "\r#{' ' * 10}" \ 18 | "\rHello, world!\n" \ 19 | "\r[ 1/100]").to_stderr 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/progress_bar_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar bar output" do 6 | let(:progress_bar) { ProgressBar.new(100) } 7 | 8 | before do 9 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 10 | allow(progress_bar).to receive(:terminal_width).and_return(60) 11 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 10) # 10 seconds later 12 | end 13 | 14 | subject { progress_bar.to_s } 15 | 16 | describe "at count=0" do 17 | before do 18 | progress_bar.count = 0 19 | end 20 | 21 | it { should == "[ ] [ 0/100] [ 0%] [00:10] [00:00] [ 0.00/s]" } 22 | end 23 | 24 | describe "at count=50" do 25 | before do 26 | progress_bar.count = 50 27 | end 28 | 29 | it { should == "[####### ] [ 50/100] [ 50%] [00:10] [00:10] [ 5.00/s]" } 30 | end 31 | 32 | describe "at count=100" do 33 | before do 34 | progress_bar.count = 100 35 | end 36 | 37 | it { should == "[##############] [100/100] [100%] [00:10] [00:00] [ 10.00/s]" } 38 | end 39 | 40 | describe "at count=105" do 41 | before do 42 | progress_bar.count = 105 43 | end 44 | 45 | it { should == "[##############] [100/100] [100%] [00:10] [00:00] [ 10.00/s]" } 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/rate_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe "ProgressBar rate output" do 6 | let(:progress_bar) { ProgressBar.new(100, :rate) } 7 | 8 | before do 9 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 10 | progress_bar 11 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 10) # 10 seconds later 12 | end 13 | 14 | subject { progress_bar.to_s } 15 | 16 | describe "at count=0" do 17 | before do 18 | progress_bar.count = 0 19 | end 20 | 21 | it { should == "[ 0.00/s]" } 22 | end 23 | 24 | describe "at count=50" do 25 | before do 26 | progress_bar.count = 50 27 | end 28 | 29 | it { should == "[ 5.00/s]" } 30 | end 31 | 32 | describe "at count=100" do 33 | before do 34 | progress_bar.count = 100 35 | end 36 | 37 | it { should == "[ 10.00/s]" } 38 | end 39 | 40 | describe "with a shorter max" do 41 | let(:progress_bar) { ProgressBar.new(42, :rate) } 42 | 43 | before do 44 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 45 | progress_bar.count = 21 46 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 10) # 10 seconds later 47 | end 48 | 49 | it { should == "[ 2.10/s]" } 50 | end 51 | 52 | describe "with a longer max" do 53 | let(:progress_bar) { ProgressBar.new(4242, :rate) } 54 | 55 | before do 56 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 0) 57 | progress_bar.count = 21 58 | Timecop.freeze Time.utc(2010, 3, 10, 0, 0, 10) # 10 seconds later 59 | end 60 | 61 | it { should == "[ 2.10/s]" } 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib/progress_bar")) 4 | 5 | require "rspec" 6 | require "timecop" 7 | 8 | RSpec.configure do |cfg| 9 | cfg.after do 10 | Timecop.return 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/with_progress_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) 4 | 5 | describe ProgressBar::WithProgress do 6 | context "with block" do 7 | let!(:bar){ ProgressBar.new } 8 | 9 | before{ 10 | Range.include ProgressBar::WithProgress 11 | allow(ProgressBar).to receive(:new){ |max| bar.max = max; bar } 12 | } 13 | 14 | it "should set max and increment on each iteration" do 15 | (1..20).each_with_progress do |i| 16 | break if i > 10 17 | end 18 | expect(bar.max).to eq 20 19 | expect(bar.count).to eq 10 20 | end 21 | end 22 | 23 | context "without block" do 24 | let!(:bar){ ProgressBar.new } 25 | 26 | before{ 27 | Range.include ProgressBar::WithProgress 28 | allow(ProgressBar).to receive(:new){ |max| bar.max = max; bar } 29 | } 30 | 31 | it "should give Enumerator" do 32 | enum = (1..20).each_with_progress 33 | expect(enum).to be_a(Enumerator) 34 | expect(bar.max).to eq 20 35 | expect(bar.count).to eq 0 36 | 37 | res = enum.map{ |i| i + 1 } 38 | expect(res).to eq (2..21).to_a 39 | expect(bar.count).to eq 20 40 | end 41 | end 42 | end 43 | --------------------------------------------------------------------------------