ruby_rich 0.3.1 → 0.4.1

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.
@@ -2,36 +2,182 @@ require 'unicode/display_width'
2
2
 
3
3
  module RubyRich
4
4
  class Table
5
- attr_accessor :headers, :rows, :align, :row_height
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
- lines << render_row(@headers, column_widths, bold: true)
24
- lines << "-" * (column_widths.sum { |w| w + 3 } + 1)
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.each do |row|
28
- lines.concat(render_multiline_row(row, column_widths))
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)
@@ -73,23 +219,23 @@ module RubyRich
73
219
  def render_multiline_row(row, column_widths)
74
220
  # Prepare each cell's lines
75
221
  row_lines = row.map.with_index do |cell, i|
76
- # 获取单元格的样式序列
222
+ # Get cell style sequence
77
223
  style_sequence = cell.render.match(/\e\[[0-9;]*m/)&.to_s || ""
78
224
  reset_sequence = style_sequence.empty? ? "" : "\e[0m"
79
-
80
- # 分割成多行并保持样式
225
+
226
+ # Split into multiple lines while preserving styles
81
227
  cell_content = cell.render.split("\n")
82
-
83
- # 为每一行添加样式
84
- cell_content.map! { |line|
85
- line = line.gsub(/\e\[[0-9;]*m/, '') # 移除可能存在的样式序列
86
- style_sequence + line + reset_sequence
228
+
229
+ # Add style to each line
230
+ cell_content.map! { |line|
231
+ line = line.gsub(/\e\[[0-9;]*m/, '') # Remove existing style sequences
232
+ style_sequence + line + reset_sequence
87
233
  }
88
-
89
- # 填充到指定的行高
234
+
235
+ # Pad to specified row height
90
236
  padded_content = cell_content + [" "] * [@row_height - cell_content.size, 0].max
91
-
92
- # 对每一行应用对齐,保持样式
237
+
238
+ # Apply alignment to each line while preserving styles
93
239
  padded_content.map { |line| align_cell(line, column_widths[i]) }
94
240
  end
95
241