ruby-progress 1.3.1 → 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
@@ -13,6 +13,8 @@
13
13
  # and clear visual separation of different features.
14
14
 
15
15
  require 'io/console'
16
+ require 'shellwords'
17
+ require 'tmpdir'
16
18
 
17
19
  # Demo runner that exercises the major features of the ruby-progress gem
18
20
  # used by the documentation and screencast recordings.
@@ -26,6 +28,11 @@ class ProgressDemo
26
28
  header: "\e[1;36m", # Bright cyan
27
29
  command: "\e[1;33m", # Bright yellow
28
30
  description: "\e[0;32m", # Green
31
+ exec: "\e[1;33m", # executable (bright yellow)
32
+ tool: "\e[1;35m", # prg/tool (bright magenta)
33
+ flag: "\e[1;36m", # flags (bright cyan)
34
+ value: "\e[1;37m", # flag values (bright white)
35
+ prompt: "\e[2m", # prompt (dim)
29
36
  reset: "\e[0m", # Reset
30
37
  dim: "\e[2m" # Dim
31
38
  }
@@ -33,12 +40,13 @@ class ProgressDemo
33
40
 
34
41
  def run
35
42
  clear_screen
36
- show_title
43
+
44
+ # show_title
37
45
 
38
46
  # Introduction
39
- show_section_header('Ruby Progress Gem Demo')
40
- show_description('Demonstrating terminal progress indicators with style!')
41
- pause_for_narration(3)
47
+ # show_section_header('Ruby Progress Gem Demo')
48
+ # show_description('Demonstrating terminal progress indicators with style!')
49
+ # pause_for_narration(3)
42
50
 
43
51
  # Basic examples for each command
44
52
  demo_basic_commands
@@ -63,17 +71,17 @@ class ProgressDemo
63
71
 
64
72
  # Ripple - basic expanding circle animation
65
73
  show_demo_header('Ripple', 'Expanding circle animation for tasks with unknown duration')
66
- run_command("#{ruby_cmd} ripple --command 'sleep 4' --success 'Download complete!' --checkmark")
74
+ run_command("#{ruby_cmd} ripple --command 'sleep 2' --success 'Download complete!' --checkmark PROCESSING")
67
75
  pause_between_demos
68
76
 
69
77
  # Worm - progress bar animation
70
78
  show_demo_header('Worm', 'Animated progress bar for visual feedback')
71
- run_command("#{ruby_cmd} worm --length 10 --command 'sleep 5' --success 'Processing finished!' --checkmark")
79
+ run_command("#{ruby_cmd} worm --length 4 --command 'sleep 5' --success 'Processing finished!' --checkmark --message 'Loading'")
72
80
  pause_between_demos
73
81
 
74
82
  # Twirl - spinning indicator
75
83
  show_demo_header('Twirl', 'Classic spinning indicator for quick tasks')
76
- 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")
77
85
  pause_between_demos
78
86
  end
79
87
 
@@ -83,39 +91,35 @@ class ProgressDemo
83
91
  # Ripple styles
84
92
  show_demo_header('Ripple Styles', 'Different visual patterns')
85
93
  show_command_info('Default ripple style')
86
- run_command("#{ruby_cmd} ripple --command 'sleep 3' --success 'Default style'")
87
- pause_between_demos(2)
88
-
89
- show_command_info('Pulse style')
90
- run_command("#{ruby_cmd} ripple --style pulse --command 'sleep 3' --success 'Pulse style'")
94
+ run_command("#{ruby_cmd} ripple --command 'sleep 3' --success 'Default style' 'Rippling Progress Default Style'")
91
95
  pause_between_demos
92
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
+
93
101
  # Worm styles
94
102
  show_demo_header('Worm Styles', 'Various progress bar animations')
95
103
  show_command_info('Classic worm style')
96
- run_command("#{ruby_cmd} worm --length 10 --style classic --command 'sleep 4' --success 'Classic worm'")
97
- pause_between_demos(2)
98
-
99
- show_command_info('Emoji worm style')
100
- run_command("#{ruby_cmd} worm --length 10 --style emoji --command 'sleep 4' --success 'Emoji worm! 🎉'")
101
- pause_between_demos(2)
104
+ run_command("#{ruby_cmd} worm --length 10 --style classic --command 'sleep 4' --success 'Classic worm' --message 'Classic'")
105
+ pause_between_demos
102
106
 
