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