reifydb_core/value/frame/
render.rs1use std::fmt::{self, Write};
5
6use reifydb_type::util::unicode::UnicodeWidthStr;
7
8use crate::value::frame::{Frame, FrameColumn};
9
10pub struct FrameRenderer;
12
13impl FrameRenderer {
14 pub fn render_full(frame: &Frame) -> Result<String, fmt::Error> {
16 let mut output = String::new();
17 Self::render_full_to(frame, &mut output)?;
18 Ok(output)
19 }
20
21 pub fn render_without_row_numbers(frame: &Frame) -> Result<String, fmt::Error> {
23 let mut output = String::new();
24 Self::render_without_row_numbers_to(frame, &mut output)?;
25 Ok(output)
26 }
27
28 pub fn render_full_to(frame: &Frame, f: &mut dyn Write) -> fmt::Result {
30 Self::render_internal(frame, f, true)
31 }
32
33 pub fn render_without_row_numbers_to(frame: &Frame, f: &mut dyn Write) -> fmt::Result {
35 Self::render_internal(frame, f, false)
36 }
37
38 fn render_internal(frame: &Frame, f: &mut dyn Write, include_row_numbers: bool) -> fmt::Result {
40 let row_count = frame.first().map_or(0, |c| c.data.len());
41 let has_row_numbers = include_row_numbers && !frame.row_numbers.is_empty();
42 let col_count = frame.len()
43 + if has_row_numbers {
44 1
45 } else {
46 0
47 };
48
49 let column_order = Self::get_column_display_order(frame);
51
52 let mut col_widths = vec![0; col_count];
53
54 let row_num_col_idx = if has_row_numbers {
56 let row_num_header = "__ROW__NUMBER__";
58 col_widths[0] = Self::display_width(row_num_header);
59
60 for row_num in &frame.row_numbers {
62 let s = row_num.to_string();
63 col_widths[0] = col_widths[0].max(Self::display_width(&s));
64 }
65 1 } else {
67 0 };
69
70 for (display_idx, &col_idx) in column_order.iter().enumerate() {
71 let col = &frame[col_idx];
72 let display_name = Self::escape_control_chars(&col.qualified_name());
73 col_widths[row_num_col_idx + display_idx] = Self::display_width(&display_name);
74 }
75
76 for row_numberx in 0..row_count {
77 for (display_idx, &col_idx) in column_order.iter().enumerate() {
78 let col = &frame[col_idx];
79 let s = Self::extract_string_value(col, row_numberx);
80 col_widths[row_num_col_idx + display_idx] =
81 col_widths[row_num_col_idx + display_idx].max(Self::display_width(&s));
82 }
83 }
84
85 for w in &mut col_widths {
87 *w += 2;
88 }
89
90 let sep = format!("+{}+", col_widths.iter().map(|w| "-".repeat(*w + 2)).collect::<Vec<_>>().join("+"));
91 writeln!(f, "{}", sep)?;
92
93 let mut header = Vec::new();
94
95 if has_row_numbers {
97 let w = col_widths[0];
98 let name = "__ROW__NUMBER__";
99 let pad = w - Self::display_width(name);
100 let l = pad / 2;
101 let r = pad - l;
102 header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
103 }
104
105 for (display_idx, &col_idx) in column_order.iter().enumerate() {
107 let col = &frame[col_idx];
108 let w = col_widths[row_num_col_idx + display_idx];
109 let name = Self::escape_control_chars(&col.qualified_name());
110 let pad = w - Self::display_width(&name);
111 let l = pad / 2;
112 let r = pad - l;
113 header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
114 }
115
116 writeln!(f, "|{}|", header.join("|"))?;
117
118 writeln!(f, "{}", sep)?;
119
120 for row_numberx in 0..row_count {
121 let mut row = Vec::new();
122
123 if has_row_numbers {
125 let w = col_widths[0];
126 let s = if row_numberx < frame.row_numbers.len() {
127 frame.row_numbers[row_numberx].to_string()
128 } else {
129 "Undefined".to_string()
130 };
131 let pad = w - Self::display_width(&s);
132 let l = pad / 2;
133 let r = pad - l;
134 row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
135 }
136
137 for (display_idx, &col_idx) in column_order.iter().enumerate() {
139 let col = &frame[col_idx];
140 let w = col_widths[row_num_col_idx + display_idx];
141 let s = Self::extract_string_value(col, row_numberx);
142 let pad = w - Self::display_width(&s);
143 let l = pad / 2;
144 let r = pad - l;
145 row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
146 }
147
148 writeln!(f, "|{}|", row.join("|"))?;
149 }
150
151 writeln!(f, "{}", sep)
152 }
153
154 fn display_width(s: &str) -> usize {
158 if s.contains('\n') {
159 s.lines().map(|line| line.width()).max().unwrap_or(0)
160 } else {
161 s.width()
162 }
163 }
164
165 fn escape_control_chars(s: &str) -> String {
168 s.replace('\n', "\\n").replace('\t', "\\t")
169 }
170
171 fn get_column_display_order(frame: &Frame) -> Vec<usize> {
173 (0..frame.len()).collect()
174 }
175
176 fn extract_string_value(col: &FrameColumn, row_numberx: usize) -> String {
178 let s = col.data.as_string(row_numberx);
179 Self::escape_control_chars(&s)
180 }
181}