Skip to main content

reifydb_core/value/frame/
render.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use 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}