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.
@@ -1,159 +1,198 @@
1
1
  # frozen_string_literal: true
2
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.
3
+ # CLI: prg job [subcommand]
4
+ # Send control messages to backgrounded progress indicators.
7
5
 
8
6
  require 'optparse'
9
7
  require 'json'
10
- require 'securerandom'
11
8
  require 'fileutils'
12
9
  require_relative '../daemon'
13
10
 
14
- # Job CLI helpers
11
+ # JobCLI - sends control messages to backgrounded progress indicators
15
12
  #
16
- # Exposed as `prg job send`.
13
+ # Usage:
14
+ # prg job stop --daemon-name mytask [--message "Done!"] [--checkmark]
15
+ # prg job advance --daemon-name mytask [--amount 10]
16
+ # prg job status --daemon-name mytask
17
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') 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
68
- end
18
+ def self.run(argv = ARGV)
19
+ if argv.empty?
20
+ print_help
21
+ exit 1
22
+ end
69
23
 
70
- rest = opt.parse(argv)
71
- options[:command] ||= rest.join(' ') unless rest.empty?
72
- options
24
+ subcommand = argv.shift
25
+
26
+ case subcommand
27
+ when 'stop'
28
+ stop(argv)
29
+ when 'advance'
30
+ advance(argv)
31
+ when 'status'
32
+ status(argv)
33
+ when 'send'
34
+ # Backward compatibility: 'send' is now 'stop'
35
+ warn "Warning: 'prg job send' is deprecated. Use 'prg job stop' instead."
36
+ stop(argv)
37
+ when '--help', '-h'
38
+ print_help
39
+ else
40
+ warn "Error: Unknown subcommand '#{subcommand}'"
41
+ print_help
42
+ exit 1
73
43
  end
74
44
  end
75
45
 
76
- def self.send(argv = ARGV)
77
- opts = Options.parse(argv)
78
-
79
- # Resolve pid file
80
- pid_file = if opts[:pid_file]
81
- opts[:pid_file]
82
- elsif opts[:daemon_name]
83
- "/tmp/ruby-progress/#{opts[:daemon_name]}.pid"
84
- else
85
- RubyProgress::Daemon.default_pid_file
86
- end
87
-
88
- job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
89
- FileUtils.mkdir_p(job_dir)
90
-
91
- cmd = if opts[:stdin]
92
- $stdin.read
93
- else
94
- opts[:command]
95
- end
96
-
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
46
+ def self.print_help
47
+ puts 'Usage: prg job [subcommand] [options]'
48
+ puts
49
+ puts 'Subcommands:'
50
+ puts ' stop Stop a running progress indicator'
51
+ puts ' advance Advance a fill progress bar'
52
+ puts ' status Check status of a running indicator'
53
+ puts
54
+ puts 'Common Options:'
55
+ puts ' --daemon-name NAME Name of the daemon to control'
56
+ puts ' --pid-file PATH Path to daemon PID file'
57
+ puts
58
+ puts 'Examples:'
59
+ puts ' prg job stop --daemon-name mytask --message "Complete!"'
60
+ puts ' prg job advance --daemon-name mybar --amount 10'
61
+ puts ' prg job status --daemon-name mytask'
62
+ end
63
+
64
+ def self.resolve_pid_file(opts)
65
+ if opts[:pid_file]
66
+ opts[:pid_file]
67
+ elsif opts[:daemon_name]
68
+ "/tmp/ruby-progress/#{opts[:daemon_name]}.pid"
104
69
  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
70
+ RubyProgress::Daemon.default_pid_file
71
+ end
72
+ end
73
+
74
+ def self.stop(argv)
75
+ opts = parse_stop_options(argv)
76
+ pid_file = resolve_pid_file(opts)
77
+
78
+ unless File.exist?(pid_file)
79
+ warn "PID file #{pid_file} not found. Is the daemon running?"
80
+ exit 1
109
81
  end
110
82
 
