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::{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 records = Vec::new();
160 for row in iter {
161 let mut list = Vec::with_capacity(T::LENGTH);
162 for text in row.fields().into_iter() {
163 list.push(text.into_owned());
164 }
165
166 records.push(list);
167 }
168
169 let mut b = Builder::from(records);
170 let _ = b.set_header(T::headers()).hint_column_size(T::LENGTH);
171
172 b
173 }
174
175 /// With is a generic function which applies options to the [`Table`].
176 ///
177 /// It applies settings immediately.
178 pub fn with<O>(&mut self, option: O) -> &mut Self
179 where
180 O: TableOption<
181 VecRecords<CellInfo<String>>,
182 CompleteDimensionVecRecords<'static>,
183 ColoredConfig,
184 >,
185 {
186 self.dimension.clear_width();
187 self.dimension.clear_height();
188
189 option.change(&mut self.records, &mut self.config, &mut self.dimension);
190
191 self
192 }
193
194 /// Returns a table shape (count rows, count columns).
195 pub fn shape(&self) -> (usize, usize) {
196 (self.count_rows(), self.count_columns())
197 }
198
199 /// Returns an amount of rows in the table.
200 pub fn count_rows(&self) -> usize {
201 self.records.count_rows()
202 }
203
204 /// Returns an amount of columns in the table.
205 pub fn count_columns(&self) -> usize {
206 self.records.count_columns()
207 }
208
209 /// Returns a table shape (count rows, count columns).
210 pub fn is_empty(&self) -> bool {
211 let (count_rows, count_cols) = self.shape();
212 count_rows == 0 || count_cols == 0
213 }
214
215 /// Returns total widths of a table, including margin and horizontal lines.
216 pub fn total_height(&self) -> usize {
217 let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension);
218 dims.estimate(&self.records, self.config.as_ref());
219
220 let total = (0..self.count_rows())
221 .map(|row| dims.get_height(row))
222 .sum::<usize>();
223 let counth = self.config.count_horizontal(self.count_rows());
224
225 let margin = self.config.get_margin();
226
227 total + counth + margin.top.size + margin.bottom.size
228 }
229
230 /// Returns total widths of a table, including margin and vertical lines.
231 pub fn total_width(&self) -> usize {
232 let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension);
233 dims.estimate(&self.records, self.config.as_ref());
234
235 let total = (0..self.count_columns())
236 .map(|col| dims.get_width(col))
237 .sum::<usize>();
238 let countv = self.config.count_vertical(self.count_columns());
239
240 let margin = self.config.get_margin();
241
242 total + countv + margin.left.size + margin.right.size
243 }
244
245 /// Returns a table config.
246 pub fn get_config(&self) -> &ColoredConfig {
247 &self.config
248 }
249
250 /// Returns a table config.
251 pub fn get_config_mut(&mut self) -> &mut ColoredConfig {
252 &mut self.config
253 }
254
255 /// Returns a used records.
256 pub fn get_records(&self) -> &VecRecords<CellInfo<String>> {
257 &self.records
258 }
259
260 /// Returns a used records.
261 pub fn get_records_mut(&mut self) -> &mut VecRecords<CellInfo<String>> {
262 &mut self.records
263 }
264}
265
266impl Default for Table {
267 fn default() -> Self {
268 Self {
269 records: VecRecords::default(),
270 config: ColoredConfig::new(config:configure_grid()),
271 dimension: CompleteDimensionVecRecords::default(),
272 }
273 }
274}
275
276impl fmt::Display for Table {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 if self.is_empty() {
279 return Ok(());
280 }
281
282 let config: Cow<'_, SpannedConfig> = use_format_configuration(f, self);
283 let colors: &ColorMap = self.config.get_colors();
284
285 if !self.dimension.is_empty() {
286 let mut dims: CompleteDimensionVecRecords<'_> = self.dimension.clone();
287 dims.estimate(&self.records, config.as_ref());
288
289 print_grid(f, &self.records, &config, &dims, colors)
290 } else {
291 let mut dims: PeekableDimension = PeekableDimension::default();
292 dims.estimate(&self.records, &config);
293
294 print_grid(f, &self.records, &config, &dims, colors)
295 }
296 }
297}
298
299impl<T, V> FromIterator<T> for Table
300where
301 T: IntoIterator<Item = V>,
302 V: Into<String>,
303{
304 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
305 Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build()
306 }
307}
308
309impl From<Builder> for Table {
310 fn from(builder: Builder) -> Self {
311 let data: Vec<Vec<CellInfo<String>>> = builder.into();
312 let records: VecRecords> = VecRecords::new(data);
313
314 Self {
315 records,
316 config: ColoredConfig::new(config:configure_grid()),
317 dimension: CompleteDimensionVecRecords::default(),
318 }
319 }
320}
321
322impl From<Table> for Builder {
323 fn from(val: Table) -> Self {
324 let count_columns: usize = val.count_columns();
325 let data: Vec<Vec<CellInfo<String>>> = val.records.into();
326 let mut builder: Builder = Builder::from(data);
327 let _ = builder.hint_column_size(count_columns);
328 builder
329 }
330}
331
332impl<R, D> TableOption<R, D, ColoredConfig> for CompactConfig {
333 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
334 *cfg.deref_mut() = self.into();
335 }
336}
337
338impl<R, D> TableOption<R, D, ColoredConfig> for ColoredConfig {
339 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
340 *cfg = self;
341 }
342}
343
344impl<R, D> TableOption<R, D, ColoredConfig> for SpannedConfig {
345 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
346 *cfg.deref_mut() = self;
347 }
348}
349
350impl<R, D> TableOption<R, D, ColoredConfig> for &SpannedConfig {
351 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
352 *cfg.deref_mut() = self.clone();
353 }
354}
355
356fn convert_fmt_alignment(alignment: fmt::Alignment) -> AlignmentHorizontal {
357 match alignment {
358 fmt::Alignment::Left => AlignmentHorizontal::Left,
359 fmt::Alignment::Right => AlignmentHorizontal::Right,
360 fmt::Alignment::Center => AlignmentHorizontal::Center,
361 }
362}
363
364fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) {
365 match alignment {
366 fmt::Alignment::Left => (available, 0),
367 fmt::Alignment::Right => (0, available),
368 fmt::Alignment::Center => {
369 let left: usize = available / 2;
370 let right: usize = available - left;
371 (left, right)
372 }
373 }
374}
375
376fn configure_grid() -> SpannedConfig {
377 let mut cfg: SpannedConfig = SpannedConfig::default();
378 cfg.set_padding(
379 Entity::Global,
380 padding:Sides::new(
381 left:Indent::spaced(1),
382 right:Indent::spaced(1),
383 top:Indent::default(),
384 bottom:Indent::default(),
385 ),
386 );
387 cfg.set_alignment_horizontal(Entity::Global, alignment:AlignmentHorizontal::Left);
388 cfg.set_formatting(Entity::Global, Formatting::new(horizontal_trim:false, vertical_trim:false, allow_lines_alignment:false));
389 cfg.set_borders(*Style::ascii().get_borders());
390
391 cfg
392}
393
394fn use_format_configuration<'a>(
395 f: &mut fmt::Formatter<'_>,
396 table: &'a Table,
397) -> Cow<'a, SpannedConfig> {
398 if f.align().is_some() || f.width().is_some() {
399 let mut cfg: SpannedConfig = table.config.as_ref().clone();
400
401 set_align_table(f, &mut cfg);
402 set_width_table(f, &mut cfg, table);
403
404 Cow::Owned(cfg)
405 } else {
406 Cow::Borrowed(table.config.as_ref())
407 }
408}
409
410fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig) {
411 if let Some(alignment: Alignment) = f.align() {
412 let alignment: AlignmentHorizontal = convert_fmt_alignment(alignment);
413 cfg.set_alignment_horizontal(Entity::Global, alignment);
414 }
415}
416
417fn set_width_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig, table: &Table) {
418 if let Some(width) = f.width() {
419 let total_width = table.total_width();
420 if total_width >= width {
421 return;
422 }
423
424 let mut fill = f.fill();
425 if fill == char::default() {
426 fill = ' ';
427 }
428
429 let available = width - total_width;
430 let alignment = f.align().unwrap_or(fmt::Alignment::Left);
431 let (left, right) = table_padding(alignment, available);
432
433 let mut margin = cfg.get_margin();
434 margin.left.size += left;
435 margin.right.size += right;
436
437 if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default()
438 {
439 margin.left.fill = fill;
440 }
441
442 if (margin.right.size > 0 && margin.right.fill == char::default())
443 || fill != char::default()
444 {
445 margin.right.fill = fill;
446 }
447
448 cfg.set_margin(margin);
449 }
450}
451
452fn print_grid<F: fmt::Write, D: Dimension>(
453 f: &mut F,
454 records: &VecRecords<CellInfo<String>>,
455 cfg: &SpannedConfig,
456 dims: D,
457 colors: &ColorMap,
458) -> fmt::Result {
459 if !colors.is_empty() {
460 PeekableGrid::new(records, config:cfg, &dims, colors).build(f)
461 } else {
462 PeekableGrid::new(records, config:cfg, &dims, colors:NoColors).build(f)
463 }
464}
465