ruby-progress 1.3.2 → 1.3.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.
data/demo_screencast.rb CHANGED
@@ -40,6 +40,7 @@ class ProgressDemo
40
40
 
41
41
  def run
42
42
  clear_screen
43
+
43
44
  # show_title
44
45
 
45
46
  # Introduction
@@ -70,17 +71,17 @@ class ProgressDemo
70
71
 
71
72
  # Ripple - basic expanding circle animation
72
73
  show_demo_header('Ripple', 'Expanding circle animation for tasks with unknown duration')
73
- run_command("#{ruby_cmd} ripple --command 'sleep 4' --success 'Download complete!' --checkmark PROCESSING")
74
+ run_command("#{ruby_cmd} ripple --command 'sleep 2' --success 'Download complete!' --checkmark PROCESSING")
74
75
  pause_between_demos
75
76
 
76
77
  # Worm - progress bar animation
77
78
  show_demo_header('Worm', 'Animated progress bar for visual feedback')
78
- run_command("#{ruby_cmd} worm --length 10 --command 'sleep 5' --success 'Processing finished!' --checkmark --message 'Loading'")
79
+ run_command("#{ruby_cmd} worm --length 4 --command 'sleep 5' --success 'Processing finished!' --checkmark --message 'Loading'")
79
80
  pause_between_demos
80
81
 
81
82
  # Twirl - spinning indicator
82
83
  show_demo_header('Twirl', 'Classic spinning indicator for quick tasks')
83
- run_command("#{ruby_cmd} twirl --command 'sleep 3' --success 'Task completed!' --checkmark")
84
+ run_command("#{ruby_cmd} twirl --command 'sleep 2' --success 'Task completed!' --checkmark")
84
85
  pause_between_demos
85
86
  end
86
87
 
@@ -91,17 +92,17 @@ class ProgressDemo
91
92
  show_demo_header('Ripple Styles', 'Different visual patterns')
92
93
  show_command_info('Default ripple style')
93
94
  run_command("#{ruby_cmd} ripple --command 'sleep 3' --success 'Default style' 'Rippling Progress Default Style'")
94
- pause_between_demos(2)
95
-
96
- show_command_info('Pulse style')
97
- run_command("#{ruby_cmd} ripple --style pulse --command 'sleep 3' --success 'Pulse style' 'Rippling Progress'")
98
95
  pause_between_demos
99
96
 
97
+ # show_command_info('Pulse style')
98
+ # run_command("#{ruby_cmd} ripple --style pulse --command 'sleep 3' --success 'Pulse style' 'Rippling Progress'")
99
+ # pause_between_demos
100
+
100
101
  # Worm styles
101
102
  show_demo_header('Worm Styles', 'Various progress bar animations')
102
103
  show_command_info('Classic worm style')
103
104
  run_command("#{ruby_cmd} worm --length 10 --style classic --command 'sleep 4' --success 'Classic worm' --message 'Classic'")
104
- pause_between_demos(2)
105
+ pause_between_demos
105
106
 
106
107
  show_command_info('Blocks worm style')
107
108
  run_command("#{ruby_cmd} worm --length 10 --style blocks --command 'sleep 4' --success 'Block worm' --message 'Blocks'")
@@ -111,14 +112,14 @@ class ProgressDemo
111
112
  show_demo_header('Twirl Styles', 'Different spinning patterns')
112
113
  show_command_info('Classic spinner')
113
114
  run_command("#{ruby_cmd} twirl --style classic --command 'sleep 3' --success 'Classic spin' --message 'Loading'")
114
- pause_between_demos(2)
115
+ pause_between_demos
115
116
 
116
117
  show_command_info('Dots spinner')
117
118
  run_command("#{ruby_cmd} twirl --style dots --command 'sleep 3' --success 'Dotty!'")
118
- pause_between_demos(2)
119
+ pause_between_demos
119
120
 
120
121
  show_command_info('Arrow spinner')
121
- run_command("#{ruby_cmd} twirl --style arrow --command 'sleep 3' --success 'Arrow spin' --message 'Loading'")
122
+ run_command("#{ruby_cmd} twirl --style arrow --command 'sleep 2' --success 'Arrow spin' --message 'Loading'")
122
123
  pause_between_demos
