philiprehberger-progress 0.1.0 → 0.1.2
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 +4 -4
- data/CHANGELOG.md +15 -8
- data/README.md +47 -66
- data/lib/philiprehberger/progress/bar.rb +24 -65
- data/lib/philiprehberger/progress/multi.rb +1 -68
- data/lib/philiprehberger/progress/spinner.rb +20 -54
- data/lib/philiprehberger/progress/version.rb +1 -1
- data/lib/philiprehberger/progress.rb +16 -56
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d278dc09081dc277006eddb173fa17baf7016199c6bd87807ac00092c77ecece
|
|
4
|
+
data.tar.gz: 69933ba0ec72a9aeed28659e7547951d126f463b6b764200ba8e08e50af03b31
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f109ddf121b047b20fbd26388dc7bdd10f5b32f4845fe6a0474d7607171f248ae24b77c05270cd642793c5ef96f4407cdbb2b76637000aababc4432bab618b1
|
|
7
|
+
data.tar.gz: 956e6524daf79f6eacefb7237b4eb84e91b4cb4ed99dc2785b51bf65b1a168f24171704a677146aebf26d92eb4f6d118a55e8b2fe263171d67239bf25bf497f8
|
data/CHANGELOG.md
CHANGED
|
@@ -2,18 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this gem will be documented in this file.
|
|
4
4
|
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.1.
|
|
10
|
+
## [0.1.2] - 2026-03-22
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fix CHANGELOG header wording
|
|
15
|
+
- Add bug_tracker_uri to gemspec
|
|
16
|
+
|
|
17
|
+
## [0.1.0] - 2026-03-22
|
|
11
18
|
|
|
12
19
|
### Added
|
|
13
|
-
|
|
14
|
-
- `Bar` class with percentage, ETA, throughput, and
|
|
15
|
-
- `Spinner` class with
|
|
16
|
-
- `
|
|
17
|
-
-
|
|
18
|
-
- `
|
|
20
|
+
|
|
21
|
+
- `Bar` class with percentage, ETA, throughput, and progress visualization
|
|
22
|
+
- `Spinner` class with braille frame animation
|
|
23
|
+
- `Progress.bar` convenience method with block support and auto-finish
|
|
24
|
+
- `Progress.spin` convenience method with block support
|
|
25
|
+
- `Progress.each` for iterating enumerables with progress display
|
|
19
26
|
- TTY detection to auto-disable rendering in non-terminal environments
|
data/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# philiprehberger-progress
|
|
2
2
|
|
|
3
|
-
[](LICENSE)
|
|
3
|
+
[](https://badge.fury.io/rb/philiprehberger-progress)
|
|
4
|
+
[](https://github.com/philiprehberger/rb-progress/actions/workflows/ci.yml)
|
|
6
5
|
|
|
7
|
-
Terminal progress bars and spinners with ETA calculation and throughput display
|
|
6
|
+
Terminal progress bars and spinners with ETA calculation and throughput display.
|
|
8
7
|
|
|
9
8
|
## Requirements
|
|
10
9
|
|
|
@@ -12,16 +11,14 @@ Terminal progress bars and spinners with ETA calculation and throughput display
|
|
|
12
11
|
|
|
13
12
|
## Installation
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
gem 'philiprehberger-progress'
|
|
14
|
+
```sh
|
|
15
|
+
gem install philiprehberger-progress
|
|
19
16
|
```
|
|
20
17
|
|
|
21
|
-
Or
|
|
18
|
+
Or add to your Gemfile:
|
|
22
19
|
|
|
23
|
-
```
|
|
24
|
-
gem
|
|
20
|
+
```ruby
|
|
21
|
+
gem 'philiprehberger-progress'
|
|
25
22
|
```
|
|
26
23
|
|
|
27
24
|
## Usage
|
|
@@ -31,110 +28,94 @@ gem install philiprehberger-progress
|
|
|
31
28
|
```ruby
|
|
32
29
|
require 'philiprehberger/progress'
|
|
33
30
|
|
|
34
|
-
Philiprehberger::Progress.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
31
|
+
bar = Philiprehberger::Progress::Bar.new(total: 100)
|
|
32
|
+
100.times do
|
|
33
|
+
sleep(0.01)
|
|
34
|
+
bar.advance
|
|
39
35
|
end
|
|
36
|
+
bar.finish
|
|
37
|
+
# [████████████████████████████████] 100.0% | 100/100 | ETA: 0s | 100.0/s
|
|
40
38
|
```
|
|
41
39
|
|
|
42
|
-
###
|
|
40
|
+
### Block Usage
|
|
43
41
|
|
|
44
42
|
```ruby
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
bar.advance(50)
|
|
51
|
-
puts bar.to_s
|
|
43
|
+
Philiprehberger::Progress.bar(total: 100) do |bar|
|
|
44
|
+
100.times { bar.advance }
|
|
45
|
+
end
|
|
46
|
+
# Auto-finishes when block completes
|
|
52
47
|
```
|
|
53
48
|
|
|
54
49
|
### Spinner
|
|
55
50
|
|
|
56
51
|
```ruby
|
|
57
|
-
Philiprehberger::Progress.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
end
|
|
52
|
+
spinner = Philiprehberger::Progress::Spinner.new(message: 'Loading...')
|
|
53
|
+
10.times do
|
|
54
|
+
sleep(0.1)
|
|
55
|
+
spinner.spin
|
|
62
56
|
end
|
|
57
|
+
spinner.stop('done')
|
|
63
58
|
```
|
|
64
59
|
|
|
65
|
-
###
|
|
60
|
+
### Block Spinner
|
|
66
61
|
|
|
67
62
|
```ruby
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sleep(0.01)
|
|
63
|
+
Philiprehberger::Progress.spin('Processing...') do |spinner|
|
|
64
|
+
10.times { spinner.spin; sleep(0.1) }
|
|
71
65
|
end
|
|
72
66
|
```
|
|
73
67
|
|
|
74
|
-
###
|
|
68
|
+
### Enumerable Integration
|
|
75
69
|
|
|
76
70
|
```ruby
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
bar1.advance(10)
|
|
82
|
-
bar2.advance(5)
|
|
83
|
-
multi.render
|
|
71
|
+
items = (1..100).to_a
|
|
72
|
+
Philiprehberger::Progress.each(items) do |item|
|
|
73
|
+
sleep(0.01)
|
|
74
|
+
end
|
|
84
75
|
```
|
|
85
76
|
|
|
86
77
|
## API
|
|
87
78
|
|
|
88
|
-
### `Philiprehberger::Progress`
|
|
89
|
-
|
|
90
|
-
| Method | Description |
|
|
91
|
-
|--------|-------------|
|
|
92
|
-
| `.bar(total:, format:, width:)` | Create a progress bar (yields if block given) |
|
|
93
|
-
| `.spin(message, frames:)` | Create a spinner (yields if block given) |
|
|
94
|
-
| `.multi` | Create a multi-bar display |
|
|
95
|
-
|
|
96
79
|
### `Philiprehberger::Progress::Bar`
|
|
97
80
|
|
|
98
81
|
| Method | Description |
|
|
99
82
|
|--------|-------------|
|
|
100
|
-
| `.new(total:,
|
|
101
|
-
| `#advance(n)` | Advance by `n` items
|
|
83
|
+
| `.new(total:, width: 30, output: $stderr)` | Create a progress bar |
|
|
84
|
+
| `#advance(n = 1)` | Advance by `n` items |
|
|
102
85
|
| `#finish` | Mark as complete |
|
|
103
86
|
| `#finished?` | Whether the bar is finished |
|
|
104
87
|
| `#percentage` | Current percentage (0.0 to 100.0) |
|
|
105
88
|
| `#elapsed` | Elapsed time in seconds |
|
|
106
89
|
| `#eta` | Estimated time remaining in seconds |
|
|
107
|
-
| `#
|
|
90
|
+
| `#throughput` | Items per second |
|
|
108
91
|
| `#to_s` | Render the bar as a string |
|
|
109
92
|
|
|
110
93
|
### `Philiprehberger::Progress::Spinner`
|
|
111
94
|
|
|
112
95
|
| Method | Description |
|
|
113
96
|
|--------|-------------|
|
|
114
|
-
| `.new(message:,
|
|
97
|
+
| `.new(message:, output: $stderr)` | Create a spinner |
|
|
115
98
|
| `#spin` | Advance to the next frame |
|
|
116
|
-
| `#done
|
|
117
|
-
| `#
|
|
118
|
-
| `#to_s` | Render the current frame |
|
|
99
|
+
| `#stop(final_message = 'done')` | Stop with a message |
|
|
100
|
+
| `#stopped?` | Whether the spinner is stopped |
|
|
101
|
+
| `#to_s` | Render the current frame with message |
|
|
119
102
|
|
|
120
|
-
###
|
|
103
|
+
### Module Methods
|
|
121
104
|
|
|
122
105
|
| Method | Description |
|
|
123
106
|
|--------|-------------|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
| `#size` | Number of bars |
|
|
128
|
-
| `#finished?` | Whether all bars are finished |
|
|
107
|
+
| `Progress.bar(total:, &block)` | Create bar, auto-finish after block |
|
|
108
|
+
| `Progress.spin(message, &block)` | Create spinner, auto-stop after block |
|
|
109
|
+
| `Progress.each(enumerable, label: nil) { \|item\| }` | Iterate with progress |
|
|
129
110
|
|
|
130
111
|
## Development
|
|
131
112
|
|
|
132
|
-
```
|
|
113
|
+
```sh
|
|
133
114
|
bundle install
|
|
134
|
-
bundle exec rspec
|
|
135
|
-
bundle exec rubocop
|
|
115
|
+
bundle exec rspec
|
|
116
|
+
bundle exec rubocop
|
|
136
117
|
```
|
|
137
118
|
|
|
138
119
|
## License
|
|
139
120
|
|
|
140
|
-
MIT
|
|
121
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -2,29 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Philiprehberger
|
|
4
4
|
module Progress
|
|
5
|
-
# A terminal progress bar with ETA and throughput display
|
|
6
|
-
#
|
|
7
|
-
# @example
|
|
8
|
-
# bar = Bar.new(total: 100)
|
|
9
|
-
# 100.times { bar.advance }
|
|
10
|
-
# bar.finish
|
|
11
5
|
class Bar
|
|
12
|
-
DEFAULT_FORMAT = ':bar :percent | :current/:total | :rate items/s | ETA: :eta'
|
|
13
|
-
DEFAULT_WIDTH = 30
|
|
14
6
|
FILL_CHAR = "\u2588"
|
|
15
7
|
EMPTY_CHAR = "\u2591"
|
|
16
8
|
|
|
17
9
|
attr_reader :current, :total
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
# @param total [Integer] total number of items
|
|
22
|
-
# @param format [String] format string with placeholders
|
|
23
|
-
# @param width [Integer] width of the bar portion in characters
|
|
24
|
-
# @param output [IO] output stream (default: $stderr)
|
|
25
|
-
def initialize(total:, format: DEFAULT_FORMAT, width: DEFAULT_WIDTH, output: $stderr)
|
|
11
|
+
def initialize(total:, width: 30, output: $stderr)
|
|
26
12
|
@total = [total, 0].max
|
|
27
|
-
@format = format
|
|
28
13
|
@width = width
|
|
29
14
|
@output = output
|
|
30
15
|
@current = 0
|
|
@@ -32,87 +17,59 @@ module Philiprehberger
|
|
|
32
17
|
@finished = false
|
|
33
18
|
end
|
|
34
19
|
|
|
35
|
-
# Advance the progress bar
|
|
36
|
-
#
|
|
37
|
-
# @param n [Integer] number of items to advance (default: 1)
|
|
38
|
-
# @return [self]
|
|
39
20
|
def advance(n = 1)
|
|
40
21
|
return self if @finished
|
|
41
22
|
|
|
42
23
|
@current = [@current + n, @total].min
|
|
43
|
-
|
|
24
|
+
render_to_output
|
|
44
25
|
self
|
|
45
26
|
end
|
|
46
27
|
|
|
47
|
-
# Mark the progress as complete
|
|
48
|
-
#
|
|
49
|
-
# @return [self]
|
|
50
28
|
def finish
|
|
51
29
|
@current = @total
|
|
52
30
|
@finished = true
|
|
53
|
-
|
|
31
|
+
render_to_output
|
|
54
32
|
@output.write("\n") if tty?
|
|
55
33
|
self
|
|
56
34
|
end
|
|
57
35
|
|
|
58
|
-
# Check if the progress is finished
|
|
59
|
-
#
|
|
60
|
-
# @return [Boolean]
|
|
61
36
|
def finished?
|
|
62
37
|
@finished
|
|
63
38
|
end
|
|
64
39
|
|
|
65
|
-
# Get the progress percentage
|
|
66
|
-
#
|
|
67
|
-
# @return [Float] percentage from 0.0 to 100.0
|
|
68
40
|
def percentage
|
|
69
41
|
return 100.0 if @total.zero?
|
|
70
42
|
|
|
71
43
|
(@current.to_f / @total * 100).round(1)
|
|
72
44
|
end
|
|
73
45
|
|
|
74
|
-
# Get the elapsed time in seconds
|
|
75
|
-
#
|
|
76
|
-
# @return [Float]
|
|
77
46
|
def elapsed
|
|
78
47
|
now - @start_time
|
|
79
48
|
end
|
|
80
49
|
|
|
81
|
-
# Get the estimated time remaining in seconds
|
|
82
|
-
#
|
|
83
|
-
# @return [Float, nil] nil if no progress has been made
|
|
84
50
|
def eta
|
|
85
51
|
return 0.0 if @current >= @total
|
|
86
52
|
return nil if @current.zero?
|
|
87
53
|
|
|
88
54
|
elapsed_time = elapsed
|
|
89
|
-
|
|
90
|
-
(@total - @current) /
|
|
55
|
+
items_per_sec = @current.to_f / elapsed_time
|
|
56
|
+
(@total - @current) / items_per_sec
|
|
91
57
|
end
|
|
92
58
|
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
# @return [Float]
|
|
96
|
-
def rate
|
|
59
|
+
def throughput
|
|
97
60
|
elapsed_time = elapsed
|
|
98
61
|
return 0.0 if elapsed_time.zero?
|
|
99
62
|
|
|
100
63
|
@current.to_f / elapsed_time
|
|
101
64
|
end
|
|
102
65
|
|
|
103
|
-
# Render the progress bar as a string
|
|
104
|
-
#
|
|
105
|
-
# @return [String]
|
|
106
66
|
def to_s
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
result.gsub!(':eta', format_duration(eta))
|
|
114
|
-
result.gsub!(':elapsed', format_duration(elapsed))
|
|
115
|
-
result
|
|
67
|
+
bar_str = render_bar
|
|
68
|
+
pct = format('%<p>5.1f%%', p: percentage)
|
|
69
|
+
eta_str = format_eta(eta)
|
|
70
|
+
tput = format('%<t>.1f/s', t: throughput)
|
|
71
|
+
|
|
72
|
+
"[#{bar_str}] #{pct} | #{@current}/#{@total} | ETA: #{eta_str} | #{tput}"
|
|
116
73
|
end
|
|
117
74
|
|
|
118
75
|
private
|
|
@@ -125,8 +82,8 @@ module Philiprehberger
|
|
|
125
82
|
@output.respond_to?(:tty?) && @output.tty?
|
|
126
83
|
end
|
|
127
84
|
|
|
128
|
-
def
|
|
129
|
-
@output.write("\r#{self}")
|
|
85
|
+
def render_to_output
|
|
86
|
+
@output.write("\r#{self}") if tty?
|
|
130
87
|
end
|
|
131
88
|
|
|
132
89
|
def render_bar
|
|
@@ -137,16 +94,18 @@ module Philiprehberger
|
|
|
137
94
|
(FILL_CHAR * filled) + (EMPTY_CHAR * empty)
|
|
138
95
|
end
|
|
139
96
|
|
|
140
|
-
def
|
|
141
|
-
return '--:--' if seconds.nil?
|
|
97
|
+
def format_eta(seconds)
|
|
98
|
+
return '--:--' if seconds.nil?
|
|
99
|
+
|
|
100
|
+
secs = seconds.to_i
|
|
101
|
+
return '0s' if secs <= 0
|
|
142
102
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
format('%d:%02d', seconds / 60, seconds % 60)
|
|
103
|
+
if secs < 60
|
|
104
|
+
"#{secs}s"
|
|
105
|
+
elsif secs < 3600
|
|
106
|
+
format('%dm%02ds', secs / 60, secs % 60)
|
|
148
107
|
else
|
|
149
|
-
format('%
|
|
108
|
+
format('%dh%02dm%02ds', secs / 3600, (secs % 3600) / 60, secs % 60)
|
|
150
109
|
end
|
|
151
110
|
end
|
|
152
111
|
end
|
|
@@ -1,70 +1,3 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Progress
|
|
5
|
-
# Multi-bar progress display for tracking multiple concurrent tasks
|
|
6
|
-
#
|
|
7
|
-
# @example
|
|
8
|
-
# multi = Multi.new
|
|
9
|
-
# bar1 = multi.bar('Downloads', total: 100)
|
|
10
|
-
# bar2 = multi.bar('Uploads', total: 50)
|
|
11
|
-
class Multi
|
|
12
|
-
# Create a new multi-bar display
|
|
13
|
-
#
|
|
14
|
-
# @param output [IO] output stream (default: $stderr)
|
|
15
|
-
def initialize(output: $stderr)
|
|
16
|
-
@output = output
|
|
17
|
-
@bars = []
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Add a new progress bar
|
|
21
|
-
#
|
|
22
|
-
# @param label [String] label for the bar
|
|
23
|
-
# @param total [Integer] total items
|
|
24
|
-
# @param format [String] format string
|
|
25
|
-
# @param width [Integer] bar width
|
|
26
|
-
# @return [Bar]
|
|
27
|
-
def bar(label, total:, format: nil, width: 20)
|
|
28
|
-
bar_format = format || ":bar :percent | #{label}"
|
|
29
|
-
progress_bar = Bar.new(total: total, format: bar_format, width: width, output: StringIO.new)
|
|
30
|
-
@bars << { label: label, bar: progress_bar }
|
|
31
|
-
progress_bar
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Render all bars
|
|
35
|
-
#
|
|
36
|
-
# @return [self]
|
|
37
|
-
def render
|
|
38
|
-
return self unless tty?
|
|
39
|
-
|
|
40
|
-
# Move cursor up to overwrite previous render
|
|
41
|
-
@output.write("\e[#{@bars.length}A") if @rendered_once
|
|
42
|
-
@bars.each do |entry|
|
|
43
|
-
@output.write("\r\e[2K#{entry[:bar]}\n")
|
|
44
|
-
end
|
|
45
|
-
@rendered_once = true
|
|
46
|
-
self
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Number of bars being tracked
|
|
50
|
-
#
|
|
51
|
-
# @return [Integer]
|
|
52
|
-
def size
|
|
53
|
-
@bars.length
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Check if all bars are finished
|
|
57
|
-
#
|
|
58
|
-
# @return [Boolean]
|
|
59
|
-
def finished?
|
|
60
|
-
@bars.all? { |entry| entry[:bar].finished? }
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
def tty?
|
|
66
|
-
@output.respond_to?(:tty?) && @output.tty?
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
3
|
+
# This file is kept for backward compatibility but is no longer actively used.
|
|
@@ -2,73 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
module Philiprehberger
|
|
4
4
|
module Progress
|
|
5
|
-
# A terminal spinner for indeterminate progress
|
|
6
|
-
#
|
|
7
|
-
# @example
|
|
8
|
-
# spinner = Spinner.new(message: 'Loading...')
|
|
9
|
-
# spinner.spin
|
|
10
|
-
# spinner.done
|
|
11
5
|
class Spinner
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
DOTS_FRAMES = %W[\u2800 \u2801 \u2803 \u2807 \u280F \u281F \u283F \u287F \u28FF].freeze
|
|
6
|
+
FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807",
|
|
7
|
+
"\u280F"].freeze
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
default: DEFAULT_FRAMES,
|
|
18
|
-
braille: BRAILLE_FRAMES,
|
|
19
|
-
dots: DOTS_FRAMES
|
|
20
|
-
}.freeze
|
|
9
|
+
attr_reader :message
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
# @param message [String] message to display next to the spinner
|
|
25
|
-
# @param frames [Symbol, Array<String>] frame set name or custom frames
|
|
26
|
-
# @param output [IO] output stream (default: $stderr)
|
|
27
|
-
def initialize(message: '', frames: :default, output: $stderr)
|
|
11
|
+
def initialize(message:, output: $stderr)
|
|
28
12
|
@message = message
|
|
29
|
-
@frames = frames.is_a?(Symbol) ? FRAME_SETS.fetch(frames, DEFAULT_FRAMES) : frames
|
|
30
13
|
@output = output
|
|
31
14
|
@index = 0
|
|
32
|
-
@
|
|
15
|
+
@stopped = false
|
|
33
16
|
end
|
|
34
17
|
|
|
35
|
-
# Advance the spinner by one frame
|
|
36
|
-
#
|
|
37
|
-
# @return [self]
|
|
38
18
|
def spin
|
|
39
|
-
return self if @
|
|
19
|
+
return self if @stopped
|
|
40
20
|
|
|
41
|
-
|
|
42
|
-
@index = (@index + 1) %
|
|
21
|
+
render_to_output
|
|
22
|
+
@index = (@index + 1) % FRAMES.length
|
|
43
23
|
self
|
|
44
24
|
end
|
|
45
25
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# @return [self]
|
|
50
|
-
def done(message = nil)
|
|
51
|
-
@done = true
|
|
52
|
-
if tty?
|
|
53
|
-
clear_line
|
|
54
|
-
@output.write("#{message || @message}\n") if message || !@message.empty?
|
|
55
|
-
end
|
|
26
|
+
def stop(final_message = 'done')
|
|
27
|
+
@stopped = true
|
|
28
|
+
@output.write("\r\e[2K#{final_message}\n") if tty?
|
|
56
29
|
self
|
|
57
30
|
end
|
|
58
31
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
32
|
+
def stopped?
|
|
33
|
+
@stopped
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def current_frame
|
|
37
|
+
FRAMES[@index % FRAMES.length]
|
|
64
38
|
end
|
|
65
39
|
|
|
66
|
-
# Return the current frame
|
|
67
|
-
#
|
|
68
|
-
# @return [String]
|
|
69
40
|
def to_s
|
|
70
|
-
|
|
71
|
-
@message.empty? ? frame : "#{frame} #{@message}"
|
|
41
|
+
"#{current_frame} #{@message}"
|
|
72
42
|
end
|
|
73
43
|
|
|
74
44
|
private
|
|
@@ -77,12 +47,8 @@ module Philiprehberger
|
|
|
77
47
|
@output.respond_to?(:tty?) && @output.tty?
|
|
78
48
|
end
|
|
79
49
|
|
|
80
|
-
def
|
|
81
|
-
@output.write("\r#{self}")
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def clear_line
|
|
85
|
-
@output.write("\r\e[2K")
|
|
50
|
+
def render_to_output
|
|
51
|
+
@output.write("\r#{self}") if tty?
|
|
86
52
|
end
|
|
87
53
|
end
|
|
88
54
|
end
|
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'stringio'
|
|
4
3
|
require_relative 'progress/version'
|
|
5
4
|
require_relative 'progress/bar'
|
|
6
5
|
require_relative 'progress/spinner'
|
|
7
|
-
require_relative 'progress/multi'
|
|
8
6
|
|
|
9
7
|
module Philiprehberger
|
|
10
8
|
module Progress
|
|
11
|
-
|
|
9
|
+
def self.bar(total:, width: 30, output: $stderr)
|
|
10
|
+
progress_bar = Bar.new(total: total, width: width, output: output)
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
# @param total [Integer] total number of items
|
|
16
|
-
# @param format [String] format string
|
|
17
|
-
# @param width [Integer] bar width
|
|
18
|
-
# @param output [IO] output stream
|
|
19
|
-
# @yield [Bar] the progress bar
|
|
20
|
-
# @return [Object] the block's return value
|
|
21
|
-
def self.bar(total:, format: Bar::DEFAULT_FORMAT, width: Bar::DEFAULT_WIDTH, output: $stderr, &block)
|
|
22
|
-
progress_bar = Bar.new(total: total, format: format, width: width, output: output)
|
|
23
|
-
|
|
24
|
-
if block
|
|
12
|
+
if block_given?
|
|
25
13
|
result = yield progress_bar
|
|
26
14
|
progress_bar.finish unless progress_bar.finished?
|
|
27
15
|
result
|
|
@@ -30,57 +18,29 @@ module Philiprehberger
|
|
|
30
18
|
end
|
|
31
19
|
end
|
|
32
20
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# @param message [String] message to display
|
|
36
|
-
# @param frames [Symbol, Array<String>] frame set
|
|
37
|
-
# @param output [IO] output stream
|
|
38
|
-
# @yield the work to perform
|
|
39
|
-
# @return [Object] the block's return value
|
|
40
|
-
def self.spin(message = 'Loading...', frames: :default, output: $stderr, &block)
|
|
41
|
-
spinner = Spinner.new(message: message, frames: frames, output: output)
|
|
21
|
+
def self.spin(message, output: $stderr)
|
|
22
|
+
spinner = Spinner.new(message: message, output: output)
|
|
42
23
|
|
|
43
|
-
if
|
|
24
|
+
if block_given?
|
|
44
25
|
result = yield spinner
|
|
45
|
-
spinner.
|
|
26
|
+
spinner.stop unless spinner.stopped?
|
|
46
27
|
result
|
|
47
28
|
else
|
|
48
29
|
spinner
|
|
49
30
|
end
|
|
50
31
|
end
|
|
51
32
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# @return [Multi]
|
|
56
|
-
def self.multi(output: $stderr)
|
|
57
|
-
Multi.new(output: output)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
33
|
+
def self.each(enumerable, label: nil, output: $stderr)
|
|
34
|
+
items = enumerable.to_a
|
|
35
|
+
bar = Bar.new(total: items.length, output: output)
|
|
61
36
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# @param message [String] label for the progress bar
|
|
67
|
-
# @param output [IO] output stream
|
|
68
|
-
# @yield [Object] each element
|
|
69
|
-
# @return [Array]
|
|
70
|
-
def each_with_progress(message = 'Processing', output: $stderr)
|
|
71
|
-
items = to_a
|
|
72
|
-
bar = Philiprehberger::Progress::Bar.new(
|
|
73
|
-
total: items.length,
|
|
74
|
-
format: ":bar :percent | #{message} | :current/:total",
|
|
75
|
-
output: output
|
|
76
|
-
)
|
|
37
|
+
items.each do |item|
|
|
38
|
+
yield item
|
|
39
|
+
bar.advance
|
|
40
|
+
end
|
|
77
41
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
bar.advance
|
|
42
|
+
bar.finish
|
|
43
|
+
items
|
|
81
44
|
end
|
|
82
|
-
|
|
83
|
-
bar.finish
|
|
84
|
-
items
|
|
85
45
|
end
|
|
86
46
|
end
|
metadata
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-progress
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- philiprehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2026-03-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Display progress bars with percentage, ETA, and throughput, or spinners
|
|
14
|
-
for indeterminate tasks. Supports
|
|
15
|
-
|
|
14
|
+
for indeterminate tasks. Supports block-based usage, enumerable iteration, and auto-disables
|
|
15
|
+
rendering when not connected to a terminal.
|
|
16
16
|
email:
|
|
17
|
-
-
|
|
17
|
+
- philiprehberger@users.noreply.github.com
|
|
18
18
|
executables: []
|
|
19
19
|
extensions: []
|
|
20
20
|
extra_rdoc_files: []
|
|
@@ -44,7 +44,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: 3.1
|
|
47
|
+
version: '3.1'
|
|
48
48
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
49
|
requirements:
|
|
50
50
|
- - ">="
|