ruby-progress 1.3.5 → 1.3.7
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 +26 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/blog-post.md +201 -109
- data/lib/ruby-progress/cli/fill_options.rb +3 -0
- data/lib/ruby-progress/cli/ripple_options.rb +3 -0
- data/lib/ruby-progress/cli/twirl_cli.rb +20 -0
- data/lib/ruby-progress/cli/twirl_options.rb +3 -0
- data/lib/ruby-progress/cli/twirl_runner.rb +29 -0
- data/lib/ruby-progress/cli/worm_cli.rb +33 -0
- data/lib/ruby-progress/cli/worm_options.rb +3 -0
- data/lib/ruby-progress/daemon.rb +42 -0
- data/lib/ruby-progress/fill.rb +51 -9
- data/lib/ruby-progress/fill_cli.rb +4 -0
- data/lib/ruby-progress/output_capture.rb +102 -27
- data/lib/ruby-progress/ripple.rb +52 -0
- data/lib/ruby-progress/utils.rb +92 -11
- data/lib/ruby-progress/version.rb +12 -5
- data/lib/ruby-progress/worm.rb +23 -1
- data/project-page.md +162 -0
- metadata +2 -1
data/lib/ruby-progress/daemon.rb
CHANGED
|
@@ -9,14 +9,39 @@ module RubyProgress
|
|
|
9
9
|
module Daemon
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
|
+
# Return the default PID file path used by ruby-progress daemons.
|
|
13
|
+
#
|
|
14
|
+
# @return [String] The filesystem path to the default PID file.
|
|
15
|
+
# @example
|
|
16
|
+
# RubyProgress::Daemon.default_pid_file
|
|
17
|
+
# #=> "/tmp/ruby-progress/progress.pid"
|
|
12
18
|
def default_pid_file
|
|
13
19
|
'/tmp/ruby-progress/progress.pid'
|
|
14
20
|
end
|
|
15
21
|
|
|
22
|
+
# Return the path for the control message file corresponding to a PID file.
|
|
23
|
+
# The control message file is used to pass JSON-encoded messages to a running
|
|
24
|
+
# daemon (for example, success/failure metadata or a final message).
|
|
25
|
+
#
|
|
26
|
+
# @param pid_file [String] the path to the pid file
|
|
27
|
+
# @return [String] the control-message file path (pid_file + ".msg")
|
|
28
|
+
# @example
|
|
29
|
+
# RubyProgress::Daemon.control_message_file('/tmp/foo.pid')
|
|
30
|
+
# #=> '/tmp/foo.pid.msg'
|
|
16
31
|
def control_message_file(pid_file)
|
|
17
32
|
"#{pid_file}.msg"
|
|
18
33
|
end
|
|
19
34
|
|
|
35
|
+
# Print the daemon status for the given PID file and exit with an appropriate
|
|
36
|
+
# exit code. If the PID file exists and the process is running this prints
|
|
37
|
+
# a message and exits 0. If the PID file exists but the process is not
|
|
38
|
+
# running this prints a warning and exits 1. If the PID file does not
|
|
39
|
+
# exist this prints that the daemon is not running and exits 1.
|
|
40
|
+
#
|
|
41
|
+
# @param pid_file [String] path to the pid file
|
|
42
|
+
# @return [void]
|
|
43
|
+
# @example
|
|
44
|
+
# RubyProgress::Daemon.show_status('/tmp/ruby-progress/progress.pid')
|
|
20
45
|
def show_status(pid_file)
|
|
21
46
|
if File.exist?(pid_file)
|
|
22
47
|
pid = File.read(pid_file).strip
|
|
@@ -29,6 +54,23 @@ module RubyProgress
|
|
|
29
54
|
end
|
|
30
55
|
end
|
|
31
56
|
|
|
57
|
+
# Stop a running daemon by reading its PID from the given pid file. Optionally
|
|
58
|
+
# write a small JSON control message (containing :checkmark and :message keys)
|
|
59
|
+
# to the corresponding control message file before signalling the process.
|
|
60
|
+
# The method attempts to send SIGUSR1 to the running process, sleeps briefly
|
|
61
|
+
# to allow the process to handle the signal, and then removes the pid file.
|
|
62
|
+
#
|
|
63
|
+
# This helper prints user-friendly errors and exits with non-zero status when
|
|
64
|
+
# the pid file is missing, the process cannot be found, or permission is
|
|
65
|
+
# denied when sending the signal.
|
|
66
|
+
#
|
|
67
|
+
# @param pid_file [String] path to the pid file
|
|
68
|
+
# @param message [String, nil] optional message to send to the daemon via the control file
|
|
69
|
+
# @param checkmark [Boolean] whether to include a checkmark flag in the control payload
|
|
70
|
+
# @param error [Boolean] whether this stop represents an error (affects success flag in payload)
|
|
71
|
+
# @return [void]
|
|
72
|
+
# @example
|
|
73
|
+
# RubyProgress::Daemon.stop_daemon_by_pid_file('/tmp/ruby-progress/progress.pid', message: 'Stopping', checkmark: true)
|
|
32
74
|
def stop_daemon_by_pid_file(pid_file, message: nil, checkmark: false, error: false)
|
|
33
75
|
unless File.exist?(pid_file)
|
|
34
76
|
puts "PID file #{pid_file} not found"
|
data/lib/ruby-progress/fill.rb
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
module RubyProgress
|
|
4
4
|
# Determinate progress bar with customizable fill styles
|
|
5
5
|
class Fill
|
|
6
|
+
# A simple determinate progress bar that can be advanced programmatically
|
|
7
|
+
# or used with the block-based convenience method {Fill.progress}.
|
|
8
|
+
#
|
|
9
|
+
# Public API (instance): #advance, #percent=, #completed?, #render, #complete, #cancel
|
|
10
|
+
# Public API (class): .progress, .parse_custom_style
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# Fill.progress(length: 10) do |bar|
|
|
14
|
+
# 10.times { bar.advance; sleep 0.1 }
|
|
15
|
+
# end
|
|
6
16
|
# Built-in fill styles with empty and full characters
|
|
7
17
|
FILL_STYLES = {
|
|
8
18
|
blocks: { empty: '▱', full: '▰' },
|
|
@@ -19,6 +29,14 @@ module RubyProgress
|
|
|
19
29
|
attr_reader :length, :style, :current_progress, :start_chars, :end_chars
|
|
20
30
|
attr_accessor :success_message, :error_message
|
|
21
31
|
|
|
32
|
+
# Initialize a progress bar instance.
|
|
33
|
+
#
|
|
34
|
+
# @param options [Hash] configuration values
|
|
35
|
+
# @option options [Integer] :length number of cells in the bar (default 20)
|
|
36
|
+
# @option options [Symbol,String,Hash] :style style name, symbol, or custom hash
|
|
37
|
+
# @option options [String] :success success message shown on completion
|
|
38
|
+
# @option options [String] :error error message shown on cancel/exception
|
|
39
|
+
# @return [void]
|
|
22
40
|
def initialize(options = {})
|
|
23
41
|
@length = options[:length] || 20
|
|
24
42
|
@style = parse_style(options[:style] || :blocks)
|
|
@@ -36,7 +54,11 @@ module RubyProgress
|
|
|
36
54
|
end
|
|
37
55
|
end
|
|
38
56
|
|
|
39
|
-
# Advance the progress bar by one step or specified increment
|
|
57
|
+
# Advance the progress bar by one step or specified increment.
|
|
58
|
+
#
|
|
59
|
+
# @param increment [Integer] amount to increase progress by (default 1)
|
|
60
|
+
# @param percent [Numeric,nil] optional percent to set the bar to (0-100)
|
|
61
|
+
# @return [Boolean] true if the bar is complete after advancing
|
|
40
62
|
def advance(increment: 1, percent: nil)
|
|
41
63
|
@current_progress = if percent
|
|
42
64
|
[@length * percent / 100.0, @length].min.round
|
|
@@ -48,7 +70,10 @@ module RubyProgress
|
|
|
48
70
|
completed?
|
|
49
71
|
end
|
|
50
72
|
|
|
51
|
-
# Set progress to specific percentage (0-100)
|
|
73
|
+
# Set progress to specific percentage (0-100).
|
|
74
|
+
#
|
|
75
|
+
# @param percent [Numeric] percentage 0..100 to set the bar to
|
|
76
|
+
# @return [Boolean] true if the bar is complete after setting percent
|
|
52
77
|
def percent=(percent)
|
|
53
78
|
percent = percent.clamp(0, 100) # Clamp between 0-100
|
|
54
79
|
@current_progress = (@length * percent / 100.0).round
|
|
@@ -56,22 +81,30 @@ module RubyProgress
|
|
|
56
81
|
completed?
|
|
57
82
|
end
|
|
58
83
|
|
|
59
|
-
# Check if progress bar is complete
|
|
84
|
+
# Check if progress bar is complete.
|
|
85
|
+
#
|
|
86
|
+
# @return [Boolean]
|
|
60
87
|
def completed?
|
|
61
88
|
@current_progress >= @length
|
|
62
89
|
end
|
|
63
90
|
|
|
64
|
-
# Get current progress as percentage
|
|
91
|
+
# Get current progress as percentage.
|
|
92
|
+
#
|
|
93
|
+
# @return [Float] percentage (0.0-100.0) rounded to 1 decimal place
|
|
65
94
|
def percent
|
|
66
95
|
(@current_progress.to_f / @length * 100).round(1)
|
|
67
96
|
end
|
|
68
97
|
|
|
69
|
-
# Get current progress as float (0.0-100.0) - for scripting
|
|
98
|
+
# Get current progress as float (0.0-100.0) - for scripting.
|
|
99
|
+
#
|
|
100
|
+
# @return [Float]
|
|
70
101
|
def current
|
|
71
102
|
(@current_progress.to_f / @length * 100).round(1)
|
|
72
103
|
end
|
|
73
104
|
|
|
74
|
-
# Get detailed progress status information
|
|
105
|
+
# Get detailed progress status information.
|
|
106
|
+
#
|
|
107
|
+
# @return [Hash] structured status information about the bar
|
|
75
108
|
def report
|
|
76
109
|
{
|
|
77
110
|
progress: [@current_progress, @length],
|
|
@@ -81,7 +114,9 @@ module RubyProgress
|
|
|
81
114
|
}
|
|
82
115
|
end
|
|
83
116
|
|
|
84
|
-
# Render the current progress bar to stderr
|
|
117
|
+
# Render the current progress bar to stderr.
|
|
118
|
+
#
|
|
119
|
+
# @return [void]
|
|
85
120
|
def render
|
|
86
121
|
# First redraw captured output (if any) so it appears above/below the bar
|
|
87
122
|
@output_capture&.redraw($stderr)
|
|
@@ -94,7 +129,11 @@ module RubyProgress
|
|
|
94
129
|
$stderr.flush
|
|
95
130
|
end
|
|
96
131
|
|
|
97
|
-
# Complete the progress bar and show success message
|
|
132
|
+
# Complete the progress bar and show success message.
|
|
133
|
+
#
|
|
134
|
+
# @param message [String,nil] optional override message
|
|
135
|
+
# @param icons [Hash] optional icons map passed to Utils.display_completion
|
|
136
|
+
# @return [void]
|
|
98
137
|
def complete(message = nil, icons: {})
|
|
99
138
|
@current_progress = @length
|
|
100
139
|
render
|
|
@@ -113,7 +152,10 @@ module RubyProgress
|
|
|
113
152
|
end
|
|
114
153
|
end
|
|
115
154
|
|
|
116
|
-
# Cancel the progress bar and show error message
|
|
155
|
+
# Cancel the progress bar and show error message.
|
|
156
|
+
#
|
|
157
|
+
# @param message [String,nil] optional override message
|
|
158
|
+
# @return [void]
|
|
117
159
|
def cancel(message = nil)
|
|
118
160
|
$stderr.print "\r\e[2K" # Clear the progress bar
|
|
119
161
|
$stderr.flush
|
|
@@ -12,6 +12,10 @@ module RubyProgress
|
|
|
12
12
|
# rubocop:disable Metrics/ClassLength
|
|
13
13
|
module FillCLI
|
|
14
14
|
class << self
|
|
15
|
+
# Entrypoint for the `prg fill` CLI. Parses options and dispatches to
|
|
16
|
+
# the matching behavior (auto-advance, command-run, daemon, report).
|
|
17
|
+
#
|
|
18
|
+
# @return [void]
|
|
15
19
|
def run
|
|
16
20
|
trap('INT') do
|
|
17
21
|
Utils.show_cursor
|
|
@@ -13,11 +13,64 @@ rescue LoadError
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
module RubyProgress
|
|
16
|
+
# Shell execution helpers for spawning commands within a PTY
|
|
17
|
+
module ShellExec
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Build argv for invoking the user's shell with a command string.
|
|
21
|
+
# Uses login shells so aliases/functions and environment are available.
|
|
22
|
+
#
|
|
23
|
+
# @param shell [String] path to shell executable
|
|
24
|
+
# @param command [String] command to execute
|
|
25
|
+
# @return [Array<String>] argv (excluding the shell path for convenience)
|
|
26
|
+
def build_shell_argv(shell, command)
|
|
27
|
+
shell_name = File.basename(shell)
|
|
28
|
+
case shell_name
|
|
29
|
+
when 'fish'
|
|
30
|
+
['-l', '-c', command]
|
|
31
|
+
else
|
|
32
|
+
['-lc', command]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Output helpers for reserving terminal space
|
|
38
|
+
module OutputUI
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
def reserve_space(io, position, lines)
|
|
42
|
+
return unless io.tty?
|
|
43
|
+
|
|
44
|
+
if position == :above
|
|
45
|
+
io.print "\e[#{lines}L"
|
|
46
|
+
else
|
|
47
|
+
io.print("\n" * lines)
|
|
48
|
+
io.print "\e[#{lines}A"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
io.flush
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
16
55
|
# PTY-based live output capture that reserves a small terminal area
|
|
17
56
|
# for printing captured output while the animation draws elsewhere.
|
|
18
57
|
class OutputCapture
|
|
19
58
|
attr_reader :exit_status
|
|
20
59
|
|
|
60
|
+
# Create a new OutputCapture instance.
|
|
61
|
+
#
|
|
62
|
+
# @param command [String] the shell command to spawn and capture via PTY
|
|
63
|
+
# @param lines [Integer] number of reserved lines to keep for captured output (minimum 1)
|
|
64
|
+
# @param position [Symbol,String] :above/:below (or :top/:bottom) to place the reserved area
|
|
65
|
+
# @param log_path [String,nil] optional path to append raw captured output
|
|
66
|
+
# @param stream [Boolean] when true, redraw captured output live into the terminal area
|
|
67
|
+
# @param debug [Boolean,nil] enable debug logging when true; nil will consult ENV['RUBY_PROGRESS_DEBUG']
|
|
68
|
+
# @return [OutputCapture]
|
|
69
|
+
# @example
|
|
70
|
+
# oc = RubyProgress::OutputCapture.new(command: 'bundle exec rspec', lines: 4, position: :below)
|
|
71
|
+
# oc.start
|
|
72
|
+
# oc.wait
|
|
73
|
+
# oc.flush_to($stdout)
|
|
21
74
|
def initialize(command:, lines: 3, position: :above, log_path: nil, stream: false, debug: nil)
|
|
22
75
|
@command = command
|
|
23
76
|
# Coerce lines into a positive Integer
|
|
@@ -63,30 +116,49 @@ module RubyProgress
|
|
|
63
116
|
end
|
|
64
117
|
|
|
65
118
|
# Start capturing the child process. Returns self.
|
|
119
|
+
#
|
|
120
|
+
# This spawns the configured command in a PTY and begins a background
|
|
121
|
+
# reader thread which buffers the most recent lines. When +stream+ is true
|
|
122
|
+
# the captured lines are redrawn into the terminal area reserved by
|
|
123
|
+
# {#reserve_space}.
|
|
66
124
|
def start
|
|
67
|
-
reserve_space($stderr) if @stream
|
|
125
|
+
OutputUI.reserve_space($stderr, @position, @lines) if @stream
|
|
68
126
|
@reader_thread = Thread.new { spawn_and_read }
|
|
69
127
|
self
|
|
70
128
|
end
|
|
71
129
|
|
|
130
|
+
# Signal the reader thread to stop and wait for it to finish.
|
|
131
|
+
# @return [void]
|
|
72
132
|
def stop
|
|
73
133
|
@stop = true
|
|
74
134
|
@reader_thread&.join
|
|
75
135
|
end
|
|
76
136
|
|
|
137
|
+
# Wait for the background reader thread to finish and return control to
|
|
138
|
+
# the caller. This is a simple join wrapper used by callers that need to
|
|
139
|
+
# block until the captured command completes.
|
|
140
|
+
#
|
|
141
|
+
# @return [Thread, nil] the joined thread or nil if not started
|
|
77
142
|
def wait
|
|
78
143
|
@reader_thread&.join
|
|
79
144
|
end
|
|
80
145
|
|
|
146
|
+
# Return a snapshot of the currently buffered lines.
|
|
147
|
+
# @return [Array<String>]
|
|
81
148
|
def lines
|
|
82
149
|
@buf_mutex.synchronize { @buffer.dup }
|
|
83
150
|
end
|
|
84
151
|
|
|
152
|
+
# Return true when the background reader thread is alive.
|
|
153
|
+
# @return [Boolean]
|
|
85
154
|
def alive?
|
|
86
155
|
@reader_thread&.alive? || false
|
|
87
156
|
end
|
|
88
157
|
|
|
89
158
|
# Redraw the reserved area using the current buffered lines.
|
|
159
|
+
#
|
|
160
|
+
# @param io [IO] the IO stream to draw into (defaults to $stderr)
|
|
161
|
+
# @return [void]
|
|
90
162
|
def redraw(io = $stderr)
|
|
91
163
|
buf = lines
|
|
92
164
|
debug_log("redraw called; buffer=#{buf.size}; lines=#{@lines}; position=#{@position}")
|
|
@@ -154,6 +226,9 @@ module RubyProgress
|
|
|
154
226
|
# Flush the buffered lines to the given IO (defaults to STDOUT).
|
|
155
227
|
# This is used when capturing non-live output: capture silently during
|
|
156
228
|
# the run and emit all captured output at the end.
|
|
229
|
+
#
|
|
230
|
+
# @param io [IO] the IO to write captured lines to (defaults to STDOUT)
|
|
231
|
+
# @return [void]
|
|
157
232
|
def flush_to(io = $stdout)
|
|
158
233
|
buf = lines
|
|
159
234
|
return if buf.empty?
|
|
@@ -171,16 +246,35 @@ module RubyProgress
|
|
|
171
246
|
private
|
|
172
247
|
|
|
173
248
|
def spawn_and_read
|
|
174
|
-
|
|
249
|
+
# Run the command through the user's login shell so that shell aliases,
|
|
250
|
+
# functions, and PATH modifications are available.
|
|
251
|
+
shell = ENV['SHELL'] || '/bin/sh'
|
|
252
|
+
argv = ShellExec.build_shell_argv(shell, @command)
|
|
253
|
+
|
|
254
|
+
debug_log("spawning via shell=#{shell} argv=#{argv.inspect} raw_cmd=#{@command.inspect}")
|
|
255
|
+
|
|
256
|
+
PTY.spawn(shell, *argv) do |reader, _writer, pid|
|
|
175
257
|
@child_pid = pid
|
|
176
258
|
debug_log("spawned pid=#{pid} cmd=#{@command}")
|
|
177
259
|
|
|
178
260
|
until reader.eof? || @stop
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
261
|
+
# Prefer IO#wait_readable when available (avoids Fiber scheduler issues).
|
|
262
|
+
# If not available, attempt to use an underlying IO via #to_io, otherwise
|
|
263
|
+
# fall back to a short sleep to avoid calling deprecated/select APIs.
|
|
264
|
+
io = if reader.respond_to?(:wait_readable)
|
|
265
|
+
reader
|
|
266
|
+
elsif reader.respond_to?(:to_io)
|
|
267
|
+
reader.to_io
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Prefer IO#wait_readable when available (use safe navigation).
|
|
271
|
+
if io.respond_to?(:wait_readable)
|
|
272
|
+
ready = io.wait_readable(0.1)
|
|
273
|
+
else
|
|
274
|
+
# Best-effort fallback: sleep briefly and continue reading loop.
|
|
275
|
+
sleep 0.1
|
|
276
|
+
ready = true
|
|
277
|
+
end
|
|
184
278
|
next unless ready
|
|
185
279
|
|
|
186
280
|
chunk = reader.read_nonblock(4096, exception: false)
|
|
@@ -249,25 +343,6 @@ module RubyProgress
|
|
|
249
343
|
end
|
|
250
344
|
end
|
|
251
345
|
|
|
252
|
-
|
|
253
|
-
return unless io.tty?
|
|
254
|
-
|
|
255
|
-
debug_log("reserve_space called; position=#{@position.inspect}; lines=#{@lines}")
|
|
256
|
-
|
|
257
|
-
if @position == :above
|
|
258
|
-
# Insert lines above current cursor using CSI n L
|
|
259
|
-
io.print "\e[#{@lines}L"
|
|
260
|
-
debug_log("reserve_space: inserted #{@lines} lines for :above")
|
|
261
|
-
else
|
|
262
|
-
# Print newlines then move cursor back up so animation stays above
|
|
263
|
-
io.print("\n" * @lines)
|
|
264
|
-
io.print "\e[#{@lines}A"
|
|
265
|
-
debug_log("reserve_space: printed #{@lines} newlines and moved up #{@lines} for :below")
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
io.flush
|
|
269
|
-
rescue StandardError => e
|
|
270
|
-
debug_log("reserve_space error: #{e.class}: #{e.message}")
|
|
271
|
-
end
|
|
346
|
+
# reserve_space moved to RubyProgress::OutputUI
|
|
272
347
|
end
|
|
273
348
|
end
|
data/lib/ruby-progress/ripple.rb
CHANGED
|
@@ -56,6 +56,11 @@ module RubyProgress
|
|
|
56
56
|
|
|
57
57
|
# String extensions for color support
|
|
58
58
|
module StringExtensions
|
|
59
|
+
# Extensions that add colorization helpers to String instances.
|
|
60
|
+
# Methods are dynamically defined from the COLORS map (e.g. #red, #green).
|
|
61
|
+
#
|
|
62
|
+
# Example:
|
|
63
|
+
# "hello".red #=> "\e[31mhello\e[0m"
|
|
59
64
|
COLORS.each do |color_name, color_code|
|
|
60
65
|
define_method(color_name) do
|
|
61
66
|
"#{color_code}#{self}#{COLORS['reset']}"
|
|
@@ -63,6 +68,11 @@ module RubyProgress
|
|
|
63
68
|
end
|
|
64
69
|
|
|
65
70
|
def rainbow(index = 0)
|
|
71
|
+
# Apply a per-character rainbow by cycling through the COLORS map.
|
|
72
|
+
#
|
|
73
|
+
# @param index [Integer] an optional offset to shift the colors
|
|
74
|
+
# @return [String] colorized string where each character is wrapped
|
|
75
|
+
# in a terminal color escape sequence
|
|
66
76
|
chars = self.chars
|
|
67
77
|
colored_chars = chars.map.with_index do |char, idx|
|
|
68
78
|
color = COLORS.values[(idx + index) % COLORS.size]
|
|
@@ -72,6 +82,11 @@ module RubyProgress
|
|
|
72
82
|
end
|
|
73
83
|
|
|
74
84
|
def normalize_type
|
|
85
|
+
# Attempt to guess a spinner indicator type from this string's
|
|
86
|
+
# characters. Returns a symbol matching one of INDICATORS keys or
|
|
87
|
+
# :classic as fallback.
|
|
88
|
+
#
|
|
89
|
+
# @return [Symbol]
|
|
75
90
|
spinner_type = :classic
|
|
76
91
|
INDICATORS.each do |spinner, _v|
|
|
77
92
|
spinner_type = spinner if spinner =~ /^#{chars.join('.*?')}/i
|
|
@@ -82,9 +97,21 @@ module RubyProgress
|
|
|
82
97
|
|
|
83
98
|
# Text ripple animation class
|
|
84
99
|
class Ripple
|
|
100
|
+
# Public API:
|
|
101
|
+
# - instance: #advance, #printout
|
|
102
|
+
# - class: .progress (block-based helper), .complete
|
|
85
103
|
attr_accessor :index, :string, :speed, :format, :inverse, :rainbow, :spinner, :spinner_position, :caps
|
|
86
104
|
|
|
87
105
|
def initialize(string, options = {})
|
|
106
|
+
# Create a new Ripple animation for the given string.
|
|
107
|
+
#
|
|
108
|
+
# @param string [String] the text to animate
|
|
109
|
+
# @param options [Hash] configuration options (speed, format, rainbow, etc.)
|
|
110
|
+
# @option options [Symbol] :speed (:medium) animation speed :fast/:medium/:slow
|
|
111
|
+
# @option options [Symbol] :format (:bidirectional) :bidirectional/:forward_only
|
|
112
|
+
# @option options [Boolean] :rainbow (false) colorize characters individually
|
|
113
|
+
# @option options [Boolean] :spinner (false) use a spinner glyph instead of ripple
|
|
114
|
+
# @return [void]
|
|
88
115
|
defaults = {
|
|
89
116
|
speed: :medium,
|
|
90
117
|
format: :bidirectional,
|
|
@@ -109,6 +136,10 @@ module RubyProgress
|
|
|
109
136
|
end
|
|
110
137
|
|
|
111
138
|
def printout
|
|
139
|
+
# Render a single frame of the ripple to stderr, preserving any
|
|
140
|
+
# reserved output area managed by OutputCapture.
|
|
141
|
+
#
|
|
142
|
+
# @return [void]
|
|
112
143
|
letters = @string.dup.chars
|
|
113
144
|
i = @index
|
|
114
145
|
if @spinner
|
|
@@ -154,7 +185,19 @@ module RubyProgress
|
|
|
154
185
|
RubyProgress::Utils.show_cursor
|
|
155
186
|
end
|
|
156
187
|
|
|
188
|
+
# Show the cursor in the terminal. Delegates to Utils.
|
|
189
|
+
#
|
|
190
|
+
# @return [void]
|
|
191
|
+
|
|
157
192
|
def self.complete(string, message, checkmark, success, icons: {})
|
|
193
|
+
# Display a final completion message for the ripple indicator.
|
|
194
|
+
#
|
|
195
|
+
# @param string [String] fallback message
|
|
196
|
+
# @param message [String,nil] explicit message to show
|
|
197
|
+
# @param checkmark [Boolean] whether to show a checkmark
|
|
198
|
+
# @param success [Boolean] whether the result is success
|
|
199
|
+
# @param icons [Hash] optional icons mapping
|
|
200
|
+
# @return [void]
|
|
158
201
|
display_message = message || (checkmark ? string : nil)
|
|
159
202
|
return unless display_message
|
|
160
203
|
|
|
@@ -197,6 +240,15 @@ module RubyProgress
|
|
|
197
240
|
end
|
|
198
241
|
|
|
199
242
|
def self.progress(string, options = {})
|
|
243
|
+
# Block-style helper which runs the ripple animation while the
|
|
244
|
+
# provided block executes. The cursor is hidden for the duration and
|
|
245
|
+
# restored afterwards. This method attempts to return a sensible
|
|
246
|
+
# value depending on the :output option (see code).
|
|
247
|
+
#
|
|
248
|
+
# @param string [String] the label text to animate
|
|
249
|
+
# @param options [Hash] configuration options forwarded to Ripple.new
|
|
250
|
+
# @yield the work to perform while the animation runs
|
|
251
|
+
# @return [Object,nil] returns block result or boolean depending on :output
|
|
200
252
|
Signal.trap('INT') do
|
|
201
253
|
Thread.current.kill
|
|
202
254
|
nil
|
data/lib/ruby-progress/utils.rb
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyProgress
|
|
4
|
-
# Universal terminal utilities shared between progress indicators
|
|
4
|
+
# Universal terminal utilities shared between progress indicators.
|
|
5
|
+
#
|
|
6
|
+
# This module provides common functionality for terminal manipulation,
|
|
7
|
+
# cursor control, and output formatting used across all progress indicator types.
|
|
5
8
|
module Utils
|
|
6
|
-
#
|
|
9
|
+
# Hides the terminal cursor.
|
|
10
|
+
#
|
|
11
|
+
# @return [void]
|
|
12
|
+
# @example
|
|
13
|
+
# RubyProgress::Utils.hide_cursor
|
|
7
14
|
def self.hide_cursor
|
|
8
15
|
$stderr.print "\e[?25l"
|
|
9
16
|
end
|
|
10
17
|
|
|
18
|
+
# Shows the terminal cursor.
|
|
19
|
+
#
|
|
20
|
+
# @return [void]
|
|
21
|
+
# @example
|
|
22
|
+
# RubyProgress::Utils.show_cursor
|
|
11
23
|
def self.show_cursor
|
|
12
24
|
$stderr.print "\e[?25h"
|
|
13
25
|
end
|
|
14
26
|
|
|
27
|
+
# Clears the current terminal line.
|
|
28
|
+
#
|
|
29
|
+
# @param output_stream [Symbol, IO] Stream to clear (:stdout, :stderr, or IO object)
|
|
30
|
+
# @return [void]
|
|
31
|
+
# @example Clear to stderr (default)
|
|
32
|
+
# RubyProgress::Utils.clear_line
|
|
33
|
+
# @example Clear to stdout
|
|
34
|
+
# RubyProgress::Utils.clear_line(:stdout)
|
|
15
35
|
def self.clear_line(output_stream = :stderr)
|
|
16
36
|
io = case output_stream
|
|
17
37
|
when :stdout
|
|
@@ -26,18 +46,40 @@ module RubyProgress
|
|
|
26
46
|
io.print "\r\e[K"
|
|
27
47
|
end
|
|
28
48
|
|
|
29
|
-
# Enhanced line clearing for daemon mode that handles output interruption
|
|
49
|
+
# Enhanced line clearing for daemon mode that handles output interruption.
|
|
50
|
+
#
|
|
51
|
+
# Clears the current line and the line above it, useful when daemon
|
|
52
|
+
# output has been interrupted by other command output.
|
|
53
|
+
#
|
|
54
|
+
# @return [void]
|
|
55
|
+
# @example
|
|
56
|
+
# RubyProgress::Utils.clear_line_aggressive
|
|
30
57
|
def self.clear_line_aggressive
|
|
31
58
|
$stderr.print "\r\e[2K" # Clear entire current line
|
|
32
59
|
$stderr.print "\e[1A\e[2K" # Move up one line and clear it too
|
|
33
60
|
$stderr.print "\r" # Return to start of line
|
|
34
61
|
end
|
|
35
62
|
|
|
36
|
-
#
|
|
63
|
+
# Displays a completion message with optional icons and formatting.
|
|
64
|
+
#
|
|
65
|
+
# Universal completion message display that handles success/error states,
|
|
66
|
+
# custom icons, and output stream selection.
|
|
67
|
+
#
|
|
37
68
|
# @param message [String] The message to display
|
|
38
|
-
# @param success [Boolean] Whether this represents success or failure
|
|
69
|
+
# @param success [Boolean] Whether this represents success (true) or failure (false)
|
|
39
70
|
# @param show_checkmark [Boolean] Whether to show checkmark/X symbols
|
|
40
|
-
# @param output_stream [Symbol] Where to output (:stdout, :stderr, :warn)
|
|
71
|
+
# @param output_stream [Symbol, IO] Where to output (:stdout, :stderr, :warn, or IO object)
|
|
72
|
+
# @param icons [Hash] Custom icons hash with :success and :error keys
|
|
73
|
+
# @option icons [String] :success Custom success icon (overrides default ✅)
|
|
74
|
+
# @option icons [String] :error Custom error icon (overrides default 🛑)
|
|
75
|
+
# @return [void]
|
|
76
|
+
# @example Basic success message
|
|
77
|
+
# RubyProgress::Utils.display_completion("Done!", success: true, show_checkmark: true)
|
|
78
|
+
# @example With custom icons
|
|
79
|
+
# RubyProgress::Utils.display_completion("Build complete",
|
|
80
|
+
# success: true,
|
|
81
|
+
# show_checkmark: true,
|
|
82
|
+
# icons: { success: '🚀', error: '💥' })
|
|
41
83
|
def self.display_completion(message, success: true, show_checkmark: false, output_stream: :warn, icons: {})
|
|
42
84
|
return unless message
|
|
43
85
|
|
|
@@ -87,16 +129,42 @@ module RubyProgress
|
|
|
87
129
|
end
|
|
88
130
|
end
|
|
89
131
|
|
|
90
|
-
#
|
|
91
|
-
#
|
|
132
|
+
# Clears the current line and displays a completion message.
|
|
133
|
+
#
|
|
134
|
+
# Convenience method that combines line clearing with message display.
|
|
135
|
+
# Note: When output_stream is :warn, line clearing is already included
|
|
136
|
+
# in display_completion.
|
|
137
|
+
#
|
|
138
|
+
# @param message [String] The message to display
|
|
139
|
+
# @param success [Boolean] Whether this represents success (true) or failure (false)
|
|
140
|
+
# @param show_checkmark [Boolean] Whether to show checkmark/X symbols
|
|
141
|
+
# @param output_stream [Symbol, IO] Where to output (:stdout, :stderr, :warn, or IO object)
|
|
142
|
+
# @param icons [Hash] Custom icons hash with :success and :error keys
|
|
143
|
+
# @return [void]
|
|
144
|
+
# @see display_completion
|
|
145
|
+
# @example
|
|
146
|
+
# RubyProgress::Utils.complete_with_clear("Task complete", success: true, show_checkmark: true)
|
|
92
147
|
def self.complete_with_clear(message, success: true, show_checkmark: false, output_stream: :warn, icons: {})
|
|
93
148
|
clear_line(output_stream) if output_stream != :warn # warn already includes clear in display_completion
|
|
94
149
|
display_completion(message, success: success, show_checkmark: show_checkmark, output_stream: output_stream, icons: icons)
|
|
95
150
|
end
|
|
96
151
|
|
|
97
|
-
#
|
|
98
|
-
#
|
|
152
|
+
# Parses start/end characters for animation wrapping.
|
|
153
|
+
#
|
|
154
|
+
# Takes an even-length string and splits it in half to create start
|
|
155
|
+
# and end decorative characters for progress indicators. Handles
|
|
156
|
+
# multi-byte characters correctly.
|
|
157
|
+
#
|
|
158
|
+
# @param ends_string [String, nil] Even-length string to split in half
|
|
99
159
|
# @return [Array<String>] Array with [start_chars, end_chars]
|
|
160
|
+
# @example Basic decoration
|
|
161
|
+
# RubyProgress::Utils.parse_ends("[]") # => ["[", "]"]
|
|
162
|
+
# @example Multi-character decoration
|
|
163
|
+
# RubyProgress::Utils.parse_ends("<<>>") # => ["<<", ">>"]
|
|
164
|
+
# @example Emoji decoration
|
|
165
|
+
# RubyProgress::Utils.parse_ends("🎯🎪") # => ["🎯", "🎪"]
|
|
166
|
+
# @example Empty or nil input
|
|
167
|
+
# RubyProgress::Utils.parse_ends(nil) # => ["", ""]
|
|
100
168
|
def self.parse_ends(ends_string)
|
|
101
169
|
return ['', ''] unless ends_string && !ends_string.empty?
|
|
102
170
|
|
|
@@ -110,7 +178,20 @@ module RubyProgress
|
|
|
110
178
|
[start_chars, end_chars]
|
|
111
179
|
end
|
|
112
180
|
|
|
113
|
-
#
|
|
181
|
+
# Validates an ends string for proper format.
|
|
182
|
+
#
|
|
183
|
+
# Checks that the string is non-empty and has an even number of
|
|
184
|
+
# characters (handles multi-byte characters correctly).
|
|
185
|
+
#
|
|
186
|
+
# @param ends_string [String, nil] String to validate
|
|
187
|
+
# @return [Boolean] true if valid, false otherwise
|
|
188
|
+
# @example Valid strings
|
|
189
|
+
# RubyProgress::Utils.ends_valid?("[]") # => true
|
|
190
|
+
# RubyProgress::Utils.ends_valid?("🎯🎪") # => true
|
|
191
|
+
# @example Invalid strings
|
|
192
|
+
# RubyProgress::Utils.ends_valid?("abc") # => false (odd length)
|
|
193
|
+
# RubyProgress::Utils.ends_valid?("") # => false (empty)
|
|
194
|
+
# RubyProgress::Utils.ends_valid?(nil) # => false (nil)
|
|
114
195
|
def self.ends_valid?(ends_string)
|
|
115
196
|
return false unless ends_string && !ends_string.empty?
|
|
116
197
|
|
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyProgress
|
|
4
4
|
# Main gem version
|
|
5
|
-
|
|
5
|
+
# Main gem version
|
|
6
|
+
# @return [String]
|
|
7
|
+
VERSION = '1.3.7'
|
|
6
8
|
|
|
7
9
|
# Component-specific versions (patch bumps)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
# Component-specific versions (patch bumps)
|
|
11
|
+
# @return [String]
|
|
12
|
+
WORM_VERSION = '1.1.6'
|
|
13
|
+
# @return [String]
|
|
14
|
+
TWIRL_VERSION = '1.1.6'
|
|
15
|
+
# @return [String]
|
|
16
|
+
RIPPLE_VERSION = '1.1.6'
|
|
17
|
+
# @return [String]
|
|
18
|
+
FILL_VERSION = '1.0.6'
|
|
12
19
|
end
|