123
124
  end
124
125
 
@@ -128,159 +129,165 @@ class ProgressDemo
128
129
  # Error handling
129
130
  show_demo_header('Error Handling', 'Graceful failure with custom messages')
130
131
  show_command_info('Simulating a failed task')
131
- run_command("#{ruby_cmd} worm --length 10 --command 'sleep 2 && exit 1' --error 'Something went wrong!' --checkmark")
132
+ run_command("#{ruby_cmd} worm --length 10 --command 'sleep 3 && exit 1' --error 'Error message!' --checkmark")
132
133
  pause_between_demos
133
134
 
134
135
  # Custom colors (if supported)
135
136
  show_demo_header('Success Messages', 'Custom completion messages')
136
137
  show_command_info('Custom success message with checkmark')
137
138
  run_command("#{ruby_cmd} ripple --command 'sleep 3' --success 'Data synchronized successfully' --checkmark --message 'Syncing data...'")
138
- pause_between_demos(2)
139
-
140
- show_command_info('Different success icon')
141
- run_command("#{ruby_cmd} twirl --command 'sleep 3' --success 'Backup completed' --success-icon '✓' --checkmark")
142
139
  pause_between_demos
143
140
 
141
+ # show_command_info('Different success icon')
142
+ # run_command("#{ruby_cmd} twirl --command 'sleep 3' --success 'Backup completed' --success-icon '✓' --checkmark")
143
+ # pause_between_demos
144
+
144
145
  # No completion message
145
146
  show_demo_header('Silent Completion', 'Progress without completion messages')
146
147
  show_command_info('Silent completion (no message)')
147
- run_command("#{ruby_cmd} worm --length 10 --command 'sleep 3'")
148
+ run_command("#{ruby_cmd} worm --length 10 --command 'sleep 2'")
148
149
  pause_between_demos
149
150
  end
150
151
 
151
152
  def demo_new_features
152
- show_section_header('New in v1.2.0 - Enhanced Features')
153
+ # show_section_header('New in v1.2.0 - Enhanced Features')
153
154
 
154
155
  # Universal --ends flag
155
156
  show_demo_header('Universal --ends Flag', 'Add decorative start/end characters')
156
157
  show_command_info("Ripple with square brackets: --ends '[]'")
157
- run_command("#{ruby_cmd} ripple --ends '[]' --command 'sleep 4' --success 'Framed ripple!' 'With a frame'")
158
- pause_between_demos(2)
158
+ run_command("#{ruby_cmd} ripple --ends '[]' --command 'sleep 3' --success 'Framed ripple!' 'With a frame'")
159
+ pause_between_demos
159
160
 
160
- show_command_info("Worm with angle brackets: --ends '<<>>'")
161
- run_command("#{ruby_cmd} worm --length 10 --ends '<<>>' --command 'sleep 4' --success 'Angled worm!'")
162
- pause_between_demos(2)
161
+ # show_command_info("Worm with angle brackets: --ends '<<>>'")
162
+ # run_command("#{ruby_cmd} worm --length 10 --ends '<<>>' --command 'sleep 4' --success 'Angled worm!'")
163
+ # pause_between_demos
163
164
 
164
- show_command_info("Twirl with emoji decoration: --ends '🎯🎪'")
165
- run_command("#{ruby_cmd} twirl --ends '🎯🎪' --command 'sleep 3' --success 'Emoji decorated!'")
166
- pause_between_demos
165
+ # show_command_info("Twirl with emoji decoration: --ends '🎯🎪'")
166
+ # run_command("#{ruby_cmd} twirl --ends '🎯🎪' --command 'sleep 3' --success 'Emoji decorated!'")
167
+ # pause_between_demos
167
168
 
168
169
  # Worm direction control
169
170
  show_demo_header('Worm Direction Control', 'Forward-only vs bidirectional movement')
170
171
  show_command_info('Bidirectional worm (default back-and-forth)')
171
- run_command("#{ruby_cmd} worm --length 10 --direction bidirectional --command 'sleep 5' --success 'Back and forth!'")
172
- pause_between_demos(2)
172
+ run_command("#{ruby_cmd} worm --length 10 --direction bidirectional --command 'sleep 3' --speed fast --success 'Both ways'")
173
+ pause_between_demos
173
174
 
