1//! This module contains a main table representation [`Table`].
2
3use core::ops::DerefMut;
4use std::{borrow::Cow, fmt, iter::FromIterator};
5
6use 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)]
59pub struct Table {
60 records: VecRecords<CellInfo<String>>,
61 config: ColoredConfig,
62 dimension: CompleteDimensionVecRecords<'static>,
63}
64
65impl 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
287impl 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
297impl 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
320impl<T> FromIterator<T> for Table
321where
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
330impl 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
343impl 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
350impl<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
356impl<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
362impl<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
368impl<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
374fn 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
382fn 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
394fn 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
412fn 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
428fn 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
435fn 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
470fn 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
484fn 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
509fn 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
527fn 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
545fn 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