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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -154
- data/DEMO_SCRIPTS.md +162 -0
- data/Gemfile.lock +1 -1
- data/README.md +128 -120
- data/Rakefile +7 -0
- data/bin/fill +10 -0
- data/bin/prg +76 -1024
- data/demo_screencast.rb +296 -0
- data/examples/daemon_job_example.sh +25 -0
- data/experimental_terminal.rb +7 -0
- data/lib/ruby-progress/cli/fill_options.rb +215 -0
- data/lib/ruby-progress/cli/job_cli.rb +99 -0
- data/lib/ruby-progress/cli/ripple_cli.rb +211 -0
- data/lib/ruby-progress/cli/ripple_options.rb +158 -0
- data/lib/ruby-progress/cli/twirl_cli.rb +173 -0
- data/lib/ruby-progress/cli/twirl_options.rb +147 -0
- data/lib/ruby-progress/cli/twirl_runner.rb +183 -0
- data/lib/ruby-progress/cli/twirl_spinner.rb +79 -0
- data/lib/ruby-progress/cli/worm_cli.rb +109 -0
- data/lib/ruby-progress/cli/worm_options.rb +173 -0
- data/lib/ruby-progress/cli/worm_runner.rb +282 -0
- data/lib/ruby-progress/daemon.rb +65 -0
- data/lib/ruby-progress/fill.rb +215 -0
- data/lib/ruby-progress/fill_cli.rb +249 -0
- data/lib/ruby-progress/output_capture.rb +136 -0
- data/lib/ruby-progress/ripple.rb +1 -0
- data/lib/ruby-progress/utils.rb +16 -2
- data/lib/ruby-progress/version.rb +8 -4
- data/lib/ruby-progress/worm.rb +2 -177
- data/lib/ruby-progress.rb +1 -0
- data/quick_demo.rb +134 -0
- data/readme_demo.rb +128 -0
- data/ruby-progress.gemspec +40 -0
- data/scripts/run_matrix_mise.fish +41 -0
- data/test_daemon_interruption.rb +2 -0
- data/test_daemon_orphan.rb +1 -0
- metadata +24 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative 'twirl_spinner'
|
|
6
|
+
require_relative '../output_capture'
|
|
7
|
+
|
|
8
|
+
# Top-level runtime helper module for the Twirl CLI.
|
|
9
|
+
#
|
|
10
|
+
# Contains helper methods used by the `TwirlCLI` dispatcher. These
|
|
11
|
+
# methods implement the runtime behavior (running a command, running
|
|
12
|
+
# indefinitely, or launching in daemon mode) and were extracted to
|
|
13
|
+
# reduce module size and improve testability.
|
|
14
|
+
module TwirlRunner
|
|
15
|
+
def self.run_with_command(options)
|
|
16
|
+
message = options[:message]
|
|
17
|
+
captured_output = nil
|
|
18
|
+
|
|
19
|
+
spinner = TwirlSpinner.new(message, options)
|
|
20
|
+
success = false
|
|
21
|
+
|
|
22
|
+
begin
|
|
23
|
+
RubyProgress::Utils.hide_cursor
|
|
24
|
+
spinner_thread = Thread.new { loop { spinner.animate } }
|
|
25
|
+
|
|
26
|
+
if $stdout.tty? && options[:stdout]
|
|
27
|
+
oc = RubyProgress::OutputCapture.new(
|
|
28
|
+
command: options[:command],
|
|
29
|
+
lines: options[:output_lines] || 3,
|
|
30
|
+
position: options[:output_position] || :above
|
|
31
|
+
)
|
|
32
|
+
oc.start
|
|
33
|
+
|
|
34
|
+
spinner.instance_variable_set(:@output_capture, oc)
|
|
35
|
+
|
|
36
|
+
# wait for command while spinner thread runs
|
|
37
|
+
oc.wait
|
|
38
|
+
captured_lines = oc.lines
|
|
39
|
+
captured_output = captured_lines.join("\n")
|
|
40
|
+
success = true
|
|
41
|
+
else
|
|
42
|
+
captured_output = `#{options[:command]} 2>&1`
|
|
43
|
+
success = $CHILD_STATUS.success?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
spinner_thread.kill
|
|
47
|
+
RubyProgress::Utils.clear_line
|
|
48
|
+
ensure
|
|
49
|
+
RubyProgress::Utils.show_cursor
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
puts captured_output if options[:stdout]
|
|
53
|
+
|
|
54
|
+
if options[:success] || options[:error] || options[:checkmark]
|
|
55
|
+
final_msg = success ? options[:success] : options[:error]
|
|
56
|
+
final_msg ||= success ? 'Success' : 'Failed'
|
|
57
|
+
|
|
58
|
+
RubyProgress::Utils.display_completion(
|
|
59
|
+
final_msg,
|
|
60
|
+
success: success,
|
|
61
|
+
show_checkmark: options[:checkmark]
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
exit success ? 0 : 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.run_indefinitely(options)
|
|
69
|
+
message = options[:message]
|
|
70
|
+
spinner = TwirlSpinner.new(message, options)
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
RubyProgress::Utils.hide_cursor
|
|
74
|
+
loop { spinner.animate }
|
|
75
|
+
ensure
|
|
76
|
+
RubyProgress::Utils.show_cursor
|
|
77
|
+
if options[:success] || options[:checkmark]
|
|
78
|
+
RubyProgress::Utils.display_completion(
|
|
79
|
+
options[:success] || 'Complete',
|
|
80
|
+
success: true,
|
|
81
|
+
show_checkmark: options[:checkmark]
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.run_daemon_mode(options)
|
|
88
|
+
pid_file = resolve_pid_file(options, :daemon_name)
|
|
89
|
+
FileUtils.mkdir_p(File.dirname(pid_file))
|
|
90
|
+
File.write(pid_file, Process.pid.to_s)
|
|
91
|
+
|
|
92
|
+
message = options[:message]
|
|
93
|
+
spinner = TwirlSpinner.new(message, options)
|
|
94
|
+
stop_requested = false
|
|
95
|
+
|
|
96
|
+
Signal.trap('INT') { stop_requested = true }
|
|
97
|
+
Signal.trap('USR1') { stop_requested = true }
|
|
98
|
+
Signal.trap('TERM') { stop_requested = true }
|
|
99
|
+
Signal.trap('HUP') { stop_requested = true }
|
|
100
|
+
|
|
101
|
+
begin
|
|
102
|
+
RubyProgress::Utils.hide_cursor
|
|
103
|
+
|
|
104
|
+
# Start job processor thread for twirl
|
|
105
|
+
job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
|
|
106
|
+
job_thread = Thread.new do
|
|
107
|
+
RubyProgress::Daemon.process_jobs(job_dir) do |job|
|
|
108
|
+
oc = RubyProgress::OutputCapture.new(
|
|
109
|
+
command: job['command'],
|
|
110
|
+
lines: options[:output_lines] || 3,
|
|
111
|
+
position: options[:output_position] || :above
|
|
112
|
+
)
|
|
113
|
+
oc.start
|
|
114
|
+
|
|
115
|
+
spinner.instance_variable_set(:@output_capture, oc)
|
|
116
|
+
oc.wait
|
|
117
|
+
captured = oc.lines.join("\n")
|
|
118
|
+
exit_status = oc.exit_status
|
|
119
|
+
spinner.instance_variable_set(:@output_capture, nil)
|
|
120
|
+
|
|
121
|
+
success = exit_status.to_i.zero?
|
|
122
|
+
if job['message']
|
|
123
|
+
RubyProgress::Utils.display_completion(
|
|
124
|
+
job['message'],
|
|
125
|
+
success: success,
|
|
126
|
+
show_checkmark: job['checkmark'] || false,
|
|
127
|
+
output_stream: :stdout
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
{ 'exit_status' => exit_status, 'output' => captured }
|
|
132
|
+
rescue StandardError
|
|
133
|
+
# ignore
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
spinner.animate until stop_requested
|
|
138
|
+
ensure
|
|
139
|
+
RubyProgress::Utils.clear_line
|
|
140
|
+
RubyProgress::Utils.show_cursor
|
|
141
|
+
|
|
142
|
+
# Check for control message
|
|
143
|
+
cmf = RubyProgress::Daemon.control_message_file(pid_file)
|
|
144
|
+
if File.exist?(cmf)
|
|
145
|
+
begin
|
|
146
|
+
data = JSON.parse(File.read(cmf))
|
|
147
|
+
message = data['message']
|
|
148
|
+
check = data.key?('checkmark') ? data['checkmark'] : false
|
|
149
|
+
success_val = data.key?('success') ? data['success'] : true
|
|
150
|
+
if message
|
|
151
|
+
RubyProgress::Utils.display_completion(
|
|
152
|
+
message,
|
|
153
|
+
success: success_val,
|
|
154
|
+
show_checkmark: check,
|
|
155
|
+
output_stream: :stdout
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
rescue StandardError
|
|
159
|
+
# ignore
|
|
160
|
+
ensure
|
|
161
|
+
begin
|
|
162
|
+
File.delete(cmf)
|
|
163
|
+
rescue StandardError
|
|
164
|
+
nil
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
job_thread&.kill
|
|
170
|
+
FileUtils.rm_f(pid_file)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def self.resolve_pid_file(options, name_key)
|
|
175
|
+
return options[:pid_file] if options[:pid_file]
|
|
176
|
+
|
|
177
|
+
if options[name_key]
|
|
178
|
+
"/tmp/ruby-progress/#{options[name_key]}.pid"
|
|
179
|
+
else
|
|
180
|
+
RubyProgress::Daemon.default_pid_file
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../utils'
|
|
4
|
+
|
|
5
|
+
# Minimal spinner implementation used by the Twirl CLI
|
|
6
|
+
#
|
|
7
|
+
# This small class handles animation frame selection and printing
|
|
8
|
+
# for the Twirl command. It was extracted to keep runtime logic
|
|
9
|
+
# out of the CLI dispatcher module.
|
|
10
|
+
class TwirlSpinner
|
|
11
|
+
def initialize(message, options = {})
|
|
12
|
+
@message = message
|
|
13
|
+
@style = parse_style(options[:style] || 'dots')
|
|
14
|
+
@speed = parse_speed(options[:speed] || 'medium')
|
|
15
|
+
@frames = RubyProgress::INDICATORS[@style] || RubyProgress::INDICATORS[:dots]
|
|
16
|
+
@start_chars, @end_chars = RubyProgress::Utils.parse_ends(options[:ends])
|
|
17
|
+
@index = 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def animate
|
|
21
|
+
@output_capture&.redraw($stderr)
|
|
22
|
+
if @message && !@message.empty?
|
|
23
|
+
$stderr.print "\r\e[2K#{@start_chars}#{@message} #{@frames[@index]}#{@end_chars}"
|
|
24
|
+
else
|
|
25
|
+
$stderr.print "\r\e[2K#{@start_chars}#{@frames[@index]}#{@end_chars}"
|
|
26
|
+
end
|
|
27
|
+
$stderr.flush
|
|
28
|
+
@index = (@index + 1) % @frames.length
|
|
29
|
+
sleep @speed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def parse_style(style_input)
|
|
35
|
+
return :dots unless style_input && !style_input.to_s.strip.empty?
|
|
36
|
+
|
|
37
|
+
style_lower = style_input.to_s.downcase.strip
|
|
38
|
+
|
|
39
|
+
indicator_keys = RubyProgress::INDICATORS.keys.map(&:to_s)
|
|
40
|
+
return style_lower.to_sym if indicator_keys.include?(style_lower)
|
|
41
|
+
|
|
42
|
+
prefix_matches = indicator_keys.select { |key| key.downcase.start_with?(style_lower) }
|
|
43
|
+
return prefix_matches.min_by(&:length).to_sym unless prefix_matches.empty?
|
|
44
|
+
|
|
45
|
+
fuzzy_matches = indicator_keys.select do |key|
|
|
46
|
+
key_chars = key.downcase.chars
|
|
47
|
+
input_chars = style_lower.chars
|
|
48
|
+
input_chars.all? do |char|
|
|
49
|
+
idx = key_chars.index(char)
|
|
50
|
+
if idx
|
|
51
|
+
key_chars = key_chars[idx + 1..-1]
|
|
52
|
+
true
|
|
53
|
+
else
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return fuzzy_matches.min_by(&:length).to_sym unless fuzzy_matches.empty?
|
|
60
|
+
|
|
61
|
+
substring_matches = indicator_keys.select { |key| key.downcase.include?(style_lower) }
|
|
62
|
+
return substring_matches.min_by(&:length).to_sym unless substring_matches.empty?
|
|
63
|
+
|
|
64
|
+
:dots
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parse_speed(speed)
|
|
68
|
+
case speed.to_s.downcase
|
|
69
|
+
when /^f/, '1', '2', '3'
|
|
70
|
+
0.05
|
|
71
|
+
when /^m/, '4', '5', '6', '7'
|
|
72
|
+
0.1
|
|
73
|
+
when /^s/, '8', '9', '10'
|
|
74
|
+
0.2
|
|
75
|
+
else
|
|
76
|
+
speed.to_f.positive? ? (1.0 / speed.to_f) : 0.1
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
require_relative 'worm_options'
|
|
6
|
+
|
|
7
|
+
# Enhanced Worm CLI (extracted from bin/prg)
|
|
8
|
+
module WormCLI
|
|
9
|
+
def self.resolve_pid_file(options, name_key = :daemon_name)
|
|
10
|
+
return options[:pid_file] if options[:pid_file]
|
|
11
|
+
|
|
12
|
+
return "/tmp/ruby-progress/#{options[name_key]}.pid" if options[name_key]
|
|
13
|
+
|
|
14
|
+
RubyProgress::Daemon.default_pid_file
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.run
|
|
18
|
+
options = WormCLI::Options.parse_cli_options
|
|
19
|
+
|
|
20
|
+
if options[:status]
|
|
21
|
+
pid_file = resolve_pid_file(options, :status_name)
|
|
22
|
+
RubyProgress::Daemon.show_status(pid_file)
|
|
23
|
+
exit
|
|
24
|
+
elsif options[:stop]
|
|
25
|
+
pid_file = resolve_pid_file(options, :stop_name)
|
|
26
|
+
stop_msg = options[:stop_error] || options[:stop_success]
|
|
27
|
+
is_error = !options[:stop_error].nil?
|
|
28
|
+
RubyProgress::Daemon.stop_daemon_by_pid_file(
|
|
29
|
+
pid_file,
|
|
30
|
+
message: stop_msg,
|
|
31
|
+
checkmark: options[:stop_checkmark],
|
|
32
|
+
error: is_error
|
|
33
|
+
)
|
|
34
|
+
exit
|
|
35
|
+
elsif options[:daemon]
|
|
36
|
+
# Detach before starting daemon logic so there's no tracked shell job
|
|
37
|
+
PrgCLI.daemonize
|
|
38
|
+
run_daemon_mode(options)
|
|
39
|
+
else
|
|
40
|
+
progress = RubyProgress::Worm.new(options)
|
|
41
|
+
|
|
42
|
+
if options[:command]
|
|
43
|
+
progress.run_with_command
|
|
44
|
+
else
|
|
45
|
+
progress.run_indefinitely
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.run_daemon_mode(options)
|
|
51
|
+
pid_file = resolve_pid_file(options, :daemon_name)
|
|
52
|
+
FileUtils.mkdir_p(File.dirname(pid_file))
|
|
53
|
+
File.write(pid_file, Process.pid.to_s)
|
|
54
|
+
|
|
55
|
+
progress = RubyProgress::Worm.new(options)
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
# Start job processor thread for worm
|
|
59
|
+
job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
|
|
60
|
+
job_thread = Thread.new do
|
|
61
|
+
RubyProgress::Daemon.process_jobs(job_dir) do |job|
|
|
62
|
+
jid = job['id'] || SecureRandom.uuid
|
|
63
|
+
log_path = begin
|
|
64
|
+
File.join(File.dirname(job_dir), "#{jid}.log")
|
|
65
|
+
rescue StandardError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
oc = RubyProgress::OutputCapture.new(
|
|
70
|
+
command: job['command'],
|
|
71
|
+
lines: options[:output_lines] || 3,
|
|
72
|
+
position: options[:output_position] || :above,
|
|
73
|
+
log_path: log_path
|
|
74
|
+
)
|
|
75
|
+
oc.start
|
|
76
|
+
|
|
77
|
+
progress.instance_variable_set(:@output_capture, oc)
|
|
78
|
+
oc.wait
|
|
79
|
+
captured = oc.lines.join("\n")
|
|
80
|
+
exit_status = oc.exit_status
|
|
81
|
+
progress.instance_variable_set(:@output_capture, nil)
|
|
82
|
+
|
|
83
|
+
success = exit_status.to_i.zero?
|
|
84
|
+
if job['message']
|
|
85
|
+
RubyProgress::Utils.display_completion(
|
|
86
|
+
job['message'],
|
|
87
|
+
success: success,
|
|
88
|
+
show_checkmark: job['checkmark'] || false,
|
|
89
|
+
output_stream: :stdout
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
{ 'exit_status' => exit_status, 'output' => captured, 'log_path' => log_path }
|
|
94
|
+
rescue StandardError
|
|
95
|
+
# ignore per-job errors
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
progress.run_daemon_mode(
|
|
100
|
+
success_message: options[:success],
|
|
101
|
+
show_checkmark: options[:checkmark],
|
|
102
|
+
control_message_file: RubyProgress::Daemon.control_message_file(pid_file)
|
|
103
|
+
)
|
|
104
|
+
ensure
|
|
105
|
+
job_thread&.kill
|
|
106
|
+
FileUtils.rm_f(pid_file)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
|
|
5
|
+
module WormCLI
|
|
6
|
+
# Option parsing helpers for the Worm subcommand.
|
|
7
|
+
#
|
|
8
|
+
# Keeps the CLI option definitions for `prg worm` extracted from
|
|
9
|
+
# the main dispatcher to keep the CLI module small and focused.
|
|
10
|
+
module Options
|
|
11
|
+
def self.parse_cli_options
|
|
12
|
+
options = {
|
|
13
|
+
output_position: :above,
|
|
14
|
+
output_lines: 3
|
|
15
|
+
}
|
|
16
|
+
# rubocop:disable Metrics/BlockLength
|
|
17
|
+
begin
|
|
18
|
+
OptionParser.new do |opts|
|
|
19
|
+
opts.banner = 'Usage: prg worm [options]'
|
|
20
|
+
opts.separator ''
|
|
21
|
+
opts.separator 'Animation Options:'
|
|
22
|
+
|
|
23
|
+
opts.on('-s', '--speed SPEED', 'Animation speed (1-10, fast/medium/slow, or f/m/s)') do |speed|
|
|
24
|
+
options[:speed] = speed
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
opts.on('-m', '--message MESSAGE', 'Message to display before animation') do |message|
|
|
28
|
+
options[:message] = message
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
opts.on('-l', '--length LENGTH', Integer, 'Number of dots to display') do |length|
|
|
32
|
+
options[:length] = length
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.on('--style STYLE', 'Animation style (circles/blocks/geometric, c/b/g, or custom=abc)') do |style|
|
|
36
|
+
options[:style] = style
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on('-d', '--direction DIRECTION', 'Animation direction (forward/bidirectional or f/b)') do |direction|
|
|
40
|
+
options[:direction] = direction =~ /^f/i ? :forward_only : :bidirectional
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
opts.on('--ends CHARS', 'Start/end characters (even number of chars, split in half)') do |chars|
|
|
44
|
+
options[:ends] = chars
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
opts.separator ''
|
|
48
|
+
opts.separator 'Command Execution:'
|
|
49
|
+
|
|
50
|
+
opts.on('-c', '--command COMMAND', 'Command to run (optional - runs indefinitely without)') do |command|
|
|
51
|
+
options[:command] = command
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
opts.on('--output-position POSITION', 'Position to render captured output: above or below (default: above)') do |pos|
|
|
55
|
+
options[:output_position] = pos.to_sym
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
opts.on('--output-lines N', Integer, 'Number of output lines to reserve for captured output (default: 3)') do |n|
|
|
59
|
+
options[:output_lines] = n
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
opts.on('--success MESSAGE', 'Success message to display') do |text|
|
|
63
|
+
options[:success] = text
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
opts.on('--error MESSAGE', 'Error message to display') do |text|
|
|
67
|
+
options[:error] = text
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
opts.on('--checkmark', 'Show checkmarks (✅ success, 🛑 failure)') do
|
|
71
|
+
options[:checkmark] = true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
opts.on('--stdout', 'Output captured command result to STDOUT') do
|
|
75
|
+
options[:stdout] = true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
opts.separator ''
|
|
79
|
+
opts.separator 'Daemon Mode:'
|
|
80
|
+
|
|
81
|
+
opts.on('--daemon', 'Run in background daemon mode') do
|
|
82
|
+
options[:daemon] = true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
opts.on('--daemon-as NAME', 'Run in daemon mode with custom name (creates /tmp/ruby-progress/NAME.pid)') do |name|
|
|
86
|
+
options[:daemon] = true
|
|
87
|
+
options[:daemon_name] = name
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Accept --daemon-name as an alias for --daemon-as for compatibility
|
|
91
|
+
opts.on('--daemon-name NAME', 'Alias for --daemon-as (compat)') do |name|
|
|
92
|
+
options[:daemon] = true
|
|
93
|
+
options[:daemon_name] = name
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
opts.on('--pid-file FILE', 'Write process ID to file (default: /tmp/ruby-progress/progress.pid)') do |file|
|
|
97
|
+
options[:pid_file] = file
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
opts.on('--stop', 'Stop daemon (uses default PID file unless --pid-file specified)') do
|
|
101
|
+
options[:stop] = true
|
|
102
|
+
end
|
|
103
|
+
opts.on('--stop-id NAME', 'Stop daemon by name (automatically implies --stop)') do |name|
|
|
104
|
+
options[:stop] = true
|
|
105
|
+
options[:stop_name] = name
|
|
106
|
+
end
|
|
107
|
+
opts.on('--status', 'Show daemon status (uses default PID file unless --pid-file specified)') do
|
|
108
|
+
options[:status] = true
|
|
109
|
+
end
|
|
110
|
+
opts.on('--status-id NAME', 'Show daemon status by name') do |name|
|
|
111
|
+
options[:status] = true
|
|
112
|
+
options[:status_name] = name
|
|
113
|
+
end
|
|
114
|
+
opts.on('--stop-success MESSAGE', 'Stop daemon with success message (automatically implies --stop)') do |msg|
|
|
115
|
+
options[:stop] = true
|
|
116
|
+
options[:stop_success] = msg
|
|
117
|
+
end
|
|
118
|
+
opts.on('--stop-error MESSAGE', 'Stop daemon with error message (automatically implies --stop)') do |msg|
|
|
119
|
+
options[:stop] = true
|
|
120
|
+
options[:stop_error] = msg
|
|
121
|
+
end
|
|
122
|
+
opts.on('--stop-checkmark', 'When stopping, include a success checkmark') { options[:stop_checkmark] = true }
|
|
123
|
+
|
|
124
|
+
opts.on('--stop-all', 'Stop all prg worm processes') do
|
|
125
|
+
success = PrgCLI.stop_subcommand_processes('worm')
|
|
126
|
+
exit(success ? 0 : 1)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
opts.on('--stop-pid FILE', 'Stop daemon by reading PID from file (deprecated: use --stop [--pid-file])') do |file|
|
|
130
|
+
RubyProgress::Daemon.stop_daemon_by_pid_file(file)
|
|
131
|
+
exit
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
opts.separator ''
|
|
135
|
+
opts.separator 'Daemon notes:'
|
|
136
|
+
opts.separator ' - Do not append &; prg detaches itself and returns immediately.'
|
|
137
|
+
opts.separator ' - Use --daemon-as NAME for named daemons, or --stop-id/--status-id for named control.'
|
|
138
|
+
|
|
139
|
+
opts.separator ''
|
|
140
|
+
opts.separator 'General:'
|
|
141
|
+
|
|
142
|
+
opts.on('--show-styles', 'Show available worm styles with visual previews') do
|
|
143
|
+
PrgCLI.show_worm_styles
|
|
144
|
+
exit
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
opts.on('--stop-all', 'Stop all prg worm processes') do
|
|
148
|
+
success = PrgCLI.stop_subcommand_processes('worm')
|
|
149
|
+
exit(success ? 0 : 1)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
opts.on('-v', '--version', 'Show version') do
|
|
153
|
+
puts "Worm version #{RubyProgress::VERSION}"
|
|
154
|
+
exit
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
158
|
+
puts opts
|
|
159
|
+
exit
|
|
160
|
+
end
|
|
161
|
+
end.parse!
|
|
162
|
+
rescue OptionParser::InvalidOption => e
|
|
163
|
+
puts "Invalid option: #{e.args.first}"
|
|
164
|
+
puts ''
|
|
165
|
+
puts 'Usage: prg worm [options]'
|
|
166
|
+
puts "Run 'prg worm --help' for more information."
|
|
167
|
+
exit 1
|
|
168
|
+
end
|
|
169
|
+
# rubocop:enable Metrics/BlockLength
|
|
170
|
+
options
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|