174
175
  show_command_info('Forward-only worm (resets at end)')
175
- run_command("#{ruby_cmd} worm --length 10 --direction forward --command 'sleep 5' --success 'Always forward!'")
176
+ run_command("#{ruby_cmd} worm --length 5 --direction forward --command 'sleep 4' --speed fast --success 'Forward only!'")
176
177
  pause_between_demos
177
178
 
178
179
  # Custom worm styles
179
- show_demo_header('Custom Worm Styles', 'User-defined 3-character patterns')
180
- show_command_info('ASCII custom style: --style custom=_-=')
181
- run_command("#{ruby_cmd} worm --length 10 --style custom=_-= --command 'sleep 4' --success 'Custom ASCII!'")
182
- pause_between_demos(2)
180
+ # show_demo_header('Custom Worm Styles', 'User-defined 3-character patterns')
181
+ # show_command_info('ASCII custom style: --style custom=_-=')
182
+ # run_command("#{ruby_cmd} worm --length 10 --style custom=_-= --command 'sleep 4' --success 'Custom ASCII!'")
183
+ # pause_between_demos
183
184
 
184
185
  show_command_info('Unicode custom style: --style custom=▫▪■')
185
- run_command("#{ruby_cmd} worm --length 10 --style custom=▫▪■ --command 'sleep 4' --success 'Custom Unicode!'")
186
- pause_between_demos(2)
186
+ run_command("#{ruby_cmd} worm --length 10 --style custom=▫▪■ --command 'sleep 3' --speed fast --success 'Custom Unicode!'")
187
+ pause_between_demos
187
188
 
188
189
  show_command_info('Emoji custom style: --style custom=🟦🟨🟥')
189
- run_command("#{ruby_cmd} worm --length 10 --style custom=🟦🟨🟥 --command 'sleep 4' --success 'Custom emoji!'")
190
+ run_command("#{ruby_cmd} worm --length 10 --style custom=🟦🟨🟥 --command 'sleep 3' --speed fast --success 'Custom emoji!'")
190
191
  pause_between_demos
191
192
 
192
193
  # Combining features
193
- show_demo_header('Feature Combinations', 'Mixing multiple options together')
194
- show_command_info('Custom style + direction + ends: the full package!')
195
- # Split the long command string to avoid RuboCop line-length issues while preserving behavior
196
- part1 = "#{ruby_cmd} worm --length 10 --style custom=.🟡* --direction forward "
197
- part2 = "--ends '【】' --command 'sleep 5' --success 'Ultimate combo!' --checkmark"
198
- run_command(part1 + part2)
199
- pause_between_demos
194
+ # show_demo_header('Feature Combinations', 'Mixing multiple options together')
195
+ # show_command_info('Custom style + direction + ends: the full package!')
196
+ # # Split the long command string to avoid RuboCop line-length issues while preserving behavior
197
+ # part1 = "#{ruby_cmd} worm --length 10 --style custom=.🟡* --direction forward "
198
+ # part2 = "--ends '【】' --command 'sleep 5' --success 'Ultimate combo!' --checkmark"
199
+ # run_command(part1 + part2)
200
+ # pause_between_demos
200
201
 
201
202
  # v1.3.x additions: output capture and job queue demo snippets
202
- show_section_header('New in v1.3.x - Output Capture & Job Queue')
203
-
204
- show_demo_header('Fill --command (output capture)', 'Run a command and reserve terminal rows for output while preserving animation')
203
+ # show_section_header('New in v1.3.x - Output Capture & Job Queue')
204
+ show_demo_header('Worm --command (output capture)', 'Run a command and reserve terminal rows for output while preserving animation')
205
205
  show_command_info('Capture command stdout/stderr into reserved rows:')
