ruby_rich 0.3.1 → 0.4.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 +4 -4
- data/lib/ruby_rich/columns.rb +244 -0
- data/lib/ruby_rich/console.rb +17 -4
- data/lib/ruby_rich/markdown.rb +343 -0
- data/lib/ruby_rich/progress_bar.rb +229 -11
- data/lib/ruby_rich/status.rb +246 -0
- data/lib/ruby_rich/syntax.rb +171 -0
- data/lib/ruby_rich/table.rb +155 -9
- data/lib/ruby_rich/text.rb +111 -1
- data/lib/ruby_rich/tree.rb +200 -0
- data/lib/ruby_rich/version.rb +1 -1
- data/lib/ruby_rich.rb +33 -3
- metadata +92 -3
data/lib/ruby_rich/table.rb
CHANGED
|
@@ -2,36 +2,182 @@ require 'unicode/display_width'
|
|
|
2
2
|
|
|
3
3
|
module RubyRich
|
|
4
4
|
class Table
|
|
5
|
-
attr_accessor :
|
|
5
|
+
attr_accessor :rows, :align, :row_height, :border_style
|
|
6
|
+
attr_reader :headers
|
|
7
|
+
|
|
8
|
+
# 边框样式定义
|
|
9
|
+
BORDER_STYLES = {
|
|
10
|
+
none: {
|
|
11
|
+
top: '', bottom: '', left: '', right: '',
|
|
12
|
+
horizontal: '-', vertical: '|',
|
|
13
|
+
top_left: '', top_right: '', bottom_left: '', bottom_right: '',
|
|
14
|
+
cross: '', top_cross: '', bottom_cross: '', left_cross: '', right_cross: ''
|
|
15
|
+
},
|
|
16
|
+
simple: {
|
|
17
|
+
top: '-', bottom: '-', left: '|', right: '|',
|
|
18
|
+
horizontal: '-', vertical: '|',
|
|
19
|
+
top_left: '+', top_right: '+', bottom_left: '+', bottom_right: '+',
|
|
20
|
+
cross: '+', top_cross: '+', bottom_cross: '+', left_cross: '+', right_cross: '+'
|
|
21
|
+
},
|
|
22
|
+
full: {
|
|
23
|
+
top: '─', bottom: '─', left: '│', right: '│',
|
|
24
|
+
horizontal: '─', vertical: '│',
|
|
25
|
+
top_left: '┌', top_right: '┐', bottom_left: '└', bottom_right: '┘',
|
|
26
|
+
cross: '┼', top_cross: '┬', bottom_cross: '┴', left_cross: '├', right_cross: '┤'
|
|
27
|
+
}
|
|
28
|
+
}.freeze
|
|
6
29
|
|
|
7
|
-
def initialize(headers: [], align: :left, row_height: 1)
|
|
30
|
+
def initialize(headers: [], align: :left, row_height: 1, border_style: :none)
|
|
8
31
|
@headers = headers.map { |h| format_cell(h) }
|
|
9
32
|
@rows = []
|
|
10
33
|
@align = align
|
|
11
34
|
@row_height = row_height
|
|
35
|
+
@border_style = border_style
|
|
12
36
|
end
|
|
13
37
|
|
|
14
38
|
def add_row(row)
|
|
15
39
|
@rows << row.map { |cell| format_cell(cell) }
|
|
16
40
|
end
|
|
41
|
+
|
|
42
|
+
def headers=(new_headers)
|
|
43
|
+
@headers = new_headers.map { |h| format_cell(h) }
|
|
44
|
+
end
|
|
17
45
|
|
|
18
46
|
def render
|
|
47
|
+
return render_empty_table if @headers.empty? && @rows.empty?
|
|
48
|
+
|
|
19
49
|
column_widths = calculate_column_widths
|
|
20
50
|
lines = []
|
|
21
|
-
|
|
51
|
+
border_chars = BORDER_STYLES[@border_style] || BORDER_STYLES[:none]
|
|
52
|
+
|
|
53
|
+
# Render top border
|
|
54
|
+
if @border_style != :none && !@headers.empty?
|
|
55
|
+
lines << render_horizontal_border(column_widths, :top)
|
|
56
|
+
end
|
|
57
|
+
|
|
22
58
|
# Render headers
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
59
|
+
unless @headers.empty?
|
|
60
|
+
lines.concat(render_styled_row(@headers, column_widths, bold: true))
|
|
61
|
+
# Header separator line
|
|
62
|
+
lines << render_horizontal_border(column_widths, :middle)
|
|
63
|
+
end
|
|
64
|
+
|
|
26
65
|
# Render rows
|
|
27
|
-
@rows.
|
|
28
|
-
lines.concat(
|
|
66
|
+
@rows.each_with_index do |row, index|
|
|
67
|
+
lines.concat(render_styled_multiline_row(row, column_widths))
|
|
29
68
|
end
|
|
30
|
-
|
|
69
|
+
|
|
70
|
+
# Render bottom border
|
|
71
|
+
if @border_style != :none && (!@headers.empty? || !@rows.empty?)
|
|
72
|
+
lines << render_horizontal_border(column_widths, :bottom)
|
|
73
|
+
end
|
|
74
|
+
|
|
31
75
|
lines.join("\n")
|
|
32
76
|
end
|
|
33
77
|
|
|
34
78
|
private
|
|
79
|
+
|
|
80
|
+
def render_empty_table
|
|
81
|
+
if @border_style == :none
|
|
82
|
+
return "| |\n-"
|
|
83
|
+
else
|
|
84
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
85
|
+
return "#{border_chars[:top_left]}#{border_chars[:horizontal] * 2}#{border_chars[:top_right]}\n" +
|
|
86
|
+
"#{border_chars[:left]} #{border_chars[:right]}\n" +
|
|
87
|
+
"#{border_chars[:bottom_left]}#{border_chars[:horizontal] * 2}#{border_chars[:bottom_right]}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_horizontal_border(column_widths, position)
|
|
92
|
+
return "-" * (column_widths.sum { |w| w + 3 } + 1) if @border_style == :none
|
|
93
|
+
|
|
94
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
95
|
+
|
|
96
|
+
# 计算每列的边框宽度(包括左右的空格)
|
|
97
|
+
segments = column_widths.map { |width| border_chars[:horizontal] * (width + 2) }
|
|
98
|
+
|
|
99
|
+
case position
|
|
100
|
+
when :top
|
|
101
|
+
left_char = border_chars[:top_left]
|
|
102
|
+
right_char = border_chars[:top_right]
|
|
103
|
+
join_char = border_chars[:top_cross]
|
|
104
|
+
when :middle
|
|
105
|
+
left_char = border_chars[:left_cross]
|
|
106
|
+
right_char = border_chars[:right_cross]
|
|
107
|
+
join_char = border_chars[:cross]
|
|
108
|
+
when :bottom
|
|
109
|
+
left_char = border_chars[:bottom_left]
|
|
110
|
+
right_char = border_chars[:bottom_right]
|
|
111
|
+
join_char = border_chars[:bottom_cross]
|
|
112
|
+
else
|
|
113
|
+
left_char = border_chars[:left_cross]
|
|
114
|
+
right_char = border_chars[:right_cross]
|
|
115
|
+
join_char = border_chars[:cross]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
left_char + segments.join(join_char) + right_char
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def render_styled_row(row, column_widths, bold: false)
|
|
122
|
+
if @border_style == :none
|
|
123
|
+
return [render_row(row, column_widths, bold: bold)]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
127
|
+
|
|
128
|
+
row_content = row.map.with_index do |cell, i|
|
|
129
|
+
content = bold ? cell.render : align_cell(cell.render, column_widths[i])
|
|
130
|
+
aligned_content = align_cell(content, column_widths[i])
|
|
131
|
+
" #{aligned_content} "
|
|
132
|
+
end.join(border_chars[:vertical])
|
|
133
|
+
|
|
134
|
+
["#{border_chars[:left]}#{row_content}#{border_chars[:right]}"]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def render_styled_multiline_row(row, column_widths)
|
|
138
|
+
if @border_style == :none
|
|
139
|
+
return render_multiline_row(row, column_widths)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
143
|
+
|
|
144
|
+
# Prepare each cell's lines
|
|
145
|
+
row_lines = row.map.with_index do |cell, i|
|
|
146
|
+
# 获取单元格的样式序列
|
|
147
|
+
style_sequence = cell.render.match(/\e\[[0-9;]*m/)&.to_s || ""
|
|
148
|
+
reset_sequence = style_sequence.empty? ? "" : "\e[0m"
|
|
149
|
+
|
|
150
|
+
# 分割成多行并保持样式
|
|
151
|
+
cell_content = cell.render.split("\n")
|
|
152
|
+
|
|
153
|
+
# 为每一行添加样式
|
|
154
|
+
cell_content.map! { |line|
|
|
155
|
+
line = line.gsub(/\e\[[0-9;]*m/, '') # 移除可能存在的样式序列
|
|
156
|
+
style_sequence + line + reset_sequence
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# 填充到指定的行高
|
|
160
|
+
padded_content = cell_content + [" "] * [@row_height - cell_content.size, 0].max
|
|
161
|
+
|
|
162
|
+
# 对每一行应用对齐,保持样式
|
|
163
|
+
padded_content.map { |line| align_cell(line, column_widths[i]) }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Normalize row height
|
|
167
|
+
max_height = row_lines.map(&:size).max
|
|
168
|
+
row_lines.each do |lines|
|
|
169
|
+
width = column_widths[row_lines.index(lines)]
|
|
170
|
+
style_sequence = lines.first.match(/\e\[[0-9;]*m/)&.to_s || ""
|
|
171
|
+
reset_sequence = style_sequence.empty? ? "" : "\e[0m"
|
|
172
|
+
lines.fill(style_sequence + " " * width + reset_sequence, lines.size...max_height)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Render each line of the row
|
|
176
|
+
(0...max_height).map do |line_index|
|
|
177
|
+
row_content = row_lines.map { |lines| " #{lines[line_index]} " }.join(border_chars[:vertical])
|
|
178
|
+
"#{border_chars[:left]}#{row_content}#{border_chars[:right]}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
35
181
|
|
|
36
182
|
def format_cell(cell)
|
|
37
183
|
cell.is_a?(RubyRich::RichText) ? cell : RubyRich::RichText.new(cell.to_s)
|
data/lib/ruby_rich/text.rb
CHANGED
|
@@ -8,6 +8,43 @@ module RubyRich
|
|
|
8
8
|
warning: { color: :yellow, bold: true }
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
# Rich markup 标记映射
|
|
12
|
+
MARKUP_PATTERNS = {
|
|
13
|
+
# Basic colors
|
|
14
|
+
/\[red\](.*?)\[\/red\]/m => proc { |text| "\e[31m#{text}\e[0m" },
|
|
15
|
+
/\[green\](.*?)\[\/green\]/m => proc { |text| "\e[32m#{text}\e[0m" },
|
|
16
|
+
/\[yellow\](.*?)\[\/yellow\]/m => proc { |text| "\e[33m#{text}\e[0m" },
|
|
17
|
+
/\[blue\](.*?)\[\/blue\]/m => proc { |text| "\e[34m#{text}\e[0m" },
|
|
18
|
+
/\[magenta\](.*?)\[\/magenta\]/m => proc { |text| "\e[35m#{text}\e[0m" },
|
|
19
|
+
/\[cyan\](.*?)\[\/cyan\]/m => proc { |text| "\e[36m#{text}\e[0m" },
|
|
20
|
+
/\[white\](.*?)\[\/white\]/m => proc { |text| "\e[37m#{text}\e[0m" },
|
|
21
|
+
/\[black\](.*?)\[\/black\]/m => proc { |text| "\e[30m#{text}\e[0m" },
|
|
22
|
+
|
|
23
|
+
# Bright colors
|
|
24
|
+
/\[bright_red\](.*?)\[\/bright_red\]/m => proc { |text| "\e[91m#{text}\e[0m" },
|
|
25
|
+
/\[bright_green\](.*?)\[\/bright_green\]/m => proc { |text| "\e[92m#{text}\e[0m" },
|
|
26
|
+
/\[bright_yellow\](.*?)\[\/bright_yellow\]/m => proc { |text| "\e[93m#{text}\e[0m" },
|
|
27
|
+
/\[bright_blue\](.*?)\[\/bright_blue\]/m => proc { |text| "\e[94m#{text}\e[0m" },
|
|
28
|
+
/\[bright_magenta\](.*?)\[\/bright_magenta\]/m => proc { |text| "\e[95m#{text}\e[0m" },
|
|
29
|
+
/\[bright_cyan\](.*?)\[\/bright_cyan\]/m => proc { |text| "\e[96m#{text}\e[0m" },
|
|
30
|
+
/\[bright_white\](.*?)\[\/bright_white\]/m => proc { |text| "\e[97m#{text}\e[0m" },
|
|
31
|
+
|
|
32
|
+
# Text styles
|
|
33
|
+
/\[bold\](.*?)\[\/bold\]/m => proc { |text| "\e[1m#{text}\e[22m" },
|
|
34
|
+
/\[dim\](.*?)\[\/dim\]/m => proc { |text| "\e[2m#{text}\e[22m" },
|
|
35
|
+
/\[italic\](.*?)\[\/italic\]/m => proc { |text| "\e[3m#{text}\e[23m" },
|
|
36
|
+
/\[underline\](.*?)\[\/underline\]/m => proc { |text| "\e[4m#{text}\e[24m" },
|
|
37
|
+
/\[blink\](.*?)\[\/blink\]/m => proc { |text| "\e[5m#{text}\e[25m" },
|
|
38
|
+
/\[reverse\](.*?)\[\/reverse\]/m => proc { |text| "\e[7m#{text}\e[27m" },
|
|
39
|
+
/\[strikethrough\](.*?)\[\/strikethrough\]/m => proc { |text| "\e[9m#{text}\e[29m" },
|
|
40
|
+
|
|
41
|
+
# Combined styles
|
|
42
|
+
/\[bold\s+(\w+)\](.*?)\[\/bold\s+\1\]/m => proc do |text, color_match|
|
|
43
|
+
color_code = color_to_ansi(color_match)
|
|
44
|
+
"\e[1m#{color_code}#{text}\e[0m"
|
|
45
|
+
end
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
11
48
|
def self.set_theme(new_theme)
|
|
12
49
|
@@theme.merge!(new_theme)
|
|
13
50
|
end
|
|
@@ -34,11 +71,84 @@ module RubyRich
|
|
|
34
71
|
end
|
|
35
72
|
|
|
36
73
|
def render
|
|
37
|
-
|
|
74
|
+
processed_text = process_markup(@text)
|
|
75
|
+
"#{@styles.join}#{processed_text}#{AnsiCode.reset}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# 处理 Rich markup 标记语言
|
|
79
|
+
def self.markup(text)
|
|
80
|
+
new(text).render_markup
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def render_markup
|
|
84
|
+
process_markup(@text)
|
|
38
85
|
end
|
|
39
86
|
|
|
40
87
|
private
|
|
41
88
|
|
|
89
|
+
def process_markup(text)
|
|
90
|
+
result = text.dup
|
|
91
|
+
|
|
92
|
+
# 处理组合样式 (如 [bold red])
|
|
93
|
+
result.gsub!(/\[(\w+)\s+(\w+)\](.*?)\[\/\1\s+\2\]/m) do |match|
|
|
94
|
+
style1, style2, content = $1, $2, $3
|
|
95
|
+
|
|
96
|
+
# 确定哪个是样式,哪个是颜色
|
|
97
|
+
if is_color?(style2)
|
|
98
|
+
apply_combined_style(content, style1, style2)
|
|
99
|
+
elsif is_color?(style1)
|
|
100
|
+
apply_combined_style(content, style2, style1)
|
|
101
|
+
else
|
|
102
|
+
match # 无法处理的组合,返回原文
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# 处理基本样式和颜色
|
|
107
|
+
MARKUP_PATTERNS.each do |pattern, processor|
|
|
108
|
+
result.gsub!(pattern) do |match|
|
|
109
|
+
if pattern.source.include?('\\s+')
|
|
110
|
+
# 这是组合样式,已经在上面处理过了
|
|
111
|
+
match
|
|
112
|
+
else
|
|
113
|
+
processor.call($1)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def apply_combined_style(content, style, color)
|
|
122
|
+
color_code = color_to_ansi(color)
|
|
123
|
+
style_code = style_to_ansi(style)
|
|
124
|
+
"#{style_code}#{color_code}#{content}\e[0m"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def is_color?(word)
|
|
128
|
+
%w[red green yellow blue magenta cyan white black bright_red bright_green bright_yellow bright_blue bright_magenta bright_cyan bright_white].include?(word)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def color_to_ansi(color)
|
|
132
|
+
color_map = {
|
|
133
|
+
'red' => "\e[31m", 'green' => "\e[32m", 'yellow' => "\e[33m",
|
|
134
|
+
'blue' => "\e[34m", 'magenta' => "\e[35m", 'cyan' => "\e[36m",
|
|
135
|
+
'white' => "\e[37m", 'black' => "\e[30m",
|
|
136
|
+
'bright_red' => "\e[91m", 'bright_green' => "\e[92m", 'bright_yellow' => "\e[93m",
|
|
137
|
+
'bright_blue' => "\e[94m", 'bright_magenta' => "\e[95m", 'bright_cyan' => "\e[96m",
|
|
138
|
+
'bright_white' => "\e[97m"
|
|
139
|
+
}
|
|
140
|
+
color_map[color] || ""
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def style_to_ansi(style)
|
|
144
|
+
style_map = {
|
|
145
|
+
'bold' => "\e[1m", 'dim' => "\e[2m", 'italic' => "\e[3m",
|
|
146
|
+
'underline' => "\e[4m", 'blink' => "\e[5m", 'reverse' => "\e[7m",
|
|
147
|
+
'strikethrough' => "\e[9m"
|
|
148
|
+
}
|
|
149
|
+
style_map[style] || ""
|
|
150
|
+
end
|
|
151
|
+
|
|
42
152
|
def add_style(code, error_message)
|
|
43
153
|
if code
|
|
44
154
|
@styles << code
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
module RubyRich
|
|
2
|
+
class Tree
|
|
3
|
+
# 树形结构显示的字符集
|
|
4
|
+
TREE_CHARS = {
|
|
5
|
+
default: {
|
|
6
|
+
vertical: '│',
|
|
7
|
+
horizontal: '─',
|
|
8
|
+
branch: '├',
|
|
9
|
+
last: '└',
|
|
10
|
+
space: ' '
|
|
11
|
+
},
|
|
12
|
+
ascii: {
|
|
13
|
+
vertical: '|',
|
|
14
|
+
horizontal: '-',
|
|
15
|
+
branch: '+',
|
|
16
|
+
last: '+',
|
|
17
|
+
space: ' '
|
|
18
|
+
},
|
|
19
|
+
rounded: {
|
|
20
|
+
vertical: '│',
|
|
21
|
+
horizontal: '─',
|
|
22
|
+
branch: '├',
|
|
23
|
+
last: '└',
|
|
24
|
+
space: ' '
|
|
25
|
+
},
|
|
26
|
+
double: {
|
|
27
|
+
vertical: '║',
|
|
28
|
+
horizontal: '═',
|
|
29
|
+
branch: '╠',
|
|
30
|
+
last: '╚',
|
|
31
|
+
space: ' '
|
|
32
|
+
}
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# 树节点类
|
|
36
|
+
class Node
|
|
37
|
+
attr_accessor :name, :children, :data, :expanded
|
|
38
|
+
|
|
39
|
+
def initialize(name, data: nil)
|
|
40
|
+
@name = name
|
|
41
|
+
@children = []
|
|
42
|
+
@data = data
|
|
43
|
+
@expanded = true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def add_child(child_node)
|
|
47
|
+
@children << child_node
|
|
48
|
+
child_node
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add(name, data: nil)
|
|
52
|
+
child = Node.new(name, data: data)
|
|
53
|
+
add_child(child)
|
|
54
|
+
child
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def leaf?
|
|
58
|
+
@children.empty?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def has_children?
|
|
62
|
+
!@children.empty?
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
attr_reader :root, :style
|
|
67
|
+
|
|
68
|
+
def initialize(root_name = 'Root', style: :default)
|
|
69
|
+
@root = Node.new(root_name)
|
|
70
|
+
@style = style
|
|
71
|
+
@chars = TREE_CHARS[@style] || TREE_CHARS[:default]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 从哈希构建树
|
|
75
|
+
def self.from_hash(hash, root_name = 'Root', style: :default)
|
|
76
|
+
tree = new(root_name, style: style)
|
|
77
|
+
build_from_hash(tree.root, hash)
|
|
78
|
+
tree
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 从文件路径构建树
|
|
82
|
+
def self.from_paths(paths, root_name = 'Root', style: :default)
|
|
83
|
+
tree = new(root_name, style: style)
|
|
84
|
+
|
|
85
|
+
paths.each do |path|
|
|
86
|
+
parts = path.split('/')
|
|
87
|
+
current_node = tree.root
|
|
88
|
+
|
|
89
|
+
parts.each do |part|
|
|
90
|
+
next if part.empty?
|
|
91
|
+
|
|
92
|
+
# 查找是否已存在该子节点
|
|
93
|
+
existing_child = current_node.children.find { |child| child.name == part }
|
|
94
|
+
|
|
95
|
+
if existing_child
|
|
96
|
+
current_node = existing_child
|
|
97
|
+
else
|
|
98
|
+
current_node = current_node.add(part)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
tree
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# 添加节点到根节点
|
|
107
|
+
def add(name, data: nil)
|
|
108
|
+
@root.add(name, data: data)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# 渲染树形结构
|
|
112
|
+
def render(show_guides: true, colors: true)
|
|
113
|
+
lines = []
|
|
114
|
+
render_node(@root, '', true, lines, show_guides, colors)
|
|
115
|
+
lines.join("\n")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# 渲染为字符串(别名)
|
|
119
|
+
def to_s
|
|
120
|
+
render
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def self.build_from_hash(node, hash)
|
|
126
|
+
hash.each do |key, value|
|
|
127
|
+
child = node.add(key.to_s)
|
|
128
|
+
if value.is_a?(Hash)
|
|
129
|
+
build_from_hash(child, value)
|
|
130
|
+
elsif value.is_a?(Array)
|
|
131
|
+
value.each_with_index do |item, index|
|
|
132
|
+
if item.is_a?(Hash)
|
|
133
|
+
item_child = child.add("[#{index}]")
|
|
134
|
+
build_from_hash(item_child, item)
|
|
135
|
+
else
|
|
136
|
+
child.add(item.to_s)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
child.add(value.to_s) unless value.nil?
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def render_node(node, prefix, is_last, lines, show_guides, colors)
|
|
146
|
+
# 根节点特殊处理
|
|
147
|
+
if node == @root
|
|
148
|
+
if colors
|
|
149
|
+
lines << "\e[1m\e[96m#{node.name}\e[0m"
|
|
150
|
+
else
|
|
151
|
+
lines << node.name
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
node.children.each_with_index do |child, index|
|
|
155
|
+
is_child_last = (index == node.children.length - 1)
|
|
156
|
+
render_node(child, '', is_child_last, lines, show_guides, colors)
|
|
157
|
+
end
|
|
158
|
+
return
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# 构建当前行的前缀和连接符
|
|
162
|
+
if show_guides
|
|
163
|
+
connector = is_last ? @chars[:last] : @chars[:branch]
|
|
164
|
+
current_prefix = prefix + connector + @chars[:horizontal] + @chars[:space]
|
|
165
|
+
else
|
|
166
|
+
current_prefix = prefix + @chars[:space] * 4
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# 渲染当前节点
|
|
170
|
+
node_text = if colors
|
|
171
|
+
case
|
|
172
|
+
when node.leaf?
|
|
173
|
+
"\e[92m#{node.name}\e[0m" # 绿色叶子节点
|
|
174
|
+
when node.has_children?
|
|
175
|
+
"\e[94m#{node.name}/\e[0m" # 蓝色目录节点
|
|
176
|
+
else
|
|
177
|
+
node.name
|
|
178
|
+
end
|
|
179
|
+
else
|
|
180
|
+
node.leaf? ? node.name : "#{node.name}/"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
lines << current_prefix + node_text
|
|
184
|
+
|
|
185
|
+
# 递归渲染子节点
|
|
186
|
+
if node.expanded && node.has_children?
|
|
187
|
+
next_prefix = if show_guides
|
|
188
|
+
prefix + (is_last ? @chars[:space] : @chars[:vertical]) + @chars[:space] * 3
|
|
189
|
+
else
|
|
190
|
+
prefix + @chars[:space] * 4
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
node.children.each_with_index do |child, index|
|
|
194
|
+
is_child_last = (index == node.children.length - 1)
|
|
195
|
+
render_node(child, next_prefix, is_child_last, lines, show_guides, colors)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
data/lib/ruby_rich/version.rb
CHANGED
data/lib/ruby_rich.rb
CHANGED
|
@@ -16,6 +16,11 @@ require_relative 'ruby_rich/text'
|
|
|
16
16
|
require_relative 'ruby_rich/print'
|
|
17
17
|
require_relative 'ruby_rich/panel'
|
|
18
18
|
require_relative 'ruby_rich/dialog'
|
|
19
|
+
require_relative 'ruby_rich/syntax'
|
|
20
|
+
require_relative 'ruby_rich/markdown'
|
|
21
|
+
require_relative 'ruby_rich/tree'
|
|
22
|
+
require_relative 'ruby_rich/columns'
|
|
23
|
+
require_relative 'ruby_rich/status'
|
|
19
24
|
require_relative 'ruby_rich/ansi_code'
|
|
20
25
|
require_relative 'ruby_rich/version'
|
|
21
26
|
|
|
@@ -34,7 +39,32 @@ module RubyRich
|
|
|
34
39
|
end
|
|
35
40
|
|
|
36
41
|
# 提供一个便捷方法来创建表格
|
|
37
|
-
def self.table
|
|
38
|
-
Table.new
|
|
42
|
+
def self.table(border_style: :none)
|
|
43
|
+
Table.new(border_style: border_style)
|
|
39
44
|
end
|
|
40
|
-
|
|
45
|
+
|
|
46
|
+
# 提供一个便捷方法来进行语法高亮
|
|
47
|
+
def self.syntax(code, language = nil, theme: :default)
|
|
48
|
+
Syntax.highlight(code, language, theme: theme)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 提供一个便捷方法来渲染 Markdown
|
|
52
|
+
def self.markdown(text, options = {})
|
|
53
|
+
Markdown.render(text, options)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 提供一个便捷方法来创建树形结构
|
|
57
|
+
def self.tree(root_name = 'Root', style: :default)
|
|
58
|
+
Tree.new(root_name, style: style)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 提供一个便捷方法来创建多列布局
|
|
62
|
+
def self.columns(total_width: 80, gutter_width: 2)
|
|
63
|
+
Columns.new(total_width: total_width, gutter_width: gutter_width)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# 提供一个便捷方法来创建状态指示器
|
|
67
|
+
def self.status(type, **options)
|
|
68
|
+
Status.indicator(type, **options)
|
|
69
|
+
end
|
|
70
|
+
end
|