ruby-progress 1.3.2 → 1.3.5

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.
@@ -13,7 +13,6 @@ module WormCLI
13
13
  output_position: :above,
14
14
  output_lines: 3
15
15
  }
16
- # rubocop:disable Metrics/BlockLength
17
16
  begin
18
17
  OptionParser.new do |opts|
19
18
  opts.banner = 'Usage: prg worm [options]'
@@ -83,6 +82,10 @@ module WormCLI
83
82
  options[:stdout] = true
84
83
  end
85
84
 
85
+ opts.on('--stdout-live', 'Stream captured output to STDOUT as it arrives (non-blocking)') do
86
+ options[:stdout_live] = true
87
+ end
88
+
86
89
  opts.separator ''
87
90
  opts.separator 'Daemon Mode:'
88
91
 
@@ -101,10 +104,6 @@ module WormCLI
101
104
  options[:daemon_name] = name
102
105
  end
103
106
 
104
- opts.on('--no-detach', 'When used with --daemon/--daemon-as: run background child but do not fully detach from the terminal') do
105
- options[:no_detach] = true
106
- end
107
-
108
107
  opts.on('--pid-file FILE', 'Write process ID to file (default: /tmp/ruby-progress/progress.pid)') do |file|
109
108
  options[:pid_file] = file
110
109
  end
@@ -178,7 +177,6 @@ module WormCLI
178
177
  puts "Run 'prg worm --help' for more information."
179
178
  exit 1
180
179
  end
181
- # rubocop:enable Metrics/BlockLength
182
180
  options
183
181
  end
184
182
  end
@@ -57,11 +57,12 @@ module WormRunner
57
57
  stdout_content = nil
58
58
 
59
59
  begin
60
- stdout_content = if $stdout.tty? && @output_stdout
60
+ stdout_content = if $stdout.tty? && (@output_stdout || @output_live)
61
61
  oc = RubyProgress::OutputCapture.new(
62
62
  command: @command,
63
63
  lines: @output_lines || 3,
64
- position: @output_position || :above
64
+ position: @output_position || :above,
65
+ stream: @output_live || false
65
66
  )
66
67
  oc.start
67
68
  @output_capture = oc
@@ -69,7 +70,13 @@ module WormRunner
69
70
  oc.wait
70
71
  end
71
72
  @output_capture = nil
72
- oc.lines.join("\n")
73
+ # For non-live capture, flush_to handles the output directly
74
+ if @output_live
75
+ oc.lines.join("\n")
76
+ else
77
+ oc.flush_to($stdout) if @output_stdout
78
+ nil # Don't return content since it's already been flushed
79
+ end
73
80
  else
74
81
  animate do
75
82
  Open3.popen3(@command) do |_stdin, stdout, stderr, wait_thr|
@@ -233,8 +240,12 @@ module WormRunner
233
240
  $stderr.print "\r\e[2K"
234
241
 
235
242
  if (frame_count % 10).zero?
236
- $stderr.print "\e[1A\e[2K"
237
- $stderr.print "\r"
243
+ # Use ANSI save/restore to clear the previous line without moving the
244
+ # global cursor position. This prevents the animation from erasing
245
+ # reserved output lines that we draw elsewhere.
246
+ $stderr.print "\e7" # save
247
+ $stderr.print "\e[1A\e[2K\r"
248
+ $stderr.print "\e8" # restore
238
249
  end
239
250
 
240
251
  $stderr.print "#{message_part}#{generate_dots(position, direction)}"
@@ -4,6 +4,8 @@ require 'json'
4
4
  require 'fileutils'
5
5
 
6
6
  module RubyProgress
7
+ # Daemon helpers for backgrounding progress indicators.
8
+ # Provides minimal daemonization, PID file management, and simple control-message signaling.
7
9
  module Daemon
8
10
  module_function
9
11
 
@@ -15,70 +17,6 @@ module RubyProgress
15
17
  "#{pid_file}.msg"
16
18
  end
17
19
 