111
- job_id = SecureRandom.uuid
112
- tmp = File.join(job_dir, "#{job_id}.json.tmp")
113
- final = File.join(job_dir, "#{job_id}.json")
114
-
115
- payload = build_payload(opts, job_id, cmd)
116
-
117
- File.write(tmp, JSON.dump(payload))
118
- FileUtils.mv(tmp, final)
119
-
120
- if opts[:wait]
121
- timeout = opts[:timeout] || 10
122
- start = Time.now
123
- result_path = "#{final}.processing.result"
124
- loop do
125
- if File.exist?(result_path)
126
- puts File.read(result_path)
127
- break
128
- end
129
- if Time.now - start > timeout
130
- warn 'Timed out waiting for result'
131
- exit 2
132
- end
133
- sleep 0.1
83
+ RubyProgress::Daemon.stop_daemon_by_pid_file(
84
+ pid_file,
85
+ message: opts[:message],
86
+ checkmark: opts[:checkmark] || false,
87
+ error: opts[:error] || false
88
+ )
89
+
90
+ # Don't output confirmation - the daemon itself shows the completion message
91
+ end
92
+
93
+ def self.advance(argv)
94
+ opts = parse_advance_options(argv)
95
+ pid_file = resolve_pid_file(opts)
96
+
97
+ unless File.exist?(pid_file)
98
+ warn "PID file #{pid_file} not found. Is the daemon running?"
99
+ exit 1
100
+ end
101
+
102
+ # Write advance command to control message file
103
+ cmf = RubyProgress::Daemon.control_message_file(pid_file)
104
+ control_data = {
105
+ action: 'advance',
106
+ amount: opts[:amount] || 1,
107
+ total: opts[:total]
108
+ }.compact
109
+
110
+ File.write(cmf, JSON.generate(control_data))
111
+
112
+ # Send signal to daemon to check for messages
113
+ pid = File.read(pid_file).strip.to_i
114
+ begin
115
+ Process.kill('USR2', pid)
116
+ rescue Errno::ESRCH
117
+ # Process doesn't exist
118
+ end
119
+
120
+ # Silent operation for script-friendly usage
121
+ end
122
+
123
+ def self.status(argv)
124
+ opts = parse_status_options(argv)
125
+ pid_file = resolve_pid_file(opts)
126
+
127
+ RubyProgress::Daemon.show_status(pid_file)
128
+ end
129
+
130
+ def self.parse_stop_options(argv)
131
+ options = {}
132
+ opt = OptionParser.new do |o|
133
+ o.banner = 'Usage: prg job stop [options]'
134
+ o.on('--pid-file PATH', 'Path to daemon PID file') do |v|
135
+ options[:pid_file] = v
136
+ end
137
+ o.on('--daemon-name NAME', 'Daemon name (maps to /tmp/ruby-progress/NAME.pid)') do |v|
138
+ options[:daemon_name] = v
139
+ end
140
+ o.on('--message MSG', 'Optional completion message to display') do |v|
141
+ options[:message] = v
142
+ end
143
+ o.on('--checkmark', 'Display a checkmark on completion') do
144
+ options[:checkmark] = true
145
+ end
146
+ o.on('--error', 'Mark completion as error state') do
147
+ options[:error] = true
134
148
  end
135
- else
136
- puts job_id
137
149
  end
150
+
151
+ opt.parse(argv)
152
+ options
138
153
  end
139
154
 
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 }
155
+ def self.parse_advance_options(argv)
156
+ options = {}
157
+ opt = OptionParser.new do |o|
158
+ o.banner = 'Usage: prg job advance [options]'
159
+ o.on('--pid-file PATH', 'Path to daemon PID file') do |v|
160
+ options[:pid_file] = v
161
+ end
162
+ o.on('--daemon-name NAME', 'Daemon name (maps to /tmp/ruby-progress/NAME.pid)') do |v|
163
+ options[:daemon_name] = v
164
+ end
165
+ o.on('--amount N', Integer, 'Amount to advance (default: 1)') do |v|
166
+ options[:amount] = v
167
+ end
168
+ o.on('--total N', Integer, 'Update total if needed') do |v|
169
+ options[:total] = v
170
+ end
171
+ end
143
172
 
144
- is_action = !opts[:action].nil? && opts[:action] != false
173
+ opt.parse(argv)
174
+ options
175
+ end
145
176
 
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
177
+ def self.parse_status_options(argv)
178
+ options = {}
179
+ opt = OptionParser.new do |o|
180
+ o.banner = 'Usage: prg job status [options]'
181
+ o.on('--pid-file PATH', 'Path to daemon PID file') do |v|
182
+ options[:pid_file] = v
183
+ end
184
+ o.on('--daemon-name NAME', 'Daemon name (maps to /tmp/ruby-progress/NAME.pid)') do |v|
185
+ options[:daemon_name] = v
152
186
  end
