ruby-progress 1.0.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.
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyProgress
4
+ # Color definitions for terminal output
5
+ COLORS = {
6
+ 'red' => "\e[31m",
7
+ 'green' => "\e[32m",
8
+ 'yellow' => "\e[33m",
9
+ 'blue' => "\e[34m",
10
+ 'magenta' => "\e[35m",
11
+ 'cyan' => "\e[36m",
12
+ 'white' => "\e[37m",
13
+ 'dark_red' => "\e[31;1m",
14
+ 'dark_green' => "\e[32;1m",
15
+ 'dark_yellow' => "\e[33;1m",
16
+ 'dark_blue' => "\e[34;1m",
17
+ 'dark_magenta' => "\e[35;1m",
18
+ 'dark_cyan' => "\e[36;1m",
19
+ 'dark_white' => "\e[37;1m",
20
+ 'light_red' => "\e[31;2m",
21
+ 'light_green' => "\e[32;2m",
22
+ 'light_yellow' => "\e[33;2m",
23
+ 'light_blue' => "\e[34;2m",
24
+ 'light_magenta' => "\e[35;2m",
25
+ 'light_cyan' => "\e[36;2m",
26
+ 'light_white' => "\e[37;2m",
27
+ 'reset' => "\e[0m"
28
+ }.freeze
29
+
30
+ # Spinner indicator definitions
31
+ INDICATORS = {
32
+ arc: %w[◜ ◠ ◝ ◞ ◡ ◟],
33
+ arrow: %w[← ↖ ↑ ↗ → ↘ ↓ ↙],
34
+ arrow_pulse: [
35
+ '▹▹▹▹▹',
36
+ '▸▹▹▹▹',
37
+ '▹▸▹▹▹',
38
+ '▹▹▸▹▹',
39
+ '▹▹▹▸▹',
40
+ '▹▹▹▹▸'
41
+ ],
42
+ balloon: %w[. o O ° O o .],
43
+ block_1: %w[▖▖▖ ▘▖▖ ▖▘▖ ▖▖▘],
44
+ block_2: %w[▌ ▀ ▐ ▄],
45
+ bounce: [
46
+ '[ ]',
47
+ '[= ]',
48
+ '[== ]',
49
+ '[=== ]',
50
+ '[====]'
51
+ ],
52
+ circle: %w[○○○ ●○○ ○●○ ○○●],
53
+ classic: ['|', '/', '—', '\\'],
54
+ dots: %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏],
55
+ dots_2: %w[⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷],
56
+ dots_3: %w[⠋ ⠙ ⠚ ⠞ ⠖ ⠦ ⠴ ⠲ ⠳ ⠓],
57
+ dots_4: %w[⠄ ⠆ ⠇ ⠋ ⠙ ⠸ ⠰ ⠠ ⠰ ⠸ ⠙ ⠋ ⠇ ⠆],
58
+ dots_5: %w[⠋ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋],
59
+ dots_6: %w[⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠴ ⠲ ⠒ ⠂ ⠂ ⠒ ⠚ ⠙ ⠉ ⠁],
60
+ dots_7: %w[⠈ ⠉ ⠋ ⠓ ⠒ ⠐ ⠐ ⠒ ⠖ ⠦ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈],
61
+ dots_8: %w[⠁ ⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈ ⠈],
62
+ dots_9: %w[⢹ ⢺ ⢼ ⣸ ⣇ ⡧ ⡗ ⡏],
63
+ dots_10: %w[⢄ ⢂ ⢁ ⡁ ⡈ ⡐ ⡠],
64
+ dots_11: %w[⠁ ⠂ ⠄ ⡀ ⢀ ⠠ ⠐ ⠈],
65
+ ellipsis: ['. ', '.. ', '... ', '....'],
66
+ lighthouse: ['∙∙∙', '●∙∙', '∙●∙', '∙∙●'],
67
+ o: %w[Ooo oOo ooO],
68
+ pipe: %w[┤ ┘ ┴ └ ├ ┌ ┬ ┐],
69
+ pulse: %w[⎺ ⎻ ⎼ ⎽ ⎼ ⎻],
70
+ pulse_2: %w[▁ ▃ ▅ ▆ ▇ █ ▇ ▆ ▅ ▃],
71
+ pulse_3: %w[▉ ▊ ▋ ▌ ▍ ▎ ▏ ▎ ▍ ▌ ▋ ▊ ▉],
72
+ pulse_4: %w[- = ≡ = -],
73
+ push: [
74
+ '[> ]',
75
+ '[=> ]',
76
+ '[==> ]',
77
+ '[===> ]',
78
+ '[====>]'
79
+ ],
80
+ spin: %w[◴ ◷ ◶ ◵],
81
+ spin_2: %w[◐ ◓ ◑ ◒],
82
+ spin_3: %w[◰ ◳ ◲ ◱],
83
+ toggle: %w[■ □ ▪ ▫],
84
+ triangle: %w[◢ ◣ ◤ ◥]
85
+ }.freeze
86
+
87
+ # String extensions for color support
88
+ module StringExtensions
89
+ COLORS.each do |color_name, color_code|
90
+ define_method(color_name) do
91
+ "#{color_code}#{self}#{COLORS['reset']}"
92
+ end
93
+ end
94
+
95
+ def rainbow(index = 0)
96
+ chars = split('')
97
+ colored_chars = chars.map.with_index do |char, idx|
98
+ color = COLORS.values[(idx + index) % COLORS.size]
99
+ "#{color}#{char}#{COLORS['reset']}"
100
+ end
101
+ colored_chars.join
102
+ end
103
+
104
+ def normalize_type
105
+ spinner_type = :classic
106
+ INDICATORS.each do |spinner, _v|
107
+ spinner_type = spinner if spinner =~ /^#{split('').join('.*?')}/i
108
+ end
109
+ spinner_type
110
+ end
111
+ end
112
+
113
+ # Text ripple animation class
114
+ class Ripple
115
+ attr_accessor :index, :string, :speed, :format, :inverse, :rainbow, :spinner, :spinner_position, :caps
116
+
117
+ def initialize(string, options = {})
118
+ defaults = {
119
+ speed: :medium,
120
+ format: :bidirectional,
121
+ rainbow: false,
122
+ spinner: false,
123
+ spinner_position: false,
124
+ caps: false,
125
+ inverse: false,
126
+ output: :error
127
+ }
128
+ @options = defaults.merge(options)
129
+ @string = string
130
+ @index = 0
131
+ @direction = :forward
132
+ @rainbow = @options[:rainbow]
133
+ @spinner = @options[:spinner]
134
+ @spinner_position = @options[:spinner_position]
135
+ @caps = @options[:caps]
136
+ @inverse = @options[:inverse]
137
+ end
138
+
139
+ def printout
140
+ letters = @string.dup.split('')
141
+ i = @index
142
+ if @spinner
143
+ case @spinner_position
144
+ when :before
145
+ pre = "#{INDICATORS[@spinner][i]} "
146
+ post = @string
147
+ else
148
+ pre = "#{@string} "
149
+ post = INDICATORS[@spinner][i]
150
+ end
151
+ elsif @caps
152
+ pre = letters.slice!(0, i).join
153
+ char = letters.slice!(0, 2).join
154
+ post = letters.slice!(0, letters.length).join
155
+ pre = @inverse ? pre.upcase : pre.downcase
156
+ char = @inverse ? char.downcase : char.upcase
157
+ post = @inverse ? post.upcase : post.downcase
158
+ elsif @inverse
159
+ pre = letters.slice!(0, i).join
160
+ pre = @rainbow ? pre.rainbow : pre.extend(StringExtensions).light_white
161
+ char = letters.slice!(0, 2).join
162
+ char = char.extend(StringExtensions).dark_white
163
+ post = letters.slice!(0, letters.length).join
164
+ post = @rainbow ? post.rainbow : post.extend(StringExtensions).light_white
165
+ else
166
+ pre = letters.slice!(0, i).join.extend(StringExtensions).dark_white
167
+ char = letters.slice!(0, 2).join
168
+ char = @rainbow ? char.rainbow(i) : char.extend(StringExtensions).light_white
169
+ post = letters.slice!(0, letters.length).join.extend(StringExtensions).dark_white
170
+ end
171
+ $stderr.print "\e[2K#{pre}#{char}#{post}\r"
172
+ end
173
+
174
+ # Hide or show the cursor (delegated to Utils)
175
+ def self.hide_cursor
176
+ RubyProgress::Utils.hide_cursor
177
+ end
178
+
179
+ def self.show_cursor
180
+ RubyProgress::Utils.show_cursor
181
+ end
182
+
183
+ def self.complete(string, message, checkmark, success)
184
+ display_message = message || (checkmark ? string : nil)
185
+ return unless display_message
186
+
187
+ RubyProgress::Utils.display_completion(
188
+ display_message,
189
+ success: success,
190
+ show_checkmark: checkmark,
191
+ output_stream: :warn
192
+ )
193
+ end
194
+
195
+ def advance
196
+ max = @spinner ? (INDICATORS[@spinner].count - 1) : (@string.length - 1)
197
+ advance = true
198
+
199
+ if @index == max && @options[:format] != :forward_only
200
+ @direction = :backward
201
+ elsif @index == max && @options[:format] == :forward_only
202
+ @index = 0
203
+ advance = false
204
+ elsif @index == 0
205
+ @direction = :forward
206
+ end
207
+
208
+ if advance
209
+ @index = @direction == :backward ? @index - 1 : @index + 1
210
+ end
211
+
212
+ printout
213
+
214
+ case @options[:speed]
215
+ when :fast
216
+ sleep 0.05
217
+ when :medium
218
+ sleep 0.1
219
+ else
220
+ sleep 0.2
221
+ end
222
+ end
223
+
224
+ def self.progress(string, options = {})
225
+ Signal.trap('INT') do
226
+ Thread.current.kill
227
+ nil
228
+ end
229
+ defaults = { speed: :medium,
230
+ format: :bidirectional,
231
+ rainbow: false,
232
+ inverse: false,
233
+ output: :error }
234
+ options = defaults.merge(options)
235
+
236
+ rippler = new(string, options)
237
+ Ripple.hide_cursor
238
+ begin
239
+ thread = Thread.new do
240
+ rippler.advance while true
241
+ end
242
+ result = yield if block_given?
243
+ thread.kill
244
+
245
+ if @options[:output] == :error
246
+ $?.exitstatus.zero?
247
+ elsif @options[:output] == :stdout
248
+ result
249
+ else
250
+ nil
251
+ end
252
+ rescue StandardError
253
+ thread&.kill
254
+ nil
255
+ ensure
256
+ Ripple.show_cursor
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ # Extend String class with color methods
263
+ class String
264
+ include RubyProgress::StringExtensions
265
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyProgress
4
+ # Universal terminal utilities shared between progress indicators
5
+ module Utils
6
+ # Terminal cursor control
7
+ def self.hide_cursor
8
+ $stderr.print "\e[?25l"
9
+ end
10
+
11
+ def self.show_cursor
12
+ $stderr.print "\e[?25h"
13
+ end
14
+
15
+ def self.clear_line
16
+ print "\r\e[K"
17
+ end
18
+
19
+ # Universal completion message display
20
+ # @param message [String] The message to display
21
+ # @param success [Boolean] Whether this represents success or failure
22
+ # @param show_checkmark [Boolean] Whether to show checkmark/X symbols
23
+ # @param output_stream [Symbol] Where to output (:stdout, :stderr, :warn)
24
+ def self.display_completion(message, success: true, show_checkmark: false, output_stream: :warn)
25
+ return unless message
26
+
27
+ mark = ''
28
+ if show_checkmark
29
+ mark = success ? '✅ ' : '🛑 '
30
+ end
31
+
32
+ formatted_message = "#{mark}#{message}"
33
+
34
+ case output_stream
35
+ when :stdout
36
+ puts formatted_message
37
+ when :stderr
38
+ $stderr.puts formatted_message
39
+ when :warn
40
+ warn "\e[2K#{formatted_message}"
41
+ else
42
+ warn "\e[2K#{formatted_message}"
43
+ end
44
+ end
45
+
46
+ # Clear current line and display completion message
47
+ # Convenience method that combines line clearing with message display
48
+ def self.complete_with_clear(message, success: true, show_checkmark: false, output_stream: :warn)
49
+ clear_line if output_stream != :warn # warn already includes clear in display_completion
50
+ display_completion(message, success: success, show_checkmark: show_checkmark, output_stream: output_stream)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyProgress
4
+ VERSION = '1.0.1'
5
+ end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'open3'
5
+
6
+ module RubyProgress
7
+ # Animated progress indicator with ripple effect using Unicode combining characters
8
+ class Worm
9
+ # Ripple effect styles
10
+ RIPPLE_STYLES = {
11
+ 'circles' => {
12
+ baseline: '·', # middle dot
13
+ midline: '●', # black circle
14
+ peak: '⬤' # large circle
15
+ },
16
+ 'blocks' => {
17
+ baseline: '▁', # lower eighth block
18
+ midline: '▄', # lower half block
19
+ peak: '█' # full block
20
+ },
21
+ 'geometric' => {
22
+ baseline: '▪', # small black square
23
+ midline: '▫', # small white square
24
+ peak: '■' # large black square
25
+ }
26
+ }.freeze
27
+
28
+ # Speed mappings
29
+ SPEED_MAP = {
30
+ 'slow' => 0.5,
31
+ 'medium' => 0.2,
32
+ 'fast' => 0.1
33
+ }.freeze
34
+
35
+ def initialize(options = {})
36
+ @length = options[:length] || 3
37
+ @message = options[:message] || 'Processing'
38
+ @speed = parse_speed(options[:speed] || 'medium')
39
+ @style = parse_style(options[:style] || 'circles')
40
+ @command = options[:command]
41
+ @success_text = options[:success]
42
+ @error_text = options[:error]
43
+ @show_checkmark = options[:checkmark] || false
44
+ @output_stdout = options[:stdout] || false
45
+ @running = false
46
+ end
47
+
48
+ def animate(message: nil, success: nil, error: nil, &block)
49
+ @message = message if message
50
+ @success_text = success if success
51
+ @error_text = error if error
52
+ @running = true
53
+
54
+ # Set up interrupt handler to ensure cursor is restored
55
+ original_int_handler = Signal.trap('INT') do
56
+ @running = false
57
+ RubyProgress::Utils.clear_line
58
+ RubyProgress::Utils.show_cursor
59
+ puts "\nInterrupted!"
60
+ exit 130
61
+ end
62
+
63
+ # Hide cursor
64
+ RubyProgress::Utils.hide_cursor
65
+
66
+ animation_thread = Thread.new { animation_loop }
67
+
68
+ begin
69
+ if block_given?
70
+ result = yield
71
+ @running = false
72
+ animation_thread.join
73
+ display_completion_message(@success_text || 'Done!', true)
74
+ result
75
+ else
76
+ animation_thread.join
77
+ end
78
+ rescue StandardError => e
79
+ @running = false
80
+ animation_thread.join
81
+ display_completion_message(@error_text || "Error: #{e.message}", false)
82
+ nil # Return nil instead of re-raising when used as a progress indicator
83
+ ensure
84
+ # Always restore cursor and signal handler
85
+ RubyProgress::Utils.show_cursor
86
+ Signal.trap('INT', original_int_handler) if original_int_handler
87
+ end
88
+ end
89
+
90
+ def run_with_command
91
+ return unless @command
92
+
93
+ exit_code = 0
94
+ stdout_content = nil
95
+
96
+ begin
97
+ stdout_content = animate do
98
+ # Use popen3 instead of capture3 for better signal handling
99
+ Open3.popen3(@command) do |stdin, stdout, stderr, wait_thr|
100
+ stdin.close
101
+ captured_stdout = stdout.read
102
+ stderr_content = stderr.read
103
+ exit_code = wait_thr.value.exitstatus
104
+
105
+ unless wait_thr.value.success?
106
+ error_msg = @error_text || "Command failed with exit code #{exit_code}"
107
+ error_msg += ": #{stderr_content.strip}" if stderr_content && !stderr_content.empty?
108
+ raise StandardError, error_msg
109
+ end
110
+ captured_stdout
111
+ end
112
+ end
113
+
114
+ # Output to stdout if --stdout flag is set
115
+ puts stdout_content if @output_stdout && stdout_content
116
+
117
+ rescue StandardError => e
118
+ # animate method handles error display, just exit with proper code
119
+ exit exit_code.nonzero? || 1
120
+ rescue Interrupt
121
+ puts "\nInterrupted!"
122
+ exit 130
123
+ end
124
+ end
125
+
126
+ def run_indefinitely
127
+ # Set up interrupt handler to ensure cursor is restored
128
+ original_int_handler = Signal.trap('INT') do
129
+ @running = false
130
+ RubyProgress::Utils.clear_line
131
+ RubyProgress::Utils.show_cursor
132
+ puts "\nInterrupted!"
133
+ exit 130
134
+ end
135
+
136
+ @running = true
137
+ RubyProgress::Utils.hide_cursor
138
+
139
+ begin
140
+ animation_loop
141
+ ensure
142
+ RubyProgress::Utils.show_cursor
143
+ Signal.trap('INT', original_int_handler) if original_int_handler
144
+ end
145
+ end
146
+
147
+ def stop
148
+ @running = false
149
+ end
150
+
151
+ private
152
+
153
+ def display_completion_message(message, success)
154
+ return unless message
155
+
156
+ mark = ''
157
+ if @show_checkmark
158
+ mark = success ? '✅ ' : '🛑 '
159
+ end
160
+
161
+ puts "#{mark}#{message}"
162
+ end
163
+
164
+ def parse_speed(speed_input)
165
+ case speed_input
166
+ when String
167
+ if speed_input.match?(/^\d+$/)
168
+ # Numeric string (1-10)
169
+ speed_num = speed_input.to_i
170
+ return 0.6 - (speed_num - 1) * 0.05 if speed_num.between?(1, 10)
171
+ end
172
+
173
+ # Check for abbreviated forms
174
+ speed_lower = speed_input.downcase
175
+ if speed_lower.start_with?('f')
176
+ SPEED_MAP['fast']
177
+ elsif speed_lower.start_with?('m')
178
+ SPEED_MAP['medium']
179
+ elsif speed_lower.start_with?('s')
180
+ SPEED_MAP['slow']
181
+ else
182
+ SPEED_MAP['medium']
183
+ end
184
+ when Numeric
185
+ speed_num = speed_input.to_i
186
+ speed_num.between?(1, 10) ? 0.6 - (speed_num - 1) * 0.05 : SPEED_MAP['medium']
187
+ else
188
+ SPEED_MAP['medium']
189
+ end
190
+ end
191
+
192
+ def parse_style(style_input)
193
+ return RIPPLE_STYLES['circles'] unless style_input
194
+
195
+ style_lower = style_input.to_s.downcase
196
+ if style_lower.start_with?('b')
197
+ RIPPLE_STYLES['blocks']
198
+ elsif style_lower.start_with?('g')
199
+ RIPPLE_STYLES['geometric']
200
+ elsif style_lower.start_with?('c')
201
+ RIPPLE_STYLES['circles']
202
+ else
203
+ RIPPLE_STYLES['circles'] # default
204
+ end
205
+ end
206
+
207
+ def animation_loop
208
+ position = 0
209
+ direction = 1
210
+
211
+ while @running
212
+ print "\r#{@message}#{generate_dots(position, direction)}"
213
+ $stdout.flush
214
+
215
+ sleep @speed
216
+
217
+ position += direction
218
+ if position >= @length - 1
219
+ direction = -1
220
+ elsif position <= 0
221
+ direction = 1
222
+ end
223
+ end
224
+ end
225
+
226
+ def generate_dots(ripple_position, direction)
227
+ dots = Array.new(@length) { @style[:baseline] }
228
+
229
+ # Apply ripple effect
230
+ (0...@length).each do |i|
231
+ distance = (i - ripple_position).abs
232
+ case distance
233
+ when 0
234
+ dots[i] = @style[:peak]
235
+ when 1
236
+ # When moving left, midline appears to the right of peak
237
+ # When moving right, midline appears to the left of peak
238
+ if direction == -1 # moving left
239
+ dots[i] = @style[:midline] if i > ripple_position
240
+ else # moving right
241
+ dots[i] = @style[:midline] if i < ripple_position
242
+ end
243
+ else
244
+ dots[i] = @style[:baseline]
245
+ end
246
+ end
247
+
248
+ dots.join
249
+ end
250
+
251
+ # Terminal utilities moved to RubyProgress::Utils
252
+ end
253
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ruby-progress/version'
4
+ require_relative 'ruby-progress/utils'
5
+ require_relative 'ruby-progress/ripple'
6
+ require_relative 'ruby-progress/worm'
7
+
8
+ module RubyProgress
9
+ # Main module for Ruby Progress indicators
10
+ # Contains both Ripple and Worm classes for different types of progress animations
11
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/ruby-progress/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ruby-progress'
7
+ spec.version = RubyProgress::VERSION
8
+ spec.authors = ['Brett Terpstra']
9
+ spec.email = ['me@brettterpstra.com']
10
+
11
+ spec.summary = 'Animated terminal progress indicators'
12
+ spec.description = 'Two different animated progress indicators for Ruby: Ripple (text ripple effects) and Worm (Unicode wave animations)'
13
+ spec.homepage = 'https://github.com/ttscoff/ruby-progress'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.5.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = spec.homepage
19
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = 'bin'
28
+ spec.executables = ['prg', 'ripple', 'worm']
29
+ spec.require_paths = ['lib']
30
+
31
+ # Runtime dependencies
32
+ # None required - uses only standard library
33
+
34
+ # Development dependencies
35
+ spec.add_development_dependency 'rake', '~> 13.0'
36
+ spec.add_development_dependency 'rspec', '~> 3.0'
37
+ spec.add_development_dependency 'rubocop', '~> 1.21'
38
+ spec.add_development_dependency 'simplecov', '~> 0.21'
39
+ end
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Test script to demonstrate the new --stdout and --checkmark flags in worm.rb
4
+
5
+ puts "Testing worm.rb with new flags:"
6
+ puts
7
+
8
+ puts "1. Testing --checkmark flag with success:"
9
+ system("ruby worm.rb --command 'echo Hello World' --message 'Running test' --success 'Test passed!' --checkmark")
10
+ puts
11
+
12
+ puts "2. Testing --stdout flag:"
13
+ system("ruby worm.rb --command 'echo This output will be displayed' --message 'Capturing output' --stdout")
14
+ puts
15
+
16
+ puts "3. Testing both --checkmark and --stdout together:"
17
+ system("ruby worm.rb --command 'echo Combined test output' --message 'Testing both flags' --success 'Both flags work!' --checkmark --stdout")
18
+ puts
19
+
20
+ puts "4. Testing --checkmark with failure:"
21
+ system("ruby worm.rb --command 'exit 1' --message 'Testing failure' --error 'Test failed!' --checkmark")
22
+ puts
23
+
24
+ puts "All tests completed!"