103
107
  show_command_info('Blocks worm style')
104
- run_command("#{ruby_cmd} worm --length 10 --style blocks --command 'sleep 4' --success 'Block worm'")
108
+ run_command("#{ruby_cmd} worm --length 10 --style blocks --command 'sleep 4' --success 'Block worm' --message 'Blocks'")
105
109
  pause_between_demos
106
110
 
107
111
  # Twirl styles
108
112
  show_demo_header('Twirl Styles', 'Different spinning patterns')
109
113
  show_command_info('Classic spinner')
110
- run_command("#{ruby_cmd} twirl --style classic --command 'sleep 3' --success 'Classic spin'")
111
- pause_between_demos(2)
114
+ run_command("#{ruby_cmd} twirl --style classic --command 'sleep 3' --success 'Classic spin' --message 'Loading'")
115
+ pause_between_demos
112
116
 
113
117
  show_command_info('Dots spinner')
114
118
  run_command("#{ruby_cmd} twirl --style dots --command 'sleep 3' --success 'Dotty!'")
115
- pause_between_demos(2)
119
+ pause_between_demos
116
120
 
117
121
  show_command_info('Arrow spinner')
118
- run_command("#{ruby_cmd} twirl --style arrow --command 'sleep 3' --success 'Arrow spin'")
122
+ run_command("#{ruby_cmd} twirl --style arrow --command 'sleep 2' --success 'Arrow spin' --message 'Loading'")
119
123
  pause_between_demos
120
124
  end
121
125
 
@@ -125,80 +129,170 @@ class ProgressDemo
125
129
  # Error handling
126
130
  show_demo_header('Error Handling', 'Graceful failure with custom messages')
127
131
  show_command_info('Simulating a failed task')
128
- run_command("#{ruby_cmd} worm --length 10 --command 'sleep 2 && exit 1' --error 'Something went wrong!' --fail-icon")
132
+ run_command("#{ruby_cmd} worm --length 10 --command 'sleep 3 && exit 1' --error 'Error message!' --checkmark")
129
133
  pause_between_demos
130
134
 
131
135
  # Custom colors (if supported)
132
136
  show_demo_header('Success Messages', 'Custom completion messages')
133
137
  show_command_info('Custom success message with checkmark')
134
- run_command("#{ruby_cmd} ripple --command 'sleep 3' --success 'Data synchronized successfully' --checkmark")
135
- pause_between_demos(2)
136
-
137
- show_command_info('Different success icon')
138
- run_command("#{ruby_cmd} twirl --command 'sleep 3' --success 'Backup completed' --icon '✓'")
138
+ run_command("#{ruby_cmd} ripple --command 'sleep 3' --success 'Data synchronized successfully' --checkmark --message 'Syncing data...'")
139
139
  pause_between_demos
140
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
+
141
145
  # No completion message
142
146
  show_demo_header('Silent Completion', 'Progress without completion messages')
143
147
  show_command_info('Silent completion (no message)')
144
- run_command("#{ruby_cmd} worm --length 10 --command 'sleep 3'")
148
+ run_command("#{ruby_cmd} worm --length 10 --command 'sleep 2'")
145
149
  pause_between_demos
146
150
  end
147
151
 
148
152
  def demo_new_features
149
- show_section_header('New in v1.2.0 - Enhanced Features')
153
+ # show_section_header('New in v1.2.0 - Enhanced Features')
150
154
 
151
155
  # Universal --ends flag
152
156
  show_demo_header('Universal --ends Flag', 'Add decorative start/end characters')
153
157
  show_command_info("Ripple with square brackets: --ends '[]'")
154
- run_command("#{ruby_cmd} ripple --ends '[]' --command 'sleep 4' --success 'Framed ripple!'")
155
- pause_between_demos(2)
158
+ run_command("#{ruby_cmd} ripple --ends '[]' --command 'sleep 3' --success 'Framed ripple!' 'With a frame'")
159
+ pause_between_demos
156
160
 
157
- show_command_info("Worm with angle brackets: --ends '<<>>'")
158
- run_command("#{ruby_cmd} worm --length 10 --ends '<<>>' --command 'sleep 4' --success 'Angled worm!'")
159
- 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
160
164
 