153
- else
154
- payload['command'] = cmd
155
187
  end
156
188
 
157
- payload
189
+ opt.parse(argv)
190
+ options
191
+ end
192
+
193
+ # Backward compatibility
194
+ def self.send(argv = ARGV)
195
+ warn "Warning: 'JobCLI.send' is deprecated. Use 'JobCLI.stop' instead."
196
+ stop(argv)
158
197
  end
159
198
  end
@@ -8,6 +8,14 @@ require_relative '../output_capture'
8
8
 
9
9
  # Enhanced Ripple CLI with unified flags (extracted from bin/prg)
10
10
  module RippleCLI
11
+ def self.resolve_pid_file(options, name_key = :daemon_name)
12
+ return options[:pid_file] if options[:pid_file]
13
+
14
+ return "/tmp/ruby-progress/#{options[name_key]}.pid" if options[name_key]
15
+
16
+ RubyProgress::Daemon.default_pid_file
17
+ end
18
+
11
19
  def self.run
12
20
  trap('INT') do
13
21
  RubyProgress::Utils.show_cursor
@@ -18,11 +26,11 @@ module RippleCLI
18
26
 
19
27
  # Daemon/status/stop handling (process these without requiring text)
20
28
  if options[:status]
21
- pid_file = options[:pid_file] || RubyProgress::Daemon.default_pid_file
29
+ pid_file = resolve_pid_file(options, :status_name)
22
30
  RubyProgress::Daemon.show_status(pid_file)
23
31
  exit
24
32
  elsif options[:stop]
25
- pid_file = options[:pid_file] || RubyProgress::Daemon.default_pid_file
33
+ pid_file = resolve_pid_file(options, :stop_name)
26
34
  stop_msg = options[:stop_error] || options[:stop_success]
27
35
  is_error = !options[:stop_error].nil?
28
36
  RubyProgress::Daemon.stop_daemon_by_pid_file(
@@ -33,13 +41,8 @@ module RippleCLI
33
41
  )
34
42
  exit
35
43
  elsif options[:daemon]
36
- # For daemon mode, detach so shell has no tracked job unless the user
37
- # requested a non-detaching background child via --no-detach.
38
- if options[:no_detach]
39
- PrgCLI.backgroundize
40
- else
41
- PrgCLI.daemonize
42
- end
44
+ # Background without detaching so ripple remains visible in current terminal
45
+ PrgCLI.backgroundize
43
46
 
44
47
  # For daemon mode, default message if none provided
45
48
  text = options[:message] || ARGV.join(' ')
@@ -71,7 +74,12 @@ module RippleCLI
71
74
  if $stdout.tty?
72
75
  # Interactive TTY: use PTY-based capture so the animation can run while the
73
76
  # 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)
77
+ oc = RubyProgress::OutputCapture.new(
78
+ command: options[:command],
79
+ lines: options[:output_lines] || 3,
80
+ position: options[:output_position] || :above,
81
+ stream: options[:output] == :stdout || options[:stdout_live]
82
+ )
75
83
  oc.start
76
84
 
77
85
  # Create rippler. Attach output capture only when the user requested
@@ -120,7 +128,7 @@ module RippleCLI
120
128
  end
121
129
 
122
130
  def self.run_daemon_mode(text, options)
123
- pid_file = options[:pid_file] || RubyProgress::Daemon.default_pid_file
131
+ pid_file = resolve_pid_file(options, :daemon_name)
124
132
  FileUtils.mkdir_p(File.dirname(pid_file))
125
133
  File.write(pid_file, Process.pid.to_s)
126
134
  begin
@@ -134,9 +142,6 @@ module RippleCLI
134
142
  Signal.trap('TERM') { stop_requested = true }
135
143
  Signal.trap('HUP') { stop_requested = true }
136
144
 
137
- job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
138
- job_thread = Thread.new { process_daemon_jobs_for_rippler(job_dir, rippler, options) }
139
-
140
145
  rippler.advance until stop_requested
141
146
  ensure
142
147
  RubyProgress::Utils.clear_line
@@ -179,51 +184,9 @@ module RippleCLI
179
184
  end
180
185
 
181
186
  # stop job thread and cleanup
182
- job_thread&.kill
183
187
  FileUtils.rm_f(pid_file)
184
188
  end
185
189
  end
186
190
 
