philiprehberger-progress 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d1ecb051d55213a818343c3133c067129bf55e65b6677a2efb9be49c249ab5b
4
- data.tar.gz: '0039452f607ef4f9fe1f1a1098b31039c6ea7ecbc9c0618c8f8fba78b5464763'
3
+ metadata.gz: 8cd9b24ec4af77caa0242168972123e46f8aefe0aa7d010f355f634462d4e7ad
4
+ data.tar.gz: 956912f23898fd45c2434b06030aea103af6d33f390f11fc432a7cf285983325
5
5
  SHA512:
6
- metadata.gz: da6edc69bcc8dc77d091ba848dae89fb8f8e5054ceb1f9c6991537e85554b8a3bbde4cef23b94089d0c6194827ae6c13e3a7cb02f875d9d7ca9e3960519d7023
7
- data.tar.gz: 5692a7b42fb906e3234117a3a96ca0ad2c1af88d558bc41e7a787cd8c1cc954a5c317613b91ff8d913b27b87263ea279d8bf497b19f17886cf57c8141578b6f8
6
+ metadata.gz: 79fcad8d626d2869ac6c3bfa9d29ec243c134f7c140dfca896282c940bb82b3879d25378b229162441708d13fc204c6e912c5f802fef0b9331fc4bd9bb845889
7
+ data.tar.gz: d0074a78f1adfa22c1c749bf6367c468dfdfb1fa011d56ab88b83faedcc86625ada2f5982430cdc2f69013c66ead9bd030058d0b538959b3778cbfc94c86cb9c
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-04-14
11
+
12
+ ### Added
13
+ - `Bar#pause` and `Bar#resume` to freeze and unfreeze elapsed time calculation
14
+ - `Bar#to_h` returns a hash with `:percentage`, `:elapsed`, `:eta`, `:throughput`, `:current`, `:total`
15
+ - Custom bar characters via `fill:`, `empty:`, `tip:` keyword arguments (defaults: `fill: '='`, `empty: ' '`, `tip: '>'`)
16
+ - `Progress.json_mode!` and `Progress.text_mode!` to toggle JSON line output for bar rendering
17
+
10
18
  ## [0.4.0] - 2026-04-09
11
19
 
12
20
  ### Added
data/README.md CHANGED
@@ -81,6 +81,58 @@ end
81
81
 
82
82
  The thread is joined automatically when `stop` is called (or when the block completes).
83
83
 
84
+ ### Pause and Resume
85
+
86
+ Pause the progress bar to freeze elapsed time calculation (e.g. while waiting for user input):
87
+
88
+ ```ruby
89
+ bar = Philiprehberger::Progress::Bar.new(total: 100)
90
+ 50.times { bar.advance }
91
+ bar.pause
92
+ # ... elapsed time is frozen ...
93
+ bar.resume
94
+ 50.times { bar.advance }
95
+ bar.finish
96
+ ```
97
+
98
+ ### Custom Bar Characters
99
+
100
+ Customize the fill, empty, and tip characters:
101
+
102
+ ```ruby
103
+ bar = Philiprehberger::Progress::Bar.new(total: 100, fill: '#', empty: '.', tip: '>')
104
+ 50.times { bar.advance }
105
+ bar.to_s
106
+ # [########################>.........................] 50.0% | 50/100 | ETA: 0s | 50.0/s
107
+ ```
108
+
109
+ Default characters are `fill: '='`, `empty: ' '`, `tip: '>'`.
110
+
111
+ ### Data Export
112
+
113
+ Export the current state as a hash:
114
+
115
+ ```ruby
116
+ bar = Philiprehberger::Progress::Bar.new(total: 100)
117
+ 50.times { bar.advance }
118
+ bar.to_h
119
+ # => { percentage: 50.0, elapsed: 1.2, eta: 1.2, throughput: 41.7, current: 50, total: 100 }
120
+ ```
121
+
122
+ ### JSON Mode
123
+
124
+ Switch to JSON line output for machine-readable progress:
125
+
126
+ ```ruby
127
+ Philiprehberger::Progress.json_mode!
128
+ bar = Philiprehberger::Progress::Bar.new(total: 100)
129
+ 50.times { bar.advance }
130
+ bar.to_s
131
+ # {"percentage":50.0,"elapsed":1.2,"eta":1.2,"throughput":41.7,"current":50,"total":100}
132
+
133
+ Philiprehberger::Progress.text_mode! # revert to ANSI bar
134
+ ```
135
+
84
136
  ### Enumerable Integration
85
137
 
86
138
  ```ruby
@@ -121,17 +173,21 @@ multi.finished? # => true
121
173
 
122
174
  | Method | Description |
123
175
  |--------|-------------|
124
- | `.new(total:, width: 30, output: $stderr)` | Create a progress bar |
176
+ | `.new(total:, width: 30, output: $stderr, fill: '=', empty: ' ', tip: '>')` | Create a progress bar |
125
177
  | `#advance(n = 1)` | Advance by `n` items |
126
178
  | `#set(n)` | Set absolute progress position (clamped to 0..total) |
127
179
  | `#reset` | Reset to 0, clear finished state, restart timer |
180
+ | `#pause` | Pause the bar, freezing elapsed time |
181
+ | `#resume` | Resume after pause |
182
+ | `#paused?` | Whether the bar is paused |
128
183
  | `#finish` | Mark as complete |
129
184
  | `#finished?` | Whether the bar is finished |
130
185
  | `#percentage` | Current percentage (0.0 to 100.0) |
131
- | `#elapsed` | Elapsed time in seconds |
186
+ | `#elapsed` | Elapsed time in seconds (excludes paused time) |
132
187
  | `#eta` | Estimated time remaining in seconds |
133
188
  | `#throughput` | Items per second |