161
- show_command_info("Twirl with emoji decoration: --ends '🎯🎪'")
162
- run_command("#{ruby_cmd} twirl --ends '🎯🎪' --command 'sleep 3' --success 'Emoji decorated!'")
163
- 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
164
168
 
165
169
  # Worm direction control
166
170
  show_demo_header('Worm Direction Control', 'Forward-only vs bidirectional movement')
167
171
  show_command_info('Bidirectional worm (default back-and-forth)')
168
- run_command("#{ruby_cmd} worm --length 10 --direction bidirectional --command 'sleep 5' --success 'Back and forth!'")
169
- 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
170
174
 
171
175
  show_command_info('Forward-only worm (resets at end)')
172
- 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!'")
173
177
  pause_between_demos
174
178
 
175
179
  # Custom worm styles
176
- show_demo_header('Custom Worm Styles', 'User-defined 3-character patterns')
177
- show_command_info('ASCII custom style: --style custom=_-=')
178
- run_command("#{ruby_cmd} worm --length 10 --style custom=_-= --command 'sleep 4' --success 'Custom ASCII!'")
179
- 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
180
184
 
181
185
  show_command_info('Unicode custom style: --style custom=▫▪■')
182
- run_command("#{ruby_cmd} worm --length 10 --style custom=▫▪■ --command 'sleep 4' --success 'Custom Unicode!'")
183
- 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
184
188
 
185
189
  show_command_info('Emoji custom style: --style custom=🟦🟨🟥')
186
- 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!'")
187
191
  pause_between_demos
188
192
 
189
193
  # Combining features
190
- show_demo_header('Feature Combinations', 'Mixing multiple options together')
191
- show_command_info('Custom style + direction + ends: the full package!')
192
- # Split the long command string to avoid RuboCop line-length issues while preserving behavior
193
- part1 = "#{ruby_cmd} worm --length 10 --style custom=.🟡* --direction forward "
194
- part2 = "--ends '【】' --command 'sleep 5' --success 'Ultimate combo!' --checkmark"
195
- run_command(part1 + part2)
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
201
+
202
+ # v1.3.x additions: output capture and job queue demo snippets
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
+ show_command_info('Capture command stdout/stderr into reserved rows:')
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"
209
+ run_command(part_a + part_b)
210
+ pause_between_demos
211
+
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)
196
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
197
291
  end
198
292
 
199
293
  def show_finale
200
294
  show_section_header('Demo Complete!')
201
- show_description('Ruby Progress Gem v1.2.0 - Making terminal progress beautiful! 🚀')
295
+ show_description('Ruby Progress Gem v1.3.2 - Making terminal progress beautiful! 🚀')
202
296
  puts
203
297
  show_description('Key features demonstrated:')
204
298
  puts "#{@colors[:description]} • Three animation types: ripple, worm, twirl#{@colors[:reset]}"
@@ -217,18 +311,20 @@ class ProgressDemo
217
311
  # Utility methods
218
312
 
219
313
  def ruby_cmd
220
- "ruby -I #{@lib_path} #{@gem_path}"
314
+ # "ruby -I #{@lib_path} #{@gem_path}"
315
+ 'bin/prg'
221
316
  end
222
317
 
223
318
  def clear_screen
224
319
  system('clear') || system('cls')
320
+ system('tput cup 15 0')
225
321
  end
226
322
 
227
323
  def show_title
228
324
  puts
229
325
  puts "#{@colors[:header]}#{'=' * 60}#{@colors[:reset]}"
230
326
  puts "#{@colors[:header]} RUBY PROGRESS GEM - DEMO SCREENCAST#{@colors[:reset]}"
231
- puts "#{@colors[:header]} Version 1.2.0 Feature Demonstration#{@colors[:reset]}"
327
+ puts "#{@colors[:header]} Version 1.3.2 Feature Demonstration#{@colors[:reset]}"
232
328
  puts "#{@colors[:header]}#{'=' * 60}#{@colors[:reset]}"
233
329
  puts
234
330
  end
@@ -257,19 +353,75 @@ class ProgressDemo
257
353
  end
258
354
 
259
355
  def run_command(cmd)
260
- puts "#{@colors[:command]}$ #{cmd}#{@colors[:reset]}"
356
+ # Type the command with syntax highlighting
357
+ type_command("$ #{cmd.sub(%r{^bin/}, '')}")
261
358
  puts