206
- part_a = "#{ruby_cmd} fill --command \"bash -lc 'for i in 1 2 3; do echo line:$i; sleep 1; done'\" "
207
- part_b = "--output-lines 3 --output-position top --success 'Captured!' --checkmark"
206
+ # ensure a trailing space so flags aren't squashed onto the command string
207
+ part_a = %(#{ruby_cmd} worm --style blocks --length 10 --speed fast --command "ruby -e 'for i in 1..3 do; puts \\"line:\#{i}\\"; sleep 1; end'" )
208
+ part_b = "--stdout --success 'Captured!' --checkmark"
208
209
  run_command(part_a + part_b)
209
210
  pause_between_demos
210
211
 
211
- show_demo_header('prg job send (enqueue a job)', 'Send a job to a running daemon using the file-based job queue')
212
- show_command_info('Example: create a job payload and atomically enqueue it for the daemon')
213
- show_command_info('Use the bundled helper to enqueue control/action jobs:')
214
- puts "#{@colors[:command]}$ prg job send --daemon-name demo --advance#{@colors[:reset]}"
215
- puts "#{@colors[:command]}$ prg job send --daemon-name demo --percent 42#{@colors[:reset]}"
216
- puts
217
- show_command_info('Or enqueue a shell command:')
218
-
219
- # For the demo we run the worker in the foreground so you can see the
220
- # live animation and completion message inline. Daemon mode (started with
221
- # `--daemon` or `--daemon-as`) detaches to the background and processes
222
- # jobs via the file-based queue; `prg job send` targets a background
223
- # daemon and prints the job result JSON, but won't show the daemon's
224
- # animation in the foreground.
225
- show_command_info('Start a named fill daemon that processes percent/action jobs')
226
- # Start the fill daemon in non-detaching background mode so animation remains visible
227
- run_command("#{ruby_cmd} fill --daemon-as demo --no-detach --output-lines 3 --output-position top --success 'Captured!' --checkmark")
228
- pause_between_demos(1)
229
-
230
- show_command_info('We will enqueue several percent actions via a small shell script')
231
- demo_script = <<~BASH
232
- #!/usr/bin/env bash
233
- set -eu
234
- echo "Sending percent updates to demo daemon (atomic mktemp+mv writes)"
235
-
236
- job_dir="/tmp/ruby-progress/demo.jobs"
237
- mkdir -p "$job_dir"
238
-
239
- enqueue_percent() {
240
- percent=$1
241
- # Build JSON payload
242
- id=$(uuidgen 2>/dev/null || echo "job-$(date +%s%N)")
243
- tmp=$(mktemp "$job_dir/${id}.json.tmp.XXXXXX")
244
- printf '%s\n' '{"id":"'"${id}"'","action":"percent","value":'"${percent}"'}' > "$tmp"
245
- mv "$tmp" "$job_dir/${id}.json"
246
-
247
- # Wait for the daemon to process the job (poll for .processing.result)
248
- result_path="$job_dir/${id}.json.processing.result"
249
- start=$(date +%s)
250
- timeout=10
251
- while [ ! -f "$result_path" ]; do
252
- sleep 0.1
253
- now=$(date +%s)
254
- if [ $((now - start)) -gt $timeout ]; then
255
- echo "Timed out waiting for result for job ${id}" >&2
256
- return 2
257
- fi
258
- done
259
- cat "$result_path"
260
- }
261
-
262
- enqueue_percent 10
263
- sleep 1
264
- enqueue_percent 40
265
- sleep 1
266
- enqueue_percent 70
267
- sleep 1
268
- enqueue_percent 100
269
-
270
- # After updates, stop the daemon cleanly (call local bin/prg to avoid global conflicts)
271
- #{ruby_cmd} fill --stop-id demo --stop-success 'Demo daemon stopped'
272
- BASH
273
-
274
- # Show the simulated script contents
275
- puts "#{@colors[:command]}$ cat demo_percent_updates.sh#{@colors[:reset]}"
276
- puts demo_script
277
-
278
- # Write and execute the script (run in a subshell so output doesn't interleave too badly)
279
- script_path = File.join(Dir.tmpdir, "demo_percent_updates_#{Time.now.to_i}.sh")
280
- File.write(script_path, demo_script)
281
- File.chmod(0o755, script_path)
282
- run_command("bash #{Shellwords.escape(script_path)}")
212
+ show_command_info('Capture command stdout/stderr and display live:')
213
+ part_a = %(#{ruby_cmd} worm --style blocks --length 10 --speed fast --command "ruby -e 'for i in 1..5 do; puts \\"line:\#{i}\\"; sleep 0.5; end'" )
214
+ part_b = "--output-lines 3 --output-position top --stdout-live --success 'Captured live!' --checkmark"
215
+ run_command(part_a + part_b)
283
216
  pause_between_demos
217
+
218
+ # show_demo_header('prg job send (enqueue a job)', 'Send a job to a running daemon using the file-based job queue')
219
+ # show_command_info('Example: create a job payload and atomically enqueue it for the daemon')
220
+ # show_command_info('Use the bundled helper to enqueue control/action jobs:')
221
+ # puts "#{@colors[:command]}$ prg job send --daemon-name demo --advance#{@colors[:reset]}"
222
+ # puts "#{@colors[:command]}$ prg job send --daemon-name demo --percent 42#{@colors[:reset]}"
223
+ # puts
224
+ # show_command_info('Or enqueue a shell command:')
225
+
226
+ # # For the demo we run the worker in the foreground so you can see the
227
+ # # live animation and completion message inline. Daemon mode (started with
228
+ # # `--daemon` or `--daemon-as`) detaches to the background and processes
229
+ # # jobs via the file-based queue; `prg job send` targets a background
230
+ # # daemon and prints the job result JSON, but won't show the daemon's
231
+ # # animation in the foreground.
232
+ # show_command_info('Start a named fill daemon that processes percent/action jobs')
233
+ # # Start the fill daemon in non-detaching background mode so animation remains visible
234
+ # run_command("#{ruby_cmd} fill --daemon-as demo --no-detach --output-lines 3 --output-position top --success 'Captured!' --checkmark")
235
+ # pause_between_demos(1)
236
+
237
+ # show_command_info('We will enqueue several percent actions via a small shell script')
238
+ # demo_script = <<~BASH
239
+ # #!/usr/bin/env bash
240
+ # set -eu
241
+ # echo "Sending percent updates to demo daemon (atomic mktemp+mv writes)"
242
+
243
+ # job_dir="/tmp/ruby-progress/demo.jobs"
244
+ # mkdir -p "$job_dir"
245
+
246
+ # enqueue_percent() {
247
+ # percent=$1
248
+ # # Build JSON payload
249
+ # id=$(uuidgen 2>/dev/null || echo "job-$(date +%s%N)")
250
+ # tmp=$(mktemp "$job_dir/${id}.json.tmp.XXXXXX")
251
+ # printf '%s\n' '{"id":"'"${id}"'","action":"percent","value":'"${percent}"'}' > "$tmp"
252
+ # mv "$tmp" "$job_dir/${id}.json"
253
+
254
+ # # Wait for the daemon to process the job (poll for .processing.result)
255
+ # result_path="$job_dir/${id}.json.processing.result"
256
+ # start=$(date +%s)
257
+ # timeout=10
258
+ # while [ ! -f "$result_path" ]; do
259
+ # sleep 0.1
260
+ # now=$(date +%s)
261
+ # if [ $((now - start)) -gt $timeout ]; then
262
+ # echo "Timed out waiting for result for job ${id}" >&2
263
+ # return 2
264
+ # fi
265
+ # done
266
+ # cat "$result_path"
267
+ # }
268
+
269
+ # enqueue_percent 10
270
+ # sleep 1
271
+ # enqueue_percent 40
272
+ # sleep 1
273
+ # enqueue_percent 70
274
+ # sleep 1
275
+ # enqueue_percent 100
276
+
277
+ # # After updates, stop the daemon cleanly (call local bin/prg to avoid global conflicts)
278
+ # #{ruby_cmd} fill --stop-id demo --stop-success 'Demo daemon stopped'
279
+ # BASH
280
+
281
+ # # Show the simulated script contents
282
+ # puts "#{@colors[:command]}$ cat demo_percent_updates.sh#{@colors[:reset]}"
283
+ # puts demo_script
284
+
285
+ # # Write and execute the script (run in a subshell so output doesn't interleave too badly)
286
+ # script_path = File.join(Dir.tmpdir, "demo_percent_updates_#{Time.now.to_i}.sh")
287
+ # File.write(script_path, demo_script)
288
+ # File.chmod(0o755, script_path)
289
+ # run_command("bash #{Shellwords.escape(script_path)}")
290
+ # pause_between_demos
284
291
  end
285
292
 
286
293
  def show_finale
@@ -310,6 +317,7 @@ class ProgressDemo
310
317
 
311
318
  def clear_screen
312
319
  system('clear') || system('cls')
320
+ system('tput cup 15 0')
313
321
  end
314
322
 
315
323
  def show_title
@@ -357,7 +365,7 @@ class ProgressDemo
357
365
  # - flags (tokens starting with '-') use @colors[:flag]
358
366
  # - values use @colors[:value]
359
367
  # The function prints one character at a time to simulate typing.
360
- def type_command(line, speed: 0.04)
368
+ def type_command(line, speed: 0.02)
361
369
  tokens = line.scan(/'[^']*'|"[^"]*"|\S+/)
362
370
  pos = 0
363
371
 
@@ -412,7 +420,7 @@ class ProgressDemo
412
420
  # sleep(seconds)
413
421
  end
414
422
 
415
- def pause_between_demos(seconds = 2)
423
+ def pause_between_demos(seconds = 1)
416
424
  # puts "#{@colors[:dim]}[Pausing #{seconds}s between demos...]#{@colors[:reset]}"
417
425
  sleep(seconds)
418
426
  end
@@ -5,7 +5,9 @@ require 'optparse'
5
5
  module RubyProgress
6
6
  module FillCLI
7
7
  # Option parsing extracted to reduce module length in FillCLI
8
+ # rubocop:disable Metrics/AbcSize, Metrics/BlockLength
8
9
  module Options
10
+ # rubocop :disable Metrics/MethodLength
9
11
  def self.parse_cli_options
10
12
  options = {
11
13
  style: :blocks,
@@ -28,7 +30,6 @@ module RubyProgress
28
30
  report: false
29
31
  }
30
32
 
31
- # rubocop:disable Metrics/BlockLength
32
33
  begin
33
34
  OptionParser.new do |opts|
34
35
  opts.banner = 'Usage: prg fill [options]'
@@ -61,6 +62,10 @@ module RubyProgress
61
62
  options[:output_lines] = n
62
63
  end
63
64
 
65
+ opts.on('--stdout-live', 'Stream captured output to STDOUT as it arrives (non-blocking)') do
66
+ options[:stdout_live] = true
67
+ end
68
+
64
69
  opts.separator ''
65
70
  opts.separator 'Progress Control:'
66
71
 
@@ -198,7 +203,6 @@ module RubyProgress
198
203
  options[:help] = true
199
204
  end
200
205
  end.parse!
201
- # rubocop:enable Metrics/BlockLength
202
206
  rescue OptionParser::InvalidOption => e
203
207
  warn "Invalid option: #{e.args.first}"
204
208
  warn ''
@@ -208,6 +212,7 @@ module RubyProgress
208
212
  end
209
213
  options
210
214
  end
215
+ # rubocop :enable Metrics/MethodLength
211
216
 
212
217
  def self.help_text
213
218
  opts = OptionParser.new
@@ -261,5 +266,6 @@ module RubyProgress
261
266
  opts.to_s
262
267
  end
263
268
  end
269
+ # rubocop:enable Metrics/AbcSize, Metrics/BlockLength
264
270
  end
265
271
  end
@@ -71,7 +71,12 @@ module RippleCLI
71
71
  if $stdout.tty?
72
72
  # Interactive TTY: use PTY-based capture so the animation can run while the
73
73
  # command executes. We only print captured stdout if options[:output] == :stdout.
74
- oc = RubyProgress::OutputCapture.new(command: options[:command], lines: options[:output_lines] || 3, position: options[:output_position] || :above)
74
+ oc = RubyProgress::OutputCapture.new(
75
+ command: options[:command],
76
+ lines: options[:output_lines] || 3,
77
+ position: options[:output_position] || :above,
78
+ stream: options[:output] == :stdout || options[:stdout_live]
79
+ )
75
80
  oc.start
76
81
 
77
82
  # Create rippler. Attach output capture only when the user requested
@@ -91,6 +91,10 @@ module RippleCLI
91
91
  options[:output] = :stdout
92
92
  end
93
93
 
94
+ opts.on('--stdout-live', 'Stream captured output to STDOUT as it arrives (non-blocking)') do
95
+ options[:stdout_live] = true
96
+ end
97
+
94
98
  opts.on('--quiet', 'Suppress all output') do
95
99
  options[:output] = :quiet
96
100
  end
@@ -81,6 +81,10 @@ module TwirlCLI
81
81
  options[:stdout] = true
82
82
  end
83
83
 
84
+ opts.on('--stdout-live', 'Stream captured output to STDOUT as it arrives (non-blocking)') do
85
+ options[:stdout_live] = true
86
+ end
87
+
84
88
  opts.separator ''
85
89
  opts.separator 'Daemon Mode:'
86
90
 
@@ -23,11 +23,12 @@ module TwirlRunner
23
23
  RubyProgress::Utils.hide_cursor
24
24
  spinner_thread = Thread.new { loop { spinner.animate } }
25
25
 
26
- if $stdout.tty? && options[:stdout]
26
+ if $stdout.tty? && (options[:stdout] || options[:stdout_live])
27
27
  oc = RubyProgress::OutputCapture.new(
28
28
  command: options[:command],
29
29
  lines: options[:output_lines] || 3,
30
- position: options[:output_position] || :above
30
+ position: options[:output_position] || :above,
31
+ stream: options[:stdout] || options[:stdout_live]
31
32
  )
32
33
  oc.start
33
34
 
@@ -110,7 +111,8 @@ module TwirlRunner
110
111
  oc = RubyProgress::OutputCapture.new(
111
112
  command: job['command'],
112
113
  lines: options[:output_lines] || 3,
113
- position: options[:output_position] || :above
114
+ position: options[:output_position] || :above,
115
+ stream: options[:stdout] || options[:stdout_live]
114
116
  )
115
117
  oc.start
116
118
 
@@ -76,7 +76,8 @@ module WormCLI
76
76
  command: job['command'],
77
77
  lines: options[:output_lines] || 3,
78
78
  position: options[:output_position] || :above,
79
- log_path: log_path
79
+ log_path: log_path,
80
+ stream: options[:stdout] || options[:stdout_live]
80
81
  )
81
82
  oc.start
82
83
 
@@ -83,6 +83,10 @@ module WormCLI
83
83
  options[:stdout] = true
84
84
  end
85
85
 
86
+ opts.on('--stdout-live', 'Stream captured output to STDOUT as it arrives (non-blocking)') do
87
+ options[:stdout_live] = true
88
+ end
89
+
86
90
  opts.separator ''
87
91
  opts.separator 'Daemon Mode:'
88
92
 
@@ -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
@@ -233,8 +234,12 @@ module WormRunner
233
234
  $stderr.print "\r\e[2K"
234
235
 
235
236
  if (frame_count % 10).zero?
236
- $stderr.print "\e[1A\e[2K"
237
- $stderr.print "\r"
237
+ # Use ANSI save/restore to clear the previous line without moving the
238
+ # global cursor position. This prevents the animation from erasing
239
+ # reserved output lines that we draw elsewhere.
240
+ $stderr.print "\e7" # save
241
+ $stderr.print "\e[1A\e[2K\r"
242
+ $stderr.print "\e8" # restore
238
243
  end
239
244
 
240
245
  $stderr.print "#{message_part}#{generate_dots(position, direction)}"
@@ -136,7 +136,8 @@ module RubyProgress
136
136
  command: job['command'],
137
137
  lines: options[:output_lines] || 3,
138
138
  position: options[:output_position] || :above,
139
- log_path: log_path
139
+ log_path: log_path,
140
+ stream: options[:stdout_live]
140
141
  )
141
142
  oc.start
142
143
 
@@ -298,7 +299,8 @@ module RubyProgress
298
299
  command: options[:command],
299
300
  lines: options[:output_lines] || 3,
300
301
  position: options[:output_position] || :above,
301
- log_path: nil
302
+ log_path: nil,
303
+ stream: options[:stdout] || options[:stdout_live]
302
304
  )
303
305
  oc.start
304
306