ruby-progress 1.1.9 → 1.2.4
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 +63 -122
- data/DEMO_SCRIPTS.md +162 -0
- data/Gemfile.lock +1 -1
- data/README.md +201 -62
- data/Rakefile +7 -0
- data/bin/fill +10 -0
- data/bin/prg +50 -1009
- data/demo_screencast.rb +296 -0
- data/experimental_terminal.rb +7 -0
- data/lib/ruby-progress/cli/fill_options.rb +193 -0
- data/lib/ruby-progress/cli/ripple_cli.rb +150 -0
- data/lib/ruby-progress/cli/ripple_options.rb +148 -0
- data/lib/ruby-progress/cli/twirl_cli.rb +173 -0
- data/lib/ruby-progress/cli/twirl_options.rb +136 -0
- data/lib/ruby-progress/cli/twirl_runner.rb +130 -0
- data/lib/ruby-progress/cli/twirl_spinner.rb +78 -0
- data/lib/ruby-progress/cli/worm_cli.rb +75 -0
- data/lib/ruby-progress/cli/worm_options.rb +156 -0
- data/lib/ruby-progress/cli/worm_runner.rb +260 -0
- data/lib/ruby-progress/fill.rb +211 -0
- data/lib/ruby-progress/fill_cli.rb +219 -0
- data/lib/ruby-progress/ripple.rb +4 -2
- data/lib/ruby-progress/utils.rb +32 -2
- data/lib/ruby-progress/version.rb +8 -4
- data/lib/ruby-progress/worm.rb +43 -178
- data/lib/ruby-progress.rb +1 -0
- data/quick_demo.rb +134 -0
- data/readme_demo.rb +128 -0
- data/ruby-progress.gemspec +40 -0
- data/scripts/run_matrix_mise.fish +41 -0
- data/test_daemon_interruption.rb +2 -0
- data/test_daemon_orphan.rb +1 -0
- metadata +21 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require_relative 'cli/fill_options'
|
|
6
|
+
|
|
7
|
+
module RubyProgress
|
|
8
|
+
# CLI module for Fill command
|
|
9
|
+
module FillCLI
|
|
10
|
+
class << self
|
|
11
|
+
def run
|
|
12
|
+
trap('INT') do
|
|
13
|
+
Utils.show_cursor
|
|
14
|
+
exit
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
options = RubyProgress::FillCLI::Options.parse_cli_options
|
|
18
|
+
|
|
19
|
+
# Handle basic output flags first
|
|
20
|
+
if options[:help]
|
|
21
|
+
puts RubyProgress::FillCLI::Options.help_text
|
|
22
|
+
exit
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if options[:version]
|
|
26
|
+
puts "Fill version #{RubyProgress::FILL_VERSION}"
|
|
27
|
+
exit
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if options[:show_styles]
|
|
31
|
+
show_fill_styles
|
|
32
|
+
exit
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Handle daemon control first
|
|
36
|
+
if options[:status] || options[:stop]
|
|
37
|
+
pid_file = options[:pid_file] || '/tmp/ruby-progress/fill.pid'
|
|
38
|
+
if options[:status]
|
|
39
|
+
Daemon.show_status(pid_file)
|
|
40
|
+
else
|
|
41
|
+
Daemon.stop_daemon_by_pid_file(pid_file)
|
|
42
|
+
end
|
|
43
|
+
exit
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Parse style option
|
|
47
|
+
parsed_style = parse_fill_style(options[:style])
|
|
48
|
+
|
|
49
|
+
if options[:daemon]
|
|
50
|
+
run_daemon_mode(options, parsed_style)
|
|
51
|
+
elsif options[:current]
|
|
52
|
+
show_current_percentage(options, parsed_style)
|
|
53
|
+
elsif options[:report]
|
|
54
|
+
show_progress_report(options, parsed_style)
|
|
55
|
+
elsif options[:advance] || options[:complete] || options[:cancel]
|
|
56
|
+
handle_progress_commands(options, parsed_style)
|
|
57
|
+
else
|
|
58
|
+
run_auto_advance_mode(options, parsed_style)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def parse_fill_style(style_option)
|
|
65
|
+
case style_option
|
|
66
|
+
when String
|
|
67
|
+
if style_option.start_with?('custom=')
|
|
68
|
+
Fill.parse_custom_style(style_option)
|
|
69
|
+
else
|
|
70
|
+
style_option.to_sym
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
style_option
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def run_daemon_mode(options, parsed_style)
|
|
78
|
+
# For daemon mode, detach the process
|
|
79
|
+
PrgCLI.daemonize
|
|
80
|
+
|
|
81
|
+
pid_file = options[:pid_file] || '/tmp/ruby-progress/fill.pid'
|
|
82
|
+
FileUtils.mkdir_p(File.dirname(pid_file))
|
|
83
|
+
File.write(pid_file, Process.pid.to_s)
|
|
84
|
+
|
|
85
|
+
# Create the fill bar and show initial empty state
|
|
86
|
+
fill_options = {
|
|
87
|
+
style: parsed_style,
|
|
88
|
+
length: options[:length],
|
|
89
|
+
ends: options[:ends],
|
|
90
|
+
success: options[:success_message],
|
|
91
|
+
error: options[:error_message]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fill_bar = Fill.new(fill_options)
|
|
95
|
+
Fill.hide_cursor
|
|
96
|
+
|
|
97
|
+
begin
|
|
98
|
+
fill_bar.render # Show initial empty bar
|
|
99
|
+
|
|
100
|
+
# Set up signal handlers for daemon control
|
|
101
|
+
stop_requested = false
|
|
102
|
+
Signal.trap('INT') { stop_requested = true }
|
|
103
|
+
Signal.trap('USR1') { stop_requested = true }
|
|
104
|
+
Signal.trap('TERM') { stop_requested = true }
|
|
105
|
+
|
|
106
|
+
# Keep daemon alive until stop requested
|
|
107
|
+
sleep(0.1) until stop_requested
|
|
108
|
+
ensure
|
|
109
|
+
Fill.show_cursor
|
|
110
|
+
FileUtils.rm_f(pid_file)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def show_current_percentage(options, _parsed_style)
|
|
115
|
+
# Just output the percentage for scripting (default to 50% for demonstration)
|
|
116
|
+
percentage = options[:percent] || 50
|
|
117
|
+
puts percentage.to_f
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def show_progress_report(options, parsed_style)
|
|
121
|
+
# Create a fill bar to demonstrate current progress
|
|
122
|
+
fill_options = {
|
|
123
|
+
style: parsed_style,
|
|
124
|
+
length: options[:length],
|
|
125
|
+
ends: options[:ends]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fill_bar = Fill.new(fill_options)
|
|
129
|
+
|
|
130
|
+
# Set percentage (default to 50% for demonstration)
|
|
131
|
+
fill_bar.percent = options[:percent] || 50
|
|
132
|
+
|
|
133
|
+
# Get detailed report
|
|
134
|
+
report = fill_bar.report
|
|
135
|
+
|
|
136
|
+
# Display the current progress bar and detailed status
|
|
137
|
+
fill_bar.render
|
|
138
|
+
puts "\nProgress Report:"
|
|
139
|
+
puts " Progress: #{report[:progress][0]}/#{report[:progress][1]}"
|
|
140
|
+
puts " Percent: #{report[:percent]}%"
|
|
141
|
+
puts " Completed: #{report[:completed] ? 'Yes' : 'No'}"
|
|
142
|
+
puts " Style: #{report[:style]}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def handle_progress_commands(_options, _parsed_style)
|
|
146
|
+
# For progress commands, we assume there's a daemon running
|
|
147
|
+
# This is a simplified version - in a real implementation,
|
|
148
|
+
# we'd need IPC to communicate with the daemon
|
|
149
|
+
warn 'Progress commands require daemon mode implementation'
|
|
150
|
+
warn "Run 'prg fill --daemon' first, then use progress commands"
|
|
151
|
+
exit 1
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def run_auto_advance_mode(options, parsed_style)
|
|
155
|
+
fill_options = {
|
|
156
|
+
style: parsed_style,
|
|
157
|
+
length: options[:length],
|
|
158
|
+
ends: options[:ends],
|
|
159
|
+
success: options[:success_message],
|
|
160
|
+
error: options[:error_message]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fill_bar = Fill.new(fill_options)
|
|
164
|
+
Fill.hide_cursor
|
|
165
|
+
|
|
166
|
+
begin
|
|
167
|
+
if options[:percent]
|
|
168
|
+
# Set to specific percentage
|
|
169
|
+
fill_bar.percent = options[:percent]
|
|
170
|
+
fill_bar.render
|
|
171
|
+
unless fill_bar.completed?
|
|
172
|
+
# For non-complete percentages, show the result briefly
|
|
173
|
+
sleep(0.1)
|
|
174
|
+
end
|
|
175
|
+
else
|
|
176
|
+
# Auto-advance mode
|
|
177
|
+
sleep_time = case options[:speed]
|
|
178
|
+
when :fast then 0.1
|
|
179
|
+
when :medium, nil then 0.2
|
|
180
|
+
when :slow then 0.5
|
|
181
|
+
when Numeric then 1.0 / options[:speed]
|
|
182
|
+
else 0.3
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
fill_bar.render
|
|
186
|
+
(1..options[:length]).each do
|
|
187
|
+
sleep(sleep_time)
|
|
188
|
+
fill_bar.advance
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
fill_bar.complete
|
|
192
|
+
rescue Interrupt
|
|
193
|
+
fill_bar.cancel
|
|
194
|
+
ensure
|
|
195
|
+
Fill.show_cursor
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def show_fill_styles
|
|
200
|
+
puts "\nAvailable Fill Styles:"
|
|
201
|
+
puts '=' * 50
|
|
202
|
+
|
|
203
|
+
Fill::FILL_STYLES.each do |name, style|
|
|
204
|
+
print "#{name.to_s.ljust(12)} : "
|
|
205
|
+
|
|
206
|
+
# Show a sample progress bar
|
|
207
|
+
filled = style[:full] * 6
|
|
208
|
+
empty = style[:empty] * 4
|
|
209
|
+
puts "[#{filled}#{empty}] (60%)"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
puts "\nCustom Style:"
|
|
213
|
+
puts "#{'custom=XY'.ljust(12)} : Specify X=empty, Y=full characters"
|
|
214
|
+
puts ' Example: --style custom=.# → [######....] (60%)'
|
|
215
|
+
puts
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
data/lib/ruby-progress/ripple.rb
CHANGED
|
@@ -93,7 +93,8 @@ module RubyProgress
|
|
|
93
93
|
spinner_position: false,
|
|
94
94
|
caps: false,
|
|
95
95
|
inverse: false,
|
|
96
|
-
output: :error
|
|
96
|
+
output: :error,
|
|
97
|
+
ends: nil
|
|
97
98
|
}
|
|
98
99
|
@options = defaults.merge(options)
|
|
99
100
|
@string = string
|
|
@@ -104,6 +105,7 @@ module RubyProgress
|
|
|
104
105
|
@spinner_position = @options[:spinner_position]
|
|
105
106
|
@caps = @options[:caps]
|
|
106
107
|
@inverse = @options[:inverse]
|
|
108
|
+
@start_chars, @end_chars = RubyProgress::Utils.parse_ends(@options[:ends])
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
def printout
|
|
@@ -138,7 +140,7 @@ module RubyProgress
|
|
|
138
140
|
char = @rainbow ? char.rainbow(i) : char.extend(StringExtensions).light_white
|
|
139
141
|
post = letters.slice!(0, letters.length).join.extend(StringExtensions).dark_white
|
|
140
142
|
end
|
|
141
|
-
$stderr.print "\r\e[2K#{pre}#{char}#{post}"
|
|
143
|
+
$stderr.print "\r\e[2K#{@start_chars}#{pre}#{char}#{post}#{@end_chars}"
|
|
142
144
|
$stderr.flush
|
|
143
145
|
end
|
|
144
146
|
|
data/lib/ruby-progress/utils.rb
CHANGED
|
@@ -49,9 +49,15 @@ module RubyProgress
|
|
|
49
49
|
when :stderr
|
|
50
50
|
warn formatted_message
|
|
51
51
|
when :warn
|
|
52
|
-
|
|
52
|
+
# Ensure we're at the beginning of a fresh line, clear it, then display message
|
|
53
|
+
$stderr.print "\r\e[2K"
|
|
54
|
+
$stderr.flush
|
|
55
|
+
warn formatted_message
|
|
53
56
|
else
|
|
54
|
-
|
|
57
|
+
# Ensure we're at the beginning of a fresh line, clear it, then display message
|
|
58
|
+
$stderr.print "\r\e[2K"
|
|
59
|
+
$stderr.flush
|
|
60
|
+
warn formatted_message
|
|
55
61
|
end
|
|
56
62
|
end
|
|
57
63
|
|
|
@@ -61,5 +67,29 @@ module RubyProgress
|
|
|
61
67
|
clear_line(output_stream) if output_stream != :warn # warn already includes clear in display_completion
|
|
62
68
|
display_completion(message, success: success, show_checkmark: show_checkmark, output_stream: output_stream)
|
|
63
69
|
end
|
|
70
|
+
|
|
71
|
+
# Parse start/end characters for animation wrapping
|
|
72
|
+
# @param ends_string [String] Even-length string to split in half for start/end chars
|
|
73
|
+
# @return [Array<String>] Array with [start_chars, end_chars]
|
|
74
|
+
def self.parse_ends(ends_string)
|
|
75
|
+
return ['', ''] unless ends_string && !ends_string.empty?
|
|
76
|
+
|
|
77
|
+
chars = ends_string.each_char.to_a
|
|
78
|
+
return ['', ''] if chars.length.odd? || chars.empty?
|
|
79
|
+
|
|
80
|
+
mid_point = chars.length / 2
|
|
81
|
+
start_chars = chars[0...mid_point].join
|
|
82
|
+
end_chars = chars[mid_point..-1].join
|
|
83
|
+
|
|
84
|
+
[start_chars, end_chars]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Validate ends string: must be non-empty and even-length (handles multi-byte chars)
|
|
88
|
+
def self.ends_valid?(ends_string)
|
|
89
|
+
return false unless ends_string && !ends_string.empty?
|
|
90
|
+
|
|
91
|
+
chars = ends_string.each_char.to_a
|
|
92
|
+
!chars.empty? && (chars.length % 2).zero?
|
|
93
|
+
end
|
|
64
94
|
end
|
|
65
95
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyProgress
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
# Main gem version
|
|
5
|
+
VERSION = '1.2.4'
|
|
6
|
+
|
|
7
|
+
# Component-specific versions
|
|
8
|
+
WORM_VERSION = '1.1.2'
|
|
9
|
+
TWIRL_VERSION = '1.1.2'
|
|
10
|
+
RIPPLE_VERSION = '1.1.2'
|
|
11
|
+
FILL_VERSION = '1.0.1'
|
|
8
12
|
end
|
data/lib/ruby-progress/worm.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'optparse'
|
|
|
4
4
|
require 'open3'
|
|
5
5
|
require 'json'
|
|
6
6
|
require_relative 'utils'
|
|
7
|
+
require_relative 'cli/worm_runner'
|
|
7
8
|
|
|
8
9
|
module RubyProgress
|
|
9
10
|
# Animated progress indicator with ripple effect using Unicode combining characters
|
|
@@ -64,187 +65,17 @@ module RubyProgress
|
|
|
64
65
|
@error_text = options[:error]
|
|
65
66
|
@show_checkmark = options[:checkmark] || false
|
|
66
67
|
@output_stdout = options[:stdout] || false
|
|
68
|
+
@direction_mode = options[:direction] || :bidirectional
|
|
69
|
+
@start_chars, @end_chars = RubyProgress::Utils.parse_ends(options[:ends])
|
|
67
70
|
@running = false
|
|
68
71
|
end
|
|
69
|
-
|
|
70
|
-
def animate(message: nil, success: nil, error: nil, &block)
|
|
71
|
-
@message = message if message
|
|
72
|
-
@success_text = success if success
|
|
73
|
-
@error_text = error if error
|
|
74
|
-
@running = true
|
|
75
|
-
|
|
76
|
-
# Set up interrupt handler to ensure cursor is restored
|
|
77
|
-
original_int_handler = Signal.trap('INT') do
|
|
78
|
-
@running = false
|
|
79
|
-
RubyProgress::Utils.clear_line
|
|
80
|
-
RubyProgress::Utils.show_cursor
|
|
81
|
-
exit 130
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Hide cursor
|
|
85
|
-
RubyProgress::Utils.hide_cursor
|
|
86
|
-
|
|
87
|
-
animation_thread = Thread.new { animation_loop }
|
|
88
|
-
|
|
89
|
-
begin
|
|
90
|
-
if block_given?
|
|
91
|
-
result = yield
|
|
92
|
-
@running = false
|
|
93
|
-
animation_thread.join
|
|
94
|
-
display_completion_message(@success_text, true)
|
|
95
|
-
result
|
|
96
|
-
else
|
|
97
|
-
animation_thread.join
|
|
98
|
-
end
|
|
99
|
-
rescue StandardError => e
|
|
100
|
-
@running = false
|
|
101
|
-
animation_thread.join
|
|
102
|
-
display_completion_message(@error_text || "Error: #{e.message}", false)
|
|
103
|
-
nil # Return nil instead of re-raising when used as a progress indicator
|
|
104
|
-
ensure
|
|
105
|
-
# Always clear animation line and restore cursor
|
|
106
|
-
$stderr.print "\r\e[2K"
|
|
107
|
-
RubyProgress::Utils.show_cursor
|
|
108
|
-
Signal.trap('INT', original_int_handler) if original_int_handler
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def run_with_command
|
|
113
|
-
return unless @command
|
|
114
|
-
|
|
115
|
-
exit_code = 0
|
|
116
|
-
stdout_content = nil
|
|
117
|
-
|
|
118
|
-
begin
|
|
119
|
-
stdout_content = animate do
|
|
120
|
-
# Use popen3 instead of capture3 for better signal handling
|
|
121
|
-
Open3.popen3(@command) do |stdin, stdout, stderr, wait_thr|
|
|
122
|
-
stdin.close
|
|
123
|
-
captured_stdout = stdout.read
|
|
124
|
-
stderr_content = stderr.read
|
|
125
|
-
exit_code = wait_thr.value.exitstatus
|
|
126
|
-
|
|
127
|
-
unless wait_thr.value.success?
|
|
128
|
-
error_msg = @error_text || "Command failed with exit code #{exit_code}"
|
|
129
|
-
error_msg += ": #{stderr_content.strip}" if stderr_content && !stderr_content.empty?
|
|
130
|
-
raise StandardError, error_msg
|
|
131
|
-
end
|
|
132
|
-
captured_stdout
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Output to stdout if --stdout flag is set
|
|
137
|
-
puts stdout_content if @output_stdout && stdout_content
|
|
138
|
-
rescue StandardError => e
|
|
139
|
-
# animate method handles error display, just exit with proper code
|
|
140
|
-
exit exit_code.nonzero? || 1
|
|
141
|
-
rescue Interrupt
|
|
142
|
-
exit 130
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def run_indefinitely
|
|
147
|
-
# Set up interrupt handler to ensure cursor is restored
|
|
148
|
-
original_int_handler = Signal.trap('INT') do
|
|
149
|
-
@running = false
|
|
150
|
-
RubyProgress::Utils.clear_line
|
|
151
|
-
RubyProgress::Utils.show_cursor
|
|
152
|
-
exit 130
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
@running = true
|
|
156
|
-
RubyProgress::Utils.hide_cursor
|
|
157
|
-
|
|
158
|
-
begin
|
|
159
|
-
animation_loop
|
|
160
|
-
ensure
|
|
161
|
-
RubyProgress::Utils.show_cursor
|
|
162
|
-
Signal.trap('INT', original_int_handler) if original_int_handler
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def stop
|
|
167
|
-
@running = false
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def run_daemon_mode(success_message: nil, show_checkmark: false, control_message_file: nil)
|
|
171
|
-
@running = true
|
|
172
|
-
stop_requested = false
|
|
173
|
-
|
|
174
|
-
# Set up signal handlers
|
|
175
|
-
original_int_handler = Signal.trap('INT') { stop_requested = true }
|
|
176
|
-
Signal.trap('USR1') { stop_requested = true }
|
|
177
|
-
Signal.trap('TERM') { stop_requested = true }
|
|
178
|
-
Signal.trap('HUP') { stop_requested = true }
|
|
179
|
-
|
|
180
|
-
RubyProgress::Utils.hide_cursor
|
|
181
|
-
|
|
182
|
-
begin
|
|
183
|
-
animation_loop_daemon_mode(stop_requested_proc: -> { stop_requested })
|
|
184
|
-
ensure
|
|
185
|
-
RubyProgress::Utils.clear_line
|
|
186
|
-
RubyProgress::Utils.show_cursor
|
|
187
|
-
|
|
188
|
-
# Display stop-time completion message, preferring control file if provided
|
|
189
|
-
final_message = success_message
|
|
190
|
-
final_checkmark = show_checkmark
|
|
191
|
-
final_success = true
|
|
192
|
-
if control_message_file && File.exist?(control_message_file)
|
|
193
|
-
begin
|
|
194
|
-
data = JSON.parse(File.read(control_message_file))
|
|
195
|
-
final_message = data['message'] if data['message']
|
|
196
|
-
final_checkmark = !!data['checkmark'] if data.key?('checkmark')
|
|
197
|
-
final_success = !!data['success'] if data.key?('success')
|
|
198
|
-
rescue StandardError
|
|
199
|
-
# ignore parse errors, fallback to provided message
|
|
200
|
-
ensure
|
|
201
|
-
begin
|
|
202
|
-
File.delete(control_message_file)
|
|
203
|
-
rescue StandardError
|
|
204
|
-
nil
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
if final_message
|
|
210
|
-
RubyProgress::Utils.display_completion(
|
|
211
|
-
final_message,
|
|
212
|
-
success: final_success,
|
|
213
|
-
show_checkmark: final_checkmark,
|
|
214
|
-
output_stream: :stdout
|
|
215
|
-
)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
Signal.trap('INT', original_int_handler) if original_int_handler
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
def animation_loop_step
|
|
223
|
-
return unless @running
|
|
224
|
-
|
|
225
|
-
@position ||= 0
|
|
226
|
-
@direction ||= 1
|
|
227
|
-
|
|
228
|
-
message_part = @message && !@message.empty? ? "#{@message} " : ''
|
|
229
|
-
$stderr.print "\r\e[2K#{message_part}#{generate_dots(@position, @direction)}"
|
|
230
|
-
$stderr.flush
|
|
231
|
-
|
|
232
|
-
sleep @speed
|
|
233
|
-
|
|
234
|
-
# Update position and direction
|
|
235
|
-
@position += @direction
|
|
236
|
-
if @position >= @length - 1
|
|
237
|
-
@direction = -1
|
|
238
|
-
elsif @position <= 0
|
|
239
|
-
@direction = 1
|
|
240
|
-
end
|
|
241
|
-
end
|
|
72
|
+
include WormRunner
|
|
242
73
|
|
|
243
74
|
private
|
|
244
75
|
|
|
245
76
|
def display_completion_message(message, success)
|
|
246
77
|
return unless message
|
|
247
|
-
|
|
78
|
+
|
|
248
79
|
mark = ''
|
|
249
80
|
if @show_checkmark
|
|
250
81
|
mark = success ? '✅ ' : '🛑 '
|
|
@@ -285,7 +116,15 @@ module RubyProgress
|
|
|
285
116
|
def parse_style(style_input)
|
|
286
117
|
return RIPPLE_STYLES['circles'] unless style_input && !style_input.to_s.strip.empty?
|
|
287
118
|
|
|
288
|
-
|
|
119
|
+
style_str = style_input.to_s.strip
|
|
120
|
+
|
|
121
|
+
# Check for custom style format: custom=abc or custom_abc or customXabc
|
|
122
|
+
if style_str.match(/^custom[_=](.+)$/i)
|
|
123
|
+
custom_chars = Regexp.last_match(1)
|
|
124
|
+
return parse_custom_style(custom_chars)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
style_lower = style_str.downcase
|
|
289
128
|
|
|
290
129
|
# First, try exact match
|
|
291
130
|
return RIPPLE_STYLES[style_lower] if RIPPLE_STYLES.key?(style_lower)
|
|
@@ -339,6 +178,24 @@ module RubyProgress
|
|
|
339
178
|
RIPPLE_STYLES['circles']
|
|
340
179
|
end
|
|
341
180
|
|
|
181
|
+
def parse_custom_style(custom_chars)
|
|
182
|
+
# Split into individual characters, properly handling multi-byte characters (emojis)
|
|
183
|
+
chars = custom_chars.each_char.to_a
|
|
184
|
+
|
|
185
|
+
# Ensure we have exactly 3 characters
|
|
186
|
+
if chars.length != 3
|
|
187
|
+
# Fallback to default if not exactly 3 characters
|
|
188
|
+
return RIPPLE_STYLES['circles']
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Create custom style hash with baseline, midline, peak
|
|
192
|
+
{
|
|
193
|
+
baseline: chars[0],
|
|
194
|
+
midline: chars[1],
|
|
195
|
+
peak: chars[2]
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
342
199
|
def animation_loop
|
|
343
200
|
position = 0
|
|
344
201
|
direction = 1
|
|
@@ -346,14 +203,18 @@ module RubyProgress
|
|
|
346
203
|
while @running
|
|
347
204
|
message_part = @message && !@message.empty? ? "#{@message} " : ''
|
|
348
205
|
# Enhanced line clearing for better daemon mode behavior
|
|
349
|
-
$stderr.print "\r\e[2K#{message_part}#{generate_dots(position, direction)}"
|
|
206
|
+
$stderr.print "\r\e[2K#{@start_chars}#{message_part}#{generate_dots(position, direction)}#{@end_chars}"
|
|
350
207
|
$stderr.flush
|
|
351
208
|
|
|
352
209
|
sleep @speed
|
|
353
210
|
|
|
354
211
|
position += direction
|
|
355
212
|
if position >= @length - 1
|
|
356
|
-
|
|
213
|
+
if @direction_mode == :forward_only
|
|
214
|
+
position = 0
|
|
215
|
+
else
|
|
216
|
+
direction = -1
|
|
217
|
+
end
|
|
357
218
|
elsif position <= 0
|
|
358
219
|
direction = 1
|
|
359
220
|
end
|
|
@@ -386,7 +247,11 @@ module RubyProgress
|
|
|
386
247
|
|
|
387
248
|
position += direction
|
|
388
249
|
if position >= @length - 1
|
|
389
|
-
|
|
250
|
+
if @direction_mode == :forward_only
|
|
251
|
+
position = 0
|
|
252
|
+
else
|
|
253
|
+
direction = -1
|
|
254
|
+
end
|
|
390
255
|
elsif position <= 0
|
|
391
256
|
direction = 1
|
|
392
257
|
end
|
data/lib/ruby-progress.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'ruby-progress/version'
|
|
|
4
4
|
require_relative 'ruby-progress/utils'
|
|
5
5
|
require_relative 'ruby-progress/ripple'
|
|
6
6
|
require_relative 'ruby-progress/worm'
|
|
7
|
+
require_relative 'ruby-progress/fill'
|
|
7
8
|
require_relative 'ruby-progress/daemon'
|
|
8
9
|
|
|
9
10
|
module RubyProgress
|