18
- # Resolve a job directory for the daemon based on pid_file or name.
19
- # If pid_file is '/tmp/ruby-progress/mytask.pid' -> jobs dir '/tmp/ruby-progress/mytask.jobs'
20
- def job_dir_for_pid(pid_file)
21
- base = File.basename(pid_file, '.*')
22
- File.join(File.dirname(pid_file), "#{base}.jobs")
23
- end
24
-
25
- # Process available job files in job_dir. Each job is a JSON file with {"id","command","meta"}.
26
- # This method polls the directory and yields each parsed job hash to the provided block.
27
- def process_jobs(job_dir, poll_interval: 0.2)
28
- FileUtils.mkdir_p(job_dir)
29
-
30
- loop do
31
- # Accept any job file ending in .json (UUID filenames are common)
32
- # Ignore processed-* archives and temporary files (e.g., .tmp)
33
- files = Dir.children(job_dir).select do |f|
34
- f.end_with?('.json') && !f.start_with?('processed-')
35
- end.sort
36
-
37
- files.each do |f|
38
- path = File.join(job_dir, f)
39
- processing = "#{path}.processing"
40
-
41
- # Claim the file atomically
42
- begin
43
- File.rename(path, processing)
44
- rescue StandardError
45
- next
46
- end
47
-
48
- job = begin
49
- JSON.parse(File.read(processing))
50
- rescue StandardError
51
- FileUtils.rm_f(processing)
52
- next
53
- end
54
-
55
- begin
56
- yielded = yield(job)
57
-
58
- # on success, write .result info and merge any returned info
59
- result = { 'id' => job['id'], 'status' => 'done', 'time' => Time.now.to_i }
60
- if yielded.is_a?(Hash)
61
- # ensure string keys
62
- extra = yielded.transform_keys(&:to_s)
63
- result.merge!(extra)
64
- end
65
- File.write("#{processing}.result", result.to_json)
66
- rescue StandardError => e
67
- result = { 'id' => job['id'], 'status' => 'error', 'error' => e.message }
68
- File.write("#{processing}.result", result.to_json)
69
- ensure
70
- begin
71
- FileUtils.mv(processing, File.join(job_dir, "processed-#{f}"))
72
- rescue StandardError
73
- FileUtils.rm_f(processing)
74
- end
75
- end
76
- end
77
-
78
- sleep(poll_interval)
79
- end
80
- end
81
-
82
20
  def show_status(pid_file)
83
21
  if File.exist?(pid_file)
84
22
  pid = File.read(pid_file).strip
@@ -58,12 +58,8 @@ module RubyProgress
58
58
  pid_file = resolve_pid_file(options, :daemon_name)
59
59
  options[:pid_file] = pid_file
60
60
 
61
- # Detach or background without detaching based on --no-detach
62
- if options[:no_detach]
63
- PrgCLI.backgroundize
64
- else
65
- PrgCLI.daemonize
66
- end
61
+ # Background without detaching so progress bar remains visible in current terminal
62
+ PrgCLI.backgroundize
67
63
 
68
64
  run_daemon_mode(options, parsed_style)
69
65
  elsif options[:current]
@@ -120,71 +116,6 @@ module RubyProgress
120
116
  begin
121
117
  fill_bar.render # Show initial empty bar
122
118
 
123
- # Start job processor thread for fill (so daemon can accept jobs)
124
- job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
125
- Thread.new do
126
- RubyProgress::Daemon.process_jobs(job_dir) do |job|
127
- jid = job['id'] || SecureRandom.uuid
128
- log_path = begin
129
- File.join(File.dirname(job_dir), "#{jid}.log")
130
- rescue StandardError
131
- nil
132
- end
133
-
134
- if job['command']
135
- oc = RubyProgress::OutputCapture.new(
136
- command: job['command'],
137
- lines: options[:output_lines] || 3,
138
- position: options[:output_position] || :above,
139
- log_path: log_path
140
- )
141
- oc.start
142
-
143
- fill_bar.instance_variable_set(:@output_capture, oc)
144
- oc.wait
145
- captured = oc.lines.join("\n")
146
- exit_status = oc.exit_status
147
- fill_bar.instance_variable_set(:@output_capture, nil)
148
-
149
- success = exit_status.to_i.zero?
150
- if job['message']
151
- RubyProgress::Utils.display_completion(
152
- job['message'],
153
- success: success,
154
- show_checkmark: job['checkmark'] || false,
155
- output_stream: :stdout,
156
- icons: { success: options[:success_icon], error: options[:error_icon] }
157
- )
158
- end
159
-
160
- { 'exit_status' => exit_status, 'output' => captured, 'log_path' => log_path }
161
-
162
- elsif job['action']
163
- case job['action']
164
- when 'advance'
165
- fill_bar.advance
166
- { 'status' => 'done', 'action' => 'advance' }
167
- when 'percent'
168
- val = job['value'] || job['percent'] || 0
169
- fill_bar.percent = val.to_f
170
- { 'status' => 'done', 'action' => 'percent', 'value' => val }
171
- when 'complete'
172
- fill_bar.complete
173
- { 'status' => 'done', 'action' => 'complete' }
174
- when 'cancel'
175
- fill_bar.cancel
176
- { 'status' => 'done', 'action' => 'cancel' }
177
- else
178
- { 'status' => 'error', 'error' => 'unknown action' }
179
- end
180
- else
181
- { 'status' => 'error', 'error' => 'no command or action provided' }
182
- end
183
- rescue StandardError
184
- nil
185
- end
186
- end
187
-
188
119
  # Set up signal handlers for daemon control
