ruby-progress 1.3.6 → 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 +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- 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 +15 -0
- data/lib/ruby-progress/cli/twirl_options.rb +3 -0
- data/lib/ruby-progress/cli/twirl_runner.rb +20 -0
- data/lib/ruby-progress/cli/worm_cli.rb +28 -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 +8 -1
- data/lib/ruby-progress/worm.rb +23 -1
- data/project-page.md +162 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed28989b5200a0ba018f976c1b7b90b8077916959633a5e2ea576de25bb6ddb5
|
4
|
+
data.tar.gz: f5c0ddea618bad931125e8f3195162e3cae7405c583fe27da5759d0febf30fa1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6722478a39177ac8c8618035abbd0b155afc275eb53120c7c89ff9f7a0c2194a02313efb5cf19ec59205d5f8002435edbef41c7cf5590f7e5f30ad00fa10f02d
|
7
|
+
data.tar.gz: 861feef40116e9c4a5f1c18e2455032a286c9c38fd7bccca19d24c1438b2554be90db4b5c820421b87301b4f21a3527a9bb17095c35140cf4ddddd0d8551539a
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [1.3.7] - 2025-10-22
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- PTY command spawning now executes via the user's login shell to support shell aliases/functions and ensure PATH customizations are honored.
|
13
|
+
- Uses `ENV['SHELL']` when available (fallback `/bin/sh`).
|
14
|
+
- `bash`/`zsh`/`sh`: `-lc` is used; `fish`: `-l -c` is used.
|
15
|
+
- Resolves `Errno::ENOENT` when running commands like `-c 'r fast'` under `--stdout-live`.
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
- Refactored output capture internals:
|
20
|
+
- Introduced `RubyProgress::ShellExec.build_shell_argv` for shell argv construction.
|
21
|
+
- Extracted terminal reservation to `RubyProgress::OutputUI.reserve_space`.
|
22
|
+
- Reduced method complexity and addressed linter warnings without behavior changes.
|
23
|
+
|
8
24
|
## [1.3.6] - 2025-10-15
|
9
25
|
|
10
26
|
### Fixed
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
[](https://www.ruby-lang.org/)
|
7
7
|
<!-- [](#) -->
|
8
8
|
|
9
|
-
This repository contains
|
9
|
+
This repository contains a collection of Ruby progress indicator projects: **Ripple**, **Worm**, **Twirl**, and **Fill**. All provide animated terminal progress indicators with different visual styles and features.
|
10
10
|
|
11
11
|
## Table of Contents
|
12
12
|
|
@@ -8,6 +8,9 @@ module RubyProgress
|
|
8
8
|
# rubocop:disable Metrics/AbcSize
|
9
9
|
module Options
|
10
10
|
# rubocop :disable Metrics/MethodLength
|
11
|
+
# Parse CLI options for the fill subcommand.
|
12
|
+
#
|
13
|
+
# @return [Hash] parsed options keyed by symbols
|
11
14
|
def self.parse_cli_options
|
12
15
|
options = {
|
13
16
|
style: :blocks,
|
@@ -6,6 +6,9 @@ require 'json'
|
|
6
6
|
module RippleCLI
|
7
7
|
# Option parsing extracted to its own file to reduce module size of RippleCLI.
|
8
8
|
module Options
|
9
|
+
# Parse CLI options for the ripple subcommand.
|
10
|
+
#
|
11
|
+
# @return [Hash] parsed options keyed by symbols
|
9
12
|
def self.parse_cli_options
|
10
13
|
options = {
|
11
14
|
speed: :medium,
|
@@ -7,6 +7,16 @@ require_relative 'twirl_runner'
|
|
7
7
|
|
8
8
|
# Twirl CLI (extracted from bin/prg)
|
9
9
|
module TwirlCLI
|
10
|
+
# CLI dispatcher for the Twirl spinner indicator. Parses options and
|
11
|
+
# dispatches to runtime helpers in TwirlRunner or controls daemons via
|
12
|
+
# RubyProgress::Daemon.
|
13
|
+
#
|
14
|
+
# Public methods:
|
15
|
+
# - .run
|
16
|
+
# - .resolve_pid_file
|
17
|
+
# - .parse_cli_options
|
18
|
+
#
|
19
|
+
# @return [void]
|
10
20
|
def self.run
|
11
21
|
trap('INT') do
|
12
22
|
RubyProgress::Utils.show_cursor
|
@@ -43,6 +53,11 @@ module TwirlCLI
|
|
43
53
|
end
|
44
54
|
|
45
55
|
# runtime methods moved to TwirlRunner
|
56
|
+
# Resolve the pid file path used for named/unnamed daemons.
|
57
|
+
#
|
58
|
+
# @param options [Hash]
|
59
|
+
# @param name_key [Symbol]
|
60
|
+
# @return [String]
|
46
61
|
def self.resolve_pid_file(options, name_key)
|
47
62
|
return options[:pid_file] if options[:pid_file]
|
48
63
|
|
@@ -9,6 +9,9 @@ module TwirlCLI
|
|
9
9
|
# Keeps the CLI option definitions extracted from the main dispatcher
|
10
10
|
# so the `TwirlCLI` module stays small and focused on dispatching.
|
11
11
|
module Options
|
12
|
+
# Parse CLI options for the twirl subcommand.
|
13
|
+
#
|
14
|
+
# @return [Hash] parsed options keyed by symbols
|
12
15
|
def self.parse_cli_options
|
13
16
|
options = {
|
14
17
|
output_position: :above,
|
@@ -12,6 +12,12 @@ require_relative '../output_capture'
|
|
12
12
|
# indefinitely, or launching in daemon mode) and were extracted to
|
13
13
|
# reduce module size and improve testability.
|
14
14
|
module TwirlRunner
|
15
|
+
# Runtime helper: run the provided command while showing a twirl spinner.
|
16
|
+
# Captures output via RubyProgress::OutputCapture when appropriate and
|
17
|
+
# prints final completion messages according to options.
|
18
|
+
#
|
19
|
+
# @param options [Hash] CLI options parsed from TwirlCLI::Options
|
20
|
+
# @return [void] exits with appropriate status code (0 success, 1 failure)
|
15
21
|
def self.run_with_command(options)
|
16
22
|
message = options[:message]
|
17
23
|
captured_output = nil
|
@@ -72,6 +78,10 @@ module TwirlRunner
|
|
72
78
|
exit success ? 0 : 1
|
73
79
|
end
|
74
80
|
|
81
|
+
# Run the spinner indefinitely until interrupted (SIGINT).
|
82
|
+
#
|
83
|
+
# @param options [Hash] CLI options used to configure the spinner
|
84
|
+
# @return [void] exits 130 on interrupt
|
75
85
|
def self.run_indefinitely(options)
|
76
86
|
message = options[:message]
|
77
87
|
spinner = TwirlSpinner.new(message, options)
|
@@ -96,6 +106,11 @@ module TwirlRunner
|
|
96
106
|
end
|
97
107
|
end
|
98
108
|
|
109
|
+
# Run the spinner in daemon mode. Writes a pid file and listens for
|
110
|
+
# control messages via the daemon control message file.
|
111
|
+
#
|
112
|
+
# @param options [Hash]
|
113
|
+
# @return [void]
|
99
114
|
def self.run_daemon_mode(options)
|
100
115
|
pid_file = resolve_pid_file(options, :daemon_name)
|
101
116
|
FileUtils.mkdir_p(File.dirname(pid_file))
|
@@ -150,6 +165,11 @@ module TwirlRunner
|
|
150
165
|
end
|
151
166
|
end
|
152
167
|
|
168
|
+
# Resolve pid file helper used by run_daemon_mode and CLI.
|
169
|
+
#
|
170
|
+
# @param options [Hash]
|
171
|
+
# @param name_key [Symbol]
|
172
|
+
# @return [String]
|
153
173
|
def self.resolve_pid_file(options, name_key)
|
154
174
|
return options[:pid_file] if options[:pid_file]
|
155
175
|
|
@@ -6,6 +6,25 @@ require_relative 'worm_options'
|
|
6
6
|
|
7
7
|
# Enhanced Worm CLI (extracted from bin/prg)
|
8
8
|
module WormCLI
|
9
|
+
# CLI dispatcher for the Worm indicator.
|
10
|
+
#
|
11
|
+
# Responsibilities:
|
12
|
+
# - parse CLI options (delegates to WormCLI::Options)
|
13
|
+
# - handle daemonization, status, and stop commands via RubyProgress::Daemon
|
14
|
+
# - launch the appropriate runtime mode (command-run, indefinite, daemon)
|
15
|
+
#
|
16
|
+
# Public methods:
|
17
|
+
# - .run
|
18
|
+
# - .resolve_pid_file
|
19
|
+
# - .run_daemon_mode
|
20
|
+
|
21
|
+
# Determine the pid file path from options. If options specify a custom
|
22
|
+
# :pid_file, return it. If a named daemon key is present, use
|
23
|
+
# /tmp/ruby-progress/<name>.pid. Otherwise fall back to the default.
|
24
|
+
#
|
25
|
+
# @param options [Hash] parsed CLI options
|
26
|
+
# @param name_key [Symbol] key used for named daemons (default :daemon_name)
|
27
|
+
# @return [String] path to the pid file
|
9
28
|
def self.resolve_pid_file(options, name_key = :daemon_name)
|
10
29
|
return options[:pid_file] if options[:pid_file]
|
11
30
|
|
@@ -14,6 +33,10 @@ module WormCLI
|
|
14
33
|
RubyProgress::Daemon.default_pid_file
|
15
34
|
end
|
16
35
|
|
36
|
+
# Entrypoint for the Worm CLI. Parses options and dispatches to status,
|
37
|
+
# stop, daemon, or runtime modes. Ensures the cursor is restored on Ctrl+C.
|
38
|
+
#
|
39
|
+
# @return [void] exits with appropriate exit codes for status/stop modes.
|
17
40
|
def self.run
|
18
41
|
trap('INT') do
|
19
42
|
RubyProgress::Utils.show_cursor
|
@@ -53,6 +76,11 @@ module WormCLI
|
|
53
76
|
end
|
54
77
|
end
|
55
78
|
|
79
|
+
# Launch the worm indicator in daemon mode writing a pid file and
|
80
|
+
# monitoring for control messages. Ensures pid file is removed on exit.
|
81
|
+
#
|
82
|
+
# @param options [Hash] parsed CLI options used to configure the Worm instance
|
83
|
+
# @return [void]
|
56
84
|
def self.run_daemon_mode(options)
|
57
85
|
pid_file = resolve_pid_file(options, :daemon_name)
|
58
86
|
FileUtils.mkdir_p(File.dirname(pid_file))
|
@@ -8,6 +8,9 @@ module WormCLI
|
|
8
8
|
# Keeps the CLI option definitions for `prg worm` extracted from
|
9
9
|
# the main dispatcher to keep the CLI module small and focused.
|
10
10
|
module Options
|
11
|
+
# Parse CLI options for the worm subcommand.
|
12
|
+
#
|
13
|
+
# @return [Hash] parsed options keyed by symbols
|
11
14
|
def self.parse_cli_options
|
12
15
|
options = {
|
13
16
|
output_position: :above,
|
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)
|
10
|
+
# Component-specific versions (patch bumps)
|
11
|
+
# @return [String]
|
8
12
|
WORM_VERSION = '1.1.6'
|
13
|
+
# @return [String]
|
9
14
|
TWIRL_VERSION = '1.1.6'
|
15
|
+
# @return [String]
|
10
16
|
RIPPLE_VERSION = '1.1.6'
|
17
|
+
# @return [String]
|
11
18
|
FILL_VERSION = '1.0.6'
|
12
19
|
end
|
data/lib/ruby-progress/worm.rb
CHANGED
@@ -9,6 +9,16 @@ require_relative 'cli/worm_runner'
|
|
9
9
|
module RubyProgress
|
10
10
|
# Animated progress indicator with ripple effect using Unicode combining characters
|
11
11
|
class Worm
|
12
|
+
# Worm indicator renders a small ripple/wave of characters. Use this
|
13
|
+
# class directly to control a running indicator, or use the CLI helpers
|
14
|
+
# which wrap this behavior for daemonization and command execution.
|
15
|
+
#
|
16
|
+
# Public instance methods (selected): #generate_dots
|
17
|
+
# Public module mixins provide the main animation loop via WormRunner.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# w = RubyProgress::Worm.new(length: 5, message: 'loading')
|
21
|
+
# w.send(:generate_dots, 2, 1) # => "..o.."
|
12
22
|
# Ripple effect styles
|
13
23
|
RIPPLE_STYLES = {
|
14
24
|
'circles' => {
|
@@ -56,6 +66,13 @@ module RubyProgress
|
|
56
66
|
}.freeze
|
57
67
|
|
58
68
|
def initialize(options = {})
|
69
|
+
# Create a new Worm indicator instance.
|
70
|
+
#
|
71
|
+
# @param options [Hash] configuration options
|
72
|
+
# @option options [Integer] :length number of characters in the ripple (default 3)
|
73
|
+
# @option options [String] :message optional label text
|
74
|
+
# @option options [String,Symbol] :style ripple style name or custom spec
|
75
|
+
# @return [void]
|
59
76
|
@length = options[:length] || 3
|
60
77
|
@message = options[:message]
|
61
78
|
@speed = parse_speed(options[:speed] || 'medium')
|
@@ -154,7 +171,7 @@ module RubyProgress
|
|
154
171
|
input_chars.all? do |char|
|
155
172
|
idx = key_chars.index(char)
|
156
173
|
if idx
|
157
|
-
key_chars = key_chars[idx + 1..-1] # Remove matched chars and continue
|
174
|
+
key_chars = key_chars[(idx + 1)..-1] # Remove matched chars and continue
|
158
175
|
true
|
159
176
|
else
|
160
177
|
false
|
@@ -207,6 +224,11 @@ module RubyProgress
|
|
207
224
|
# @output_capture&.redraw) will be overridden.
|
208
225
|
|
209
226
|
def generate_dots(ripple_position, direction)
|
227
|
+
# Generate the string representing the ripple at a given position.
|
228
|
+
#
|
229
|
+
# @param ripple_position [Integer] index of the ripple peak
|
230
|
+
# @param direction [Integer] -1 for left-moving, +1 for right-moving
|
231
|
+
# @return [String] composed characters for current frame
|
210
232
|
dots = Array.new(@length) { @style[:baseline] }
|
211
233
|
|
212
234
|
# Apply ripple effect
|
data/project-page.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# Ruby Progress Indicators
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/ruby-progress)
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
5
|
+
[](https://www.ruby-lang.org/)
|
6
|
+
|
7
|
+
A collection of Ruby progress indicator projects: **Ripple**, **Worm**,
|
8
|
+
**Twirl**, and **Fill**. All provide animated terminal progress indicators
|
9
|
+
with different visual styles and features.
|
10
|
+
|
11
|
+
## Quick Start
|
12
|
+
|
13
|
+
Install the gem:
|
14
|
+
|
15
|
+
{% iterm "gem install ruby-progress" %}
|
16
|
+
|
17
|
+
Use the unified interface:
|
18
|
+
|
19
|
+
{% iterm "prg worm --message 'Processing data' --style blocks --checkmark" %}
|
20
|
+
|
21
|
+
{% iterm "prg ripple 'Loading...' --style rainbow --speed fast" %}
|
22
|
+
|
23
|
+
{% iterm "prg twirl --message 'Working...' --style dots --speed fast" %}
|
24
|
+
|
25
|
+
## Unified Interface
|
26
|
+
|
27
|
+
The gem provides a unified `prg` command that supports all progress
|
28
|
+
indicators through subcommands. Run commands with progress animation:
|
29
|
+
|
30
|
+
{% iterm "prg worm --command 'sleep 5' --success 'Completed!' --error 'Failed!' --checkmark" %}
|
31
|
+
|
32
|
+
{% iterm "prg ripple 'Building...' --command 'make build' --success 'Build complete!' --stdout" %}
|
33
|
+
|
34
|
+
{% iterm "prg twirl --command 'npm install' --message 'Installing packages' --style arc" %}
|
35
|
+
|
36
|
+
[See the README for more CLI examples](https://github.com/ttscoff/ruby-progress/blob/main/README.md#ripple-cli-examples)
|
37
|
+
|
38
|
+
## Ripple
|
39
|
+
|
40
|
+
Sophisticated text animation library that creates ripple effects across
|
41
|
+
text strings in the terminal. Supports various animation modes including
|
42
|
+
bidirectional movement and rainbow colors.
|
43
|
+
|
44
|
+
**Key Features:**
|
45
|
+
|
46
|
+
- Text ripple animations with customizable speed and direction
|
47
|
+
- Style system supporting rainbow colors and inverse highlighting
|
48
|
+
- Multiple animation formats: forward-only, bidirectional
|
49
|
+
- Command execution with animated progress display
|
50
|
+
|
51
|
+
Basic text animation:
|
52
|
+
|
53
|
+
{% iterm "prg ripple 'Loading...'" %}
|
54
|
+
|
55
|
+
With style options:
|
56
|
+
|
57
|
+
{% iterm "prg ripple 'Processing Data' --speed fast --style rainbow --direction bidirectional" %}
|
58
|
+
|
59
|
+
Run a command with progress animation:
|
60
|
+
|
61
|
+
{% iterm "prg ripple 'Installing packages' --command 'sleep 5' --success 'Installation complete!' --checkmark" %}
|
62
|
+
|
63
|
+
[See the README for more Ripple CLI examples](https://github.com/ttscoff/ruby-progress/blob/main/README.md#ripple-cli-examples)
|
64
|
+
|
65
|
+
## Twirl
|
66
|
+
|
67
|
+
Lightweight spinner animation system providing over 35 different spinner
|
68
|
+
styles for terminal progress indication. Perfect for showing indefinite
|
69
|
+
progress during command execution.
|
70
|
+
|
71
|
+
**Key Features:**
|
72
|
+
|
73
|
+
- 35+ spinner styles including dots, arrows, blocks, and geometric patterns
|
74
|
+
- Flexible speed control (1-10 scale or named speeds)
|
75
|
+
- Command execution with animated progress display
|
76
|
+
- Daemon mode for background progress indication
|
77
|
+
|
78
|
+
Basic spinner animation:
|
79
|
+
|
80
|
+
{% iterm "prg twirl --message 'Processing...' --style dots" %}
|
81
|
+
|
82
|
+
With command execution:
|
83
|
+
|
84
|
+
{% iterm "prg twirl --command 'npm install' --message 'Installing' --style arc" %}
|
85
|
+
|
86
|
+
Different spinner styles:
|
87
|
+
|
88
|
+
{% iterm "prg twirl --message 'Working' --style arrows --speed fast" %}
|
89
|
+
|
90
|
+
[See the README for more Twirl CLI examples](https://github.com/ttscoff/ruby-progress/blob/main/README.md#twirl-usage)
|
91
|
+
|
92
|
+
## Worm
|
93
|
+
|
94
|
+
Clean, Unicode-based progress indicator that creates a ripple effect using
|
95
|
+
combining characters. Designed for running commands with visual progress
|
96
|
+
feedback.
|
97
|
+
|
98
|
+
**Key Features:**
|
99
|
+
|
100
|
+
- Ripple wave animation using Unicode characters
|
101
|
+
- Multiple visual styles (circles, blocks, geometric)
|
102
|
+
- Configurable speed and customizable length
|
103
|
+
- Command execution with progress indication
|
104
|
+
- Custom styles with 3-character patterns
|
105
|
+
|
106
|
+
Run indefinitely without a command:
|
107
|
+
|
108
|
+
{% iterm "prg worm --message 'Loading...' --speed fast --style circles" %}
|
109
|
+
|
110
|
+
Run a command with progress animation:
|
111
|
+
|
112
|
+
{% iterm "prg worm --command 'sleep 5' --message 'Installing' --success 'Done!'" %}
|
113
|
+
|
114
|
+
Custom animations with 3-character patterns:
|
115
|
+
|
116
|
+
{% iterm "prg worm --message 'Custom style' --style 'custom=_-=' --command 'sleep 2'" %}
|
117
|
+
|
118
|
+
{% iterm "prg worm --message 'Emoji worm!' --style 'custom=🟦🟨🟥' --success 'Complete!'" %}
|
119
|
+
|
120
|
+
[See the README for more Worm CLI examples](https://github.com/ttscoff/ruby-progress/blob/main/README.md#worm-usage)
|
121
|
+
|
122
|
+
## Background Mode
|
123
|
+
|
124
|
+
All progress indicators support daemon mode for background tasks:
|
125
|
+
|
126
|
+
Start a background indicator:
|
127
|
+
|
128
|
+
{% iterm "prg worm --daemon-as mytask --message 'Background processing'" %}
|
129
|
+
|
130
|
+
Stop it later with a message:
|
131
|
+
|
132
|
+
{% iterm "prg job stop --daemon-name mytask --message 'Task complete!' --checkmark" %}
|
133
|
+
|
134
|
+
[See the README for more background mode examples](https://github.com/ttscoff/ruby-progress/blob/main/README.md#example-background-mode-demo)
|
135
|
+
|
136
|
+
## Installation
|
137
|
+
|
138
|
+
As a gem (recommended):
|
139
|
+
|
140
|
+
{% iterm "gem install ruby-progress" %}
|
141
|
+
|
142
|
+
From source:
|
143
|
+
|
144
|
+
{% iterm "git clone https://github.com/ttscoff/ruby-progress.git" %}
|
145
|
+
|
146
|
+
{% iterm "cd ruby-progress" %}
|
147
|
+
|
148
|
+
{% iterm "bundle install" %}
|
149
|
+
|
150
|
+
{% iterm "bundle exec rake build" %}
|
151
|
+
|
152
|
+
{% iterm "gem install pkg/ruby-progress-*.gem" %}
|
153
|
+
|
154
|
+
[See the README for more installation options](https://github.com/ttscoff/ruby-progress/blob/main/README.md#installation)
|
155
|
+
|
156
|
+
## Requirements
|
157
|
+
|
158
|
+
- Ruby 2.7 or higher
|
159
|
+
- Terminal with Unicode support (for Worm)
|
160
|
+
- ANSI color support (for Ripple rainbow effects)
|
161
|
+
|
162
|
+
[See the README for complete documentation](https://github.com/ttscoff/ruby-progress/blob/main/README.md)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-progress
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
@@ -140,6 +140,7 @@ files:
|
|
140
140
|
- lib/ruby-progress/utils.rb
|
141
141
|
- lib/ruby-progress/version.rb
|
142
142
|
- lib/ruby-progress/worm.rb
|
143
|
+
- project-page.md
|
143
144
|
- quick_demo.rb
|
144
145
|
- readme_demo.rb
|
145
146
|
- ruby-progress.gemspec
|