ruby-progress 1.2.0 → 1.3.1

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.
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Ruby Progress Gem Demo Screencast Script
5
+ # ========================================
6
+ #
7
+ # This script creates a comprehensive demonstration of the ruby-progress gem
8
+ # showing all major features across the three subcommands: ripple, worm, and twirl.
9
+ #
10
+ # Usage: ruby demo_screencast.rb
11
+ #
12
+ # The script includes pauses between demonstrations to allow for narration
13
+ # and clear visual separation of different features.
14
+
15
+ require 'io/console'
16
+
17
+ # Demo runner that exercises the major features of the ruby-progress gem
18
+ # used by the documentation and screencast recordings.
19
+ class ProgressDemo
20
+ def initialize
21
+ @gem_path = File.expand_path('bin/prg', __dir__)
22
+ @lib_path = File.expand_path('lib', __dir__)
23
+
24
+ # Colors for terminal output
25
+ @colors = {
26
+ header: "\e[1;36m", # Bright cyan
27
+ command: "\e[1;33m", # Bright yellow
28
+ description: "\e[0;32m", # Green
29
+ reset: "\e[0m", # Reset
30
+ dim: "\e[2m" # Dim
31
+ }
32
+ end
33
+
34
+ def run
35
+ clear_screen
36
+ show_title
37
+
38
+ # Introduction
39
+ show_section_header('Ruby Progress Gem Demo')
40
+ show_description('Demonstrating terminal progress indicators with style!')
41
+ pause_for_narration(3)
42
+
43
+ # Basic examples for each command
44
+ demo_basic_commands
45
+
46
+ # Style demonstrations
47
+ demo_styles
48
+
49
+ # Advanced options
50
+ demo_advanced_options
51
+
52
+ # New v1.2.0 features
53
+ demo_new_features
54
+
55
+ # Finale
56
+ show_finale
57
+ end
58
+
59
+ private
60
+
61
+ def demo_basic_commands
62
+ show_section_header('Basic Commands Overview')
63
+
64
+ # Ripple - basic expanding circle animation
65
+ 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")
67
+ pause_between_demos
68
+
69
+ # Worm - progress bar animation
70
+ 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")
72
+ pause_between_demos
73
+
74
+ # Twirl - spinning indicator
75
+ show_demo_header('Twirl', 'Classic spinning indicator for quick tasks')
76
+ run_command("#{ruby_cmd} twirl --command 'sleep 3' --success 'Task completed!' --checkmark")
77
+ pause_between_demos
78
+ end
79
+
80
+ def demo_styles
81
+ show_section_header('Style Variations')
82
+
83
+ # Ripple styles
84
+ show_demo_header('Ripple Styles', 'Different visual patterns')
85
+ 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'")
91
+ pause_between_demos
92
+
93
+ # Worm styles
94
+ show_demo_header('Worm Styles', 'Various progress bar animations')
95
+ 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)
102
+
103
+ show_command_info('Blocks worm style')
104
+ run_command("#{ruby_cmd} worm --length 10 --style blocks --command 'sleep 4' --success 'Block worm'")
105
+ pause_between_demos
106
+
107
+ # Twirl styles
108
+ show_demo_header('Twirl Styles', 'Different spinning patterns')
109
+ show_command_info('Classic spinner')
110
+ run_command("#{ruby_cmd} twirl --style classic --command 'sleep 3' --success 'Classic spin'")
111
+ pause_between_demos(2)
112
+
113
+ show_command_info('Dots spinner')
114
+ run_command("#{ruby_cmd} twirl --style dots --command 'sleep 3' --success 'Dotty!'")
115
+ pause_between_demos(2)
116
+
117
+ show_command_info('Arrow spinner')
118
+ run_command("#{ruby_cmd} twirl --style arrow --command 'sleep 3' --success 'Arrow spin'")
119
+ pause_between_demos
120
+ end
121
+
122
+ def demo_advanced_options
123
+ show_section_header('Advanced Options')
124
+
125
+ # Error handling
126
+ show_demo_header('Error Handling', 'Graceful failure with custom messages')
127
+ 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")
129
+ pause_between_demos
130
+
131
+ # Custom colors (if supported)
132
+ show_demo_header('Success Messages', 'Custom completion messages')
133
+ 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 '✓'")
139
+ pause_between_demos
140
+
141
+ # No completion message
142
+ show_demo_header('Silent Completion', 'Progress without completion messages')
143
+ show_command_info('Silent completion (no message)')
144
+ run_command("#{ruby_cmd} worm --length 10 --command 'sleep 3'")
145
+ pause_between_demos
146
+ end
147
+
148
+ def demo_new_features
149
+ show_section_header('New in v1.2.0 - Enhanced Features')
150
+
151
+ # Universal --ends flag
152
+ show_demo_header('Universal --ends Flag', 'Add decorative start/end characters')
153
+ 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)
156
+
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)
160
+
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
164
+
165
+ # Worm direction control
166
+ show_demo_header('Worm Direction Control', 'Forward-only vs bidirectional movement')
167
+ 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)
170
+
171
+ 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!'")
173
+ pause_between_demos
174
+
175
+ # 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
+
181
+ 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)
184
+
185
+ show_command_info('Emoji custom style: --style custom=🟦🟨🟥')
186
+ run_command("#{ruby_cmd} worm --length 10 --style custom=🟦🟨🟥 --command 'sleep 4' --success 'Custom emoji!'")
187
+ pause_between_demos
188
+
189
+ # 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)
196
+ pause_between_demos
197
+ end
198
+
199
+ def show_finale
200
+ show_section_header('Demo Complete!')
201
+ show_description('Ruby Progress Gem v1.2.0 - Making terminal progress beautiful! 🚀')
202
+ puts
203
+ show_description('Key features demonstrated:')
204
+ puts "#{@colors[:description]} • Three animation types: ripple, worm, twirl#{@colors[:reset]}"
205
+ puts "#{@colors[:description]} • Multiple built-in styles#{@colors[:reset]}"
206
+ puts "#{@colors[:description]} • Universal --ends flag for decoration#{@colors[:reset]}"
207
+ puts "#{@colors[:description]} • Worm direction control#{@colors[:reset]}"
208
+ puts "#{@colors[:description]} • Custom user-defined styles#{@colors[:reset]}"
209
+ puts "#{@colors[:description]} • Success/error handling#{@colors[:reset]}"
210
+ puts "#{@colors[:description]} • Multi-byte character support (Unicode/emoji)#{@colors[:reset]}"
211
+ puts
212
+ show_description('Install: gem install ruby-progress')
213
+ show_description('GitHub: https://github.com/ttscoff/ruby-progress')
214
+ puts
215
+ end
216
+
217
+ # Utility methods
218
+
219
+ def ruby_cmd
220
+ "ruby -I #{@lib_path} #{@gem_path}"
221
+ end
222
+
223
+ def clear_screen
224
+ system('clear') || system('cls')
225
+ end
226
+
227
+ def show_title
228
+ puts
229
+ puts "#{@colors[:header]}#{'=' * 60}#{@colors[:reset]}"
230
+ puts "#{@colors[:header]} RUBY PROGRESS GEM - DEMO SCREENCAST#{@colors[:reset]}"
231
+ puts "#{@colors[:header]} Version 1.2.0 Feature Demonstration#{@colors[:reset]}"
232
+ puts "#{@colors[:header]}#{'=' * 60}#{@colors[:reset]}"
233
+ puts
234
+ end
235
+
236
+ def show_section_header(title)
237
+ puts
238
+ puts "#{@colors[:header]}#{'-' * 50}#{@colors[:reset]}"
239
+ puts "#{@colors[:header]} #{title}#{@colors[:reset]}"
240
+ puts "#{@colors[:header]}#{'-' * 50}#{@colors[:reset]}"
241
+ puts
242
+ end
243
+
244
+ def show_demo_header(title, description)
245
+ puts
246
+ puts "#{@colors[:header]}### #{title}#{@colors[:reset]}"
247
+ puts "#{@colors[:description]}#{description}#{@colors[:reset]}"
248
+ puts
249
+ end
250
+
251
+ def show_command_info(description)
252
+ puts "#{@colors[:dim]}#{description}#{@colors[:reset]}"
253
+ end
254
+
255
+ def show_description(text)
256
+ puts "#{@colors[:description]}#{text}#{@colors[:reset]}"
257
+ end
258
+
259
+ def run_command(cmd)
260
+ puts "#{@colors[:command]}$ #{cmd}#{@colors[:reset]}"
261
+ puts
262
+ system(cmd)
263
+ puts
264
+ end
265
+
266
+ def pause_for_narration(seconds = 2)
267
+ puts "#{@colors[:dim]}[Pausing #{seconds}s for narration...]#{@colors[:reset]}"
268
+ sleep(seconds)
269
+ end
270
+
271
+ def pause_between_demos(seconds = 3)
272
+ puts "#{@colors[:dim]}[Pausing #{seconds}s between demos...]#{@colors[:reset]}"
273
+ sleep(seconds)
274
+ end
275
+ end
276
+
277
+ # Script execution
278
+ if __FILE__ == $PROGRAM_NAME
279
+ demo = ProgressDemo.new
280
+
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
289
+
290
+ begin
291
+ demo.run
292
+ rescue Interrupt
293
+ puts "\n\nDemo interrupted. Thanks for watching!"
294
+ exit(0)
295
+ end
296
+ end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env sh
2
+ # Example: start a worm daemon, send a job, wait for result, then stop.
3
+ # This script assumes you're running from the project root and have a working
4
+ # `bin/prg` script in the repository.
5
+
6
+ set -eu
7
+
8
+ PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ PRG_BIN="$PROJECT_ROOT/bin/prg"
10
+
11
+ echo "Starting worm daemon (named 'example')..."
12
+ # prg detaches in daemon mode so no & needed
13
+ $PRG_BIN worm --daemon-as example --message "Example daemon"
14
+
15
+ sleep 0.2
16
+
17
+ echo "Sending job and waiting for result..."
18
+ $PRG_BIN job send --daemon-name example --command "echo hello; sleep 0.1" --wait --timeout 10
19
+
20
+ sleep 0.1
21
+
22
+ echo "Stopping daemon with success message..."
23
+ $PRG_BIN worm --stop-success "Example finished" --stop-checkmark --daemon-name example
24
+
25
+ echo "Done."
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Experimental terminal line protection for daemon mode
2
4
  # This demonstrates potential solutions for the daemon output interruption problem
