1 | //! This module contains a main table representation [`Table`]. |
2 | |
3 | use core::ops::DerefMut; |
4 | use std::{borrow::Cow, fmt, iter::FromIterator}; |
5 | |
6 | use crate::{ |
7 | builder::Builder, |
8 | grid::{ |
9 | colors::NoColors, |
10 | config::{ |
11 | AlignmentHorizontal, ColorMap, ColoredConfig, CompactConfig, Entity, Formatting, |
12 | Indent, Sides, SpannedConfig, |
13 | }, |
14 | dimension::{CompleteDimensionVecRecords, Dimension, Estimate, PeekableDimension}, |
15 | records::{ |
16 | vec_records::{CellInfo, VecRecords}, |
17 | ExactRecords, Records, |
18 | }, |
19 | PeekableGrid, |
20 | }, |
21 | settings::{object::Object, CellOption, Style, TableOption}, |
22 | Tabled, |
23 | }; |
24 | |
25 | /// The structure provides an interface for building a table for types that implements [`Tabled`]. |
26 | /// |
27 | /// To build a string representation of a table you must use a [`std::fmt::Display`]. |
28 | /// Or simply call `.to_string()` method. |
29 | /// |
30 | /// The default table [`Style`] is [`Style::ascii`], |
31 | /// with a 1 left and right [`Padding`]. |
32 | /// |
33 | /// ## Example |
34 | /// |
35 | /// ### Basic usage |
36 | /// |
37 | /// ```rust,no_run |
38 | /// use tabled::Table; |
39 | /// |
40 | /// let table = Table::new(&["Year" , "2021" ]); |
41 | /// ``` |
42 | /// |
43 | /// ### With settings |
44 | /// |
45 | /// ```rust,no_run |
46 | /// use tabled::{Table, settings::{Style, Alignment}}; |
47 | /// |
48 | /// let data = vec!["Hello" , "2021" ]; |
49 | /// let mut table = Table::new(&data); |
50 | /// table.with(Style::psql()).with(Alignment::left()); |
51 | /// |
52 | /// println!("{}" , table); |
53 | /// ``` |
54 | /// |
55 | /// [`Padding`]: crate::settings::Padding |
56 | /// [`Style`]: crate::settings::Style |
57 | /// [`Style::ascii`]: crate::settings::Style::ascii |
58 | #[derive (Debug, Clone, PartialEq, Eq)] |
59 | pub struct Table { |
60 | records: VecRecords<CellInfo<String>>, |
61 | config: ColoredConfig, |
62 | dimension: CompleteDimensionVecRecords<'static>, |
63 | } |
64 | |
65 | impl Table { |
66 | /// New creates a Table instance. |
67 | /// |
68 | /// If you use a reference iterator you'd better use [`FromIterator`] instead. |
69 | /// As it has a different lifetime constraints and make less copies therefore. |
70 | pub fn new<I, T>(iter: I) -> Self |
71 | where |
72 | I: IntoIterator<Item = T>, |
73 | T: Tabled, |
74 | { |
75 | let mut header = Vec::with_capacity(T::LENGTH); |
76 | for text in T::headers() { |
77 | let text = text.into_owned(); |
78 | let cell = CellInfo::new(text); |
79 | header.push(cell); |
80 | } |
81 | |
82 | let mut records = vec![header]; |
83 | for row in iter.into_iter() { |
84 | let mut list = Vec::with_capacity(T::LENGTH); |
85 | for text in row.fields().into_iter() { |
86 | let text = text.into_owned(); |
87 | let cell = CellInfo::new(text); |
88 | |
89 | list.push(cell); |
90 | } |
91 | |
92 | records.push(list); |
93 | } |
94 | |
95 | let records = VecRecords::new(records); |
96 | |
97 | Self { |
98 | records, |
99 | config: ColoredConfig::new(configure_grid()), |
100 | dimension: CompleteDimensionVecRecords::default(), |
101 | } |
102 | } |
103 | |
104 | /// Creates a builder from a data set given. |
105 | /// |
106 | /// # Example |
107 | /// |
108 | /// |
109 | #[cfg_attr (feature = "derive" , doc = "```" )] |
110 | #[cfg_attr (not(feature = "derive" ), doc = "```ignore" )] |
111 | /// use tabled::{ |
112 | /// Table, Tabled, |
113 | /// settings::{object::Segment, Modify, Alignment} |
114 | /// }; |
115 | /// |
116 | /// #[derive(Tabled)] |
117 | /// struct User { |
118 | /// name: &'static str, |
119 | /// #[tabled(inline("device::" ))] |
120 | /// device: Device, |
121 | /// } |
122 | /// |
123 | /// #[derive(Tabled)] |
124 | /// enum Device { |
125 | /// PC, |
126 | /// Mobile |
127 | /// } |
128 | /// |
129 | /// let data = vec![ |
130 | /// User { name: "Vlad" , device: Device::Mobile }, |
131 | /// User { name: "Dimitry" , device: Device::PC }, |
132 | /// User { name: "John" , device: Device::PC }, |
133 | /// ]; |
134 | /// |
135 | /// let mut table = Table::builder(data) |
136 | /// .index() |
137 | /// .column(0) |
138 | /// .transpose() |
139 | /// .build() |
140 | /// .with(Modify::new(Segment::new(1.., 1..)).with(Alignment::center())) |
141 | /// .to_string(); |
142 | /// |
143 | /// assert_eq!( |
144 | /// table, |
145 | /// "+----------------+------+---------+------+ \n\ |
146 | /// | name | Vlad | Dimitry | John | \n\ |
147 | /// +----------------+------+---------+------+ \n\ |
148 | /// | device::PC | | + | + | \n\ |
149 | /// +----------------+------+---------+------+ \n\ |
150 | /// | device::Mobile | + | | | \n\ |
151 | /// +----------------+------+---------+------+" |
152 | /// ) |
153 | /// ``` |
154 | pub fn builder<I, T>(iter: I) -> Builder |
155 | where |
156 | T: Tabled, |
157 | I: IntoIterator<Item = T>, |
158 | { |
159 | let mut builder = Builder::with_capacity(0, T::LENGTH); |
160 | builder.push_record(T::headers()); |
161 | |
162 | for row in iter { |
163 | builder.push_record(row.fields().into_iter()); |
164 | } |
165 | |
166 | builder |
167 | } |
168 | |
169 | /// It's a generic function which applies options to the [`Table`]. |
170 | /// |
171 | /// It applies settings immediately. |
172 | pub fn with<O>(&mut self, option: O) -> &mut Self |
173 | where |
174 | for<'a> O: TableOption< |
175 | VecRecords<CellInfo<String>>, |
176 | ColoredConfig, |
177 | CompleteDimensionVecRecords<'a>, |
178 | >, |
179 | { |
180 | let reastimation_hint = option.hint_change(); |
181 | let mut dims = self.dimension.from_origin(); |
182 | |
183 | option.change(&mut self.records, &mut self.config, &mut dims); |
184 | |
185 | let (widths, heights) = dims.into_inner(); |
186 | dimension_reastimate(&mut self.dimension, widths, heights, reastimation_hint); |
187 | |
188 | self |
189 | } |
190 | |
191 | /// It's a generic function which applies options to a particalar cells on the [`Table`]. |
192 | /// Target cells using [`Object`]s such as [`Cell`], [`Rows`], [`Location`] and more. |
193 | /// |
194 | /// It applies settings immediately. |
195 | /// |
196 | /// [`Cell`]: crate::settings::object::Cell |
197 | /// [`Rows`]: crate::settings::object::Rows |
198 | /// [`Location`]: crate::settings::location::Locator |
199 | pub fn modify<T, O>(&mut self, target: T, option: O) -> &mut Self |
200 | where |
201 | T: Object<VecRecords<CellInfo<String>>>, |
202 | O: CellOption<VecRecords<CellInfo<String>>, ColoredConfig> + Clone, |
203 | { |
204 | for entity in target.cells(&self.records) { |
205 | let opt = option.clone(); |
206 | opt.change(&mut self.records, &mut self.config, entity); |
207 | } |
208 | |
209 | let reastimation_hint = option.hint_change(); |
210 | dimension_reastimate_likely(&mut self.dimension, reastimation_hint); |
211 | |
212 | self |
213 | } |
214 | |
215 | /// Returns a table shape (count rows, count columns). |
216 | pub fn shape(&self) -> (usize, usize) { |
217 | (self.count_rows(), self.count_columns()) |
218 | } |
219 | |
220 | /// Returns an amount of rows in the table. |
221 | pub fn count_rows(&self) -> usize { |
222 | self.records.count_rows() |
223 | } |
224 | |
225 | /// Returns an amount of columns in the table. |
226 | pub fn count_columns(&self) -> usize { |
227 | self.records.count_columns() |
228 | } |
229 | |
230 | /// Returns a table shape (count rows, count columns). |
231 | pub fn is_empty(&self) -> bool { |
232 | let (count_rows, count_cols) = self.shape(); |
233 | count_rows == 0 || count_cols == 0 |
234 | } |
235 | |
236 | /// Returns total widths of a table, including margin and horizontal lines. |
237 | pub fn total_height(&self) -> usize { |
238 | let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); |
239 | dims.estimate(&self.records, self.config.as_ref()); |
240 | |
241 | let total = (0..self.count_rows()) |
242 | .map(|row| dims.get_height(row)) |
243 | .sum::<usize>(); |
244 | let counth = self.config.count_horizontal(self.count_rows()); |
245 | |
246 | let margin = self.config.get_margin(); |
247 | |
248 | total + counth + margin.top.size + margin.bottom.size |
249 | } |
250 | |
251 | /// Returns total widths of a table, including margin and vertical lines. |
252 | pub fn total_width(&self) -> usize { |
253 | let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); |
254 | dims.estimate(&self.records, self.config.as_ref()); |
255 | |
256 | let total = (0..self.count_columns()) |
257 | .map(|col| dims.get_width(col)) |
258 | .sum::<usize>(); |
259 | let countv = self.config.count_vertical(self.count_columns()); |
260 | |
261 | let margin = self.config.get_margin(); |
262 | |
263 | total + countv + margin.left.size + margin.right.size |
264 | } |
265 | |
266 | /// Returns a table config. |
267 | pub fn get_config(&self) -> &ColoredConfig { |
268 | &self.config |
269 | } |
270 | |
271 | /// Returns a table config. |
272 | pub fn get_config_mut(&mut self) -> &mut ColoredConfig { |
273 | &mut self.config |
274 | } |
275 | |
276 | /// Returns a used records. |
277 | pub fn get_records(&self) -> &VecRecords<CellInfo<String>> { |
278 | &self.records |
279 | } |
280 | |
281 | /// Returns a used records. |
282 | pub fn get_records_mut(&mut self) -> &mut VecRecords<CellInfo<String>> { |
283 | &mut self.records |
284 | } |
285 | } |
286 | |
287 | impl Default for Table { |
288 | fn default() -> Self { |
289 | Self { |
290 | records: VecRecords::default(), |
291 | config: ColoredConfig::new(config:configure_grid()), |
292 | dimension: CompleteDimensionVecRecords::default(), |
293 | } |
294 | } |
295 | } |
296 | |
297 | impl fmt::Display for Table { |
298 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
299 | if self.is_empty() { |
300 | return Ok(()); |
301 | } |
302 | |
303 | let config: Cow<'_, SpannedConfig> = use_format_configuration(f, self); |
304 | let colors: &ColorMap = self.config.get_colors(); |
305 | |
306 | if !self.dimension.is_empty() { |
307 | let mut dims: CompleteDimensionVecRecords<'static> = self.dimension.clone(); |
308 | dims.estimate(&self.records, config.as_ref()); |
309 | |
310 | print_grid(f, &self.records, &config, &dims, colors) |
311 | } else { |
312 | let mut dims: PeekableDimension = PeekableDimension::default(); |
313 | dims.estimate(&self.records, &config); |
314 | |
315 | print_grid(f, &self.records, &config, &dims, colors) |
316 | } |
317 | } |
318 | } |
319 | |
320 | impl<T> FromIterator<T> for Table |
321 | where |
322 | T: IntoIterator, |
323 | T::Item: Into<String>, |
324 | { |
325 | fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { |
326 | Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build() |
327 | } |
328 | } |
329 | |
330 | impl From<Builder> for Table { |
331 | fn from(builder: Builder) -> Self { |
332 | let data: Vec>> = builder.into(); |
333 | let records: VecRecords> = VecRecords::new(data); |
334 | |
335 | Self { |
336 | records, |
337 | config: ColoredConfig::new(config:configure_grid()), |
338 | dimension: CompleteDimensionVecRecords::default(), |
339 | } |
340 | } |
341 | } |
342 | |
343 | impl From<Table> for Builder { |
344 | fn from(val: Table) -> Self { |
345 | let data: Vec>> = val.records.into(); |
346 | Builder::from_vec(data) |
347 | } |
348 | } |
349 | |
350 | impl<R, D> TableOption<R, ColoredConfig, D> for CompactConfig { |
351 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
352 | *cfg.deref_mut() = self.into(); |
353 | } |
354 | } |
355 | |
356 | impl<R, D> TableOption<R, ColoredConfig, D> for ColoredConfig { |
357 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
358 | *cfg = self; |
359 | } |
360 | } |
361 | |
362 | impl<R, D> TableOption<R, ColoredConfig, D> for SpannedConfig { |
363 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
364 | *cfg.deref_mut() = self; |
365 | } |
366 | } |
367 | |
368 | impl<R, D> TableOption<R, ColoredConfig, D> for &SpannedConfig { |
369 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
370 | *cfg.deref_mut() = self.clone(); |
371 | } |
372 | } |
373 | |
374 | fn convert_fmt_alignment(alignment: fmt::Alignment) -> AlignmentHorizontal { |
375 | match alignment { |
376 | fmt::Alignment::Left => AlignmentHorizontal::Left, |
377 | fmt::Alignment::Right => AlignmentHorizontal::Right, |
378 | fmt::Alignment::Center => AlignmentHorizontal::Center, |
379 | } |
380 | } |
381 | |
382 | fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) { |
383 | match alignment { |
384 | fmt::Alignment::Left => (available, 0), |
385 | fmt::Alignment::Right => (0, available), |
386 | fmt::Alignment::Center => { |
387 | let left: usize = available / 2; |
388 | let right: usize = available - left; |
389 | (left, right) |
390 | } |
391 | } |
392 | } |
393 | |
394 | fn configure_grid() -> SpannedConfig { |
395 | let mut cfg: SpannedConfig = SpannedConfig::default(); |
396 | cfg.set_padding( |
397 | Entity::Global, |
398 | padding:Sides::new( |
399 | left:Indent::spaced(1), |
400 | right:Indent::spaced(1), |
401 | top:Indent::default(), |
402 | bottom:Indent::default(), |
403 | ), |
404 | ); |
405 | cfg.set_alignment_horizontal(Entity::Global, alignment:AlignmentHorizontal::Left); |
406 | cfg.set_formatting(Entity::Global, Formatting::new(horizontal_trim:false, vertical_trim:false, lines_alignment:false)); |
407 | cfg.set_borders(Style::ascii().get_borders()); |
408 | |
409 | cfg |
410 | } |
411 | |
412 | fn use_format_configuration<'a>( |
413 | f: &mut fmt::Formatter<'_>, |
414 | table: &'a Table, |
415 | ) -> Cow<'a, SpannedConfig> { |
416 | if f.align().is_some() || f.width().is_some() { |
417 | let mut cfg: SpannedConfig = table.config.as_ref().clone(); |
418 | |
419 | set_align_table(f, &mut cfg); |
420 | set_width_table(f, &mut cfg, table); |
421 | |
422 | Cow::Owned(cfg) |
423 | } else { |
424 | Cow::Borrowed(table.config.as_ref()) |
425 | } |
426 | } |
427 | |
428 | fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig) { |
429 | if let Some(alignment: Alignment) = f.align() { |
430 | let alignment: AlignmentHorizontal = convert_fmt_alignment(alignment); |
431 | cfg.set_alignment_horizontal(Entity::Global, alignment); |
432 | } |
433 | } |
434 | |
435 | fn set_width_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig, table: &Table) { |
436 | if let Some(width) = f.width() { |
437 | let total_width = table.total_width(); |
438 | if total_width >= width { |
439 | return; |
440 | } |
441 | |
442 | let mut fill = f.fill(); |
443 | if fill == char::default() { |
444 | fill = ' ' ; |
445 | } |
446 | |
447 | let available = width - total_width; |
448 | let alignment = f.align().unwrap_or(fmt::Alignment::Left); |
449 | let (left, right) = table_padding(alignment, available); |
450 | |
451 | let mut margin = cfg.get_margin(); |
452 | margin.left.size += left; |
453 | margin.right.size += right; |
454 | |
455 | if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default() |
456 | { |
457 | margin.left.fill = fill; |
458 | } |
459 | |
460 | if (margin.right.size > 0 && margin.right.fill == char::default()) |
461 | || fill != char::default() |
462 | { |
463 | margin.right.fill = fill; |
464 | } |
465 | |
466 | cfg.set_margin(margin); |
467 | } |
468 | } |
469 | |
470 | fn print_grid<F: fmt::Write, D: Dimension>( |
471 | f: &mut F, |
472 | records: &VecRecords<CellInfo<String>>, |
473 | cfg: &SpannedConfig, |
474 | dims: D, |
475 | colors: &ColorMap, |
476 | ) -> fmt::Result { |
477 | if !colors.is_empty() { |
478 | PeekableGrid::new(records, config:cfg, &dims, colors).build(f) |
479 | } else { |
480 | PeekableGrid::new(records, config:cfg, &dims, colors:NoColors).build(f) |
481 | } |
482 | } |
483 | |
484 | fn dimension_reastimate( |
485 | dims: &mut CompleteDimensionVecRecords<'_>, |
486 | widths: Option<Vec<usize>>, |
487 | heights: Option<Vec<usize>>, |
488 | hint: Option<Entity>, |
489 | ) { |
490 | let hint: Entity = match hint { |
491 | Some(hint: Entity) => hint, |
492 | None => return, |
493 | }; |
494 | |
495 | match hint { |
496 | Entity::Global | Entity::Cell(_, _) => { |
497 | dims_set_widths(dims, list:widths); |
498 | dims_set_heights(dims, list:heights); |
499 | } |
500 | Entity::Column(_) => { |
501 | dims_set_widths(dims, list:widths); |
502 | } |
503 | Entity::Row(_) => { |
504 | dims_set_heights(dims, list:heights); |
505 | } |
506 | } |
507 | } |
508 | |
509 | fn dims_set_widths(dims: &mut CompleteDimensionVecRecords<'_>, list: Option<Vec<usize>>) { |
510 | match list { |
511 | Some(list: Vec) => match dims.get_widths() { |
512 | Some(widths: &[usize]) => { |
513 | if widths == list { |
514 | dims.clear_width(); |
515 | } else { |
516 | dims.set_widths(columns:list); |
517 | } |
518 | } |
519 | None => dims.set_widths(columns:list), |
520 | }, |
521 | None => { |
522 | dims.clear_width(); |
523 | } |
524 | } |
525 | } |
526 | |
527 | fn dims_set_heights(dims: &mut CompleteDimensionVecRecords<'_>, list: Option<Vec<usize>>) { |
528 | match list { |
529 | Some(list: Vec) => match dims.get_heights() { |
530 | Some(heights: &[usize]) => { |
531 | if heights == list { |
532 | dims.clear_height(); |
533 | } else { |
534 | dims.set_heights(rows:list); |
535 | } |
536 | } |
537 | None => dims.set_heights(rows:list), |
538 | }, |
539 | None => { |
540 | dims.clear_height(); |
541 | } |
542 | } |
543 | } |
544 | |
545 | fn dimension_reastimate_likely(dims: &mut CompleteDimensionVecRecords<'_>, hint: Option<Entity>) { |
546 | let hint: Entity = match hint { |
547 | Some(hint: Entity) => hint, |
548 | None => return, |
549 | }; |
550 | |
551 | match hint { |
552 | Entity::Global | Entity::Cell(_, _) => { |
553 | dims.clear_width(); |
554 | dims.clear_height() |
555 | } |
556 | Entity::Column(_) => { |
557 | dims.clear_width(); |
558 | } |
559 | Entity::Row(_) => dims.clear_height(), |
560 | } |
561 | } |
562 | |