187
- def self.process_daemon_jobs_for_rippler(job_dir, rippler, options)
188
- RubyProgress::Daemon.process_jobs(job_dir) do |job|
189
- jid = job['id'] || SecureRandom.uuid
190
- log_path = begin
191
- File.join(File.dirname(job_dir), "#{jid}.log")
192
- rescue StandardError
193
- nil
194
- end
195
-
196
- oc = RubyProgress::OutputCapture.new(
197
- command: job['command'],
198
- lines: options[:output_lines] || 3,
199
- position: options[:output_position] || :above,
200
- log_path: log_path
201
- )
202
- oc.start
203
-
204
- rippler.instance_variable_set(:@output_capture, oc)
205
- oc.wait
206
- captured = oc.lines.join("\n")
207
- exit_status = oc.exit_status
208
- rippler.instance_variable_set(:@output_capture, nil)
209
-
210
- success = exit_status.to_i.zero?
211
- if job['message']
212
- RubyProgress::Utils.display_completion(
213
- job['message'],
214
- success: success,
215
- show_checkmark: job['checkmark'] || false,
216
- output_stream: :stdout,
217
- icons: { success: options[:success_icon], error: options[:error_icon] }
218
- )
219
- end
220
-
221
- { 'exit_status' => exit_status, 'output' => captured, 'log_path' => log_path }
222
- rescue StandardError
223
- # ignore per-job errors; process_jobs will write result
224
- nil
225
- end
226
- end
227
-
228
191
  # Options parsing moved to ripple_options.rb
229
192
  end
@@ -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
@@ -102,8 +106,9 @@ module RippleCLI
102
106
  options[:daemon] = true
103
107
  end
104
108
 
105
- opts.on('--no-detach', 'When used with --daemon: run background child but do not fully detach from the terminal') do
106
- options[:no_detach] = true
109
+ opts.on('--daemon-as NAME', 'Run in daemon mode with custom name (creates /tmp/ruby-progress/NAME.pid)') do |name|
110
+ options[:daemon] = true
111
+ options[:daemon_name] = name
107
112
  end
108
113
 
109
114
  opts.on('--pid-file FILE', 'Write process ID to file (default: /tmp/ruby-progress/progress.pid)') do |file|
@@ -114,10 +119,20 @@ module RippleCLI
114
119
  options[:stop] = true
115
120
  end
116
121
 
122
+ opts.on('--stop-id NAME', 'Stop daemon by name (automatically implies --stop)') do |name|
123
+ options[:stop] = true
124
+ options[:stop_name] = name
125
+ end
126
+
117
127
  opts.on('--status', 'Show daemon status (running/not running)') do
118
128
  options[:status] = true
119
129
  end
120
130
 
131
+ opts.on('--status-id NAME', 'Show daemon status by name') do |name|
132
+ options[:status] = true
133
+ options[:status_name] = name
134
+ end
135
+
121
136
  opts.on('--stop-success MESSAGE', 'When stopping, show this success message') do |msg|
122
137
  options[:stop_success] = msg
123
138
  end
@@ -131,7 +146,7 @@ module RippleCLI
131
146
  opts.separator ''
132
147
  opts.separator 'Daemon notes:'
133
148
  opts.separator ' - Do not append &; prg detaches itself and returns immediately.'
134
- opts.separator ' - Use --status/--stop with optional --pid-file to control it.'
149
+ opts.separator ' - Use --daemon-as NAME for named daemons, or --stop-id/--status-id for named control.'
135
150
 
136
151
  opts.separator ''
137
152
  opts.separator 'General:'
@@ -26,7 +26,9 @@ module TwirlCLI
26
26
  )
27
27
  exit
28
28
  elsif options[:daemon]
29
- PrgCLI.daemonize
29
+ # Background without detaching so spinner remains visible in current terminal
30
+ PrgCLI.backgroundize
31
+
30
32
  TwirlRunner.run_daemon_mode(options)
31
33
  elsif options[:command]
32
34
  TwirlRunner.run_with_command(options)
@@ -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
 
@@ -88,10 +92,6 @@ module TwirlCLI
88
92
  options[:daemon] = true
89
93
  end
90
94
 
91
- opts.on('--no-detach', 'When used with --daemon/--daemon-as: run background child but do not fully detach from the terminal') do
92
- options[:no_detach] = true
93
- end
94
-
95
95
  opts.on('--daemon-as NAME', 'Run in daemon mode with custom name (creates /tmp/ruby-progress/NAME.pid)') do |name|
