ruby-progress 1.0.1 → 1.1.2
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/.editorconfig +14 -0
- data/.markdownlint.json +9 -0
- data/CHANGELOG.md +63 -1
- data/Gemfile.lock +1 -1
- data/README.md +200 -45
- data/Rakefile +121 -0
- data/bin/prg +584 -65
- data/bin/ripple +6 -143
- data/bin/twirl +10 -0
- data/bin/worm +6 -76
- data/examples/bash_daemon_demo.sh +67 -0
- data/examples/daemon_demo.rb +67 -0
- data/examples/utils_demo.rb +21 -20
- data/lib/ruby-progress/daemon.rb +60 -0
- data/lib/ruby-progress/utils.rb +2 -2
- data/lib/ruby-progress/version.rb +1 -1
- data/lib/ruby-progress/worm.rb +78 -6
- data/lib/ruby-progress.rb +1 -0
- data/ruby-progress.gemspec +2 -2
- data/worm.rb +1 -0
- metadata +11 -3
data/bin/ripple
CHANGED
|
@@ -1,147 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
trap('INT') do
|
|
11
|
-
RubyProgress::Utils.show_cursor
|
|
12
|
-
exit
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
options = {
|
|
16
|
-
speed: :medium,
|
|
17
|
-
direction: :bidirectional,
|
|
18
|
-
rainbow: false,
|
|
19
|
-
spinner: false,
|
|
20
|
-
spinner_position: :before,
|
|
21
|
-
caps: false,
|
|
22
|
-
inverse: false,
|
|
23
|
-
command: nil,
|
|
24
|
-
success_message: nil,
|
|
25
|
-
fail_message: nil,
|
|
26
|
-
complete_checkmark: false
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
OptionParser.new do |opts|
|
|
30
|
-
opts.banner = 'Usage: ripple [options] STRING'
|
|
31
|
-
|
|
32
|
-
opts.on('-s', '--speed SPEED', %i[fast medium slow], 'Set animation speed ((f)ast/(m)edium/(s)low)') do |s|
|
|
33
|
-
options[:speed] = case s
|
|
34
|
-
when /^f/ then :fast
|
|
35
|
-
when /^m/ then :medium
|
|
36
|
-
when /^s/ then :slow
|
|
37
|
-
else :slow
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
opts.on('-r', '--rainbow', 'Enable rainbow mode') do
|
|
42
|
-
options[:rainbow] = true
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
opts.on('-d', '--direction DIRECTION', 'Set animation format ((f)orward/(b)ack-and-forth)') do |f|
|
|
46
|
-
options[:format] = f =~ /^f/ ? :forward_only : :bidirectional
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
opts.on('-i', '--inverse', 'Enable inverse mode') do
|
|
50
|
-
options[:inverse] = true
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
opts.on('-c', '--command COMMAND', 'Run a command during the animation') do |command|
|
|
54
|
-
options[:command] = command
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
opts.on('--success MESSAGE', 'Message to display on success') do |msg|
|
|
58
|
-
options[:success_message] = msg
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
opts.on('--fail MESSAGE', 'Message to display on error') do |msg|
|
|
62
|
-
options[:fail_message] = msg
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
opts.on('--checkmark') do
|
|
66
|
-
options[:complete_checkmark] = true
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
opts.on('--spinner TYPE', 'Display a rippling spinner with the message') do |type|
|
|
70
|
-
options[:spinner] = type.normalize_type
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
opts.on('--spinner-pos POSITION', 'Display spinner [b]efore or [a]fter message') do |pos|
|
|
74
|
-
options[:spinner_position] = pos =~ /^a/ ? :after : :before
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
opts.on('--list-spinners', 'List available spinners') do
|
|
78
|
-
out = "Spinners:\n"
|
|
79
|
-
RubyProgress::INDICATORS.each do |k, v|
|
|
80
|
-
out += "- #{k}: #{v[2]}\n"
|
|
81
|
-
end
|
|
82
|
-
puts out
|
|
83
|
-
exit
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
opts.on('--caps') do
|
|
87
|
-
options[:caps] = true
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
opts.on('--stdout', 'Output captured command result to STDOUT') do |_output|
|
|
91
|
-
options[:output] = :stdout
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
opts.on('--quiet', 'Suppress all output') do |_quiet|
|
|
95
|
-
options[:output] = :quiet
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
opts.on('-v', '--version', 'Display the version') do
|
|
99
|
-
puts "Ripple version #{RubyProgress::VERSION}"
|
|
100
|
-
exit
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
opts.on('-h', '--help', 'Display this help message') do
|
|
104
|
-
puts opts
|
|
105
|
-
exit
|
|
106
|
-
end
|
|
107
|
-
end.parse!
|
|
108
|
-
|
|
109
|
-
if ARGV.empty?
|
|
110
|
-
puts 'Please provide a string to animate as an argument.'
|
|
111
|
-
exit 1
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
if options[:command]
|
|
115
|
-
captured_output = nil
|
|
116
|
-
res = RubyProgress::Ripple.progress(ARGV.join(' '), options) do
|
|
117
|
-
captured_output = `#{options[:command]} 2>&1`
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
res = $?.success?
|
|
121
|
-
|
|
122
|
-
puts captured_output if options[:output] == :stdout
|
|
123
|
-
if options[:success_message]
|
|
124
|
-
message = if res
|
|
125
|
-
options[:success_message]
|
|
126
|
-
else
|
|
127
|
-
options[:fail_message] || options[:success_message]
|
|
128
|
-
end
|
|
129
|
-
RubyProgress::Ripple.complete(ARGV.join(' '), message, options[:complete_checkmark], res)
|
|
130
|
-
end
|
|
131
|
-
exit res ? 0 : 1
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
rippler = RubyProgress::Ripple.new(ARGV.join(' '), {
|
|
135
|
-
speed: options[:speed],
|
|
136
|
-
format: options[:format],
|
|
137
|
-
rainbow: options[:rainbow],
|
|
138
|
-
inverse: options[:inverse]
|
|
139
|
-
})
|
|
140
|
-
RubyProgress::Utils.hide_cursor
|
|
141
|
-
rippler.advance while true
|
|
142
|
-
RubyProgress::Utils.show_cursor
|
|
143
|
-
RubyProgress::Ripple.complete(ARGV.join(' '), options[:success_message], options[:complete_checkmark], true)
|
|
144
|
-
end
|
|
4
|
+
# Thin shim to delegate to the unified prg CLI
|
|
5
|
+
prg = File.expand_path(File.join(__dir__, 'prg'))
|
|
6
|
+
if File.executable?(prg)
|
|
7
|
+
exec prg, 'ripple', *ARGV
|
|
8
|
+
else
|
|
9
|
+
exec 'prg', 'ripple', *ARGV
|
|
145
10
|
end
|
|
146
|
-
|
|
147
|
-
RippleCLI.run if __FILE__ == $PROGRAM_NAME
|
data/bin/twirl
ADDED
data/bin/worm
CHANGED
|
@@ -1,80 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
options = parse_cli_options
|
|
11
|
-
progress = RubyProgress::Worm.new(options)
|
|
12
|
-
|
|
13
|
-
if options[:command]
|
|
14
|
-
progress.run_with_command
|
|
15
|
-
else
|
|
16
|
-
# Run indefinitely like ripple does when no command is specified
|
|
17
|
-
progress.run_indefinitely
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def self.parse_cli_options
|
|
22
|
-
options = {}
|
|
23
|
-
|
|
24
|
-
OptionParser.new do |opts|
|
|
25
|
-
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
|
26
|
-
opts.separator ''
|
|
27
|
-
opts.separator 'Options:'
|
|
28
|
-
|
|
29
|
-
opts.on('-s', '--speed SPEED', 'Animation speed (1-10, fast, medium, slow, or f/m/s)') do |speed|
|
|
30
|
-
options[:speed] = speed
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
opts.on('-l', '--length LENGTH', Integer, 'Number of dots to display') do |length|
|
|
34
|
-
options[:length] = length
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
opts.on('-m', '--message MESSAGE', 'Message to display before dots') do |message|
|
|
38
|
-
options[:message] = message
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
opts.on('--style STYLE', 'Animation style (blocks, geometric, circles, or b/g/c)') do |style|
|
|
42
|
-
options[:style] = style
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
opts.on('-c', '--command COMMAND', 'Command to run (optional - runs indefinitely without command)') do |command|
|
|
46
|
-
options[:command] = command
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
opts.on('--success TEXT', 'Text to display on successful completion') do |text|
|
|
50
|
-
options[:success] = text
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
opts.on('--error TEXT', 'Text to display on error') do |text|
|
|
54
|
-
options[:error] = text
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
opts.on('--checkmark', 'Show checkmarks (✅/🛑) in completion messages') do
|
|
58
|
-
options[:checkmark] = true
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
opts.on('--stdout', 'Output captured command result to STDOUT') do
|
|
62
|
-
options[:stdout] = true
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
opts.on('-h', '--help', 'Show this help message') do
|
|
66
|
-
puts opts
|
|
67
|
-
exit
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
opts.on('-v', '--version', 'Display the version') do
|
|
71
|
-
puts "Worm version #{RubyProgress::VERSION}"
|
|
72
|
-
exit
|
|
73
|
-
end
|
|
74
|
-
end.parse!
|
|
75
|
-
|
|
76
|
-
options
|
|
77
|
-
end
|
|
4
|
+
# Thin shim to delegate to the unified prg CLI
|
|
5
|
+
prg = File.expand_path(File.join(__dir__, 'prg'))
|
|
6
|
+
if File.executable?(prg)
|
|
7
|
+
exec prg, 'worm', *ARGV
|
|
8
|
+
else
|
|
9
|
+
exec 'prg', 'worm', *ARGV
|
|
78
10
|
end
|
|
79
|
-
|
|
80
|
-
WormCLI.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Example bash script using ruby-progress daemon mode
|
|
4
|
+
# This demonstrates how to use background progress indicators in shell scripts
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "=== Bash Script with Background Progress Demo ==="
|
|
9
|
+
echo
|
|
10
|
+
|
|
11
|
+
# Configuration
|
|
12
|
+
PID_FILE="/tmp/bash_progress.pid"
|
|
13
|
+
PRG_BIN="$(dirname "$0")/../bin/prg"
|
|
14
|
+
|
|
15
|
+
# Clean up function
|
|
16
|
+
cleanup() {
|
|
17
|
+
if [ -f "$PID_FILE" ]; then
|
|
18
|
+
echo "Cleaning up progress indicator..."
|
|
19
|
+
"$PRG_BIN" worm --stop-pid "$PID_FILE" 2>/dev/null || true
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Set up cleanup on exit
|
|
24
|
+
trap cleanup EXIT
|
|
25
|
+
|
|
26
|
+
echo "1. Starting background progress indicator..."
|
|
27
|
+
"$PRG_BIN" worm --daemon --pid-file "$PID_FILE" \
|
|
28
|
+
--message "Processing multiple tasks..." \
|
|
29
|
+
--success "All tasks completed successfully!" \
|
|
30
|
+
--checkmark &
|
|
31
|
+
|
|
32
|
+
# Give daemon a moment to start
|
|
33
|
+
sleep 0.5
|
|
34
|
+
|
|
35
|
+
echo "2. Executing tasks with background progress..."
|
|
36
|
+
|
|
37
|
+
echo " - Downloading files..."
|
|
38
|
+
sleep 2
|
|
39
|
+
|
|
40
|
+
echo " - Processing data..."
|
|
41
|
+
sleep 2
|
|
42
|
+
|
|
43
|
+
echo " - Generating reports..."
|
|
44
|
+
sleep 1
|
|
45
|
+
|
|
46
|
+
echo " - Uploading results..."
|
|
47
|
+
sleep 1
|
|
48
|
+
|
|
49
|
+
echo "3. All tasks complete, stopping progress indicator..."
|
|
50
|
+
cleanup
|
|
51
|
+
|
|
52
|
+
echo
|
|
53
|
+
echo "=== Script Complete ==="
|
|
54
|
+
|
|
55
|
+
# Usage in your own scripts:
|
|
56
|
+
#
|
|
57
|
+
# # Start progress in background
|
|
58
|
+
# prg worm --daemon --pid-file /tmp/my_progress.pid \
|
|
59
|
+
# --message "Working..." --success "Done!" --checkmark &
|
|
60
|
+
#
|
|
61
|
+
# # Do your actual work
|
|
62
|
+
# long_running_command_1
|
|
63
|
+
# long_running_command_2
|
|
64
|
+
# long_running_command_3
|
|
65
|
+
#
|
|
66
|
+
# # Stop progress indicator
|
|
67
|
+
# prg worm --stop-pid /tmp/my_progress.pid
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Daemon mode test script for ruby-progress
|
|
5
|
+
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
|
|
8
|
+
puts '=== Ruby Progress Daemon Mode Demo ==='
|
|
9
|
+
puts
|
|
10
|
+
|
|
11
|
+
# Test directories
|
|
12
|
+
test_dir = '/tmp/progress_test'
|
|
13
|
+
FileUtils.mkdir_p(test_dir)
|
|
14
|
+
pid_file = "#{test_dir}/progress.pid"
|
|
15
|
+
|
|
16
|
+
puts "Test directory: #{test_dir}"
|
|
17
|
+
puts "PID file: #{pid_file}"
|
|
18
|
+
puts
|
|
19
|
+
|
|
20
|
+
# Clean up any existing PID file
|
|
21
|
+
File.delete(pid_file) if File.exist?(pid_file)
|
|
22
|
+
|
|
23
|
+
puts '1. Starting worm progress indicator in daemon mode...'
|
|
24
|
+
bin_path = File.join(File.dirname(__dir__), 'bin', 'prg')
|
|
25
|
+
system("#{bin_path} worm --daemon --pid-file #{pid_file} --message 'Processing in background...' --success 'All tasks completed!' --checkmark &")
|
|
26
|
+
|
|
27
|
+
# Wait a moment for daemon to start
|
|
28
|
+
sleep 1
|
|
29
|
+
|
|
30
|
+
if File.exist?(pid_file)
|
|
31
|
+
pid = File.read(pid_file).strip
|
|
32
|
+
puts " ✅ Daemon started with PID: #{pid}"
|
|
33
|
+
else
|
|
34
|
+
puts ' ❌ Failed to start daemon'
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
puts
|
|
39
|
+
puts '2. Simulating background work (5 seconds)...'
|
|
40
|
+
puts ' - Task 1: Processing data...'
|
|
41
|
+
sleep 2
|
|
42
|
+
puts ' - Task 2: Running analysis...'
|
|
43
|
+
sleep 2
|
|
44
|
+
puts ' - Task 3: Generating report...'
|
|
45
|
+
sleep 1
|
|
46
|
+
|
|
47
|
+
puts
|
|
48
|
+
puts '3. Stopping daemon...'
|
|
49
|
+
system("#{bin_path} worm --stop-pid #{pid_file}")
|
|
50
|
+
|
|
51
|
+
puts
|
|
52
|
+
puts '4. Cleaning up...'
|
|
53
|
+
FileUtils.rm_rf(test_dir)
|
|
54
|
+
|
|
55
|
+
puts
|
|
56
|
+
puts '=== Demo Complete ==='
|
|
57
|
+
puts
|
|
58
|
+
puts 'Usage in bash scripts:'
|
|
59
|
+
puts ' # Start daemon'
|
|
60
|
+
puts " prg worm --daemon --pid-file /tmp/my_progress.pid --message 'Working...' --success 'Done!' &"
|
|
61
|
+
puts ' '
|
|
62
|
+
puts ' # Do your work'
|
|
63
|
+
puts ' my_long_running_task_1'
|
|
64
|
+
puts ' my_long_running_task_2'
|
|
65
|
+
puts ' '
|
|
66
|
+
puts ' # Stop daemon'
|
|
67
|
+
puts ' prg worm --stop-pid /tmp/my_progress.pid'
|
data/examples/utils_demo.rb
CHANGED
|
@@ -4,49 +4,50 @@
|
|
|
4
4
|
# Demo of RubyProgress::Utils universal utilities
|
|
5
5
|
require_relative '../lib/ruby-progress'
|
|
6
6
|
|
|
7
|
-
puts
|
|
7
|
+
puts '=== RubyProgress::Utils Demo ==='
|
|
8
8
|
puts
|
|
9
9
|
|
|
10
10
|
# Test cursor control
|
|
11
|
-
puts
|
|
12
|
-
print
|
|
11
|
+
puts 'Testing cursor control...'
|
|
12
|
+
print 'Hiding cursor for 2 seconds...'
|
|
13
13
|
RubyProgress::Utils.hide_cursor
|
|
14
14
|
sleep 2
|
|
15
|
-
print
|
|
15
|
+
print ' showing cursor again.'
|
|
16
16
|
RubyProgress::Utils.show_cursor
|
|
17
17
|
puts
|
|
18
18
|
puts
|
|
19
19
|
|
|
20
20
|
# Test line clearing
|
|
21
|
-
puts
|
|
22
|
-
print
|
|
21
|
+
puts 'Testing line clearing...'
|
|
22
|
+
print 'This line will be cleared...'
|
|
23
23
|
sleep 1
|
|
24
24
|
RubyProgress::Utils.clear_line
|
|
25
|
-
print
|
|
25
|
+
print 'New content on the same line!'
|
|
26
26
|
puts
|
|
27
27
|
puts
|
|
28
28
|
|
|
29
29
|
# Test completion messages
|
|
30
|
-
puts
|
|
30
|
+
puts 'Testing completion messages...'
|
|
31
31
|
|
|
32
|
-
RubyProgress::Utils.display_completion(
|
|
33
|
-
RubyProgress::Utils.display_completion(
|
|
32
|
+
RubyProgress::Utils.display_completion('Basic success message', success: true)
|
|
33
|
+
RubyProgress::Utils.display_completion('Basic failure message', success: false)
|
|
34
34
|
|
|
35
35
|
puts
|
|
36
|
-
puts
|
|
37
|
-
RubyProgress::Utils.display_completion(
|
|
38
|
-
RubyProgress::Utils.display_completion(
|
|
36
|
+
puts 'With checkmarks:'
|
|
37
|
+
RubyProgress::Utils.display_completion('Success with checkmark', success: true, show_checkmark: true)
|
|
38
|
+
RubyProgress::Utils.display_completion('Failure with checkmark', success: false, show_checkmark: true)
|
|
39
39
|
|
|
40
40
|
puts
|
|
41
|
-
puts
|
|
42
|
-
RubyProgress::Utils.display_completion(
|
|
43
|
-
RubyProgress::Utils.display_completion(
|
|
41
|
+
puts 'Different output streams:'
|
|
42
|
+
RubyProgress::Utils.display_completion('To STDOUT', success: true, show_checkmark: true, output_stream: :stdout)
|
|
43
|
+
RubyProgress::Utils.display_completion('To STDERR', success: false, show_checkmark: true, output_stream: :stderr)
|
|
44
44
|
|
|
45
45
|
puts
|
|
46
|
-
puts
|
|
47
|
-
print
|
|
46
|
+
puts 'Complete with clear:'
|
|
47
|
+
print 'Content to be cleared and replaced...'
|
|
48
48
|
sleep 1
|
|
49
|
-
RubyProgress::Utils.complete_with_clear(
|
|
49
|
+
RubyProgress::Utils.complete_with_clear('Cleared and completed!', success: true, show_checkmark: true,
|
|
50
|
+
output_stream: :stdout)
|
|
50
51
|
|
|
51
52
|
puts
|
|
52
|
-
puts
|
|
53
|
+
puts '=== Demo Complete ==='
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module RubyProgress
|
|
6
|
+
module Daemon
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def default_pid_file
|
|
10
|
+
'/tmp/ruby-progress/progress.pid'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def control_message_file(pid_file)
|
|
14
|
+
"#{pid_file}.msg"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def show_status(pid_file)
|
|
18
|
+
if File.exist?(pid_file)
|
|
19
|
+
pid = File.read(pid_file).strip
|
|
20
|
+
running = system("ps -p #{pid} > /dev/null")
|
|
21
|
+
puts(running ? "Daemon running (pid #{pid})" : 'PID file present but process not running')
|
|
22
|
+
exit(running ? 0 : 1)
|
|
23
|
+
else
|
|
24
|
+
puts 'Daemon not running'
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop_daemon_by_pid_file(pid_file, message: nil, checkmark: false, error: false)
|
|
30
|
+
unless File.exist?(pid_file)
|
|
31
|
+
puts "PID file #{pid_file} not found"
|
|
32
|
+
exit 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
pid = File.read(pid_file).strip.to_i
|
|
36
|
+
|
|
37
|
+
# Write control message file if provided
|
|
38
|
+
if message || error
|
|
39
|
+
cmf = control_message_file(pid_file)
|
|
40
|
+
payload = { checkmark: checkmark, success: !error }
|
|
41
|
+
payload[:message] = message if message
|
|
42
|
+
File.write(cmf, payload.to_json)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
Process.kill('USR1', pid)
|
|
47
|
+
puts "Stop signal sent to process #{pid}"
|
|
48
|
+
sleep 0.5
|
|
49
|
+
File.delete(pid_file) if File.exist?(pid_file)
|
|
50
|
+
rescue Errno::ESRCH
|
|
51
|
+
puts "Process #{pid} not found (may have already stopped)"
|
|
52
|
+
File.delete(pid_file) if File.exist?(pid_file)
|
|
53
|
+
exit 1
|
|
54
|
+
rescue Errno::EPERM
|
|
55
|
+
puts "Permission denied sending signal to process #{pid}"
|
|
56
|
+
exit 1
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/ruby-progress/utils.rb
CHANGED
|
@@ -35,7 +35,7 @@ module RubyProgress
|
|
|
35
35
|
when :stdout
|
|
36
36
|
puts formatted_message
|
|
37
37
|
when :stderr
|
|
38
|
-
|
|
38
|
+
warn formatted_message
|
|
39
39
|
when :warn
|
|
40
40
|
warn "\e[2K#{formatted_message}"
|
|
41
41
|
else
|
|
@@ -50,4 +50,4 @@ module RubyProgress
|
|
|
50
50
|
display_completion(message, success: success, show_checkmark: show_checkmark, output_stream: output_stream)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
-
end
|
|
53
|
+
end
|
data/lib/ruby-progress/worm.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'optparse'
|
|
4
4
|
require 'open3'
|
|
5
|
+
require 'json'
|
|
5
6
|
|
|
6
7
|
module RubyProgress
|
|
7
8
|
# Animated progress indicator with ripple effect using Unicode combining characters
|
|
@@ -11,7 +12,7 @@ module RubyProgress
|
|
|
11
12
|
'circles' => {
|
|
12
13
|
baseline: '·', # middle dot
|
|
13
14
|
midline: '●', # black circle
|
|
14
|
-
peak: '⬤'
|
|
15
|
+
peak: '⬤' # large circle
|
|
15
16
|
},
|
|
16
17
|
'blocks' => {
|
|
17
18
|
baseline: '▁', # lower eighth block
|
|
@@ -70,7 +71,7 @@ module RubyProgress
|
|
|
70
71
|
result = yield
|
|
71
72
|
@running = false
|
|
72
73
|
animation_thread.join
|
|
73
|
-
display_completion_message(@success_text
|
|
74
|
+
display_completion_message(@success_text, true)
|
|
74
75
|
result
|
|
75
76
|
else
|
|
76
77
|
animation_thread.join
|
|
@@ -113,7 +114,6 @@ module RubyProgress
|
|
|
113
114
|
|
|
114
115
|
# Output to stdout if --stdout flag is set
|
|
115
116
|
puts stdout_content if @output_stdout && stdout_content
|
|
116
|
-
|
|
117
117
|
rescue StandardError => e
|
|
118
118
|
# animate method handles error display, just exit with proper code
|
|
119
119
|
exit exit_code.nonzero? || 1
|
|
@@ -148,6 +148,78 @@ module RubyProgress
|
|
|
148
148
|
@running = false
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
+
def run_daemon_mode(success_message: nil, show_checkmark: false, control_message_file: nil)
|
|
152
|
+
@running = true
|
|
153
|
+
stop_requested = false
|
|
154
|
+
|
|
155
|
+
# Set up signal handlers
|
|
156
|
+
original_int_handler = Signal.trap('INT') { stop_requested = true }
|
|
157
|
+
Signal.trap('USR1') { stop_requested = true }
|
|
158
|
+
Signal.trap('TERM') { stop_requested = true }
|
|
159
|
+
Signal.trap('HUP') { stop_requested = true }
|
|
160
|
+
|
|
161
|
+
RubyProgress::Utils.hide_cursor
|
|
162
|
+
|
|
163
|
+
begin
|
|
164
|
+
animation_loop_step while @running && !stop_requested
|
|
165
|
+
ensure
|
|
166
|
+
RubyProgress::Utils.clear_line
|
|
167
|
+
RubyProgress::Utils.show_cursor
|
|
168
|
+
|
|
169
|
+
# Display stop-time completion message, preferring control file if provided
|
|
170
|
+
final_message = success_message
|
|
171
|
+
final_checkmark = show_checkmark
|
|
172
|
+
final_success = true
|
|
173
|
+
if control_message_file && File.exist?(control_message_file)
|
|
174
|
+
begin
|
|
175
|
+
data = JSON.parse(File.read(control_message_file))
|
|
176
|
+
final_message = data['message'] if data['message']
|
|
177
|
+
final_checkmark = !!data['checkmark'] if data.key?('checkmark')
|
|
178
|
+
final_success = !!data['success'] if data.key?('success')
|
|
179
|
+
rescue StandardError
|
|
180
|
+
# ignore parse errors, fallback to provided message
|
|
181
|
+
ensure
|
|
182
|
+
begin
|
|
183
|
+
File.delete(control_message_file)
|
|
184
|
+
rescue StandardError
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if final_message
|
|
191
|
+
RubyProgress::Utils.display_completion(
|
|
192
|
+
final_message,
|
|
193
|
+
success: final_success,
|
|
194
|
+
show_checkmark: final_checkmark,
|
|
195
|
+
output_stream: :stdout
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
Signal.trap('INT', original_int_handler) if original_int_handler
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def animation_loop_step
|
|
204
|
+
return unless @running
|
|
205
|
+
|
|
206
|
+
@position ||= 0
|
|
207
|
+
@direction ||= 1
|
|
208
|
+
|
|
209
|
+
print "\r#{@message}#{generate_dots(@position, @direction)}"
|
|
210
|
+
$stdout.flush
|
|
211
|
+
|
|
212
|
+
sleep @speed
|
|
213
|
+
|
|
214
|
+
# Update position and direction
|
|
215
|
+
@position += @direction
|
|
216
|
+
if @position >= @length - 1
|
|
217
|
+
@direction = -1
|
|
218
|
+
elsif @position <= 0
|
|
219
|
+
@direction = 1
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
151
223
|
private
|
|
152
224
|
|
|
153
225
|
def display_completion_message(message, success)
|
|
@@ -237,8 +309,8 @@ module RubyProgress
|
|
|
237
309
|
# When moving right, midline appears to the left of peak
|
|
238
310
|
if direction == -1 # moving left
|
|
239
311
|
dots[i] = @style[:midline] if i > ripple_position
|
|
240
|
-
|
|
241
|
-
dots[i] = @style[:midline]
|
|
312
|
+
elsif i < ripple_position # moving right
|
|
313
|
+
dots[i] = @style[:midline]
|
|
242
314
|
end
|
|
243
315
|
else
|
|
244
316
|
dots[i] = @style[:baseline]
|
|
@@ -250,4 +322,4 @@ module RubyProgress
|
|
|
250
322
|
|
|
251
323
|
# Terminal utilities moved to RubyProgress::Utils
|
|
252
324
|
end
|
|
253
|
-
end
|
|
325
|
+
end
|
data/lib/ruby-progress.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'ruby-progress/version'
|
|
|
4
4
|
require_relative 'ruby-progress/utils'
|
|
5
5
|
require_relative 'ruby-progress/ripple'
|
|
6
6
|
require_relative 'ruby-progress/worm'
|
|
7
|
+
require_relative 'ruby-progress/daemon'
|
|
7
8
|
|
|
8
9
|
module RubyProgress
|
|
9
10
|
# Main module for Ruby Progress indicators
|