reifydb_core/value/frame/
render.rs1use std::fmt::{self, Write};
5
6use reifydb_type::{
7 util::unicode::UnicodeWidthStr,
8 value::frame::{column::FrameColumn, frame::Frame},
9};
10
11pub struct FrameRenderer;
13
14impl FrameRenderer {
15 pub fn render_full(frame: &Frame) -> Result<String, fmt::Error> {
17 let mut output = String::new();
18 Self::render_full_to(frame, &mut output)?;
19 Ok(output)
20 }
21
22 pub fn render_without_row_numbers(frame: &Frame) -> Result<String, fmt::Error> {
24 let mut output = String::new();
25 Self::render_without_row_numbers_to(frame, &mut output)?;
26 Ok(output)
27 }
28
29 pub fn render_full_to(frame: &Frame, f: &mut dyn Write) -> fmt::Result {
31 Self::render_internal(frame, f, true)
32 }
33
34 pub fn render_without_row_numbers_to(frame: &Frame, f: &mut dyn Write) -> fmt::Result {
36 Self::render_internal(frame, f, false)
37 }
38
39 fn render_internal(frame: &Frame, f: &mut dyn Write, include_row_numbers: bool) -> fmt::Result {
41 let row_count = frame.first().map_or(0, |c| c.data.len());
42 let has_row_numbers = include_row_numbers && !frame.row_numbers.is_empty();
43 let has_created_at = !frame.created_at.is_empty();
44 let has_updated_at = !frame.updated_at.is_empty();
45 let col_count = frame.len()
46 + if has_row_numbers {
47 1
48 } else {
49 0
50 } + if has_created_at {
51 1
52 } else {
53 0
54 } + if has_updated_at {
55 1
56 } else {
57 0
58 };
59
60 let column_order = Self::get_column_display_order(frame);
62
63 let mut col_widths = vec![0; col_count];
64
65 let mut sys_col_idx = 0;
67 if has_row_numbers {
68 col_widths[sys_col_idx] = Self::display_width("#rownum");
69 for row_num in &frame.row_numbers {
70 col_widths[sys_col_idx] =
71 col_widths[sys_col_idx].max(Self::display_width(&row_num.to_string()));
72 }
73 sys_col_idx += 1;
74 }
75 if has_created_at {
76 col_widths[sys_col_idx] = Self::display_width("#created_at");
77 for ts in &frame.created_at {
78 col_widths[sys_col_idx] =
79 col_widths[sys_col_idx].max(Self::display_width(&ts.to_string()));
80 }
81 sys_col_idx += 1;
82 }
83 if has_updated_at {
84 col_widths[sys_col_idx] = Self::display_width("#updated_at");
85 for ts in &frame.updated_at {
86 col_widths[sys_col_idx] =
87 col_widths[sys_col_idx].max(Self::display_width(&ts.to_string()));
88 }
89 sys_col_idx += 1;
90 }
91 let row_num_col_idx = sys_col_idx;
92
93 for (display_idx, &col_idx) in column_order.iter().enumerate() {
94 let col = &frame[col_idx];
95 let display_name = Self::escape_control_chars(&col.name);
96 col_widths[row_num_col_idx + display_idx] = Self::display_width(&display_name);
97 }
98
99 for row_numberx in 0..row_count {
100 for (display_idx, &col_idx) in column_order.iter().enumerate() {
101 let col = &frame[col_idx];
102 let s = Self::extract_string_value(col, row_numberx);
103 col_widths[row_num_col_idx + display_idx] =
104 col_widths[row_num_col_idx + display_idx].max(Self::display_width(&s));
105 }
106 }
107
108 for w in &mut col_widths {
110 *w += 2;
111 }
112
113 let sep = format!("+{}+", col_widths.iter().map(|w| "-".repeat(*w + 2)).collect::<Vec<_>>().join("+"));
114 writeln!(f, "{}", sep)?;
115
116 let mut header = Vec::new();
117
118 let mut sys_idx = 0;
120 if has_row_numbers {
121 let w = col_widths[sys_idx];
122 let name = "#rownum";
123 let pad = w - Self::display_width(name);
124 let l = pad / 2;
125 let r = pad - l;
126 header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
127 sys_idx += 1;
128 }
129 if has_created_at {
130 let w = col_widths[sys_idx];
131 let name = "#created_at";
132 let pad = w - Self::display_width(name);
133 let l = pad / 2;
134 let r = pad - l;
135 header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
136 sys_idx += 1;
137 }
138 if has_updated_at {
139 let w = col_widths[sys_idx];
140 let name = "#updated_at";
141 let pad = w - Self::display_width(name);
142 let l = pad / 2;
143 let r = pad - l;
144 header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
145 sys_idx += 1;
146 }
147 let _ = sys_idx;
148
149 for (display_idx, &col_idx) in column_order.iter().enumerate() {
151 let col = &frame[col_idx];
152 let w = col_widths[row_num_col_idx + display_idx];
153 let name = Self::escape_control_chars(&col.name);
154 let pad = w - Self::display_width(&name);
155 let l = pad / 2;
156 let r = pad - l;
157 header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
158 }
159
160 writeln!(f, "|{}|", header.join("|"))?;
161
162 writeln!(f, "{}", sep)?;
163
164 for row_numberx in 0..row_count {
165 let mut row = Vec::new();
166
167 let mut sys_idx = 0;
169 if has_row_numbers {
170 let w = col_widths[sys_idx];
171 let s = frame.row_numbers[row_numberx].to_string();
172 let pad = w - Self::display_width(&s);
173 let l = pad / 2;
174 let r = pad - l;
175 row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
176 sys_idx += 1;
177 }
178 if has_created_at {
179 let w = col_widths[sys_idx];
180 let s = frame.created_at[row_numberx].to_string();
181 let pad = w - Self::display_width(&s);
182 let l = pad / 2;
183 let r = pad - l;
184 row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
185 sys_idx += 1;
186 }
187 if has_updated_at {
188 let w = col_widths[sys_idx];
189 let s = frame.updated_at[row_numberx].to_string();
190 let pad = w - Self::display_width(&s);
191 let l = pad / 2;
192 let r = pad - l;
193 row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
194 sys_idx += 1;
195 }
196 let _ = sys_idx;
197
198 for (display_idx, &col_idx) in column_order.iter().enumerate() {
200 let col = &frame[col_idx];
201 let w = col_widths[row_num_col_idx + display_idx];
202 let s = Self::extract_string_value(col, row_numberx);
203 let pad = w - Self::display_width(&s);
204 let l = pad / 2;
205 let r = pad - l;
206 row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
207 }
208
209 writeln!(f, "|{}|", row.join("|"))?;
210 }
211
212 writeln!(f, "{}", sep)
213 }
214
215 fn display_width(s: &str) -> usize {
219 if s.contains('\n') {
220 s.lines().map(|line| line.width()).max().unwrap_or(0)
221 } else {
222 s.width()
223 }
224 }
225
226 fn escape_control_chars(s: &str) -> String {
229 s.replace('\n', "\\n").replace('\t', "\\t")
230 }
231
232 fn get_column_display_order(frame: &Frame) -> Vec<usize> {
234 (0..frame.len()).collect()
235 }
236
237 fn extract_string_value(col: &FrameColumn, row_numberx: usize) -> String {
239 let s = col.data.as_string(row_numberx);
240 Self::escape_control_chars(&s)
241 }
242}