262
359
  system(cmd)
263
360
  puts
264
361
  end
265
362
 
363
+ # Type out a shell command with simple token-based syntax highlighting.
364
+ # - executable (first token) uses @colors[:exec]
365
+ # - flags (tokens starting with '-') use @colors[:flag]
366
+ # - values use @colors[:value]
367
+ # The function prints one character at a time to simulate typing.
368
+ def type_command(line, speed: 0.02)
369
+ tokens = line.scan(/'[^']*'|"[^"]*"|\S+/)
370
+ pos = 0
371
+
372
+ # Determine the executable token position (skip leading prompt '$' tokens)
373
+ exec_pos = tokens.find_index { |t| !t.start_with?('$') }
374
+ subcmd_pos = exec_pos ? exec_pos + 1 : nil
375
+
376
+ tokens.each_with_index do |token, idx|
377
+ # Find token in original line starting at pos (to preserve spacing)
378
+ start_idx = line.index(token, pos) || pos
379
+
380
+ # print any intermediate whitespace
381
+ inter = line[pos...start_idx]
382
+ $stdout.print(inter) if inter && !inter.empty?
383
+
384
+ # Default color for tokens is value
385
+ color = @colors[:value]
386
+
387
+ if token.start_with?('$')
388
+ color = @colors[:prompt]
389
+ elsif idx == exec_pos && token == 'prg'
390
+ # Highlight the bundled tool name in magenta
391
+ color = @colors[:tool]
392
+ elsif idx == exec_pos
393
+ # Executable token (e.g. ruby or other) - keep exec color
394
+ color = @colors[:exec]
395
+ elsif idx == subcmd_pos
396
+ # Subcommand (ripple/twirl/worm/fill etc.) highlighted in yellow
397
+ color = @colors[:command]
398
+ elsif token.start_with?('-')
399
+ color = @colors[:flag]
400
+ end
401
+
402
+ # Print the token one char at a time
403
+ token.each_char do |ch|
404
+ $stdout.print("#{color}#{ch}#{@colors[:reset]}")
405
+ $stdout.flush
406
+ sleep(speed)
407
+ end
408
+
409
+ pos = start_idx + token.length
410
+ end
411
+
412
+ # Print any trailing whitespace/newline
413
+ trailing = line[pos..-1]
414
+ $stdout.print(trailing) if trailing
415
+ $stdout.flush
416
+ end
417
+
266
418
  def pause_for_narration(seconds = 2)
267
- puts "#{@colors[:dim]}[Pausing #{seconds}s for narration...]#{@colors[:reset]}"
268
- sleep(seconds)
419
+ # puts "#{@colors[:dim]}[Pausing #{seconds}s for narration...]#{@colors[:reset]}"
420
+ # sleep(seconds)
269
421
  end
270
422
 
271
- def pause_between_demos(seconds = 3)
272
- puts "#{@colors[:dim]}[Pausing #{seconds}s between demos...]#{@colors[:reset]}"
423
+ def pause_between_demos(seconds = 1)
424
+ # puts "#{@colors[:dim]}[Pausing #{seconds}s between demos...]#{@colors[:reset]}"
273
425
  sleep(seconds)
274
426
  end
275
427
  end
@@ -278,14 +430,14 @@ end
278
430
  if __FILE__ == $PROGRAM_NAME
279
431
  demo = ProgressDemo.new
280
432
 
281
- puts 'Ruby Progress Gem Demo Screencast'
282
- puts '================================='
283
- puts
284
- puts 'This script will demonstrate various features of the ruby-progress gem.'
285
- puts 'Press ENTER to start the demo, or Ctrl+C to exit.'
286
- puts
287
- print 'Ready? '
288
- gets
433
+ # puts 'Ruby Progress Gem Demo Screencast'
434
+ # puts '================================='
435
+ # puts
436
+ # puts 'This script will demonstrate various features of the ruby-progress gem.'
437
+ # puts 'Press ENTER to start the demo, or Ctrl+C to exit.'
438
+ # puts
439
+ # print 'Ready? '
440
+ # gets
289
441
 
290
442
  begin
291
443
  demo.run
@@ -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,
@@ -60,6 +62,10 @@ module RubyProgress
60
62
  options[:output_lines] = n