134
- | `#to_s` | Render the bar as a string |
189
+ | `#to_h` | Hash with `:percentage`, `:elapsed`, `:eta`, `:throughput`, `:current`, `:total` |
190
+ | `#to_s` | Render the bar as a string (or JSON line in json_mode) |
135
191
 
136
192
  ### `Philiprehberger::Progress::Spinner`
137
193
 
@@ -167,6 +223,9 @@ multi.finished? # => true
167
223
  | `Progress.multi(output: $stderr, &block)` | Create multi-bar tracker |
168
224
  | `Progress.each(enumerable, label: nil) { \|item\| }` | Iterate with progress |
169
225
  | `Progress.map(enumerable, label: nil) { \|item\| }` | Transform with progress, returns results |
226
+ | `Progress.json_mode!` | Switch bar rendering to JSON line output |
227
+ | `Progress.text_mode!` | Switch bar rendering back to ANSI text |
228
+ | `Progress.json_mode?` | Whether JSON mode is active |
170
229
 
171
230
  ## Development
172
231
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Philiprehberger
4
6
  module Progress
5
7
  class Bar
@@ -8,13 +10,19 @@ module Philiprehberger
8
10
 
9
11
  attr_reader :current, :total
10
12
 
11
- def initialize(total:, width: 30, output: $stderr)
13
+ def initialize(total:, width: 30, output: $stderr, fill: '=', empty: ' ', tip: '>')
12
14
  @total = [total, 0].max
13
15
  @width = width
14
16
  @output = output
15
17
  @current = 0
16
18
  @start_time = now
17
19
  @finished = false
20
+ @paused = false
21
+ @pause_elapsed = 0.0
22
+ @pause_start = nil
23
+ @fill = fill
24
+ @empty = empty
25
+ @tip = tip
18
26
  end
19
27
 
20
28
  def advance(n = 1)
@@ -43,10 +51,40 @@ module Philiprehberger
43
51
  def reset
44
52
  @current = 0
45
53
  @finished = false
54
+ @paused = false
55
+ @pause_elapsed = 0.0
56
+ @pause_start = nil
46
57
  @start_time = now
47
58
  self
48
59
  end
49
60
 
61
+ # Pause the progress bar, freezing elapsed time calculation.
62
+ #
63
+ # @return [self]
64
+ def pause
65
+ return self if @paused || @finished
66
+
67
+ @paused = true
68
+ @pause_start = now
69
+ self
70
+ end
71
+
72
+ # Resume after pause.
73
+ #
74
+ # @return [self]
75
+ def resume
76
+ return self unless @paused
77
+
78
+ @pause_elapsed += now - @pause_start
79
+ @pause_start = nil
80
+ @paused = false
81
+ self
82
+ end
83
+
84
+ def paused?
85
+ @paused
86
+ end
87
+
50
88
  def finish
51
89
  @current = @total
52
90
  @finished = true
@@ -66,7 +104,9 @@ module Philiprehberger
66
104
  end
67
105
 
68
106
  def elapsed
69
- now - @start_time
107
+ raw = now - @start_time - @pause_elapsed
108
+ raw -= (now - @pause_start) if @paused
109
+ raw
70
110
  end
71
111
 
72
112
  def eta
@@ -85,7 +125,20 @@ module Philiprehberger
85
125
  @current.to_f / elapsed_time
86
126
  end
87
127
 
128
+ def to_h
129
+ {
130
+ percentage: percentage,
131
+ elapsed: elapsed,
132
+ eta: eta,
133
+ throughput: throughput,
134
+ current: @current,
135
+ total: @total
136
+ }
137
+ end
138
+
88
139
  def to_s
140
+ return to_h.to_json if Philiprehberger::Progress.json_mode?
141
+
89
142
  bar_str = render_bar
90
143
  pct = format('%<p>5.1f%%', p: percentage)
91
144
  eta_str = format_eta(eta)
@@ -109,11 +162,16 @@ module Philiprehberger
109
162
  end
110
163
 
111
164
  def render_bar
112
- return FILL_CHAR * @width if @total.zero?
165
+ return @fill * @width if @total.zero?
113
166
 
114
167
  filled = (@current.to_f / @total * @width).round
115
168
  empty = @width - filled
116
- (FILL_CHAR * filled) + (EMPTY_CHAR * empty)
169
+
170
+ if filled.positive? && filled < @width
171
+ (@fill * (filled - 1)) + @tip + (@empty * empty)
172
+ else
173
+ (@fill * filled) + (@empty * empty)
174
+ end
117
175
  end
118
176
 
119
177
  def format_eta(seconds)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module Progress
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -7,8 +7,22 @@ require_relative 'progress/multi'
7
7
 
8
8
  module Philiprehberger
9
9
  module Progress
10
- def self.bar(total:, width: 30, output: $stderr)
11
- progress_bar = Bar.new(total: total, width: width, output: output)
10
+ @json_mode = false
11
+
12
+ def self.json_mode!
13
+ @json_mode = true
14
+ end
15
+
16
+ def self.text_mode!
17
+ @json_mode = false
18
+ end
19
+
20
+ def self.json_mode?
21
+ @json_mode
22
+ end
23
+
24
+ def self.bar(total:, width: 30, output: $stderr, fill: '=', empty: ' ', tip: '>')
25
+ progress_bar = Bar.new(total: total, width: width, output: output, fill: fill, empty: empty, tip: tip)
12
26
 
13
27
  if block_given?
14
28
  result = yield progress_bar
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-progress
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-10 00:00:00.000000000 Z
11
+ date: 2026-04-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Display progress bars with percentage, ETA, and throughput, or spinners
14
14
  for indeterminate tasks. Supports block-based usage, enumerable iteration with each/map,