ruby-progress 1.3.4 → 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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +26 -13
- data/CHANGELOG.md +29 -49
- data/DAEMON_MODE.md +127 -0
- data/Gemfile +7 -1
- data/Gemfile.lock +19 -18
- data/JOB_CLI_REFACTOR.md +67 -0
- data/README.md +90 -94
- data/bin/prg +10 -16
- data/examples/daemon_job_example.sh +8 -10
- data/lib/ruby-progress/cli/fill_options.rb +2 -6
- data/lib/ruby-progress/cli/job_cli.rb +170 -131
- data/lib/ruby-progress/cli/ripple_cli.rb +13 -55
- data/lib/ruby-progress/cli/ripple_options.rb +14 -3
- data/lib/ruby-progress/cli/twirl_cli.rb +3 -1
- data/lib/ruby-progress/cli/twirl_options.rb +0 -4
- data/lib/ruby-progress/cli/twirl_runner.rb +0 -36
- data/lib/ruby-progress/cli/worm_cli.rb +2 -51
- data/lib/ruby-progress/cli/worm_options.rb +0 -6
- data/lib/ruby-progress/cli/worm_runner.rb +7 -1
- data/lib/ruby-progress/daemon.rb +2 -64
- data/lib/ruby-progress/fill_cli.rb +2 -72
- data/lib/ruby-progress/output_capture.rb +7 -2
- data/lib/ruby-progress/utils.rb +11 -6
- data/lib/ruby-progress/version.rb +5 -5
- data/ruby-progress.gemspec +41 -0
- data/scripts/coverage_analysis.rb +49 -0
- data/test_daemon.sh +20 -0
- metadata +6 -15
|
@@ -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
|
-
#
|
|
11
|
+
# JobCLI - sends control messages to backgrounded progress indicators
|
|
15
12
|
#
|
|
16
|
-
#
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
173
|
+
opt.parse(argv)
|
|
174
|
+
options
|
|
175
|
+
end
|
|
145
176
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
37
|
-
|
|
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(' ')
|
|
@@ -125,7 +128,7 @@ module RippleCLI
|
|
|
125
128
|
end
|
|
126
129
|
|
|
127
130
|
def self.run_daemon_mode(text, options)
|
|
128
|
-
pid_file = options
|
|
131
|
+
pid_file = resolve_pid_file(options, :daemon_name)
|
|
129
132
|
FileUtils.mkdir_p(File.dirname(pid_file))
|
|
130
133
|
File.write(pid_file, Process.pid.to_s)
|
|
131
134
|
begin
|
|
@@ -139,9 +142,6 @@ module RippleCLI
|
|
|
139
142
|
Signal.trap('TERM') { stop_requested = true }
|
|
140
143
|
Signal.trap('HUP') { stop_requested = true }
|
|
141
144
|
|
|
142
|
-
job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
|
|
143
|
-
job_thread = Thread.new { process_daemon_jobs_for_rippler(job_dir, rippler, options) }
|
|
144
|
-
|
|
145
145
|
rippler.advance until stop_requested
|
|
146
146
|
ensure
|
|
147
147
|
RubyProgress::Utils.clear_line
|
|
@@ -184,51 +184,9 @@ module RippleCLI
|
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
# stop job thread and cleanup
|
|
187
|
-
job_thread&.kill
|
|
188
187
|
FileUtils.rm_f(pid_file)
|
|
189
188
|
end
|
|
190
189
|
end
|
|
191
190
|
|
|
192
|
-
def self.process_daemon_jobs_for_rippler(job_dir, rippler, options)
|
|
193
|
-
RubyProgress::Daemon.process_jobs(job_dir) do |job|
|
|
194
|
-
jid = job['id'] || SecureRandom.uuid
|
|
195
|
-
log_path = begin
|
|
196
|
-
File.join(File.dirname(job_dir), "#{jid}.log")
|
|
197
|
-
rescue StandardError
|
|
198
|
-
nil
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
oc = RubyProgress::OutputCapture.new(
|
|
202
|
-
command: job['command'],
|
|
203
|
-
lines: options[:output_lines] || 3,
|
|
204
|
-
position: options[:output_position] || :above,
|
|
205
|
-
log_path: log_path
|
|
206
|
-
)
|
|
207
|
-
oc.start
|
|
208
|
-
|
|
209
|
-
rippler.instance_variable_set(:@output_capture, oc)
|
|
210
|
-
oc.wait
|
|
211
|
-
captured = oc.lines.join("\n")
|
|
212
|
-
exit_status = oc.exit_status
|
|
213
|
-
rippler.instance_variable_set(:@output_capture, nil)
|
|
214
|
-
|
|
215
|
-
success = exit_status.to_i.zero?
|
|
216
|
-
if job['message']
|
|
217
|
-
RubyProgress::Utils.display_completion(
|
|
218
|
-
job['message'],
|
|
219
|
-
success: success,
|
|
220
|
-
show_checkmark: job['checkmark'] || false,
|
|
221
|
-
output_stream: :stdout,
|
|
222
|
-
icons: { success: options[:success_icon], error: options[:error_icon] }
|
|
223
|
-
)
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
{ 'exit_status' => exit_status, 'output' => captured, 'log_path' => log_path }
|
|
227
|
-
rescue StandardError
|
|
228
|
-
# ignore per-job errors; process_jobs will write result
|
|
229
|
-
nil
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
|
|
233
191
|
# Options parsing moved to ripple_options.rb
|
|
234
192
|
end
|
|
@@ -106,8 +106,9 @@ module RippleCLI
|
|
|
106
106
|
options[:daemon] = true
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
opts.on('--
|
|
110
|
-
options[:
|
|
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
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
opts.on('--pid-file FILE', 'Write process ID to file (default: /tmp/ruby-progress/progress.pid)') do |file|
|
|
@@ -118,10 +119,20 @@ module RippleCLI
|
|
|
118
119
|
options[:stop] = true
|
|
119
120
|
end
|
|
120
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
|
+
|
|
121
127
|
opts.on('--status', 'Show daemon status (running/not running)') do
|
|
122
128
|
options[:status] = true
|
|
123
129
|
end
|
|
124
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
|
+
|
|
125
136
|
opts.on('--stop-success MESSAGE', 'When stopping, show this success message') do |msg|
|
|
126
137
|
options[:stop_success] = msg
|
|
127
138
|
end
|
|
@@ -135,7 +146,7 @@ module RippleCLI
|
|
|
135
146
|
opts.separator ''
|
|
136
147
|
opts.separator 'Daemon notes:'
|
|
137
148
|
opts.separator ' - Do not append &; prg detaches itself and returns immediately.'
|
|
138
|
-
opts.separator ' - Use --
|
|
149
|
+
opts.separator ' - Use --daemon-as NAME for named daemons, or --stop-id/--status-id for named control.'
|
|
139
150
|
|
|
140
151
|
opts.separator ''
|
|
141
152
|
opts.separator 'General:'
|
|
@@ -26,7 +26,9 @@ module TwirlCLI
|
|
|
26
26
|
)
|
|
27
27
|
exit
|
|
28
28
|
elsif options[:daemon]
|
|
29
|
-
|
|
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)
|
|
@@ -92,10 +92,6 @@ module TwirlCLI
|
|
|
92
92
|
options[:daemon] = true
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
-
opts.on('--no-detach', 'When used with --daemon/--daemon-as: run background child but do not fully detach from the terminal') do
|
|
96
|
-
options[:no_detach] = true
|
|
97
|
-
end
|
|
98
|
-
|
|
99
95
|
opts.on('--daemon-as NAME', 'Run in daemon mode with custom name (creates /tmp/ruby-progress/NAME.pid)') do |name|
|
|
100
96
|
options[:daemon] = true
|
|
101
97
|
options[:daemon_name] = name
|
|
@@ -104,41 +104,6 @@ module TwirlRunner
|
|
|
104
104
|
begin
|
|
105
105
|
RubyProgress::Utils.hide_cursor
|
|
106
106
|
|
|
107
|
-
# Start job processor thread for twirl
|
|
108
|
-
job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
|
|
109
|
-
job_thread = Thread.new do
|
|
110
|
-
RubyProgress::Daemon.process_jobs(job_dir) do |job|
|
|
111
|
-
oc = RubyProgress::OutputCapture.new(
|
|
112
|
-
command: job['command'],
|
|
113
|
-
lines: options[:output_lines] || 3,
|
|
114
|
-
position: options[:output_position] || :above,
|
|
115
|
-
stream: options[:stdout] || options[:stdout_live]
|
|
116
|
-
)
|
|
117
|
-
oc.start
|
|
118
|
-
|
|
119
|
-
spinner.instance_variable_set(:@output_capture, oc)
|
|
120
|
-
oc.wait
|
|
121
|
-
captured = oc.lines.join("\n")
|
|
122
|
-
exit_status = oc.exit_status
|
|
123
|
-
spinner.instance_variable_set(:@output_capture, nil)
|
|
124
|
-
|
|
125
|
-
success = exit_status.to_i.zero?
|
|
126
|
-
if job['message']
|
|
127
|
-
RubyProgress::Utils.display_completion(
|
|
128
|
-
job['message'],
|
|
129
|
-
success: success,
|
|
130
|
-
show_checkmark: job['checkmark'] || false,
|
|
131
|
-
output_stream: :stdout,
|
|
132
|
-
icons: { success: options[:success_icon], error: options[:error_icon] }
|
|
133
|
-
)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
{ 'exit_status' => exit_status, 'output' => captured }
|
|
137
|
-
rescue StandardError
|
|
138
|
-
# ignore
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
107
|
spinner.animate until stop_requested
|
|
143
108
|
ensure
|
|
144
109
|
RubyProgress::Utils.clear_line
|
|
@@ -172,7 +137,6 @@ module TwirlRunner
|
|
|
172
137
|
end
|
|
173
138
|
end
|
|
174
139
|
|
|
175
|
-
job_thread&.kill
|
|
176
140
|
FileUtils.rm_f(pid_file)
|
|
177
141
|
end
|
|
178
142
|
end
|
|
@@ -33,13 +33,8 @@ module WormCLI
|
|
|
33
33
|
)
|
|
34
34
|
exit
|
|
35
35
|
elsif options[:daemon]
|
|
36
|
-
#
|
|
37
|
-
|
|
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,49 +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
|
-
stream: options[:stdout] || options[:stdout_live]
|
|
81
|
-
)
|
|
82
|
-
oc.start
|
|
83
|
-
|
|
84
|
-
progress.instance_variable_set(:@output_capture, oc)
|
|
85
|
-
oc.wait
|
|
86
|
-
captured = oc.lines.join("\n")
|
|
87
|
-
exit_status = oc.exit_status
|
|
88
|
-
progress.instance_variable_set(:@output_capture, nil)
|
|
89
|
-
|
|
90
|
-
success = exit_status.to_i.zero?
|
|
91
|
-
if job['message']
|
|
92
|
-
RubyProgress::Utils.display_completion(
|
|
93
|
-
job['message'],
|
|
94
|
-
success: success,
|
|
95
|
-
show_checkmark: job['checkmark'] || false,
|
|
96
|
-
output_stream: :stdout,
|
|
97
|
-
icons: { success: options[:success_icon], error: options[:error_icon] }
|
|
98
|
-
)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
{ 'exit_status' => exit_status, 'output' => captured, 'log_path' => log_path }
|
|
102
|
-
rescue StandardError
|
|
103
|
-
# ignore per-job errors
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
59
|
progress.run_daemon_mode(
|
|
108
60
|
success_message: options[:success],
|
|
109
61
|
show_checkmark: options[:checkmark],
|
|
@@ -111,7 +63,6 @@ module WormCLI
|
|
|
111
63
|
icons: { success: options[:success_icon], error: options[:error_icon] }
|
|
112
64
|
)
|
|
113
65
|
ensure
|
|
114
|
-
job_thread&.kill
|
|
115
66
|
FileUtils.rm_f(pid_file)
|
|
116
67
|
end
|
|
117
68
|
end
|
|
@@ -13,7 +13,6 @@ module WormCLI
|
|
|
13
13
|
output_position: :above,
|
|
14
14
|
output_lines: 3
|
|
15
15
|
}
|
|
16
|
-
# rubocop:disable Metrics/BlockLength
|
|
17
16
|
begin
|
|
18
17
|
OptionParser.new do |opts|
|
|
19
18
|
opts.banner = 'Usage: prg worm [options]'
|
|
@@ -105,10 +104,6 @@ module WormCLI
|
|
|
105
104
|
options[:daemon_name] = name
|
|
106
105
|
end
|
|
107
106
|
|
|
108
|
-
opts.on('--no-detach', 'When used with --daemon/--daemon-as: run background child but do not fully detach from the terminal') do
|
|
109
|
-
options[:no_detach] = true
|
|
110
|
-
end
|
|
111
|
-
|
|
112
107
|
opts.on('--pid-file FILE', 'Write process ID to file (default: /tmp/ruby-progress/progress.pid)') do |file|
|
|
113
108
|
options[:pid_file] = file
|
|
114
109
|
end
|
|
@@ -182,7 +177,6 @@ module WormCLI
|
|
|
182
177
|
puts "Run 'prg worm --help' for more information."
|
|
183
178
|
exit 1
|
|
184
179
|
end
|
|
185
|
-
# rubocop:enable Metrics/BlockLength
|
|
186
180
|
options
|
|
187
181
|
end
|
|
188
182
|
end
|
|
@@ -70,7 +70,13 @@ module WormRunner
|
|
|
70
70
|
oc.wait
|
|
71
71
|
end
|
|
72
72
|
@output_capture = nil
|
|
73
|
-
|
|
73
|
+
# For non-live capture, flush_to handles the output directly
|
|
74
|
+
if @output_live
|
|
75
|
+
oc.lines.join("\n")
|
|
76
|
+
else
|
|
77
|
+
oc.flush_to($stdout) if @output_stdout
|
|
78
|
+
nil # Don't return content since it's already been flushed
|
|
79
|
+
end
|
|
74
80
|
else
|
|
75
81
|
animate do
|
|
76
82
|
Open3.popen3(@command) do |_stdin, stdout, stderr, wait_thr|
|