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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +546 -0
- data/examples/demo.rb +106 -0
- data/examples/showcase.rb +420 -0
- data/examples/smoke_test.rb +41 -0
- data/examples/stress_test.rb +604 -0
- data/examples/syntax_markdown_demo.rb +166 -0
- data/examples/verify.rb +215 -0
- data/examples/visual_demo.rb +145 -0
- data/lib/rich/_palettes.rb +148 -0
- data/lib/rich/box.rb +342 -0
- data/lib/rich/cells.rb +512 -0
- data/lib/rich/color.rb +628 -0
- data/lib/rich/color_triplet.rb +220 -0
- data/lib/rich/console.rb +549 -0
- data/lib/rich/control.rb +332 -0
- data/lib/rich/json.rb +254 -0
- data/lib/rich/layout.rb +314 -0
- data/lib/rich/markdown.rb +509 -0
- data/lib/rich/markup.rb +175 -0
- data/lib/rich/panel.rb +311 -0
- data/lib/rich/progress.rb +430 -0
- data/lib/rich/segment.rb +387 -0
- data/lib/rich/style.rb +433 -0
- data/lib/rich/syntax.rb +1145 -0
- data/lib/rich/table.rb +525 -0
- data/lib/rich/terminal_theme.rb +126 -0
- data/lib/rich/text.rb +433 -0
- data/lib/rich/tree.rb +220 -0
- data/lib/rich/version.rb +5 -0
- data/lib/rich/win32_console.rb +582 -0
- data/lib/rich.rb +108 -0
- metadata +106 -0
|
@@ -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")
|
data/examples/verify.rb
ADDED
|
@@ -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
|