rich-ruby 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,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Demo of Syntax Highlighting and Markdown Rendering
4
+
5
+ require_relative "../lib/rich"
6
+
7
+ console = Rich::Console.new
8
+
9
+ console.rule("Syntax Highlighting Demo", style: "bold cyan")
10
+ puts ""
11
+
12
+ # Ruby code
13
+ ruby_code = <<~RUBY
14
+ # Ruby example
15
+ class User
16
+ attr_accessor :name, :email
17
+
18
+ def initialize(name, email)
19
+ @name = name
20
+ @email = email
21
+ end
22
+
23
+ def greet
24
+ puts "Hello, \#{@name}!"
25
+ end
26
+ end
27
+
28
+ user = User.new("Alice", "alice@example.com")
29
+ user.greet
30
+ RUBY
31
+
32
+ puts "Ruby:"
33
+ puts ""
34
+ syntax = Rich::Syntax.new(ruby_code, language: "ruby", line_numbers: true)
35
+ puts syntax.render
36
+ puts ""
37
+
38
+ # Python code
39
+ python_code = <<~PYTHON
40
+ # Python example
41
+ import json
42
+ from dataclasses import dataclass
43
+
44
+ @dataclass
45
+ class Config:
46
+ name: str
47
+ debug: bool = False
48
+
49
+ def load_config(path: str) -> Config:
50
+ with open(path) as f:
51
+ data = json.load(f)
52
+ return Config(**data)
53
+
54
+ if __name__ == "__main__":
55
+ config = load_config("config.json")
56
+ print(f"Loaded: {config.name}")
57
+ PYTHON
58
+
59
+ puts "Python:"
60
+ puts ""
61
+ syntax = Rich::Syntax.new(python_code, language: "python", line_numbers: true, theme: :monokai)
62
+ puts syntax.render
63
+ puts ""
64
+
65
+ # JavaScript
66
+ js_code = <<~JS
67
+ // JavaScript example
68
+ const express = require('express');
69
+ const app = express();
70
+
71
+ app.get('/api/users', async (req, res) => {
72
+ const users = await User.findAll();
73
+ res.json({ data: users, count: users.length });
74
+ });
75
+
76
+ app.listen(3000, () => console.log('Server running'));
77
+ JS
78
+
79
+ puts "JavaScript (Dracula theme):"
80
+ puts ""
81
+ syntax = Rich::Syntax.new(js_code, language: "javascript", line_numbers: true, theme: :dracula)
82
+ puts syntax.render
83
+ puts ""
84
+
85
+ # SQL
86
+ sql_code = <<~SQL
87
+ -- Get active users with their orders
88
+ SELECT u.name, u.email, COUNT(o.id) AS order_count
89
+ FROM users u
90
+ LEFT JOIN orders o ON u.id = o.user_id
91
+ WHERE u.status = 'active'
92
+ AND o.created_at >= '2025-01-01'
93
+ GROUP BY u.id
94
+ HAVING order_count > 5
95
+ ORDER BY order_count DESC
96
+ LIMIT 10;
97
+ SQL
98
+
99
+ puts "SQL:"
100
+ puts ""
101
+ syntax = Rich::Syntax.new(sql_code, language: "sql")
102
+ puts syntax.render
103
+ puts ""
104
+
105
+ console.rule("Markdown Rendering Demo", style: "bold cyan")
106
+ puts ""
107
+
108
+ markdown = <<~MD
109
+ # Welcome to Rich Markdown
110
+
111
+ This is a **full-featured** Markdown renderer for your *terminal*.
112
+
113
+ ## Features
114
+
115
+ ### Text Formatting
116
+
117
+ You can use **bold**, *italic*, ***bold italic***, ~~strikethrough~~, and `inline code`.
118
+
119
+ ### Lists
120
+
121
+ Unordered list:
122
+ - First item
123
+ - Second item
124
+ - Nested item
125
+ - Another nested
126
+
127
+ Ordered list:
128
+ 1. Step one
129
+ 2. Step two
130
+ 3. Step three
131
+
132
+ ### Blockquotes
133
+
134
+ > This is a blockquote.
135
+ > It can span multiple lines.
136
+ > -- Someone Famous
137
+
138
+ ### Code Blocks
139
+
140
+ ```ruby
141
+ def hello(name)
142
+ puts "Hello, \#{name}!"
143
+ end
144
+ ```
145
+
146
+ ### Tables
147
+
148
+ | Name | Language | Stars |
149
+ |----------|----------|-------|
150
+ | Ruby | Ruby | 21k |
151
+ | Python | Python | 58k |
152
+ | Node.js | JS | 102k |
153
+
154
+ ### Links
155
+
156
+ Check out [Rich Library](https://github.com/Textualize/rich) for more!
157
+
158
+ ---
159
+
160
+ That's all folks!
161
+ MD
162
+
163
+ md = Rich::Markdown.new(markdown)
164
+ puts md.render(max_width: 70)
165
+
166
+ console.rule("Demo Complete!", style: "bold green")
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Full verification test for the Rich library
4
+
5
+ require_relative "../lib/rich"
6
+
7
+ puts "=" * 60
8
+ puts "Rich Library Verification Test"
9
+ puts "=" * 60
10
+ puts ""
11
+
12
+ # Track results
13
+ results = []
14
+
15
+ def test(name)
16
+ print "Testing #{name}... "
17
+ begin
18
+ yield
19
+ puts "✓ PASS"
20
+ true
21
+ rescue StandardError => e
22
+ puts "✗ FAIL: #{e.message}"
23
+ false
24
+ end
25
+ end
26
+
27
+ # Test 1: Basic imports
28
+ results << test("Module loading") do
29
+ raise "Console not defined" unless defined?(Rich::Console)
30
+ raise "Color not defined" unless defined?(Rich::Color)
31
+ raise "Style not defined" unless defined?(Rich::Style)
32
+ raise "Text not defined" unless defined?(Rich::Text)
33
+ raise "Panel not defined" unless defined?(Rich::Panel)
34
+ raise "Table not defined" unless defined?(Rich::Table)
35
+ raise "Tree not defined" unless defined?(Rich::Tree)
36
+ raise "Progress not defined" unless defined?(Rich::ProgressBar)
37
+ end
38
+
39
+ # Test 2: ColorTriplet
40
+ results << test("ColorTriplet") do
41
+ triplet = Rich::ColorTriplet.new(255, 128, 64)
42
+ raise "Hex wrong" unless triplet.hex == "#ff8040"
43
+ raise "RGB wrong" unless triplet.red == 255 && triplet.green == 128
44
+
45
+ from_hex = Rich::ColorTriplet.from_hex("#00ff00")
46
+ raise "From hex wrong" unless from_hex.green == 255
47
+ end
48
+
49
+ # Test 3: Color parsing
50
+ results << test("Color parsing") do
51
+ red = Rich::Color.parse("red")
52
+ raise "Red not parsed" unless red.type == Rich::ColorType::STANDARD
53
+
54
+ hex = Rich::Color.parse("#ff5500")
55
+ raise "Hex not parsed" unless hex.type == Rich::ColorType::TRUECOLOR
56
+
57
+ named = Rich::Color.parse("bright_blue")
58
+ raise "Named not parsed" unless named.number == 12
59
+ end
60
+
61
+ # Test 4: Style parsing
62
+ results << test("Style parsing") do
63
+ style = Rich::Style.parse("bold red on white")
64
+ raise "Bold not set" unless style.bold?
65
+ raise "Color wrong" unless style.color.name == "red"
66
+ raise "Bgcolor wrong" unless style.bgcolor.name == "white"
67
+ end
68
+
69
+ # Test 5: Style combination
70
+ results << test("Style combination") do
71
+ s1 = Rich::Style.parse("bold")
72
+ s2 = Rich::Style.parse("red")
73
+ combined = s1 + s2
74
+ raise "Combination failed" unless combined.bold? && combined.color.name == "red"
75
+ end
76
+
77
+ # Test 6: Console creation
78
+ results << test("Console creation") do
79
+ console = Rich::Console.new
80
+ raise "Width missing" unless console.width > 0
81
+ raise "Height missing" unless console.height > 0
82
+ raise "Color system missing" unless Rich::ColorSystem::ALL.include?(console.color_system)
83
+ end
84
+
85
+ # Test 7: Cell width calculation
86
+ results << test("Cell width calculation") do
87
+ raise "ASCII wrong" unless Rich::Cells.cell_len("Hello") == 5
88
+ raise "CJK wrong" unless Rich::Cells.cell_len("你好") == 4 # 2 chars × 2 width
89
+ raise "Empty wrong" unless Rich::Cells.cell_len("") == 0
90
+ end
91
+
92
+ # Test 8: Segment creation
93
+ results << test("Segment creation") do
94
+ seg = Rich::Segment.new("Hello", style: Rich::Style.parse("bold"))
95
+ raise "Text wrong" unless seg.text == "Hello"
96
+ raise "Style missing" unless seg.style.bold?
97
+ end
98
+
99
+ # Test 9: Text with spans
100
+ results << test("Text with spans") do
101
+ text = Rich::Text.new("Hello ")
102
+ text.append("World", style: "red")
103
+ raise "Plain wrong" unless text.plain == "Hello World"
104
+ raise "Spans wrong" unless text.spans.length == 1
105
+ end
106
+
107
+ # Test 10: Markup parsing
108
+ results << test("Markup parsing") do
109
+ text = Rich::Markup.parse("[bold]Hello[/bold]")
110
+ raise "Parse failed" unless text.plain == "Hello"
111
+ raise "Style not applied" unless text.spans.any? { |s| s.style.bold? }
112
+ end
113
+
114
+ # Test 11: Panel rendering
115
+ results << test("Panel rendering") do
116
+ panel = Rich::Panel.new("Content", title: "Title")
117
+ output = panel.render(max_width: 40)
118
+ raise "No border" unless output.include?("╭") || output.include?("+")
119
+ raise "No content" unless output.include?("Content")
120
+ end
121
+
122
+ # Test 12: Table rendering
123
+ results << test("Table rendering") do
124
+ table = Rich::Table.new
125
+ table.add_column("Name")
126
+ table.add_column("Value")
127
+ table.add_row("Key", "123")
128
+ output = table.render(max_width: 40)
129
+ raise "No header" unless output.include?("Name")
130
+ raise "No data" unless output.include?("123")
131
+ end
132
+
133
+ # Test 13: Tree rendering
134
+ results << test("Tree rendering") do
135
+ tree = Rich::Tree.new("Root")
136
+ tree.add("Child 1")
137
+ tree.add("Child 2")
138
+ output = tree.render
139
+ raise "No root" unless output.include?("Root")
140
+ raise "No children" unless output.include?("Child")
141
+ end
142
+
143
+ # Test 14: Progress bar
144
+ results << test("Progress bar") do
145
+ bar = Rich::ProgressBar.new(total: 100, completed: 50)
146
+ raise "Progress wrong" unless bar.progress == 0.5
147
+ raise "Percentage wrong" unless bar.percentage == 50
148
+
149
+ bar.advance(25)
150
+ raise "Advance failed" unless bar.percentage == 75
151
+ end
152
+
153
+ # Test 15: Spinner
154
+ results << test("Spinner") do
155
+ spinner = Rich::Spinner.new
156
+ frame1 = spinner.frame
157
+ spinner.advance
158
+ frame2 = spinner.frame
159
+ raise "Spinner not advancing" if frame1 == frame2 && spinner.frames.length > 1
160
+ end
161
+
162
+ # Test 16: Box styles
163
+ results << test("Box styles") do
164
+ boxes = [Rich::Box::ASCII, Rich::Box::ROUNDED, Rich::Box::HEAVY, Rich::Box::DOUBLE]
165
+ boxes.each do |box|
166
+ raise "Missing top_left" if box.top_left.nil?
167
+ raise "Missing horizontal" if box.horizontal.nil?
168
+ end
169
+ end
170
+
171
+ # Test 17: JSON highlighting
172
+ results << test("JSON highlighting") do
173
+ data = { "name" => "test", "value" => 123 }
174
+ output = Rich::JSON.to_s(data)
175
+ raise "No output" if output.empty?
176
+ end
177
+
178
+ # Test 18: Columns layout
179
+ results << test("Columns layout") do
180
+ cols = Rich::Columns.new(%w[one two three four])
181
+ output = cols.render(max_width: 40)
182
+ raise "No content" unless output.include?("one")
183
+ end
184
+
185
+ # Test 19: Windows Console API (on Windows)
186
+ if Gem.win_platform?
187
+ results << test("Windows Console API") do
188
+ raise "Win32Console not loaded" unless defined?(Rich::Win32Console)
189
+ raise "supports_ansi? missing" unless Rich::Win32Console.respond_to?(:supports_ansi?)
190
+
191
+ # Get console size
192
+ size = Rich::Win32Console.get_size
193
+ raise "Size detection failed" unless size && size[0] > 0
194
+ end
195
+ end
196
+
197
+ # Test 20: Control codes
198
+ results << test("Control codes") do
199
+ raise "Clear missing" if Rich::Control.clear_screen.empty?
200
+ raise "Cursor up missing" if Rich::Control.cursor_up(5).empty?
201
+ raise "Reset missing" if Rich::Control.reset.empty?
202
+ end
203
+
204
+ puts ""
205
+ puts "=" * 60
206
+ passed = results.count(true)
207
+ total = results.length
208
+ puts "Results: #{passed}/#{total} tests passed"
209
+ puts "=" * 60
210
+
211
+ if passed == total
212
+ puts "\n🎉 All tests passed! Rich library is fully functional."
213
+ else
214
+ puts "\n⚠️ Some tests failed. Please review the output above."
215
+ end
@@ -0,0 +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 ""
@@ -0,0 +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