189
120
  stop_requested = false
190
121
  Signal.trap('INT') { stop_requested = true }
@@ -298,7 +229,8 @@ module RubyProgress
298
229
  command: options[:command],
299
230
  lines: options[:output_lines] || 3,
300
231
  position: options[:output_position] || :above,
301
- log_path: nil
232
+ log_path: nil,
233
+ stream: options[:stdout] || options[:stdout_live]
302
234
  )
303
235
  oc.start
304
236
 
@@ -3,26 +3,68 @@
3
3
  require 'pty'
4
4
  require 'io/console'
5
5
  require 'English'
6
+ require 'fileutils'
7
+
8
+ begin
9
+ require 'tty-cursor'
10
+ require 'tty-screen'
11
+ rescue LoadError
12
+ # fall back to ANSI sequences when tty gems are not installed
13
+ end
6
14
 
7
15
  module RubyProgress
8
- # Simple PTY-based output capture that reserves a small area of the terminal
9
- # for printing captured output while the animation is drawn separately.
16
+ # PTY-based live output capture that reserves a small terminal area
17
+ # for printing captured output while the animation draws elsewhere.
10
18
  class OutputCapture
11
19
  attr_reader :exit_status
12
20
 
13
- def initialize(command:, lines: 3, position: :above, log_path: nil)
21
+ def initialize(command:, lines: 3, position: :above, log_path: nil, stream: false, debug: nil)
14
22
  @command = command
15
- @lines = lines
16
- @position = position
23
+ # Coerce lines into a positive Integer
24
+ @lines = (lines || 3).to_i
25
+ @lines = 1 if @lines < 1
26
+
27
+ # Normalize position (accept :top/:bottom or :above/:below or strings)
28
+ pos = position.respond_to?(:to_sym) ? position.to_sym : position
29
+ @position = case pos
30
+ when :top, 'top' then :above
31
+ when :bottom, 'bottom' then :below
32
+ when :above, 'above' then :above
33
+ when :below, 'below' then :below
34
+ else
35
+ :above
36
+ end
37
+
17
38
  @buffer = []
18
39
  @buf_mutex = Mutex.new
19
40
  @stop = false
20
41
  @log_path = log_path
21
42
  @log_file = nil
43
+ @stream = stream
44
+
45
+ @debug = if debug.nil?
46
+ ENV.fetch('RUBY_PROGRESS_DEBUG', nil) && ENV['RUBY_PROGRESS_DEBUG'] != '0'
47
+ else
48
+ debug
49
+ end
50
+ @debug_path = '/tmp/ruby-progress-debug.log'
51
+
52
+ if @debug
53
+ begin
54
+ FileUtils.mkdir_p(File.dirname(@debug_path))
55
+ File.open(@debug_path, 'w') { |f| f.puts("debug start: #{Time.now}") }
56
+ rescue StandardError
57
+ @debug = false
58
+ end
59
+ end
60
+
61
+ # Debug: log init if requested via ENV or explicit debug flag
62
+ debug_log("init: position=#{@position.inspect}; lines=#{@lines}")
22
63
  end
23
64
 
