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/lib/rich/tree.rb
CHANGED
|
@@ -1,220 +1,220 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "style"
|
|
4
|
-
require_relative "segment"
|
|
5
|
-
require_relative "cells"
|
|
6
|
-
|
|
7
|
-
module Rich
|
|
8
|
-
# Tree guide characters for different styles
|
|
9
|
-
module TreeGuide
|
|
10
|
-
ASCII = {
|
|
11
|
-
vertical: "| ",
|
|
12
|
-
branch: "+-- ",
|
|
13
|
-
last: "`-- ",
|
|
14
|
-
space: " "
|
|
15
|
-
}.freeze
|
|
16
|
-
|
|
17
|
-
UNICODE = {
|
|
18
|
-
vertical: "│ ",
|
|
19
|
-
branch: "├── ",
|
|
20
|
-
last: "└── ",
|
|
21
|
-
space: " "
|
|
22
|
-
}.freeze
|
|
23
|
-
|
|
24
|
-
ROUNDED = {
|
|
25
|
-
vertical: "│ ",
|
|
26
|
-
branch: "├── ",
|
|
27
|
-
last: "╰── ",
|
|
28
|
-
space: " "
|
|
29
|
-
}.freeze
|
|
30
|
-
|
|
31
|
-
BOLD = {
|
|
32
|
-
vertical: "┃ ",
|
|
33
|
-
branch: "┣━━ ",
|
|
34
|
-
last: "┗━━ ",
|
|
35
|
-
space: " "
|
|
36
|
-
}.freeze
|
|
37
|
-
|
|
38
|
-
DOUBLE = {
|
|
39
|
-
vertical: "║ ",
|
|
40
|
-
branch: "╠══ ",
|
|
41
|
-
last: "╚══ ",
|
|
42
|
-
space: " "
|
|
43
|
-
}.freeze
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# A node in a tree structure
|
|
47
|
-
class TreeNode
|
|
48
|
-
# @return [String] Node label
|
|
49
|
-
attr_reader :label
|
|
50
|
-
|
|
51
|
-
# @return [Style, nil] Label style
|
|
52
|
-
attr_reader :style
|
|
53
|
-
|
|
54
|
-
# @return [Array<TreeNode>] Child nodes
|
|
55
|
-
attr_reader :children
|
|
56
|
-
|
|
57
|
-
# @return [Object, nil] Associated data
|
|
58
|
-
attr_reader :data
|
|
59
|
-
|
|
60
|
-
# @return [Boolean] Expanded state
|
|
61
|
-
attr_accessor :expanded
|
|
62
|
-
|
|
63
|
-
def initialize(label, style: nil, data: nil, expanded: true)
|
|
64
|
-
@label = label.to_s
|
|
65
|
-
@style = style.is_a?(String) ? Style.parse(style) : style
|
|
66
|
-
@children = []
|
|
67
|
-
@data = data
|
|
68
|
-
@expanded = expanded
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Add a child node
|
|
72
|
-
# @param label [String] Child label
|
|
73
|
-
# @param kwargs [Hash] Node options
|
|
74
|
-
# @return [TreeNode] The new child node
|
|
75
|
-
def add(label, **kwargs)
|
|
76
|
-
child = TreeNode.new(label, **kwargs)
|
|
77
|
-
@children << child
|
|
78
|
-
child
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# @return [Boolean] True if node has children
|
|
82
|
-
def leaf?
|
|
83
|
-
@children.empty?
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# @return [Integer] Number of children
|
|
87
|
-
def child_count
|
|
88
|
-
@children.length
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# @return [Integer] Total descendant count
|
|
92
|
-
def descendant_count
|
|
93
|
-
@children.sum { |c| 1 + c.descendant_count }
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Iterate through all descendants
|
|
97
|
-
# @yield [TreeNode, Integer] Each node and its depth
|
|
98
|
-
def each_descendant(depth = 0, &block)
|
|
99
|
-
yield(self, depth)
|
|
100
|
-
@children.each do |child|
|
|
101
|
-
child.each_descendant(depth + 1, &block)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# A tree display for hierarchical data
|
|
107
|
-
class Tree
|
|
108
|
-
# @return [TreeNode] Root node
|
|
109
|
-
attr_reader :root
|
|
110
|
-
|
|
111
|
-
# @return [Hash] Guide characters
|
|
112
|
-
attr_reader :guide
|
|
113
|
-
|
|
114
|
-
# @return [Style, nil] Guide style
|
|
115
|
-
attr_reader :guide_style
|
|
116
|
-
|
|
117
|
-
# @return [Boolean] Hide root node
|
|
118
|
-
attr_reader :hide_root
|
|
119
|
-
|
|
120
|
-
def initialize(
|
|
121
|
-
label,
|
|
122
|
-
style: nil,
|
|
123
|
-
guide: TreeGuide::UNICODE,
|
|
124
|
-
guide_style: nil,
|
|
125
|
-
hide_root: false
|
|
126
|
-
)
|
|
127
|
-
@root = TreeNode.new(label, style: style)
|
|
128
|
-
@guide = guide
|
|
129
|
-
@guide_style = guide_style.is_a?(String) ? Style.parse(guide_style) : guide_style
|
|
130
|
-
@hide_root = hide_root
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Add a child to root
|
|
134
|
-
# @param label [String] Child label
|
|
135
|
-
# @param kwargs [Hash] Node options
|
|
136
|
-
# @return [TreeNode]
|
|
137
|
-
def add(label, **kwargs)
|
|
138
|
-
@root.add(label, **kwargs)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Render tree to segments
|
|
142
|
-
# @return [Array<Segment>]
|
|
143
|
-
def to_segments
|
|
144
|
-
segments = []
|
|
145
|
-
|
|
146
|
-
unless @hide_root
|
|
147
|
-
segments << Segment.new(@root.label, style: @root.style)
|
|
148
|
-
segments << Segment.new("\n")
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
if @root.expanded
|
|
152
|
-
render_children(@root.children, [], segments)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
segments
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Render to string
|
|
159
|
-
# @param color_system [Symbol] Color system
|
|
160
|
-
# @return [String]
|
|
161
|
-
def render(color_system: ColorSystem::TRUECOLOR)
|
|
162
|
-
Segment.render(to_segments, color_system: color_system)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Build tree from nested hash/array structure
|
|
166
|
-
# @param data [Hash, Array] Nested data
|
|
167
|
-
# @param label [String] Root label
|
|
168
|
-
# @return [Tree]
|
|
169
|
-
def self.from_data(data, label: "root", **kwargs)
|
|
170
|
-
tree = new(label, **kwargs)
|
|
171
|
-
add_data_to_node(tree.root, data)
|
|
172
|
-
tree
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
private
|
|
176
|
-
|
|
177
|
-
def render_children(children, prefix_parts, segments)
|
|
178
|
-
children.each_with_index do |child, index|
|
|
179
|
-
is_last = index == children.length - 1
|
|
180
|
-
|
|
181
|
-
# Build prefix
|
|
182
|
-
prefix = prefix_parts.join
|
|
183
|
-
|
|
184
|
-
# Add guide character
|
|
185
|
-
guide_char = is_last ? @guide[:last] : @guide[:branch]
|
|
186
|
-
segments << Segment.new(prefix, style: @guide_style)
|
|
187
|
-
segments << Segment.new(guide_char, style: @guide_style)
|
|
188
|
-
segments << Segment.new(child.label, style: child.style)
|
|
189
|
-
segments << Segment.new("\n")
|
|
190
|
-
|
|
191
|
-
# Recurse for children
|
|
192
|
-
if child.expanded && !child.children.empty?
|
|
193
|
-
new_part = is_last ? @guide[:space] : @guide[:vertical]
|
|
194
|
-
render_children(child.children, prefix_parts + [new_part], segments)
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def self.add_data_to_node(node, data)
|
|
200
|
-
case data
|
|
201
|
-
when Hash
|
|
202
|
-
data.each do |key, value|
|
|
203
|
-
child = node.add(key.to_s)
|
|
204
|
-
add_data_to_node(child, value)
|
|
205
|
-
end
|
|
206
|
-
when Array
|
|
207
|
-
data.each_with_index do |item, index|
|
|
208
|
-
if item.is_a?(Hash) || item.is_a?(Array)
|
|
209
|
-
child = node.add("[#{index}]")
|
|
210
|
-
add_data_to_node(child, item)
|
|
211
|
-
else
|
|
212
|
-
node.add(item.to_s)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
else
|
|
216
|
-
node.add(data.to_s) unless data.nil?
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "style"
|
|
4
|
+
require_relative "segment"
|
|
5
|
+
require_relative "cells"
|
|
6
|
+
|
|
7
|
+
module Rich
|
|
8
|
+
# Tree guide characters for different styles
|
|
9
|
+
module TreeGuide
|
|
10
|
+
ASCII = {
|
|
11
|
+
vertical: "| ",
|
|
12
|
+
branch: "+-- ",
|
|
13
|
+
last: "`-- ",
|
|
14
|
+
space: " "
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
UNICODE = {
|
|
18
|
+
vertical: "│ ",
|
|
19
|
+
branch: "├── ",
|
|
20
|
+
last: "└── ",
|
|
21
|
+
space: " "
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
ROUNDED = {
|
|
25
|
+
vertical: "│ ",
|
|
26
|
+
branch: "├── ",
|
|
27
|
+
last: "╰── ",
|
|
28
|
+
space: " "
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
BOLD = {
|
|
32
|
+
vertical: "┃ ",
|
|
33
|
+
branch: "┣━━ ",
|
|
34
|
+
last: "┗━━ ",
|
|
35
|
+
space: " "
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
DOUBLE = {
|
|
39
|
+
vertical: "║ ",
|
|
40
|
+
branch: "╠══ ",
|
|
41
|
+
last: "╚══ ",
|
|
42
|
+
space: " "
|
|
43
|
+
}.freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# A node in a tree structure
|
|
47
|
+
class TreeNode
|
|
48
|
+
# @return [String] Node label
|
|
49
|
+
attr_reader :label
|
|
50
|
+
|
|
51
|
+
# @return [Style, nil] Label style
|
|
52
|
+
attr_reader :style
|
|
53
|
+
|
|
54
|
+
# @return [Array<TreeNode>] Child nodes
|
|
55
|
+
attr_reader :children
|
|
56
|
+
|
|
57
|
+
# @return [Object, nil] Associated data
|
|
58
|
+
attr_reader :data
|
|
59
|
+
|
|
60
|
+
# @return [Boolean] Expanded state
|
|
61
|
+
attr_accessor :expanded
|
|
62
|
+
|
|
63
|
+
def initialize(label, style: nil, data: nil, expanded: true)
|
|
64
|
+
@label = label.to_s
|
|
65
|
+
@style = style.is_a?(String) ? Style.parse(style) : style
|
|
66
|
+
@children = []
|
|
67
|
+
@data = data
|
|
68
|
+
@expanded = expanded
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Add a child node
|
|
72
|
+
# @param label [String] Child label
|
|
73
|
+
# @param kwargs [Hash] Node options
|
|
74
|
+
# @return [TreeNode] The new child node
|
|
75
|
+
def add(label, **kwargs)
|
|
76
|
+
child = TreeNode.new(label, **kwargs)
|
|
77
|
+
@children << child
|
|
78
|
+
child
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [Boolean] True if node has children
|
|
82
|
+
def leaf?
|
|
83
|
+
@children.empty?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [Integer] Number of children
|
|
87
|
+
def child_count
|
|
88
|
+
@children.length
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [Integer] Total descendant count
|
|
92
|
+
def descendant_count
|
|
93
|
+
@children.sum { |c| 1 + c.descendant_count }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Iterate through all descendants
|
|
97
|
+
# @yield [TreeNode, Integer] Each node and its depth
|
|
98
|
+
def each_descendant(depth = 0, &block)
|
|
99
|
+
yield(self, depth)
|
|
100
|
+
@children.each do |child|
|
|
101
|
+
child.each_descendant(depth + 1, &block)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A tree display for hierarchical data
|
|
107
|
+
class Tree
|
|
108
|
+
# @return [TreeNode] Root node
|
|
109
|
+
attr_reader :root
|
|
110
|
+
|
|
111
|
+
# @return [Hash] Guide characters
|
|
112
|
+
attr_reader :guide
|
|
113
|
+
|
|
114
|
+
# @return [Style, nil] Guide style
|
|
115
|
+
attr_reader :guide_style
|
|
116
|
+
|
|
117
|
+
# @return [Boolean] Hide root node
|
|
118
|
+
attr_reader :hide_root
|
|
119
|
+
|
|
120
|
+
def initialize(
|
|
121
|
+
label,
|
|
122
|
+
style: nil,
|
|
123
|
+
guide: TreeGuide::UNICODE,
|
|
124
|
+
guide_style: nil,
|
|
125
|
+
hide_root: false
|
|
126
|
+
)
|
|
127
|
+
@root = TreeNode.new(label, style: style)
|
|
128
|
+
@guide = guide
|
|
129
|
+
@guide_style = guide_style.is_a?(String) ? Style.parse(guide_style) : guide_style
|
|
130
|
+
@hide_root = hide_root
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Add a child to root
|
|
134
|
+
# @param label [String] Child label
|
|
135
|
+
# @param kwargs [Hash] Node options
|
|
136
|
+
# @return [TreeNode]
|
|
137
|
+
def add(label, **kwargs)
|
|
138
|
+
@root.add(label, **kwargs)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Render tree to segments
|
|
142
|
+
# @return [Array<Segment>]
|
|
143
|
+
def to_segments
|
|
144
|
+
segments = []
|
|
145
|
+
|
|
146
|
+
unless @hide_root
|
|
147
|
+
segments << Segment.new(@root.label, style: @root.style)
|
|
148
|
+
segments << Segment.new("\n")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if @root.expanded
|
|
152
|
+
render_children(@root.children, [], segments)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
segments
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Render to string
|
|
159
|
+
# @param color_system [Symbol] Color system
|
|
160
|
+
# @return [String]
|
|
161
|
+
def render(color_system: ColorSystem::TRUECOLOR)
|
|
162
|
+
Segment.render(to_segments, color_system: color_system)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Build tree from nested hash/array structure
|
|
166
|
+
# @param data [Hash, Array] Nested data
|
|
167
|
+
# @param label [String] Root label
|
|
168
|
+
# @return [Tree]
|
|
169
|
+
def self.from_data(data, label: "root", **kwargs)
|
|
170
|
+
tree = new(label, **kwargs)
|
|
171
|
+
add_data_to_node(tree.root, data)
|
|
172
|
+
tree
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def render_children(children, prefix_parts, segments)
|
|
178
|
+
children.each_with_index do |child, index|
|
|
179
|
+
is_last = index == children.length - 1
|
|
180
|
+
|
|
181
|
+
# Build prefix
|
|
182
|
+
prefix = prefix_parts.join
|
|
183
|
+
|
|
184
|
+
# Add guide character
|
|
185
|
+
guide_char = is_last ? @guide[:last] : @guide[:branch]
|
|
186
|
+
segments << Segment.new(prefix, style: @guide_style)
|
|
187
|
+
segments << Segment.new(guide_char, style: @guide_style)
|
|
188
|
+
segments << Segment.new(child.label, style: child.style)
|
|
189
|
+
segments << Segment.new("\n")
|
|
190
|
+
|
|
191
|
+
# Recurse for children
|
|
192
|
+
if child.expanded && !child.children.empty?
|
|
193
|
+
new_part = is_last ? @guide[:space] : @guide[:vertical]
|
|
194
|
+
render_children(child.children, prefix_parts + [new_part], segments)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def self.add_data_to_node(node, data)
|
|
200
|
+
case data
|
|
201
|
+
when Hash
|
|
202
|
+
data.each do |key, value|
|
|
203
|
+
child = node.add(key.to_s)
|
|
204
|
+
add_data_to_node(child, value)
|
|
205
|
+
end
|
|
206
|
+
when Array
|
|
207
|
+
data.each_with_index do |item, index|
|
|
208
|
+
if item.is_a?(Hash) || item.is_a?(Array)
|
|
209
|
+
child = node.add("[#{index}]")
|
|
210
|
+
add_data_to_node(child, item)
|
|
211
|
+
else
|
|
212
|
+
node.add(item.to_s)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
else
|
|
216
|
+
node.add(data.to_s) unless data.nil?
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
data/lib/rich/version.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Rich
|
|
4
|
-
VERSION = "1.0.
|
|
5
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rich
|
|
4
|
+
VERSION = "1.0.2"
|
|
5
|
+
end
|