96
96
  options[:daemon] = true
97
97
  options[:daemon_name] = name
@@ -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
 
@@ -103,40 +104,6 @@ module TwirlRunner
103
104
  begin
104
105
  RubyProgress::Utils.hide_cursor
105
106
 
106
- # Start job processor thread for twirl
107
- job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
108
- job_thread = Thread.new do
109
- RubyProgress::Daemon.process_jobs(job_dir) do |job|
110
- oc = RubyProgress::OutputCapture.new(
111
- command: job['command'],
112
- lines: options[:output_lines] || 3,
113
- position: options[:output_position] || :above
114
- )
115
- oc.start
116
-
117
- spinner.instance_variable_set(:@output_capture, oc)
118
- oc.wait
119
- captured = oc.lines.join("\n")
120
- exit_status = oc.exit_status
121
- spinner.instance_variable_set(:@output_capture, nil)
122
-
123
- success = exit_status.to_i.zero?
124
- if job['message']
125
- RubyProgress::Utils.display_completion(
126
- job['message'],
127
- success: success,
128
- show_checkmark: job['checkmark'] || false,
129
- output_stream: :stdout,
130
- icons: { success: options[:success_icon], error: options[:error_icon] }
131
- )
132
- end
133
-
134
- { 'exit_status' => exit_status, 'output' => captured }
135
- rescue StandardError
136
- # ignore
137
- end
138
- end
139
-
140
107
  spinner.animate until stop_requested
141
108
  ensure
142
109
  RubyProgress::Utils.clear_line
@@ -170,7 +137,6 @@ module TwirlRunner
170
137
  end
171
138
  end
172
139
 
173
- job_thread&.kill
174
140
  FileUtils.rm_f(pid_file)
175
141
  end
176
142
  end
@@ -33,13 +33,8 @@ module WormCLI
33
33
  )
34
34
  exit
35
35
  elsif options[:daemon]
36
- # Detach (or background without detaching) before starting daemon logic
37
- # so the invoking shell/script continues immediately.
38
- if options[:no_detach]
39
- PrgCLI.backgroundize
40
- else
41
- PrgCLI.daemonize
42
- end
36
+ # Background without detaching so worm remains visible in current terminal
37
+ PrgCLI.backgroundize
43
38
 
44
39
  run_daemon_mode(options)
45
40
  else
@@ -61,48 +56,6 @@ module WormCLI
61
56
  progress = RubyProgress::Worm.new(options)
62
57
 
63
58
  begin
64
- # Start job processor thread for worm
65
- job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
66
- job_thread = Thread.new do
67
- RubyProgress::Daemon.process_jobs(job_dir) do |job|
68
- jid = job['id'] || SecureRandom.uuid
69
- log_path = begin
70
- File.join(File.dirname(job_dir), "#{jid}.log")
71
- rescue StandardError
72
- nil
73
- end
74
-
75
- oc = RubyProgress::OutputCapture.new(
76
- command: job['command'],
77
- lines: options[:output_lines] || 3,
78
- position: options[:output_position] || :above,
79
- log_path: log_path
80
- )
81
- oc.start
82
-
83
- progress.instance_variable_set(:@output_capture, oc)
84
- oc.wait
85
- captured = oc.lines.join("\n")
86
- exit_status = oc.exit_status
87
- progress.instance_variable_set(:@output_capture, nil)
88
-
89
- success = exit_status.to_i.zero?
90
- if job['message']
91
- RubyProgress::Utils.display_completion(
92
- job['message'],
93
- success: success,
94
- show_checkmark: job['checkmark'] || false,
95
- output_stream: :stdout,
96
- icons: { success: options[:success_icon], error: options[:error_icon] }
97
- )
98
- end
99
-
100
- { 'exit_status' => exit_status, 'output' => captured, 'log_path' => log_path }
101
- rescue StandardError
102
- # ignore per-job errors
103
- end
104
- end
105
-
106
59
  progress.run_daemon_mode(
107
60
  success_message: options[:success],
108
61
  show_checkmark: options[:checkmark],
@@ -110,7 +63,6 @@ module WormCLI
110
63
  icons: { success: options[:success_icon], error: options[:error_icon] }
111
64
  )
112
65
  ensure
113
- job_thread&.kill
114
66
  FileUtils.rm_f(pid_file)
115
67
  end
116
68
  end