24
- # Start the child process and return a thread that manages capture.
65
+ # Start capturing the child process. Returns self.
25
66
  def start
67
+ reserve_space($stderr) if @stream
26
68
  @reader_thread = Thread.new { spawn_and_read }
27
69
  self
28
70
  end
@@ -32,45 +74,98 @@ module RubyProgress
32
74
  @reader_thread&.join
33
75
  end
34
76
 
35
- # Wait for the reader thread to complete
36
77
  def wait
37
78
  @reader_thread&.join
38
79
  end
39
80
 
40
- # Return snapshot of buffered lines (thread-safe)
41
81
  def lines
42
82
  @buf_mutex.synchronize { @buffer.dup }
43
83
  end
44
84
 
45
- # Returns whether the reader thread is still alive
46
85
  def alive?
47
86
  @reader_thread&.alive? || false
48
87
  end
49
88
 
50
- # Redraw buffered output into the terminal above/below the current cursor
51
- # io - IO object to write to (default $stderr)
89
+ # Redraw the reserved area using the current buffered lines.
52
90
  def redraw(io = $stderr)
53
91
  buf = lines
54
- _rows, cols = IO.console.winsize
92
+ debug_log("redraw called; buffer=#{buf.size}; lines=#{@lines}; position=#{@position}")
93
+
94
+ # If not streaming live to the terminal, don't redraw during capture.
95
+ return unless @stream
55
96
 
56
- # Ensure we have exactly @lines entries (pad with empty strings)
57
- display_lines = Array.new(@lines) { '' }
58
- start = [0, buf.size - @lines].max
59
- buf[start, @lines]&.each_with_index do |l, i|
60
- display_lines[i + (@lines - [buf.size, @lines].min)] = l.to_s
97
+ cols = if defined?(TTY::Screen)
98
+ TTY::Screen.columns
99
+ else
100
+ IO.console.winsize[1]
101
+ end
102
+
103
+ display_lines = Array.new(@lines, '')
104
+ if buf.empty?
105
+ # leave display_lines as blanks
106
+ elsif buf.size <= @lines
107
+ buf.each_with_index { |l, i| display_lines[i] = l.to_s }
108
+ else
109
+ buf.last(@lines).each_with_index { |l, i| display_lines[i] = l.to_s }
61
110
  end
62
111
 
63
- # Save cursor, move up N lines, clear and print buffer, restore cursor
64
- io.print "\e[s" # save position
65
- io.print "\e[#{@lines}A" # move up @lines
66
- display_lines.each do |line|
67
- io.print "\e[2K" # clear line
68
- io.print "\r" # move cursor to start of line
69
- io.print line[0, cols]
70
- io.print "\n"
112
+ if defined?(TTY::Cursor)
113
+ cursor = TTY::Cursor
114
+ io.print cursor.save
115
+
116
+ if @position == :above
117
+ io.print cursor.up(@lines)
118
+ else
119
+ io.print cursor.down(1)
120
+ end
121
+
122
+ display_lines.each_with_index do |line, idx|
123
+ io.print cursor.clear_line
124
+ io.print line[0, cols]
125
+ io.print cursor.down(1) unless idx == display_lines.length - 1
126
+ end
127
+
128
+ io.print cursor.restore
129
+ debug_log('redraw finished (TTY)')
130
+ else
131
+ io.print "\e7"
132
+
133
+ if @position == :above
134
+ io.print "\e[#{@lines}A"
135
+ else
136
+ io.print "\e[1B"
137
+ end
138
+
139
+ display_lines.each_with_index do |line, idx|
140
+ io.print "\e[2K\r"
141
+ io.print line[0, cols]
142
+ io.print "\e[1B" unless idx == display_lines.length - 1
143
+ end
144
+
145
+ io.print "\e8"
146
+ debug_log('redraw finished (ANSI)')
71
147
  end
72
- io.print "\e[u" # restore
148
+
73
149
  io.flush
