libtableformat/table/
cell.rs1use crate::content::{Content, ContentIterator, ContentStyle, CellWidth};
2use crate::data_item::DataItem;
3use std::clone::Clone;
4
5pub struct TableCellContentIterator<'a> {
6 content: &'a Vec<Content>,
7 current_content_iterator: ContentIterator,
8 current_line_ix: usize,
9 base_style: ContentStyle,
10 width: usize,
11 target_height: usize,
12 current_height: usize,
13}
14
15impl<'a> Iterator for TableCellContentIterator<'a> {
16 type Item = String;
17
18 fn next(&mut self) -> Option<String> {
19 let line =
20 if self.current_line_ix < self.content.len() {
21 self.current_content_iterator.next().map_or_else(|| {
23 self.current_line_ix += 1;
25
26 if self.current_line_ix < self.content.len() {
28 self.current_content_iterator =
29 self.content[self.current_line_ix].get_iterator(
30 &self.base_style.clone(), self.width);
31
32 self.current_content_iterator.next()
34 } else {
35 None
37 }
38 }, Some)
39 } else {
40 None
41 };
42
43 match line {
44 Some(content) => {
45 Some(content)
46 } ,
47 None => {
48 if self.current_height < self.target_height {
49 let result =
51 (0..self.width)
52 .map(|_| " ")
53 .collect::<String>();
54 self.current_height += 1;
55 Some(result)
56 } else {
57 None
58 }
59 }
60 }
61 }
62}
63
64#[allow(unused_macros)]
65#[macro_export]
66macro_rules! cell_content {
67 ($($out:tt)*) => {
68 vec!($($out)*)
69 };
70}
71
72#[allow(unused_macros)]
73#[macro_export]
74macro_rules! cell {
75 ($style:expr, $($content:tt)*) => {
76 Cell::from_styled_content(
77 $style,
78 crate::cell_content!($($content)*)
79 );
80 }
81}
82
83#[derive(Debug)]
87pub struct Cell {
88 contents: Vec<Content>,
89 base_style: ContentStyle,
90}
91
92impl Cell {
93 #[must_use]
94 pub fn empty() -> Cell {
95 Cell {
96 contents: Vec::new(),
97 base_style: ContentStyle::default(),
98 }
99 }
100
101 #[must_use]
102 pub fn new(
103 contents: Vec<Content>,
104 base_style: ContentStyle,
105 ) -> Cell {
106 Cell {
107 contents,
108 base_style
109 }
110 }
111
112 #[must_use]
119 pub fn from_styled_content(
120 format: &str,
121 contents: Vec<&str>,
122 ) -> Cell {
123 let styles: Vec<&str> = format.split(' ').collect();
125 let mut table_cell = Cell::empty();
126
127 for (style_ix, content) in contents.into_iter().enumerate() {
129 let style =
131 if style_ix < styles.len() {
132 ContentStyle::from_format(styles[style_ix]) }
133 else { ContentStyle::default() };
134
135 table_cell.contents.push(
137 Content::new(content.to_string(), Some(style)));
138 }
139 table_cell
140 }
141
142 #[must_use]
149 pub fn from_data_item(
150 data_item: &DataItem,
151 base_style: ContentStyle,
152 ) -> Cell {
153 Cell::new(
154 data_item.lines.clone(),
155 base_style,
156 )
157 }
158
159 #[must_use]
165 pub fn get_cell_width(
166 self: &Cell
167 ) -> CellWidth {
168 if self.contents.is_empty() {
169 CellWidth::default() }
170 else {
171 match &self.contents[0].style {
172 Some(style) => style.width.clone(),
173 None => CellWidth::default()
174 }
175 }
176 }
177
178 #[must_use]
185 pub fn get_iterator(
186 self: &Cell,
187 column_break: &CellWidth
188 ) -> TableCellContentIterator {
189 let cell_width = self.measure_width(column_break);
191
192 TableCellContentIterator {
193 content: &self.contents,
194 current_content_iterator:
195 self.contents[0].get_iterator(&self.base_style.clone(), cell_width),
196 current_line_ix: 0,
197 base_style: self.base_style.clone(),
198 width: cell_width,
199 target_height: self.measure_height(column_break),
200 current_height: 0
201 }
202 }
203
204 #[must_use]
212 pub fn measure_height(
213 self: &Cell,
214 column_break: &CellWidth,
215 ) -> usize {
216 let mut height = 0;
217
218 let cell_width = self.measure_width(column_break);
220
221 for line in &self.contents {
222 let line_width = line.measure_width();
223 if !line.will_wrap() || (line_width <= cell_width) {
225 height += 1
226 } else {
227 height += line_width.div_euclid(cell_width) + 1;
229 }
230 }
231
232 height
233 }
234
235 #[must_use]
242 pub fn measure_width(
243 self: &Cell,
244 column_break: &CellWidth,
245 ) -> usize {
246 match column_break {
247 CellWidth::Fixed(fixed) => *fixed,
248 CellWidth::Minimum(minimum_width) => {
249 let content_width = self.measure_content_width();
250 if minimum_width > &content_width {
251 *minimum_width
252 } else {
253 content_width
254 }
255 },
256 CellWidth::Content => {
257 self.measure_content_width()
258 }
259 }
260 }
261
262 fn measure_content_width(
267 self: &Cell
268 ) -> usize {
269 let mut largest = 0;
270 for content in &self.contents {
271 let content_width = content.measure_width();
272 if content_width > largest {
273 largest = content_width;
274 }
275 }
276
277 largest
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use colored::Color;
285 use crate::content::{Alignment, Wrap};
286
287 #[test]
288 fn test_table_cell_macro() {
289 let tc = cell!("{r<;} {G-b^}", "testing", "this");
290
291 assert_eq!(tc.contents.len(), 2);
292
293 assert_eq!(
294 format!("{:?}", tc.contents[0]),
295 format!("{:?}", Content::new(
296 "testing".to_string(),
297 Some(ContentStyle::new(
298 Some(Color::Red),
299 None,
300 Alignment::Left,
301 Wrap::Wrap,
302 CellWidth::Content
303 ))
304 ))
305 );
306 }
307}