61
63
  end
62
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
+
63
69
  opts.separator ''
64
70
  opts.separator 'Progress Control:'
65
71
 
@@ -106,6 +112,14 @@ module RubyProgress
106
112
  options[:success_message] = msg
107
113
  end
108
114
 
115
+ opts.on('--success-icon ICON', 'Custom success icon to show with completion messages') do |ic|
116
+ options[:success_icon] = ic
117
+ end
118
+
119
+ opts.on('--error-icon ICON', 'Custom error icon to show with failure messages') do |ic|
120
+ options[:error_icon] = ic
121
+ end
122
+
109
123
  opts.on('--error MESSAGE', 'Error message to display on cancellation') do |msg|
110
124
  options[:error_message] = msg
111
125
  end
@@ -121,6 +135,21 @@ module RubyProgress
121
135
  options[:daemon] = true
122
136
  end
123
137
 
138
+ opts.on('--daemon-as NAME', 'Run in daemon mode with custom name (creates /tmp/ruby-progress/NAME.pid)') do |name|
139
+ options[:daemon] = true
140
+ options[:daemon_name] = name
141
+ end
142
+
143
+ # Accept --daemon-name as alias for --daemon-as
144
+ opts.on('--daemon-name NAME', 'Alias for --daemon-as (compat)') do |name|
145
+ options[:daemon] = true
146
+ options[:daemon_name] = name
147
+ end
148
+
149
+ opts.on('--no-detach', 'When used with --daemon/--daemon-as: run background child but do not fully detach from the terminal') do
150
+ options[:no_detach] = true
151
+ end
152
+
124
153
  opts.on('--pid-file FILE', 'PID file location (default: /tmp/ruby-progress/fill.pid)') do |file|
125
154
  options[:pid_file] = file
126
155
  end
@@ -129,10 +158,36 @@ module RubyProgress
129
158
  options[:stop] = true
130
159
  end
131
160
 
161
+ opts.on('--stop-id NAME', 'Stop daemon by name (implies --stop)') do |name|
162
+ # Backwards-compatible shorthand used in demos: set stop flag
163
+ options[:stop] = true
164
+ # Normalize to canonical keys used by FillCLI (daemon_name/status_name)
165
+ options[:stop_name] = name
166
+ options[:daemon_name] = name
167
+ options[:status_name] = name
168
+ end
169
+
132
170
  opts.on('--status', 'Show daemon status') do
133
171
  options[:status] = true
134
172
  end
135
173
 
174
+ opts.on('--status-id NAME', 'Show daemon status by name') do |name|
175
+ options[:status] = true
176
+ # Normalize to canonical key
177
+ options[:status_name] = name
178
+ options[:daemon_name] = name
179
+ end
180
+
181
+ opts.on('--stop-success MESSAGE', 'Stop daemon with success message (implies --stop)') do |msg|
182
+ options[:stop] = true
183
+ options[:stop_success] = msg
184
+ end
185
+ opts.on('--stop-error MESSAGE', 'Stop daemon with error message (implies --stop)') do |msg|
186
+ options[:stop] = true
187
+ options[:stop_error] = msg
188
+ end
189
+ opts.on('--stop-checkmark', 'When stopping, include a success checkmark') { options[:stop_checkmark] = true }
190
+
136
191
  opts.separator ''
137
192
  opts.separator 'General:'
138
193
 
@@ -155,9 +210,9 @@ module RubyProgress
155
210
  warn "Run 'prg fill --help' for more information."
156
211
  exit 1
157
212
  end
158
-
159
213
  options
160
214
  end
215
+ # rubocop :enable Metrics/MethodLength
161
216
 
162
217
  def self.help_text
163
218
  opts = OptionParser.new
@@ -211,5 +266,6 @@ module RubyProgress
211
266
  opts.to_s
212
267
  end
213
268
  end
269
+ # rubocop:enable Metrics/AbcSize, Metrics/BlockLength
214
270
  end
215
271
  end
@@ -28,12 +28,43 @@ module JobCLI
28
28
  options = { wait: false }
29
29
  opt = OptionParser.new do |o|
30
30
  o.banner = 'Usage: prg job send [options]'
