diff --git a/CHANGELOG.md b/CHANGELOG.md index f177cf8..baa309d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Add :bar_format option to allow selecting preconfigured bar displays * Add :eta_time format token to display the estimated time of day at completion * Add measurement of the total elapsed time that ignores stopped time intervals +* Add #pause to prevent bar from continuing progression and suspend time measurements ### Changed * Change Multi#stopped? to check that all bars are stopped diff --git a/README.md b/README.md index 2e78b42..a156773 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,13 @@ Or install it yourself as: * [2.7 update](#27-update) * [2.8 finish](#28-finish) * [2.9 stop](#29-stop) - * [2.10 reset](#210-reset) - * [2.11 resume](#211-resume) - * [2.12 complete?](#212-complete) - * [2.13 indeterminate?](#213-indeterminate) - * [2.14 resize](#214-resize) - * [2.15 on](#215-on) + * [2.10 pause](#210-pause) + * [2.11 reset](#211-reset) + * [2.12 resume](#212-resume) + * [2.13 complete?](#213-complete) + * [2.14 indeterminate?](#214-indeterminate) + * [2.15 resize](#215-resize) + * [2.16 on](#216-on) * [3. Configuration](#3-configuration) * [3.1 :total](#31-total) * [3.1 :width](#32-width) @@ -291,7 +292,17 @@ In order to immediately stop the bar in the current position and thus prevent an bar.stop ``` -### 2.10 reset +### 2.10 pause + +A running progress bar can be paused at the current position using `pause` method: + +```ruby +bar.pause +``` + +A paused progress bar will stop accumulating any time measurements like elapsed time. It also won't return to a new line, so a progress animation can be smoothly resumed. + +### 2.11 reset In order to reset currently running or finished progress bar to its original configuration and initial position use `reset` like so: @@ -301,15 +312,17 @@ bar.reset After resetting the bar if you wish to draw and start the bar and its timers use `start` call. -### 2.11 resume +### 2.12 resume -When a bar is stopped, you can continue its progression using the `resume` method. +When a bar is stopped or paused, you can continue its progression using the `resume` method. ```ruby bar.resume ``` -### 2.12 complete? +A resumed progression will continue accumulating the total elapsed time without including time intervals for pausing or stopping. + +### 2.13 complete? During progression you can check if a bar is finished or not by calling `complete?`. The bar will only return `true` if the progression finished successfully, otherwise `false` will be returned. @@ -317,7 +330,7 @@ During progression you can check if a bar is finished or not by calling `complet bar.complete? # => false ``` -### 2.13 indeterminate? +### 2.14 indeterminate? You can make a progress bar indeterminate by setting `:total` to nil. In this state, an animation is displayed to show unbounded task. You can check if the progress bar is indeterminate with the `indeterminate?` method: @@ -325,7 +338,7 @@ You can make a progress bar indeterminate by setting `:total` to nil. In this st bar.indeterminate? # => false ``` -### 2.14 resize +### 2.15 resize If you wish for a progress bar to change it's current width, you can use `resize` by passing in a new desired length. However, if you don't provide any width the `resize` will use terminal current width as its base for scaling. @@ -340,7 +353,7 @@ To handle automatic resizing you can trap `:WINCH` signal: trap(:WINCH) { bar.resize } ``` -### 2.15 on +### 2.16 on The progress bar fires events when it is progressing, stopped or finished. You can register to listen for events using the `on` message. @@ -362,6 +375,12 @@ Alternatively, when the progress bar gets stopped the `:stopped` event is fired. bar.on(:stopped) { ... } ``` +Anytime a progress bar is paused the `:paused` event will be fired. To listen for this event do: + +```ruby +bar.on(:paused) { ... } +``` + ## 3. Configuration There are number of configuration options that can be provided: diff --git a/lib/tty/progressbar.rb b/lib/tty/progressbar.rb index 532dd17..2260ad6 100644 --- a/lib/tty/progressbar.rb +++ b/lib/tty/progressbar.rb @@ -127,6 +127,7 @@ def reset @last_render_width = 0 @done = false @stopped = false + @paused = false @start_at = Time.now @elapsed_time = 0 @time_offset = 0 @@ -435,13 +436,16 @@ def finish emit(:done) end - # Resume rendering when bar is done or stopped to update information + # Resume rendering when bar is done, stopped or paused # # @api public def resume - @started = false - @done = false - @stopped = false + synchronize do + @started = false + @done = false + @stopped = false + @paused = false + end end # Stop and cancel the progress at the current position @@ -463,6 +467,15 @@ def stop emit(:stopped) end + # Pause the progress at the current position + # + # @api public + def pause + @paused = true + @time_offset += Time.now - @start_at + emit(:paused) + end + # Clear current line # # @api public @@ -489,13 +502,22 @@ def stopped? @stopped end - # Check if progress is finished or stopped + # Check if progress is paused + # + # @return [Boolean] + # + # @api public + def paused? + @paused + end + + # Check if progress is finished, stopped or paused # # @return [Boolean] # # @api public def done? - @done || @stopped + @done || @stopped || @paused end # Register callback with this bar diff --git a/spec/unit/events_spec.rb b/spec/unit/events_spec.rb index 2f28294..ac3f287 100644 --- a/spec/unit/events_spec.rb +++ b/spec/unit/events_spec.rb @@ -32,4 +32,14 @@ expect(events).to eq([:stopped]) end + + it "emits :paused event" do + events = [] + bar = TTY::ProgressBar.new("[:bar]", output: output, total: 5) + bar.on(:paused) { events << :paused } + + bar.pause + + expect(events).to eq([:paused]) + end end diff --git a/spec/unit/resume_spec.rb b/spec/unit/resume_spec.rb index 0ee6c2b..772b121 100644 --- a/spec/unit/resume_spec.rb +++ b/spec/unit/resume_spec.rb @@ -101,4 +101,41 @@ "\e[1G[==========] 13/13 100% 13B/13B 1B/s 1B/s 1.00/s 1.00/s 4s 0s\n" ].join) end + + it "resumes paused progression with all the metrics" do + time_now = Time.local(2021, 1, 11, 12, 0, 0) + Timecop.freeze(time_now) + format = "[:bar] :current/:total :percent " \ + ":current_byte/:total_byte :byte_rate/s :mean_byte/s " \ + ":rate/s :mean_rate/s :elapsed :eta" + + progress = described_class.new(format, output: output, total: 10) + + 3.times do |i| + time_now = Time.local(2021, 1, 11, 12, 0, 1 + i) + Timecop.freeze(time_now) + progress.advance + end + progress.pause + expect(progress.paused?).to eq(true) + + progress.resume + expect(progress.paused?).to eq(false) + + 3.times do |i| + time_now = Time.local(2021, 1, 11, 12, 0, 4 + i) + Timecop.freeze(time_now) + progress.advance + end + + output.rewind + expect(output.read).to eq([ + "\e[1G[= ] 1/10 10% 1B/10B 1B/s 1B/s 1.00/s 1.00/s 0s 0s", + "\e[1G[== ] 2/10 20% 2B/10B 1B/s 1B/s 1.00/s 1.00/s 1s 4s", + "\e[1G[=== ] 3/10 30% 3B/10B 1B/s 1B/s 1.00/s 1.00/s 2s 4s", # pause render + "\e[1G[==== ] 4/10 40% 4B/10B 1B/s 1B/s 1.00/s 1.00/s 2s 3s", + "\e[1G[===== ] 5/10 50% 5B/10B 1B/s 1B/s 1.00/s 1.00/s 3s 3s", + "\e[1G[====== ] 6/10 60% 6B/10B 1B/s 1B/s 1.00/s 1.00/s 4s 2s" + ].join) + end end