150
+ rescue StandardError => e
151
+ debug_log("redraw error: #{e.class}: #{e.message}")
152
+ end
153
+
154
+ # Flush the buffered lines to the given IO (defaults to STDOUT).
155
+ # This is used when capturing non-live output: capture silently during
156
+ # the run and emit all captured output at the end.
157
+ def flush_to(io = $stdout)
158
+ buf = lines
159
+ return if buf.empty?
160
+
161
+ begin
162
+ buf.each do |line|
163
+ io.puts(line)
164
+ end
165
+ io.flush
166
+ rescue StandardError => e
167
+ debug_log("flush_to error: #{e.class}: #{e.message}")
168
+ end
74
169
  end
75
170
 
76
171
  private
@@ -78,13 +173,21 @@ module RubyProgress
78
173
  def spawn_and_read
79
174
  PTY.spawn(@command) do |reader, _writer, pid|
80
175
  @child_pid = pid
176
+ debug_log("spawned pid=#{pid} cmd=#{@command}")
177
+
81
178
  until reader.eof? || @stop
82
- next unless reader.wait_readable(0.1)
179
+ ready = if reader.respond_to?(:wait_readable)
180
+ reader.wait_readable(0.1)
181
+ else
182
+ IO.select([reader], nil, nil, 0.1)
183
+ end
184
+ next unless ready
83
185
 
84
186
  chunk = reader.read_nonblock(4096, exception: false)
85
187
  next if chunk.nil? || chunk.empty?
86
188
 
87
- # lazily open log file if requested
189
+ debug_log("read chunk=#{chunk.inspect}")
190
+
88
191
  if @log_path && !@log_file
89
192
  begin
90
193
  FileUtils.mkdir_p(File.dirname(@log_path))
@@ -95,29 +198,26 @@ module RubyProgress
95
198
  end
96
199
 
97
200
  process_chunk(chunk)
201
+ debug_log("after process_chunk buffer_size=#{@buffer.size}")
202
+
98
203
  next unless @log_file
99
204
 
100
205
  begin
101
206
  @log_file.write(chunk)
102
207
  @log_file.flush
103
208
  rescue StandardError
104
- # ignore logging errors
209
+ # ignore
105
210
  end
106
211
  end
107
212
  end
213
+
108
214
  begin
109
215
  Process.wait(@child_pid) if @child_pid
110
216
  @exit_status = $CHILD_STATUS.exitstatus if $CHILD_STATUS
111
217
  rescue StandardError
112
218
  @exit_status = nil
113
219
  ensure
114
- if @log_file
115
- begin
116
- @log_file.close
117
- rescue StandardError
118
- nil
119
- end
120
- end
220
+ @log_file&.close
121
221
  end
122
222
  rescue Errno::EIO
123
223
  # PTY finished
@@ -125,12 +225,49 @@ module RubyProgress
125
225
 
126
226
  def process_chunk(chunk)
127
227
  @buf_mutex.synchronize do
128
- # split into lines, keep last N lines
129
228
  chunk.each_line do |line|
130
229
  @buffer << line.chomp
131
230
  @buffer.shift while @buffer.size > @lines
132
231
  end
133
232
  end
233
+
234
+ debug_log("process_chunk: buffer=#{@buffer.inspect}")
235
+ redraw($stderr) if @stream
236
+ rescue StandardError => e
237
+ debug_log("process_chunk error: #{e.class}: #{e.message}")
238
+ end
239
+
240
+ def debug_log(msg)
241
+ return unless ENV['RUBY_PROGRESS_DEBUG'] || @debug
242
+
243
+ begin
244
+ File.open(@debug_path, 'a') do |f|
245
+ f.puts("#{Time.now.iso8601} PID=#{Process.pid} #{msg}")
246
+ end
247
+ rescue StandardError
248
+ # swallow logging errors
249
+ end
250
+ end
251
+
252
+ def reserve_space(io = $stderr)
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}")
134
271
  end
135
272
  end
136
273
  end
@@ -41,12 +41,17 @@ module RubyProgress
41
41
  def self.display_completion(message, success: true, show_checkmark: false, output_stream: :warn, icons: {})
42
42
  return unless message
43
43
 
