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
data/demo_screencast.rb
ADDED
|
@@ -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."
|
data/experimental_terminal.rb
CHANGED
|
@@ -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
|