3
5
 
4
6
  module RubyProgress
7
+ # SmartTerminal: experimental helpers for safer terminal cursor/line operations
8
+ # used by daemon-mode demos and scripts where output can interleave with animations.
5
9
  module SmartTerminal
6
10
  # Save current cursor position
7
11
  def self.save_cursor_position
@@ -16,6 +20,8 @@ module RubyProgress
16
20
  end
17
21
 
18
22
  # Get current cursor position (requires terminal interaction)
23
+ # rubocop:disable Naming/AccessorMethodName
24
+ # Intentionally named get_cursor_position because it performs an I/O request
19
25
  def self.get_cursor_position
20
26
  # This is complex and requires reading from stdin
21
27
  # which may not work reliably in daemon mode
@@ -24,6 +30,7 @@ module RubyProgress
24
30
  # Would need to read response: "\e[{row};{col}R"
25
31
  # But this requires terminal input capability
26
32
  end
33
+ # rubocop:enable Naming/AccessorMethodName
27
34
 
28
35
  # Alternative: Use absolute positioning
29
36
  def self.position_cursor_absolute(row, col)
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module RubyProgress
6
+ module FillCLI
7
+ # Option parsing extracted to reduce module length in FillCLI
8
+ module Options
9
+ def self.parse_cli_options
10
+ options = {
11
+ style: :blocks,
12
+ length: 20,
13
+ speed: :medium,
14
+ percent: nil,
15
+ advance: false,
16
+ complete: false,
17
+ cancel: false,
18
+ success_message: nil,
19
+ error_message: nil,
20
+ checkmark: false,
21
+ daemon: false,
22
+ pid_file: nil,
23
+ stop: false,
24
+ status: false,
25
+ current: false,
26
+ output_position: :above,
27
+ output_lines: 3,
28
+ report: false
29
+ }
30
+
31
+ begin
32
+ OptionParser.new do |opts|
33
+ opts.banner = 'Usage: prg fill [options]'
34
+ opts.separator ''
35
+ opts.separator 'Progress Bar Options:'
36
+
37
+ opts.on('-l', '--length LENGTH', Integer, 'Progress bar length (default: 20)') do |length|
38
+ options[:length] = length
39
+ end
40
+
41
+ opts.on('-s', '--style STYLE', 'Progress bar style (blocks, classic, dots, etc. or custom=XY)') do |style|
42
+ options[:style] = style
43
+ end
44
+
45
+ opts.on('--ends CHARS', 'Start/end characters (even number of chars, split in half)') do |chars|
46
+ options[:ends] = chars
47
+ end
48
+
49
+ opts.separator 'Output capture:'
50
+
51
+ opts.on('-c', '--command COMMAND', 'Command to run and capture output (optional)') do |cmd|
52
+ options[:command] = cmd
53
+ end
54
+
55
+ opts.on('--output-position POSITION', 'Position to render captured output: above or below (default: above)') do |pos|
56
+ options[:output_position] = pos.to_sym
57
+ end
58
+
59
+ opts.on('--output-lines N', Integer, 'Number of output lines to reserve for captured output (default: 3)') do |n|
60
+ options[:output_lines] = n
61
+ end
62
+
63
+ opts.separator ''
64
+ opts.separator 'Progress Control:'
65
+
66
+ opts.on('-p', '--percent PERCENT', Float, 'Set progress to percentage (0-100)') do |percent|
67
+ options[:percent] = percent
68
+ end
69
+
70
+ opts.on('--advance', 'Advance progress by one step') do
71
+ options[:advance] = true
72
+ end
73
+
74
+ opts.on('--complete', 'Complete the progress bar') do
75
+ options[:complete] = true
76
+ end
77
+
78
+ opts.on('--cancel', 'Cancel the progress bar') do
79
+ options[:cancel] = true
80
+ end
81
+
82
+ opts.on('--current', 'Show current progress percentage (0-100 float)') do
83
+ options[:current] = true
84
+ end
85
+
86
+ opts.on('--report', 'Show detailed progress report') do
87
+ options[:report] = true
88
+ end
89
+
90
+ opts.separator ''
91
+ opts.separator 'Auto-advance Mode:'
92
+
93
+ opts.on('--speed SPEED', 'Auto-advance speed (fast/medium/slow or numeric)') do |speed|
94
+ options[:speed] = case speed.downcase
95
+ when /^f/ then :fast
96
+ when /^m/ then :medium
97
+ when /^s/ then :slow
98
+ else speed.to_f.positive? ? speed.to_f : :medium
99
+ end
100
+ end
101
+
102
+ opts.separator ''
103
+ opts.separator 'Messages:'
104
+
105
+ opts.on('--success MESSAGE', 'Success message to display on completion') do |msg|
106
+ options[:success_message] = msg
107
+ end
108
+
109
+ opts.on('--error MESSAGE', 'Error message to display on cancellation') do |msg|
110
+ options[:error_message] = msg
111
+ end
112
+
113
+ opts.on('--checkmark', 'Show checkmarks (✅ success, 🛑 failure)') do
114
+ options[:checkmark] = true
115
+ end
116
+
117
+ opts.separator ''
118
+ opts.separator 'Daemon Mode:'
119
+
120
+ opts.on('--daemon', 'Run in background daemon mode') do
121
+ options[:daemon] = true
122
+ end
123
+
124
+ opts.on('--pid-file FILE', 'PID file location (default: /tmp/ruby-progress/fill.pid)') do |file|
125
+ options[:pid_file] = file
126
+ end
127
+
128
+ opts.on('--stop', 'Stop daemon') do
129
+ options[:stop] = true
130
+ end
131
+
132
+ opts.on('--status', 'Show daemon status') do
133
+ options[:status] = true
134
+ end
135
+
136
+ opts.separator ''
137
+ opts.separator 'General:'
138
+
139
+ opts.on('--show-styles', 'Show available fill styles with visual previews') do
140
+ options[:show_styles] = true
141
+ end
142
+
143
+ opts.on('-v', '--version', 'Show version') do
144
+ options[:version] = true
145
+ end
146
+
147
+ opts.on('-h', '--help', 'Show this help') do
148
+ options[:help] = true
149
+ end
150
+ end.parse!
151
+ rescue OptionParser::InvalidOption => e
152
+ warn "Invalid option: #{e.args.first}"
153
+ warn ''
154
+ warn 'Usage: prg fill [options]'
155
+ warn "Run 'prg fill --help' for more information."
156
+ exit 1
157
+ end
158
+
159
+ options
160
+ end
161
+
162
+ def self.help_text
163
+ opts = OptionParser.new
164
+
165
+ opts.banner = 'Usage: prg fill [options]'
166
+ opts.separator ''
167
+ opts.separator 'Progress Bar Options:'
168
+
169
+ opts.on('-l', '--length LENGTH', Integer, 'Progress bar length (default: 20)')
170
+ opts.on('-s', '--style STYLE', 'Progress bar style (blocks, classic, dots, etc. or custom=XY)')
171
+ opts.on('--ends CHARS', 'Start/end characters (even number of chars, split in half)')
172
+
173
+ opts.separator ''
174
+ opts.separator 'Progress Control:'
175
+ opts.on('-p', '--percent PERCENT', Float, 'Set progress to percentage (0-100)')
176
+ opts.on('--advance', 'Advance progress by one step')
177
+ opts.on('--complete', 'Complete the progress bar')
178
+ opts.on('--cancel', 'Cancel the progress bar')
179
+ opts.on('--current', 'Show current progress percentage (0-100 float)')
180
+ opts.on('--report', 'Show detailed progress report')
181
+
182
+ opts.separator ''
183
+ opts.separator 'Auto-advance Mode:'
184
+ opts.on('--speed SPEED', 'Auto-advance speed (fast/medium/slow or numeric)')
185
+
186
+ opts.separator ''
187
+ opts.separator 'Messages:'
188
+ opts.on('--success MESSAGE', 'Success message to display on completion')
189
+ opts.on('--error MESSAGE', 'Error message to display on cancellation')
190
+ opts.on('--checkmark', 'Show checkmarks (✅ success, 🛑 failure)')
191
+
192
+ opts.separator ''
193
+ opts.separator ''
194
+ opts.separator 'Output capture:'
195
+ opts.on('--output-position POSITION', 'Position to render captured output: above or below (default: above)')
196
+ opts.on('--output-lines N', Integer, 'Number of output lines to reserve for captured output (default: 3)')
197
+
198
+ opts.separator ''
199
+ opts.separator 'Daemon Mode:'
200
+ opts.on('--daemon', 'Run in background daemon mode')
201
+ opts.on('--pid-file FILE', 'PID file location (default: /tmp/ruby-progress/fill.pid)')
202
+ opts.on('--stop', 'Stop daemon')
203
+ opts.on('--status', 'Show daemon status')
204
+
205
+ opts.separator ''
206
+ opts.separator 'General:'
207
+ opts.on('--show-styles', 'Show available fill styles with visual previews')
208
+ opts.on('-v', '--version', 'Show version')
209
+ opts.on('-h', '--help', 'Show this help')
210
+
211
+ opts.to_s
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CLI: prg job
4
+
5
+ # Provides the `prg job send` helper to enqueue commands into a daemon's
6
+ # job directory. This file contains a minimal implementation used by tests.
7
+
8
+ require 'optparse'
9
+ require 'json'
10
+ require 'securerandom'
11
+ require 'fileutils'
12
+ require_relative '../daemon'
13
+
14
+ # Job CLI helpers
15
+ #
16
+ # Exposed as `prg job send`.
17
+ module JobCLI
18
+ # JobCLI
19
+ #
20
+ # Small CLI module that exposes `prg job send` for enqueuing jobs into the
21
+ # daemon job directory. This is intentionally minimal: it writes a single
22
+ # JSON file atomically and optionally waits for a result file created by
23
+ # the daemon's job processor.
24
+ # Simple CLI for submitting jobs to a running daemon job directory.
25
+ # Usage: prg job send --pid-file /tmp/... --command "echo hi" [--wait]
26
+ class Options
27
+ def self.parse(argv)
28
+ options = { wait: false }
29
+ opt = OptionParser.new do |o|
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 }
37
+ end
38
+
39
+ rest = opt.parse(argv)
40
+ options[:command] ||= rest.join(' ') unless rest.empty?
41
+ options
42
+ end
43
+ end
44
+
45
+ def self.send(argv = ARGV)
46
+ opts = Options.parse(argv)
47
+
48
+ # Resolve pid file
49
+ pid_file = if opts[:pid_file]
50
+ opts[:pid_file]
51
+ elsif opts[:daemon_name]
52
+ "/tmp/ruby-progress/#{opts[:daemon_name]}.pid"
53
+ else
54
+ RubyProgress::Daemon.default_pid_file
55
+ end
56
+
57
+ job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
58
+ FileUtils.mkdir_p(job_dir)
59
+
60
+ cmd = if opts[:stdin]
61
+ $stdin.read
62
+ else
63
+ opts[:command]
64
+ end
65
+
66
+ unless cmd && !cmd.strip.empty?
67
+ warn 'No command specified. Use --command or --stdin.'
68
+ exit 1
69
+ end
70
+
71
+ job_id = SecureRandom.uuid
72
+ tmp = File.join(job_dir, "#{job_id}.json.tmp")
73
+ final = File.join(job_dir, "#{job_id}.json")
74
+
75
+ payload = { 'id' => job_id, 'command' => cmd }
76
+
77
+ File.write(tmp, JSON.dump(payload))
78
+ FileUtils.mv(tmp, final)
79
+
80
+ if opts[:wait]
81
+ timeout = opts[:timeout] || 10
82
+ start = Time.now
83
+ result_path = "#{final}.processing.result"
84
+ loop do
85
+ if File.exist?(result_path)
86
+ puts File.read(result_path)
87
+ break
88
+ end
89
+ if Time.now - start > timeout
90
+ warn 'Timed out waiting for result'
91
+ exit 2
92
+ end
93
+ sleep 0.1
94
+ end
95
+ else
96
+ puts job_id
97
+ end
98
+ end
99
+ end