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.
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.1"
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Rich
4
+ VERSION = "1.0.2"
5
+ end