44
- mark = if show_checkmark
45
- icon = success ? (icons[:success] || '✅') : (icons[:error] || '🛑')
46
- "#{icon} "
47
- else
48
- ''
49
- end
44
+ # Determine the mark to show. If checkmarks are enabled, prefer the
45
+ # default icons but allow overrides via icons hash. If checkmarks are not
46
+ # enabled, still show a custom icon when provided via CLI options.
47
+ mark = ''
48
+ if show_checkmark
49
+ icon = success ? (icons[:success] || '✅') : (icons[:error] || '🛑')
50
+ mark = "#{icon} "
51
+ else
52
+ custom_icon = success ? icons[:success] : icons[:error]
53
+ mark = custom_icon ? "#{custom_icon} " : ''
54
+ end
50
55
 
51
56
  formatted_message = "#{mark}#{message}"
52
57
 
@@ -2,11 +2,11 @@
2
2
 
3
3
  module RubyProgress
4
4
  # Main gem version
5
- VERSION = '1.3.2'
5
+ VERSION = '1.3.5'
6
6
 
7
7
  # Component-specific versions (patch bumps)
8
- WORM_VERSION = '1.1.4'
9
- TWIRL_VERSION = '1.1.4'
10
- RIPPLE_VERSION = '1.1.4'
11
- FILL_VERSION = '1.0.4'
8
+ WORM_VERSION = '1.1.5'
9
+ TWIRL_VERSION = '1.1.5'
10
+ RIPPLE_VERSION = '1.1.5'
11
+ FILL_VERSION = '1.0.5'
12
12
  end
@@ -65,6 +65,9 @@ module RubyProgress
65
65
  @error_text = options[:error]
66
66
  @show_checkmark = options[:checkmark] || false
67
67
  @output_stdout = options[:stdout] || false
68
+ @output_lines = options[:output_lines]
69
+ @output_position = options[:output_position]
70
+ @output_live = options[:stdout_live] || false
68
71
  @direction_mode = options[:direction] || :bidirectional
69
72
  @start_chars, @end_chars = RubyProgress::Utils.parse_ends(options[:ends])
70
73
  @running = false
@@ -197,67 +200,11 @@ module RubyProgress
197
200
  }
198
201
  end
199
202
 
200
- def animation_loop
201
- position = 0
202
- direction = 1
203
-
204
- while @running
205
- message_part = @message && !@message.empty? ? "#{@message} " : ''
206
- # Enhanced line clearing for better daemon mode behavior
207
- $stderr.print "\r\e[2K#{@start_chars}#{message_part}#{generate_dots(position, direction)}#{@end_chars}"
208
- $stderr.flush
209
-
210
- sleep @speed
211
-
212
- position += direction
213
- if position >= @length - 1
214
- if @direction_mode == :forward_only
215
- position = 0
216
- else
217
- direction = -1
218
- end
219
- elsif position <= 0
220
- direction = 1
221
- end
222
- end
223
- end
224
-
225
- # Enhanced animation loop for daemon mode with aggressive line clearing
226
- def animation_loop_daemon_mode(stop_requested_proc: -> { false })
227
- position = 0
228
- direction = 1
229
- frame_count = 0
230
-
231
- while @running && !stop_requested_proc.call
232
- message_part = @message && !@message.empty? ? "#{@message} " : ''
233
-
234
- # Always clear current line
235
- $stderr.print "\r\e[2K"
236
-
237
- # Every few frames, use aggressive clearing to handle interruptions
238
- if (frame_count % 10).zero?
239
- $stderr.print "\e[1A\e[2K" # Move up and clear that line too (in case of interruption)
240
- $stderr.print "\r" # Return to start
241
- end
242
-
243
- $stderr.print "#{message_part}#{generate_dots(position, direction)}"
244
- $stderr.flush
245
-
246
- sleep @speed
247
- frame_count += 1
248
-
249
- position += direction
250
- if position >= @length - 1
251
- if @direction_mode == :forward_only
252
- position = 0
253
- else
254
- direction = -1
255
- end
256
- elsif position <= 0
257
- direction = 1
258
- end
259
- end
260
- end
203
+ # animation_loop and animation_loop_daemon_mode are implemented in
204
+ # the WormRunner module so they can share the redraw behavior that
205
+ # integrates with RubyProgress::OutputCapture. Do not redefine them
206
+ # here, otherwise the module implementations (which call
207
+ # @output_capture&.redraw) will be overridden.
261
208
 
262
209
  def generate_dots(ripple_position, direction)
263
210
  dots = Array.new(@length) { @style[:baseline] }