31
- o.on('--pid-file PATH', 'Path to daemon pid file') { |v| options[:pid_file] = v }
32
- o.on('--daemon-name NAME', 'Daemon name (maps to /tmp/ruby-progress/NAME.pid)') { |v| options[:daemon_name] = v }
33
- o.on('--command CMD', 'Command to run') { |v| options[:command] = v }
34
- o.on('--stdin', 'Read command from stdin (overrides --command)') { options[:stdin] = true }
35
- o.on('--wait', 'Wait for result file and print it') { options[:wait] = true }
36
- o.on('--timeout SECONDS', Integer, 'Timeout seconds for wait') { |v| options[:timeout] = v }
31
+ o.on('--pid-file PATH', 'Path to daemon pid file') do |v|
32
+ options[:pid_file] = v
33
+ end
34
+ o.on('--daemon-name NAME', 'Daemon name (maps to /tmp/ruby-progress/NAME.pid)') do |v|
35
+ options[:daemon_name] = v
36
+ end
37
+ o.on('--command CMD', 'Command to run') do |v|
38
+ options[:command] = v
39
+ end
40
+ o.on('--stdin', 'Read command from stdin (overrides --command)') do
41
+ options[:stdin] = true
42
+ end
43
+ o.on('--advance', 'Send an advance action (no value)') do
44
+ options[:action] = 'advance'
45
+ end
46
+ o.on('--percent N', Integer, 'Send a percent action with value N') do |v|
47
+ options[:action] = 'percent'
48
+ options[:value] = v
49
+ end
50
+ o.on('--complete', 'Send a complete action (no value)') do
51
+ options[:action] = 'complete'
52
+ end
53
+ o.on('--cancel', 'Send a cancel action (no value)') do
54
+ options[:action] = 'cancel'
55
+ end
56
+ o.on('--action ACTION', 'Send a custom action name') do |v|
57
+ options[:action] = v
58
+ end
59
+ o.on('--value VAL', 'Value for the action (string or number)') do |v|
60
+ options[:value] = v
61
+ end
62
+ o.on('--wait', 'Wait for result file and print it') do
63
+ options[:wait] = true
64
+ end
65
+ o.on('--timeout SECONDS', Integer, 'Timeout seconds for wait') do |v|
66
+ options[:timeout] = v
67
+ end
37
68
  end
38
69
 
39
70
  rest = opt.parse(argv)
@@ -63,16 +94,25 @@ module JobCLI
63
94
  opts[:command]
64
95
  end
65
96
 
66
- unless cmd && !cmd.strip.empty?
67
- warn 'No command specified. Use --command or --stdin.'
68
- exit 1
97
+ is_action = !opts[:action].nil? && opts[:action] != false
98
+
99
+ if is_action
100
+ if cmd && !cmd.strip.empty?
101
+ warn 'Cannot specify both --command/--stdin and an action flag'
102
+ exit 1
103
+ end
104
+ else
105
+ unless cmd && !cmd.strip.empty?
106
+ warn 'No command specified. Use --command, --stdin, or pass an action flag.'
107
+ exit 1
108
+ end
69
109
  end
70
110
 
71
111
  job_id = SecureRandom.uuid
72
112
  tmp = File.join(job_dir, "#{job_id}.json.tmp")
73
113
  final = File.join(job_dir, "#{job_id}.json")
74
114
 
75
- payload = { 'id' => job_id, 'command' => cmd }
115
+ payload = build_payload(opts, job_id, cmd)
76
116
 
77
117
  File.write(tmp, JSON.dump(payload))
78
118
  FileUtils.mv(tmp, final)
@@ -96,4 +136,24 @@ module JobCLI
96
136
  puts job_id
97
137
  end
98
138
  end
139
+
140
+ # Build the JSON payload for a job based on parsed options.
141
+ def self.build_payload(opts, job_id, cmd)
142
+ payload = { 'id' => job_id }
143
+
144
+ is_action = !opts[:action].nil? && opts[:action] != false
145
+
146
+ if is_action
147
+ payload['action'] = opts[:action]
148
+ if opts.key?(:value)
149
+ val = opts[:value]
150
+ payload['value'] = val.to_i if val.is_a?(String) && val =~ /^\d+$/
151
+ payload['value'] ||= val
152
+ end
153
+ else
154
+ payload['command'] = cmd
155
+ end
156
+
157
+ payload
158
+ end
99
159
  end