rich-ruby 1.0.1 → 1.0.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/CHANGELOG.md +80 -0
- data/LICENSE +21 -21
- data/README.md +547 -547
- data/docs/architecture.md +43 -0
- data/docs/cheat-sheet.md +52 -0
- data/docs/customization.md +53 -0
- data/docs/how-to-use.md +96 -0
- data/docs/test-report.md +112 -0
- data/docs/troubleshooting.md +36 -0
- data/docs/windows-notes.md +30 -0
- data/examples/demo.rb +106 -106
- data/examples/showcase.rb +420 -420
- data/examples/smoke_test.rb +41 -41
- data/examples/stress_test.rb +604 -604
- data/examples/syntax_markdown_demo.rb +166 -166
- data/examples/verify.rb +216 -215
- data/examples/visual_demo.rb +145 -145
- data/lib/rich/_palettes.rb +148 -148
- data/lib/rich/box.rb +342 -342
- data/lib/rich/cells.rb +524 -512
- data/lib/rich/color.rb +631 -628
- data/lib/rich/color_triplet.rb +227 -220
- data/lib/rich/console.rb +604 -549
- data/lib/rich/control.rb +332 -332
- data/lib/rich/json.rb +260 -254
- data/lib/rich/layout.rb +314 -314
- data/lib/rich/markdown.rb +531 -509
- data/lib/rich/markup.rb +186 -175
- data/lib/rich/panel.rb +318 -311
- data/lib/rich/progress.rb +430 -430
- data/lib/rich/segment.rb +387 -387
- data/lib/rich/style.rb +464 -433
- data/lib/rich/syntax.rb +1220 -1145
- data/lib/rich/table.rb +547 -525
- data/lib/rich/terminal_theme.rb +126 -126
- data/lib/rich/text.rb +460 -433
- data/lib/rich/tree.rb +220 -220
- data/lib/rich/version.rb +5 -5
- data/lib/rich/win32_console.rb +620 -582
- data/lib/rich.rb +108 -108
- metadata +15 -5
data/examples/visual_demo.rb
CHANGED
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Quick visual demo - no interaction needed
|
|
4
|
-
|
|
5
|
-
require_relative "../lib/rich"
|
|
6
|
-
|
|
7
|
-
console = Rich::Console.new
|
|
8
|
-
|
|
9
|
-
puts ""
|
|
10
|
-
console.rule("🎨 Rich Ruby - Quick Visual Demo", style: "bold magenta")
|
|
11
|
-
puts ""
|
|
12
|
-
|
|
13
|
-
# =============================================================================
|
|
14
|
-
# COLORS & STYLES
|
|
15
|
-
# =============================================================================
|
|
16
|
-
puts "▸ Color & Style Examples:"
|
|
17
|
-
puts ""
|
|
18
|
-
console.print(" Normal text")
|
|
19
|
-
console.print(" Bold text", style: "bold")
|
|
20
|
-
console.print(" Italic text", style: "italic")
|
|
21
|
-
console.print(" Underlined text", style: "underline")
|
|
22
|
-
console.print(" Red text", style: "red")
|
|
23
|
-
console.print(" Green on black", style: "green on black")
|
|
24
|
-
console.print(" Bold blue", style: "bold blue")
|
|
25
|
-
console.print(" Bright magenta italic", style: "bright_magenta italic")
|
|
26
|
-
puts ""
|
|
27
|
-
|
|
28
|
-
# Color gradient
|
|
29
|
-
print " Gradient: "
|
|
30
|
-
40.times do |i|
|
|
31
|
-
r = (255 * i / 40.0).to_i
|
|
32
|
-
g = (128).to_i
|
|
33
|
-
b = (255 * (40 - i) / 40.0).to_i
|
|
34
|
-
style = Rich::Style.new(bgcolor: Rich::Color.from_triplet(Rich::ColorTriplet.new(r, g, b)))
|
|
35
|
-
console.write(style.render + " " + "\e[0m")
|
|
36
|
-
end
|
|
37
|
-
puts ""
|
|
38
|
-
puts ""
|
|
39
|
-
|
|
40
|
-
# =============================================================================
|
|
41
|
-
# PANEL
|
|
42
|
-
# =============================================================================
|
|
43
|
-
puts "▸ Panel Example:"
|
|
44
|
-
puts ""
|
|
45
|
-
panel = Rich::Panel.new(
|
|
46
|
-
"Panels are great for highlighting content!\nThey support titles, subtitles, and styles.",
|
|
47
|
-
title: "📦 My Panel",
|
|
48
|
-
subtitle: "Rich Ruby Library",
|
|
49
|
-
border_style: "cyan",
|
|
50
|
-
title_style: "bold white"
|
|
51
|
-
)
|
|
52
|
-
puts panel.render(max_width: 55)
|
|
53
|
-
puts ""
|
|
54
|
-
|
|
55
|
-
# =============================================================================
|
|
56
|
-
# TABLE
|
|
57
|
-
# =============================================================================
|
|
58
|
-
puts "▸ Table Example:"
|
|
59
|
-
puts ""
|
|
60
|
-
table = Rich::Table.new(title: "📊 System Stats", border_style: "green")
|
|
61
|
-
table.add_column("Metric", header_style: "bold cyan")
|
|
62
|
-
table.add_column("Value", justify: :right, header_style: "bold cyan")
|
|
63
|
-
table.add_column("Status", justify: :center, header_style: "bold cyan")
|
|
64
|
-
|
|
65
|
-
table.add_row("CPU Usage", "45%", "🟢 Normal")
|
|
66
|
-
table.add_row("Memory", "2.4 GB", "🟢 Normal")
|
|
67
|
-
table.add_row("Disk I/O", "120 MB/s", "🟡 High")
|
|
68
|
-
table.add_row("Network", "50 Mbps", "🟢 Normal")
|
|
69
|
-
|
|
70
|
-
puts table.render(max_width: 55)
|
|
71
|
-
puts ""
|
|
72
|
-
|
|
73
|
-
# =============================================================================
|
|
74
|
-
# TREE
|
|
75
|
-
# =============================================================================
|
|
76
|
-
puts "▸ Tree Example:"
|
|
77
|
-
puts ""
|
|
78
|
-
tree = Rich::Tree.new("📁 my_project/", style: "bold yellow")
|
|
79
|
-
src = tree.add("📁 src/", style: "bold")
|
|
80
|
-
src.add("📄 main.rb", style: "green")
|
|
81
|
-
src.add("📄 config.rb", style: "green")
|
|
82
|
-
models = src.add("📁 models/", style: "bold")
|
|
83
|
-
models.add("📄 user.rb", style: "green")
|
|
84
|
-
models.add("📄 post.rb", style: "green")
|
|
85
|
-
tree.add("📄 Gemfile", style: "cyan")
|
|
86
|
-
tree.add("📄 README.md", style: "cyan")
|
|
87
|
-
|
|
88
|
-
puts tree.render
|
|
89
|
-
puts ""
|
|
90
|
-
|
|
91
|
-
# =============================================================================
|
|
92
|
-
# PROGRESS BARS
|
|
93
|
-
# =============================================================================
|
|
94
|
-
puts "▸ Progress Bar Examples:"
|
|
95
|
-
puts ""
|
|
96
|
-
|
|
97
|
-
[0, 25, 50, 75, 100].each do |pct|
|
|
98
|
-
bar = Rich::ProgressBar.new(total: 100, completed: pct, width: 30, complete_style: "green", incomplete_style: "dim")
|
|
99
|
-
print " #{pct.to_s.rjust(3)}%: "
|
|
100
|
-
puts bar.render
|
|
101
|
-
end
|
|
102
|
-
puts ""
|
|
103
|
-
|
|
104
|
-
# =============================================================================
|
|
105
|
-
# SPINNERS
|
|
106
|
-
# =============================================================================
|
|
107
|
-
puts "▸ Spinner Styles:"
|
|
108
|
-
puts ""
|
|
109
|
-
spinners = [
|
|
110
|
-
[Rich::ProgressStyle::DOTS, "Dots "],
|
|
111
|
-
[Rich::ProgressStyle::LINE, "Line "],
|
|
112
|
-
[Rich::ProgressStyle::CIRCLE, "Circle "],
|
|
113
|
-
[Rich::ProgressStyle::BOUNCE, "Bounce "]
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
spinners.each do |frames, name|
|
|
117
|
-
print " #{name}: "
|
|
118
|
-
frames.each { |f| print "#{f} " }
|
|
119
|
-
puts ""
|
|
120
|
-
end
|
|
121
|
-
puts ""
|
|
122
|
-
|
|
123
|
-
# =============================================================================
|
|
124
|
-
# JSON
|
|
125
|
-
# =============================================================================
|
|
126
|
-
puts "▸ JSON Highlighting:"
|
|
127
|
-
puts ""
|
|
128
|
-
data = {
|
|
129
|
-
"name" => "Rich",
|
|
130
|
-
"version" => "0.1.0",
|
|
131
|
-
"features" => ["colors", "tables", "trees"],
|
|
132
|
-
"windows" => true
|
|
133
|
-
}
|
|
134
|
-
puts Rich::JSON.to_s(data)
|
|
135
|
-
puts ""
|
|
136
|
-
|
|
137
|
-
# =============================================================================
|
|
138
|
-
# FINALE
|
|
139
|
-
# =============================================================================
|
|
140
|
-
console.rule("✅ Demo Complete!", style: "bold green")
|
|
141
|
-
puts ""
|
|
142
|
-
console.print("Run ", end_str: "")
|
|
143
|
-
console.print("examples/showcase.rb", style: "bold cyan")
|
|
144
|
-
console.print(" for the interactive version with animations!")
|
|
145
|
-
puts ""
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Quick visual demo - no interaction needed
|
|
4
|
+
|
|
5
|
+
require_relative "../lib/rich"
|
|
6
|
+
|
|
7
|
+
console = Rich::Console.new
|
|
8
|
+
|
|
9
|
+
puts ""
|
|
10
|
+
console.rule("🎨 Rich Ruby - Quick Visual Demo", style: "bold magenta")
|
|
11
|
+
puts ""
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# COLORS & STYLES
|
|
15
|
+
# =============================================================================
|
|
16
|
+
puts "▸ Color & Style Examples:"
|
|
17
|
+
puts ""
|
|
18
|
+
console.print(" Normal text")
|
|
19
|
+
console.print(" Bold text", style: "bold")
|
|
20
|
+
console.print(" Italic text", style: "italic")
|
|
21
|
+
console.print(" Underlined text", style: "underline")
|
|
22
|
+
console.print(" Red text", style: "red")
|
|
23
|
+
console.print(" Green on black", style: "green on black")
|
|
24
|
+
console.print(" Bold blue", style: "bold blue")
|
|
25
|
+
console.print(" Bright magenta italic", style: "bright_magenta italic")
|
|
26
|
+
puts ""
|
|
27
|
+
|
|
28
|
+
# Color gradient
|
|
29
|
+
print " Gradient: "
|
|
30
|
+
40.times do |i|
|
|
31
|
+
r = (255 * i / 40.0).to_i
|
|
32
|
+
g = (128).to_i
|
|
33
|
+
b = (255 * (40 - i) / 40.0).to_i
|
|
34
|
+
style = Rich::Style.new(bgcolor: Rich::Color.from_triplet(Rich::ColorTriplet.new(r, g, b)))
|
|
35
|
+
console.write(style.render + " " + "\e[0m")
|
|
36
|
+
end
|
|
37
|
+
puts ""
|
|
38
|
+
puts ""
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# PANEL
|
|
42
|
+
# =============================================================================
|
|
43
|
+
puts "▸ Panel Example:"
|
|
44
|
+
puts ""
|
|
45
|
+
panel = Rich::Panel.new(
|
|
46
|
+
"Panels are great for highlighting content!\nThey support titles, subtitles, and styles.",
|
|
47
|
+
title: "📦 My Panel",
|
|
48
|
+
subtitle: "Rich Ruby Library",
|
|
49
|
+
border_style: "cyan",
|
|
50
|
+
title_style: "bold white"
|
|
51
|
+
)
|
|
52
|
+
puts panel.render(max_width: 55)
|
|
53
|
+
puts ""
|
|
54
|
+
|
|
55
|
+
# =============================================================================
|
|
56
|
+
# TABLE
|
|
57
|
+
# =============================================================================
|
|
58
|
+
puts "▸ Table Example:"
|
|
59
|
+
puts ""
|
|
60
|
+
table = Rich::Table.new(title: "📊 System Stats", border_style: "green")
|
|
61
|
+
table.add_column("Metric", header_style: "bold cyan")
|
|
62
|
+
table.add_column("Value", justify: :right, header_style: "bold cyan")
|
|
63
|
+
table.add_column("Status", justify: :center, header_style: "bold cyan")
|
|
64
|
+
|
|
65
|
+
table.add_row("CPU Usage", "45%", "🟢 Normal")
|
|
66
|
+
table.add_row("Memory", "2.4 GB", "🟢 Normal")
|
|
67
|
+
table.add_row("Disk I/O", "120 MB/s", "🟡 High")
|
|
68
|
+
table.add_row("Network", "50 Mbps", "🟢 Normal")
|
|
69
|
+
|
|
70
|
+
puts table.render(max_width: 55)
|
|
71
|
+
puts ""
|
|
72
|
+
|
|
73
|
+
# =============================================================================
|
|
74
|
+
# TREE
|
|
75
|
+
# =============================================================================
|
|
76
|
+
puts "▸ Tree Example:"
|
|
77
|
+
puts ""
|
|
78
|
+
tree = Rich::Tree.new("📁 my_project/", style: "bold yellow")
|
|
79
|
+
src = tree.add("📁 src/", style: "bold")
|
|
80
|
+
src.add("📄 main.rb", style: "green")
|
|
81
|
+
src.add("📄 config.rb", style: "green")
|
|
82
|
+
models = src.add("📁 models/", style: "bold")
|
|
83
|
+
models.add("📄 user.rb", style: "green")
|
|
84
|
+
models.add("📄 post.rb", style: "green")
|
|
85
|
+
tree.add("📄 Gemfile", style: "cyan")
|
|
86
|
+
tree.add("📄 README.md", style: "cyan")
|
|
87
|
+
|
|
88
|
+
puts tree.render
|
|
89
|
+
puts ""
|
|
90
|
+
|
|
91
|
+
# =============================================================================
|
|
92
|
+
# PROGRESS BARS
|
|
93
|
+
# =============================================================================
|
|
94
|
+
puts "▸ Progress Bar Examples:"
|
|
95
|
+
puts ""
|
|
96
|
+
|
|
97
|
+
[0, 25, 50, 75, 100].each do |pct|
|
|
98
|
+
bar = Rich::ProgressBar.new(total: 100, completed: pct, width: 30, complete_style: "green", incomplete_style: "dim")
|
|
99
|
+
print " #{pct.to_s.rjust(3)}%: "
|
|
100
|
+
puts bar.render
|
|
101
|
+
end
|
|
102
|
+
puts ""
|
|
103
|
+
|
|
104
|
+
# =============================================================================
|
|
105
|
+
# SPINNERS
|
|
106
|
+
# =============================================================================
|
|
107
|
+
puts "▸ Spinner Styles:"
|
|
108
|
+
puts ""
|
|
109
|
+
spinners = [
|
|
110
|
+
[Rich::ProgressStyle::DOTS, "Dots "],
|
|
111
|
+
[Rich::ProgressStyle::LINE, "Line "],
|
|
112
|
+
[Rich::ProgressStyle::CIRCLE, "Circle "],
|
|
113
|
+
[Rich::ProgressStyle::BOUNCE, "Bounce "]
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
spinners.each do |frames, name|
|
|
117
|
+
print " #{name}: "
|
|
118
|
+
frames.each { |f| print "#{f} " }
|
|
119
|
+
puts ""
|
|
120
|
+
end
|
|
121
|
+
puts ""
|
|
122
|
+
|
|
123
|
+
# =============================================================================
|
|
124
|
+
# JSON
|
|
125
|
+
# =============================================================================
|
|
126
|
+
puts "▸ JSON Highlighting:"
|
|
127
|
+
puts ""
|
|
128
|
+
data = {
|
|
129
|
+
"name" => "Rich",
|
|
130
|
+
"version" => "0.1.0",
|
|
131
|
+
"features" => ["colors", "tables", "trees"],
|
|
132
|
+
"windows" => true
|
|
133
|
+
}
|
|
134
|
+
puts Rich::JSON.to_s(data)
|
|
135
|
+
puts ""
|
|
136
|
+
|
|
137
|
+
# =============================================================================
|
|
138
|
+
# FINALE
|
|
139
|
+
# =============================================================================
|
|
140
|
+
console.rule("✅ Demo Complete!", style: "bold green")
|
|
141
|
+
puts ""
|
|
142
|
+
console.print("Run ", end_str: "")
|
|
143
|
+
console.print("examples/showcase.rb", style: "bold cyan")
|
|
144
|
+
console.print(" for the interactive version with animations!")
|
|
145
|
+
puts ""
|
data/lib/rich/_palettes.rb
CHANGED
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "color_triplet"
|
|
4
|
-
|
|
5
|
-
module Rich
|
|
6
|
-
# Color palette definitions for terminal color systems.
|
|
7
|
-
# Provides lookup tables for standard 16-color, 256-color (8-bit),
|
|
8
|
-
# and Windows console color palettes.
|
|
9
|
-
module Palettes
|
|
10
|
-
# Standard 16-color ANSI palette (colors 0-15)
|
|
11
|
-
# These are the typical default colors, but terminals may customize them
|
|
12
|
-
STANDARD_PALETTE = [
|
|
13
|
-
ColorTriplet.new(0, 0, 0), # 0: Black
|
|
14
|
-
ColorTriplet.new(128, 0, 0), # 1: Red
|
|
15
|
-
ColorTriplet.new(0, 128, 0), # 2: Green
|
|
16
|
-
ColorTriplet.new(128, 128, 0), # 3: Yellow
|
|
17
|
-
ColorTriplet.new(0, 0, 128), # 4: Blue
|
|
18
|
-
ColorTriplet.new(128, 0, 128), # 5: Magenta
|
|
19
|
-
ColorTriplet.new(0, 128, 128), # 6: Cyan
|
|
20
|
-
ColorTriplet.new(192, 192, 192), # 7: White
|
|
21
|
-
ColorTriplet.new(128, 128, 128), # 8: Bright Black (Gray)
|
|
22
|
-
ColorTriplet.new(255, 0, 0), # 9: Bright Red
|
|
23
|
-
ColorTriplet.new(0, 255, 0), # 10: Bright Green
|
|
24
|
-
ColorTriplet.new(255, 255, 0), # 11: Bright Yellow
|
|
25
|
-
ColorTriplet.new(0, 0, 255), # 12: Bright Blue
|
|
26
|
-
ColorTriplet.new(255, 0, 255), # 13: Bright Magenta
|
|
27
|
-
ColorTriplet.new(0, 255, 255), # 14: Bright Cyan
|
|
28
|
-
ColorTriplet.new(255, 255, 255) # 15: Bright White
|
|
29
|
-
].freeze
|
|
30
|
-
|
|
31
|
-
# Windows Console palette (slightly different from ANSI standard)
|
|
32
|
-
WINDOWS_PALETTE = [
|
|
33
|
-
ColorTriplet.new(12, 12, 12), # 0: Black
|
|
34
|
-
ColorTriplet.new(197, 15, 31), # 1: Red
|
|
35
|
-
ColorTriplet.new(19, 161, 14), # 2: Green
|
|
36
|
-
ColorTriplet.new(193, 156, 0), # 3: Yellow
|
|
37
|
-
ColorTriplet.new(0, 55, 218), # 4: Blue
|
|
38
|
-
ColorTriplet.new(136, 23, 152), # 5: Magenta
|
|
39
|
-
ColorTriplet.new(58, 150, 221), # 6: Cyan
|
|
40
|
-
ColorTriplet.new(204, 204, 204), # 7: White
|
|
41
|
-
ColorTriplet.new(118, 118, 118), # 8: Bright Black (Gray)
|
|
42
|
-
ColorTriplet.new(231, 72, 86), # 9: Bright Red
|
|
43
|
-
ColorTriplet.new(22, 198, 12), # 10: Bright Green
|
|
44
|
-
ColorTriplet.new(249, 241, 165), # 11: Bright Yellow
|
|
45
|
-
ColorTriplet.new(59, 120, 255), # 12: Bright Blue
|
|
46
|
-
ColorTriplet.new(180, 0, 158), # 13: Bright Magenta
|
|
47
|
-
ColorTriplet.new(97, 214, 214), # 14: Bright Cyan
|
|
48
|
-
ColorTriplet.new(242, 242, 242) # 15: Bright White
|
|
49
|
-
].freeze
|
|
50
|
-
|
|
51
|
-
# Generate the 256-color (8-bit) palette
|
|
52
|
-
# Colors 0-15: Standard colors
|
|
53
|
-
# Colors 16-231: 6x6x6 color cube
|
|
54
|
-
# Colors 232-255: Grayscale ramp
|
|
55
|
-
EIGHT_BIT_PALETTE = begin
|
|
56
|
-
palette = []
|
|
57
|
-
|
|
58
|
-
# Colors 0-15: Standard palette
|
|
59
|
-
STANDARD_PALETTE.each { |color| palette << color }
|
|
60
|
-
|
|
61
|
-
# Colors 16-231: 6x6x6 color cube
|
|
62
|
-
# Each component can be 0, 95, 135, 175, 215, or 255
|
|
63
|
-
cube_values = [0, 95, 135, 175, 215, 255]
|
|
64
|
-
(0...6).each do |r|
|
|
65
|
-
(0...6).each do |g|
|
|
66
|
-
(0...6).each do |b|
|
|
67
|
-
palette << ColorTriplet.new(cube_values[r], cube_values[g], cube_values[b])
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Colors 232-255: Grayscale ramp (24 shades, excluding black and white)
|
|
73
|
-
(0...24).each do |i|
|
|
74
|
-
gray = 8 + i * 10
|
|
75
|
-
palette << ColorTriplet.new(gray, gray, gray)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
palette.freeze
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
class << self
|
|
82
|
-
# Find the closest color in a palette
|
|
83
|
-
# @param triplet [ColorTriplet] Color to match
|
|
84
|
-
# @param palette [Array<ColorTriplet>] Palette to search
|
|
85
|
-
# @param start_index [Integer] Starting index in palette
|
|
86
|
-
# @param end_index [Integer] Ending index in palette (exclusive)
|
|
87
|
-
# @return [Integer] Index of closest matching color
|
|
88
|
-
def match_color(triplet, palette: EIGHT_BIT_PALETTE, start_index: 0, end_index: nil)
|
|
89
|
-
end_index ||= palette.length
|
|
90
|
-
|
|
91
|
-
best_index = start_index
|
|
92
|
-
best_distance = Float::INFINITY
|
|
93
|
-
|
|
94
|
-
(start_index...end_index).each do |i|
|
|
95
|
-
distance = triplet.weighted_distance(palette[i])
|
|
96
|
-
if distance < best_distance
|
|
97
|
-
best_distance = distance
|
|
98
|
-
best_index = i
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
best_index
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Match to standard 16-color palette
|
|
106
|
-
# @param triplet [ColorTriplet] Color to match
|
|
107
|
-
# @return [Integer] Standard color index (0-15)
|
|
108
|
-
def match_standard(triplet)
|
|
109
|
-
match_color(triplet, palette: STANDARD_PALETTE, start_index: 0, end_index: 16)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Match to 8-bit palette (256 colors)
|
|
113
|
-
# @param triplet [ColorTriplet] Color to match
|
|
114
|
-
# @return [Integer] 8-bit color index (0-255)
|
|
115
|
-
def match_eight_bit(triplet)
|
|
116
|
-
match_color(triplet, palette: EIGHT_BIT_PALETTE, start_index: 0, end_index: 256)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Match to Windows console palette
|
|
120
|
-
# @param triplet [ColorTriplet] Color to match
|
|
121
|
-
# @return [Integer] Windows color index (0-15)
|
|
122
|
-
def match_windows(triplet)
|
|
123
|
-
match_color(triplet, palette: WINDOWS_PALETTE, start_index: 0, end_index: 16)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Get a color from the 8-bit palette
|
|
127
|
-
# @param index [Integer] Color index (0-255)
|
|
128
|
-
# @return [ColorTriplet]
|
|
129
|
-
def get_eight_bit(index)
|
|
130
|
-
EIGHT_BIT_PALETTE[index.clamp(0, 255)]
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Get a color from the standard palette
|
|
134
|
-
# @param index [Integer] Color index (0-15)
|
|
135
|
-
# @return [ColorTriplet]
|
|
136
|
-
def get_standard(index)
|
|
137
|
-
STANDARD_PALETTE[index.clamp(0, 15)]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Get a color from the Windows palette
|
|
141
|
-
# @param index [Integer] Color index (0-15)
|
|
142
|
-
# @return [ColorTriplet]
|
|
143
|
-
def get_windows(index)
|
|
144
|
-
WINDOWS_PALETTE[index.clamp(0, 15)]
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "color_triplet"
|
|
4
|
+
|
|
5
|
+
module Rich
|
|
6
|
+
# Color palette definitions for terminal color systems.
|
|
7
|
+
# Provides lookup tables for standard 16-color, 256-color (8-bit),
|
|
8
|
+
# and Windows console color palettes.
|
|
9
|
+
module Palettes
|
|
10
|
+
# Standard 16-color ANSI palette (colors 0-15)
|
|
11
|
+
# These are the typical default colors, but terminals may customize them
|
|
12
|
+
STANDARD_PALETTE = [
|
|
13
|
+
ColorTriplet.new(0, 0, 0), # 0: Black
|
|
14
|
+
ColorTriplet.new(128, 0, 0), # 1: Red
|
|
15
|
+
ColorTriplet.new(0, 128, 0), # 2: Green
|
|
16
|
+
ColorTriplet.new(128, 128, 0), # 3: Yellow
|
|
17
|
+
ColorTriplet.new(0, 0, 128), # 4: Blue
|
|
18
|
+
ColorTriplet.new(128, 0, 128), # 5: Magenta
|
|
19
|
+
ColorTriplet.new(0, 128, 128), # 6: Cyan
|
|
20
|
+
ColorTriplet.new(192, 192, 192), # 7: White
|
|
21
|
+
ColorTriplet.new(128, 128, 128), # 8: Bright Black (Gray)
|
|
22
|
+
ColorTriplet.new(255, 0, 0), # 9: Bright Red
|
|
23
|
+
ColorTriplet.new(0, 255, 0), # 10: Bright Green
|
|
24
|
+
ColorTriplet.new(255, 255, 0), # 11: Bright Yellow
|
|
25
|
+
ColorTriplet.new(0, 0, 255), # 12: Bright Blue
|
|
26
|
+
ColorTriplet.new(255, 0, 255), # 13: Bright Magenta
|
|
27
|
+
ColorTriplet.new(0, 255, 255), # 14: Bright Cyan
|
|
28
|
+
ColorTriplet.new(255, 255, 255) # 15: Bright White
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
# Windows Console palette (slightly different from ANSI standard)
|
|
32
|
+
WINDOWS_PALETTE = [
|
|
33
|
+
ColorTriplet.new(12, 12, 12), # 0: Black
|
|
34
|
+
ColorTriplet.new(197, 15, 31), # 1: Red
|
|
35
|
+
ColorTriplet.new(19, 161, 14), # 2: Green
|
|
36
|
+
ColorTriplet.new(193, 156, 0), # 3: Yellow
|
|
37
|
+
ColorTriplet.new(0, 55, 218), # 4: Blue
|
|
38
|
+
ColorTriplet.new(136, 23, 152), # 5: Magenta
|
|
39
|
+
ColorTriplet.new(58, 150, 221), # 6: Cyan
|
|
40
|
+
ColorTriplet.new(204, 204, 204), # 7: White
|
|
41
|
+
ColorTriplet.new(118, 118, 118), # 8: Bright Black (Gray)
|
|
42
|
+
ColorTriplet.new(231, 72, 86), # 9: Bright Red
|
|
43
|
+
ColorTriplet.new(22, 198, 12), # 10: Bright Green
|
|
44
|
+
ColorTriplet.new(249, 241, 165), # 11: Bright Yellow
|
|
45
|
+
ColorTriplet.new(59, 120, 255), # 12: Bright Blue
|
|
46
|
+
ColorTriplet.new(180, 0, 158), # 13: Bright Magenta
|
|
47
|
+
ColorTriplet.new(97, 214, 214), # 14: Bright Cyan
|
|
48
|
+
ColorTriplet.new(242, 242, 242) # 15: Bright White
|
|
49
|
+
].freeze
|
|
50
|
+
|
|
51
|
+
# Generate the 256-color (8-bit) palette
|
|
52
|
+
# Colors 0-15: Standard colors
|
|
53
|
+
# Colors 16-231: 6x6x6 color cube
|
|
54
|
+
# Colors 232-255: Grayscale ramp
|
|
55
|
+
EIGHT_BIT_PALETTE = begin
|
|
56
|
+
palette = []
|
|
57
|
+
|
|
58
|
+
# Colors 0-15: Standard palette
|
|
59
|
+
STANDARD_PALETTE.each { |color| palette << color }
|
|
60
|
+
|
|
61
|
+
# Colors 16-231: 6x6x6 color cube
|
|
62
|
+
# Each component can be 0, 95, 135, 175, 215, or 255
|
|
63
|
+
cube_values = [0, 95, 135, 175, 215, 255]
|
|
64
|
+
(0...6).each do |r|
|
|
65
|
+
(0...6).each do |g|
|
|
66
|
+
(0...6).each do |b|
|
|
67
|
+
palette << ColorTriplet.new(cube_values[r], cube_values[g], cube_values[b])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Colors 232-255: Grayscale ramp (24 shades, excluding black and white)
|
|
73
|
+
(0...24).each do |i|
|
|
74
|
+
gray = 8 + i * 10
|
|
75
|
+
palette << ColorTriplet.new(gray, gray, gray)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
palette.freeze
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class << self
|
|
82
|
+
# Find the closest color in a palette
|
|
83
|
+
# @param triplet [ColorTriplet] Color to match
|
|
84
|
+
# @param palette [Array<ColorTriplet>] Palette to search
|
|
85
|
+
# @param start_index [Integer] Starting index in palette
|
|
86
|
+
# @param end_index [Integer] Ending index in palette (exclusive)
|
|
87
|
+
# @return [Integer] Index of closest matching color
|
|
88
|
+
def match_color(triplet, palette: EIGHT_BIT_PALETTE, start_index: 0, end_index: nil)
|
|
89
|
+
end_index ||= palette.length
|
|
90
|
+
|
|
91
|
+
best_index = start_index
|
|
92
|
+
best_distance = Float::INFINITY
|
|
93
|
+
|
|
94
|
+
(start_index...end_index).each do |i|
|
|
95
|
+
distance = triplet.weighted_distance(palette[i])
|
|
96
|
+
if distance < best_distance
|
|
97
|
+
best_distance = distance
|
|
98
|
+
best_index = i
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
best_index
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Match to standard 16-color palette
|
|
106
|
+
# @param triplet [ColorTriplet] Color to match
|
|
107
|
+
# @return [Integer] Standard color index (0-15)
|
|
108
|
+
def match_standard(triplet)
|
|
109
|
+
match_color(triplet, palette: STANDARD_PALETTE, start_index: 0, end_index: 16)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Match to 8-bit palette (256 colors)
|
|
113
|
+
# @param triplet [ColorTriplet] Color to match
|
|
114
|
+
# @return [Integer] 8-bit color index (0-255)
|
|
115
|
+
def match_eight_bit(triplet)
|
|
116
|
+
match_color(triplet, palette: EIGHT_BIT_PALETTE, start_index: 0, end_index: 256)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Match to Windows console palette
|
|
120
|
+
# @param triplet [ColorTriplet] Color to match
|
|
121
|
+
# @return [Integer] Windows color index (0-15)
|
|
122
|
+
def match_windows(triplet)
|
|
123
|
+
match_color(triplet, palette: WINDOWS_PALETTE, start_index: 0, end_index: 16)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Get a color from the 8-bit palette
|
|
127
|
+
# @param index [Integer] Color index (0-255)
|
|
128
|
+
# @return [ColorTriplet]
|
|
129
|
+
def get_eight_bit(index)
|
|
130
|
+
EIGHT_BIT_PALETTE[index.clamp(0, 255)]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Get a color from the standard palette
|
|
134
|
+
# @param index [Integer] Color index (0-15)
|
|
135
|
+
# @return [ColorTriplet]
|
|
136
|
+
def get_standard(index)
|
|
137
|
+
STANDARD_PALETTE[index.clamp(0, 15)]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Get a color from the Windows palette
|
|
141
|
+
# @param index [Integer] Color index (0-15)
|
|
142
|
+
# @return [ColorTriplet]
|
|
143
|
+
def get_windows(index)
|
|
144
|
+
WINDOWS_PALETTE[index.clamp(0, 15)]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|