progress_bar_none_overload_3000 1.0.0

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,473 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProgressBarNone
4
+ # Renders the progress bar and metrics display
5
+ class Renderer
6
+ # Crystal-inspired bar characters
7
+ CRYSTAL_CHARS = {
8
+ filled: "█",
9
+ partial: ["░", "▒", "▓"],
10
+ empty: "░",
11
+ left_cap: "❮",
12
+ right_cap: "❯",
13
+ shimmer: ["✦", "✧", "⬥", "⬦", "◆", "◇"],
14
+ }.freeze
15
+
16
+ # Alternative styles
17
+ STYLES = {
18
+ crystal: {
19
+ filled: "█",
20
+ partial: ["░", "▒", "▓"],
21
+ empty: "░",
22
+ left: "⟨",
23
+ right: "⟩",
24
+ pulse: ["◈", "◇", "◆"],
25
+ },
26
+ blocks: {
27
+ filled: "█",
28
+ partial: ["▏", "▎", "▍", "▌", "▋", "▊", "▉"],
29
+ empty: "░",
30
+ left: "[",
31
+ right: "]",
32
+ pulse: ["█", "▓", "▒"],
33
+ },
34
+ dots: {
35
+ filled: "●",
36
+ partial: ["○", "◐", "◑", "●"],
37
+ empty: "○",
38
+ left: "(",
39
+ right: ")",
40
+ pulse: ["●", "◉", "○"],
41
+ },
42
+ arrows: {
43
+ filled: "▶",
44
+ partial: ["▷", "▸", "▹"],
45
+ empty: "▹",
46
+ left: "«",
47
+ right: "»",
48
+ pulse: ["▸", "▹", "▸"],
49
+ },
50
+ ascii: {
51
+ filled: "#",
52
+ partial: ["-", "=", "#"],
53
+ empty: "-",
54
+ left: "[",
55
+ right: "]",
56
+ pulse: ["#", "=", "-"],
57
+ },
58
+ # Animated styles
59
+ fire: {
60
+ filled: "█",
61
+ partial: ["░", "▒", "▓", "█"],
62
+ empty: "░",
63
+ left: "🔥",
64
+ right: "🔥",
65
+ pulse: ["🔥", "💥", "✨"],
66
+ animated: true,
67
+ },
68
+ nyan: {
69
+ filled: "█",
70
+ partial: ["░", "▒", "▓"],
71
+ empty: "░",
72
+ left: "🐱",
73
+ right: "🌈",
74
+ pulse: ["⭐", "✨", "💫"],
75
+ animated: true,
76
+ },
77
+ matrix: {
78
+ filled: "█",
79
+ partial: ["░", "▒", "▓"],
80
+ empty: "░",
81
+ left: "⟨",
82
+ right: "⟩",
83
+ pulse: ["0", "1", "█"],
84
+ animated: true,
85
+ },
86
+ glitch: {
87
+ filled: "█",
88
+ partial: ["▓", "▒", "░"],
89
+ empty: "░",
90
+ left: "⌈",
91
+ right: "⌉",
92
+ pulse: ["▓", "▒", "░", "█"],
93
+ animated: true,
94
+ },
95
+ plasma: {
96
+ filled: "◆",
97
+ partial: ["◇", "◈", "◆"],
98
+ empty: "·",
99
+ left: "《",
100
+ right: "》",
101
+ pulse: ["◆", "◈", "◇"],
102
+ animated: true,
103
+ },
104
+ wave: {
105
+ filled: "█",
106
+ partial: ["▁", "▂", "▃", "▄", "▅", "▆", "▇"],
107
+ empty: "▁",
108
+ left: "〈",
109
+ right: "〉",
110
+ pulse: ["~", "≈", "∿"],
111
+ animated: true,
112
+ },
113
+ electric: {
114
+ filled: "⚡",
115
+ partial: ["·", "∘", "○", "◉"],
116
+ empty: "·",
117
+ left: "⟪",
118
+ right: "⟫",
119
+ pulse: ["⚡", "✧", "★"],
120
+ animated: true,
121
+ },
122
+ skull: {
123
+ filled: "█",
124
+ partial: ["░", "▒", "▓"],
125
+ empty: "░",
126
+ left: "💀",
127
+ right: "☠️",
128
+ pulse: ["💀", "☠️", "🔥"],
129
+ animated: true,
130
+ },
131
+ retro: {
132
+ filled: "■",
133
+ partial: ["□", "▪", "■"],
134
+ empty: "□",
135
+ left: "[",
136
+ right: "]",
137
+ pulse: ["■", "□", "▪"],
138
+ },
139
+ hearts: {
140
+ filled: "♥",
141
+ partial: ["♡", "❤", "♥"],
142
+ empty: "♡",
143
+ left: "💕",
144
+ right: "💕",
145
+ pulse: ["💖", "💗", "💓"],
146
+ animated: true,
147
+ },
148
+ stars: {
149
+ filled: "★",
150
+ partial: ["☆", "✦", "★"],
151
+ empty: "☆",
152
+ left: "⭐",
153
+ right: "🌟",
154
+ pulse: ["✨", "💫", "⭐"],
155
+ animated: true,
156
+ },
157
+ cyberpunk: {
158
+ filled: "▰",
159
+ partial: ["▱", "▰"],
160
+ empty: "▱",
161
+ left: "⟦",
162
+ right: "⟧",
163
+ pulse: ["▰", "▱", "▰"],
164
+ animated: true,
165
+ },
166
+ pixel: {
167
+ filled: "▓",
168
+ partial: ["░", "▒", "▓"],
169
+ empty: "░",
170
+ left: "⌜",
171
+ right: "⌝",
172
+ pulse: ["▓", "▒", "░"],
173
+ },
174
+ snake: {
175
+ filled: "◉",
176
+ partial: ["○", "◎", "◉"],
177
+ empty: "·",
178
+ left: "🐍",
179
+ right: "🍎",
180
+ pulse: ["◉", "◎", "○"],
181
+ animated: true,
182
+ },
183
+ music: {
184
+ filled: "♫",
185
+ partial: ["♪", "♬", "♫"],
186
+ empty: "·",
187
+ left: "🎵",
188
+ right: "🎶",
189
+ pulse: ["🎵", "🎶", "🎼"],
190
+ animated: true,
191
+ },
192
+ rocket: {
193
+ filled: "█",
194
+ partial: ["░", "▒", "▓"],
195
+ empty: "·",
196
+ left: "🚀",
197
+ right: "🌙",
198
+ pulse: ["🚀", "💨", "✨"],
199
+ animated: true,
200
+ },
201
+ }.freeze
202
+
203
+ attr_reader :style, :width, :palette, :spinner_style, :rainbow_mode, :celebration_mode
204
+
205
+ def initialize(style: :crystal, width: 40, palette: :crystal, spinner: :braille,
206
+ rainbow_mode: false, celebration_mode: :confetti, glow: false)
207
+ @style = STYLES[style] || STYLES[:crystal]
208
+ @style_name = style
209
+ @width = width
210
+ @palette = palette
211
+ @spinner_style = spinner
212
+ @rainbow_mode = rainbow_mode
213
+ @celebration_mode = celebration_mode
214
+ @glow = glow
215
+ @animation_frame = 0
216
+ @last_render_time = Time.now
217
+ @start_time = Time.now
218
+ end
219
+
220
+ # Render the complete progress display
221
+ def render(state)
222
+ @animation_frame += 1
223
+ lines = []
224
+
225
+ # Main progress bar line
226
+ lines << render_progress_bar(state)
227
+
228
+ # Stats line
229
+ lines << render_stats(state)
230
+
231
+ # Metrics lines (if any)
232
+ if state[:metrics]&.any?
233
+ lines << "" # Spacer
234
+ lines.concat(render_metrics(state[:metrics]))
235
+ end
236
+
237
+ lines
238
+ end
239
+
240
+ # Render just the progress bar
241
+ def render_progress_bar(state)
242
+ progress = state[:progress] || 0.0
243
+ progress = [[progress, 0.0].max, 1.0].min
244
+
245
+ filled_width = (progress * @width).floor
246
+ remaining = @width - filled_width
247
+
248
+ # Build the bar with gradient colors
249
+ bar = build_gradient_bar(filled_width, remaining, progress)
250
+
251
+ # Percentage with color
252
+ pct = (progress * 100).round(1)
253
+ pct_color = ANSI.palette_color(@palette, progress)
254
+ pct_str = "#{pct_color}#{format("%5.1f", pct)}%#{ANSI::RESET}"
255
+
256
+ # Caps
257
+ left = "#{ANSI::DIM}#{@style[:left]}#{ANSI::RESET}"
258
+ right = "#{ANSI::DIM}#{@style[:right]}#{ANSI::RESET}"
259
+
260
+ "#{pct_str} #{left}#{bar}#{right}"
261
+ end
262
+
263
+ # Render statistics line
264
+ def render_stats(state)
265
+ parts = []
266
+
267
+ # Items done / total
268
+ if state[:total]
269
+ done_color = ANSI.palette_color(@palette, 0.5)
270
+ total_color = ANSI::DIM
271
+ parts << "#{done_color}#{state[:current]}#{ANSI::RESET}#{total_color}/#{state[:total]}#{ANSI::RESET}"
272
+ end
273
+
274
+ # Rate
275
+ if state[:rate] && state[:rate] > 0
276
+ rate_color = ANSI.palette_color(@palette, 0.3)
277
+ rate_str = format_rate(state[:rate])
278
+ parts << "#{rate_color}#{rate_str}#{ANSI::RESET}"
279
+ end
280
+
281
+ # Elapsed time
282
+ if state[:elapsed]
283
+ elapsed_str = format_time(state[:elapsed])
284
+ parts << "#{ANSI::DIM}⏱#{ANSI::RESET} #{elapsed_str}"
285
+ end
286
+
287
+ # ETA
288
+ if state[:eta] && state[:eta] > 0 && state[:eta] < Float::INFINITY
289
+ eta_color = ANSI.palette_color(@palette, 0.7)
290
+ eta_str = format_time(state[:eta])
291
+ parts << "#{ANSI::DIM}→#{ANSI::RESET} #{eta_color}#{eta_str}#{ANSI::RESET}"
292
+ elsif state[:progress] && state[:progress] >= 1.0
293
+ # CELEBRATION TIME!
294
+ celebration = render_celebration
295
+ done_color = ANSI.palette_color(@palette, 1.0)
296
+ parts << "#{celebration} #{done_color}✓ Done!#{ANSI::RESET} #{celebration}"
297
+ end
298
+
299
+ # Spinner for active work
300
+ if state[:progress] && state[:progress] < 1.0 && state[:progress] > 0
301
+ spinner = render_spinner
302
+ parts.unshift(spinner)
303
+ end
304
+
305
+ parts.join(" ")
306
+ end
307
+
308
+ # Render metrics section
309
+ def render_metrics(metrics)
310
+ metrics.format_all(palette: @palette)
311
+ end
312
+
313
+ private
314
+
315
+ def build_gradient_bar(filled, remaining, progress)
316
+ bar = ""
317
+ time = Time.now - @start_time
318
+
319
+ # Filled portion with gradient (with optional rainbow cycling and shimmer)
320
+ filled.times do |i|
321
+ char_progress = i.to_f / [@width, 1].max
322
+
323
+ # Choose color based on mode
324
+ color = if @rainbow_mode
325
+ # Rainbow cycling animation
326
+ ANSI.rainbow_cycle(char_progress, time, 0.5)
327
+ elsif @style[:animated] && @style_name == :fire
328
+ # Fire flicker effect
329
+ base_colors = [[255, 80, 0], [255, 160, 0], [255, 200, 0]]
330
+ c = base_colors[(char_progress * (base_colors.length - 1)).round]
331
+ ANSI.fire_flicker(c[0], c[1], c[2], time + i * 0.1)
332
+ elsif @style[:animated] && @style_name == :glitch
333
+ # Glitch effect
334
+ palette = ANSI::CRYSTAL_PALETTE[@palette] || ANSI::CRYSTAL_PALETTE[:crystal]
335
+ c = palette[(char_progress * (palette.length - 1)).round]
336
+ ANSI.glitch(c[0], c[1], c[2], 0.05)
337
+ elsif @style[:animated] && @style_name == :matrix
338
+ # Matrix rain effect - occasionally show 0 or 1
339
+ if rand < 0.1
340
+ ANSI.rgb(0, 255, 0)
341
+ else
342
+ ANSI.palette_color(@palette, char_progress)
343
+ end
344
+ else
345
+ ANSI.palette_color(@palette, char_progress)
346
+ end
347
+
348
+ # Add shimmer wave effect
349
+ shimmer_intensity = calculate_shimmer(i, filled, time)
350
+ if shimmer_intensity > 0
351
+ color = apply_shimmer_to_color(color, shimmer_intensity)
352
+ end
353
+
354
+ # Apply glow effect if enabled
355
+ char = @style[:filled]
356
+ if @glow && i == filled - 1
357
+ # Leading edge gets glow
358
+ bar += "#{ANSI::BOLD}#{color}#{char}#{ANSI::RESET}"
359
+ else
360
+ bar += "#{color}#{char}#{ANSI::RESET}"
361
+ end
362
+ end
363
+
364
+ # Partial block at the edge with pulsing
365
+ if remaining > 0 && progress > 0
366
+ partial_progress = (progress * @width) - filled
367
+ partial_index = (partial_progress * (@style[:partial].length - 1)).round
368
+ partial_char = @style[:partial][partial_index] || @style[:partial].last
369
+
370
+ edge_color = if @rainbow_mode
371
+ ANSI.rainbow_cycle(progress, time, 0.5)
372
+ else
373
+ ANSI.palette_color(@palette, progress)
374
+ end
375
+
376
+ bar += "#{ANSI::BOLD}#{edge_color}#{partial_char}#{ANSI::RESET}"
377
+ remaining -= 1
378
+ end
379
+
380
+ # Empty portion with subtle animation
381
+ remaining.times do |i|
382
+ if @style[:animated] && rand < 0.02
383
+ # Occasional sparkle in empty space
384
+ sparkle = ["·", "∙", "•"][rand(3)]
385
+ bar += "#{ANSI::DIM}#{sparkle}#{ANSI::RESET}"
386
+ else
387
+ bar += "#{ANSI::DIM}#{@style[:empty]}#{ANSI::RESET}"
388
+ end
389
+ end
390
+
391
+ bar
392
+ end
393
+
394
+ def calculate_shimmer(position, total_filled, time)
395
+ return 0 if total_filled < 3
396
+
397
+ # Traveling wave shimmer
398
+ wave_speed = 3.0
399
+ wave_width = 5.0
400
+ wave_pos = (time * wave_speed * @width) % (@width * 2)
401
+
402
+ distance = (position - wave_pos).abs
403
+ if distance < wave_width
404
+ (1.0 - distance / wave_width) * 0.5
405
+ else
406
+ 0
407
+ end
408
+ end
409
+
410
+ def apply_shimmer_to_color(color_code, intensity)
411
+ # Extract RGB from color code if possible, boost brightness
412
+ # For simplicity, we'll just add BOLD for shimmer
413
+ "#{ANSI::BOLD}#{color_code}"
414
+ end
415
+
416
+ def render_spinner
417
+ spinners = ANSI::SPINNERS[@spinner_style] || ANSI::SPINNERS[:braille]
418
+ time = Time.now - @start_time
419
+
420
+ # Animate color
421
+ spinner_color = if @rainbow_mode
422
+ ANSI.rainbow_cycle(0, time, 2.0)
423
+ else
424
+ ANSI.palette_color(@palette, (time * 2) % 1.0)
425
+ end
426
+
427
+ spinner_char = spinners[@animation_frame % spinners.length]
428
+ "#{spinner_color}#{spinner_char}#{ANSI::RESET}"
429
+ end
430
+
431
+ # Render celebration effects when complete
432
+ def render_celebration
433
+ time = Time.now - @start_time
434
+ effects = ANSI::CELEBRATIONS[@celebration_mode] || ANSI::CELEBRATIONS[:confetti]
435
+
436
+ # Animated celebration
437
+ celebration = ""
438
+ 5.times do |i|
439
+ char = effects[((@animation_frame + i * 3) % effects.length)]
440
+ color = ANSI.rainbow_cycle(i / 5.0, time, 3.0)
441
+ celebration += "#{color}#{char}#{ANSI::RESET}"
442
+ end
443
+ celebration
444
+ end
445
+
446
+ def format_rate(rate)
447
+ if rate >= 1000
448
+ "#{(rate / 1000.0).round(1)}K/s"
449
+ elsif rate >= 1
450
+ "#{rate.round(1)}/s"
451
+ else
452
+ "#{(rate * 60).round(1)}/min"
453
+ end
454
+ end
455
+
456
+ def format_time(seconds)
457
+ return "∞" if seconds == Float::INFINITY
458
+ return "0s" if seconds <= 0
459
+
460
+ if seconds < 60
461
+ "#{seconds.round(1)}s"
462
+ elsif seconds < 3600
463
+ mins = (seconds / 60).floor
464
+ secs = (seconds % 60).round
465
+ "#{mins}m#{secs}s"
466
+ else
467
+ hours = (seconds / 3600).floor
468
+ mins = ((seconds % 3600) / 60).round
469
+ "#{hours}h#{mins}m"
470
+ end
471
+ end
472
+ end
473
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProgressBarNone
4
+ # Sparkline visualization for metrics
5
+ module Sparkline
6
+ # Unicode block characters for vertical bars (8 levels)
7
+ BLOCKS = [" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"].freeze
8
+
9
+ # Braille patterns for high-resolution sparklines
10
+ BRAILLE_BASE = 0x2800
11
+
12
+ class << self
13
+ # Generate a sparkline from an array of values
14
+ # @param values [Array<Numeric>] The values to visualize
15
+ # @param width [Integer] Maximum width of the sparkline
16
+ # @param min [Numeric, nil] Minimum value (auto-detect if nil)
17
+ # @param max [Numeric, nil] Maximum value (auto-detect if nil)
18
+ # @param style [Symbol] :blocks or :braille
19
+ # @return [String] The sparkline string
20
+ def generate(values, width: 20, min: nil, max: nil, style: :blocks)
21
+ return "" if values.empty?
22
+
23
+ # Sample values if we have more than width
24
+ sampled = sample_values(values, width)
25
+ return "" if sampled.empty?
26
+
27
+ min ||= sampled.min
28
+ max ||= sampled.max
29
+ range = max - min
30
+
31
+ case style
32
+ when :braille
33
+ generate_braille(sampled, min, range)
34
+ else
35
+ generate_blocks(sampled, min, range)
36
+ end
37
+ end
38
+
39
+ # Generate with color gradient
40
+ def generate_colored(values, width: 20, palette: :crystal, min: nil, max: nil)
41
+ return "" if values.empty?
42
+
43
+ sampled = sample_values(values, width)
44
+ return "" if sampled.empty?
45
+
46
+ min ||= sampled.min
47
+ max ||= sampled.max
48
+ range = [max - min, 0.001].max
49
+
50
+ sampled.each_with_index.map do |val, i|
51
+ normalized = range.zero? ? 0.5 : (val - min) / range
52
+ block_index = (normalized * (BLOCKS.length - 1)).round
53
+ block = BLOCKS[block_index]
54
+
55
+ # Color based on position in the sparkline
56
+ progress = i.to_f / [sampled.length - 1, 1].max
57
+ color = ANSI.palette_color(palette, progress)
58
+
59
+ "#{color}#{block}#{ANSI::RESET}"
60
+ end.join
61
+ end
62
+
63
+ # Mini histogram
64
+ def histogram(values, width: 10, height: 3, palette: :crystal)
65
+ return "" if values.empty?
66
+
67
+ # Create buckets
68
+ min = values.min
69
+ max = values.max
70
+ range = [max - min, 0.001].max
71
+
72
+ buckets = Array.new(width, 0)
73
+ values.each do |v|
74
+ bucket = ((v - min) / range * (width - 1)).floor
75
+ bucket = [[bucket, 0].max, width - 1].min
76
+ buckets[bucket] += 1
77
+ end
78
+
79
+ max_count = buckets.max
80
+ return " " * width if max_count.zero?
81
+
82
+ # Generate rows from top to bottom
83
+ rows = (0...height).map do |row|
84
+ threshold = (height - row) / height.to_f * max_count
85
+ buckets.each_with_index.map do |count, i|
86
+ progress = i.to_f / [width - 1, 1].max
87
+ color = ANSI.palette_color(palette, progress)
88
+ char = count >= threshold ? "█" : " "
89
+ "#{color}#{char}#{ANSI::RESET}"
90
+ end.join
91
+ end
92
+
93
+ rows.join("\n")
94
+ end
95
+
96
+ private
97
+
98
+ def sample_values(values, width)
99
+ return values if values.length <= width
100
+
101
+ # Downsample by taking averages
102
+ chunk_size = (values.length / width.to_f).ceil
103
+ values.each_slice(chunk_size).map do |chunk|
104
+ chunk.sum / chunk.length.to_f
105
+ end.first(width)
106
+ end
107
+
108
+ def generate_blocks(values, min, range)
109
+ values.map do |val|
110
+ normalized = range.zero? ? 0.5 : (val - min) / range
111
+ index = (normalized * (BLOCKS.length - 1)).round
112
+ BLOCKS[index]
113
+ end.join
114
+ end
115
+
116
+ def generate_braille(values, min, range)
117
+ # Braille uses 2x4 dot patterns per character
118
+ # Group values in pairs
119
+ values.each_slice(2).map do |pair|
120
+ val1 = pair[0]
121
+ val2 = pair[1] || val1
122
+
123
+ n1 = range.zero? ? 0.5 : (val1 - min) / range
124
+ n2 = range.zero? ? 0.5 : (val2 - min) / range
125
+
126
+ # Map to 4 vertical dots
127
+ dots1 = (n1 * 4).round
128
+ dots2 = (n2 * 4).round
129
+
130
+ braille_char(dots1, dots2)
131
+ end.join
132
+ end
133
+
134
+ def braille_char(left_dots, right_dots)
135
+ # Braille dot positions:
136
+ # 1 4
137
+ # 2 5
138
+ # 3 6
139
+ # 7 8
140
+ pattern = 0
141
+
142
+ # Left column (dots 1,2,3,7)
143
+ pattern |= 0x01 if left_dots >= 1
144
+ pattern |= 0x02 if left_dots >= 2
145
+ pattern |= 0x04 if left_dots >= 3
146
+ pattern |= 0x40 if left_dots >= 4
147
+
148
+ # Right column (dots 4,5,6,8)
149
+ pattern |= 0x08 if right_dots >= 1
150
+ pattern |= 0x10 if right_dots >= 2
151
+ pattern |= 0x20 if right_dots >= 3
152
+ pattern |= 0x80 if right_dots >= 4
153
+
154
+ (BRAILLE_BASE + pattern).chr(Encoding::UTF_8)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProgressBarNone
4
+ VERSION = "1.0.0"
5
+ end