tabled/
table.rs

1//! This module contains a main table representation of this crate [`Table`].
2
3use std::{borrow::Cow, fmt, iter::FromIterator};
4
5use papergrid::{
6    height::HeightEstimator,
7    records::{
8        cell_info::CellInfo,
9        vec_records::{CellMut, VecRecords},
10        Records, RecordsMut,
11    },
12    width::{CfgWidthFunction, WidthEstimator},
13    Estimate, Grid, GridConfig,
14};
15
16use crate::{
17    builder::Builder, height::get_table_total_height, object::Entity, width::get_table_total_width,
18    Tabled,
19};
20
21/// A trait which is responsilbe for configuration of a [`Table`].
22pub trait TableOption<R> {
23    /// The function modifies a [`Grid`] object.
24    fn change(&mut self, table: &mut Table<R>);
25}
26
27impl<T, R> TableOption<R> for &mut T
28where
29    T: TableOption<R> + ?Sized,
30{
31    fn change(&mut self, table: &mut Table<R>) {
32        T::change(self, table);
33    }
34}
35
36/// A trait for configuring a single cell.
37/// Where cell represented by 'row' and 'column' indexes.
38///
39/// A cell can be targeted by [`Cell`].
40///
41/// [`Cell`]: crate::object::Cell
42pub trait CellOption<R> {
43    /// Modification function of a single cell.
44    fn change_cell(&mut self, table: &mut Table<R>, entity: Entity);
45}
46
47/// The structure provides an interface for building a table for types that implements [`Tabled`].
48///
49/// To build a string representation of a table you must use a [`std::fmt::Display`].
50/// Or simply call `.to_string()` method.
51///
52/// The default table [`Style`] is [`Style::ascii`],
53/// with a 1 left and right [`Padding`].
54///
55/// ## Example
56///
57/// ### Basic usage
58///
59/// ```rust,no_run
60/// use tabled::Table;
61///
62/// let table = Table::new(&["Year", "2021"]);
63/// ```
64///
65/// ### With settings
66///
67/// ```rust,no_run
68/// use tabled::{Table, Style, Alignment};
69///
70/// let data = vec!["Hello", "2021"];
71/// let mut table = Table::new(&data);
72/// table.with(Style::psql()).with(Alignment::left());
73///
74/// println!("{}", table);
75/// ```
76///
77/// [`Padding`]: crate::Padding
78/// [`Style`]: crate::Style
79/// [`Style::ascii`]: crate::Style::ascii
80#[derive(Debug, Clone)]
81pub struct Table<R = VecRecords<CellInfo<'static>>> {
82    records: R,
83    cfg: GridConfig,
84    has_header: bool,
85    widths: Option<Vec<usize>>,
86    heights: Option<Vec<usize>>,
87}
88
89impl Table<VecRecords<CellInfo<'static>>> {
90    /// New creates a Table instance.
91    ///
92    /// If you use a reference iterator you'd better use [`FromIterator`] instead.
93    /// As it has a different lifetime constraints and make less copies therefore.
94    pub fn new<I, T>(iter: I) -> Self
95    where
96        I: IntoIterator<Item = T>,
97        T: Tabled,
98    {
99        let ctrl = CfgWidthFunction::new(4);
100
101        let mut header = vec![CellInfo::default(); T::LENGTH];
102        for (text, cell) in T::headers().into_iter().zip(header.iter_mut()) {
103            CellMut::set(cell, text, &ctrl);
104        }
105
106        let mut records = vec![header];
107        for row in iter.into_iter() {
108            let mut list = vec![CellInfo::default(); T::LENGTH];
109            for (text, cell) in row.fields().into_iter().zip(list.iter_mut()) {
110                CellMut::set(cell, text.into_owned(), &ctrl);
111            }
112
113            records.push(list);
114        }
115
116        let mut b = Builder::custom(VecRecords::from(records));
117        b.with_header();
118        b.build()
119    }
120}
121
122impl Table<()> {
123    /// Creates a builder from a data set given.
124    ///
125    /// # Example
126    ///
127    ///
128    #[cfg_attr(feature = "derive", doc = "```")]
129    #[cfg_attr(not(feature = "derive"), doc = "```ignore")]
130    /// use tabled::{Table, Tabled, object::Segment, ModifyObject, Alignment};
131    ///
132    /// #[derive(Tabled)]
133    /// struct User {
134    ///     name: &'static str,
135    ///     #[tabled(inline("device::"))]
136    ///     device: Device,
137    /// }
138    ///
139    /// #[derive(Tabled)]
140    /// enum Device {
141    ///     PC,
142    ///     Mobile
143    /// }
144    ///
145    /// let data = vec![
146    ///     User { name: "Vlad", device: Device::Mobile },
147    ///     User { name: "Dimitry", device: Device::PC },
148    ///     User { name: "John", device: Device::PC },
149    /// ];
150    ///
151    /// let mut builder = Table::builder(data).index();
152    /// builder.set_index(0);
153    /// builder.transpose();
154    ///
155    /// let table = builder.build()
156    ///     .with(Segment::new(1.., 1..).modify().with(Alignment::center()))
157    ///     .to_string();
158    ///
159    /// assert_eq!(
160    ///     table,
161    ///     "+----------------+------+---------+------+\n\
162    ///      | name           | Vlad | Dimitry | John |\n\
163    ///      +----------------+------+---------+------+\n\
164    ///      | device::PC     |      |    +    |  +   |\n\
165    ///      +----------------+------+---------+------+\n\
166    ///      | device::Mobile |  +   |         |      |\n\
167    ///      +----------------+------+---------+------+"
168    /// )
169    /// ```
170    pub fn builder<I, T>(iter: I) -> Builder<'static>
171    where
172        T: Tabled,
173        I: IntoIterator<Item = T>,
174    {
175        let ctrl = CfgWidthFunction::new(4);
176        let mut records = Vec::new();
177        for row in iter {
178            let mut list = vec![CellInfo::default(); T::LENGTH];
179            for (text, cell) in row.fields().into_iter().zip(list.iter_mut()) {
180                CellMut::set(cell, text.into_owned(), &ctrl);
181            }
182
183            records.push(list);
184        }
185
186        let mut b = Builder::from(records);
187        b.hint_column_size(T::LENGTH);
188        b.set_columns(T::headers());
189
190        b
191    }
192}
193
194impl<R> Table<R> {
195    /// Get a reference to the table's cfg.
196    pub fn get_config(&self) -> &GridConfig {
197        &self.cfg
198    }
199
200    /// Get a reference to the table's cfg.
201    pub fn get_config_mut(&mut self) -> &mut GridConfig {
202        &mut self.cfg
203    }
204
205    /// Get a reference to the table's records.
206    pub fn get_records(&self) -> &R {
207        &self.records
208    }
209
210    /// Get a reference to the table's records.
211    pub fn get_records_mut(&mut self) -> &mut R {
212        &mut self.records
213    }
214
215    /// With is a generic function which applies options to the [`Table`].
216    ///
217    /// It applies settings immediately.
218    pub fn with<O>(&mut self, mut option: O) -> &mut Self
219    where
220        O: TableOption<R>,
221    {
222        option.change(self);
223        self
224    }
225
226    /// A verification that first row is actually a header.
227    ///
228    /// It's `true` when [`Table::new`] and [`Table::builder`] is used.
229    /// In many other cases it's `false`.
230    pub fn has_header(&self) -> bool {
231        self.has_header
232    }
233
234    pub(crate) fn cache_width(&mut self, widths: Vec<usize>) {
235        self.widths = Some(widths);
236    }
237
238    pub(crate) fn destroy_width_cache(&mut self) {
239        self.widths = None;
240    }
241
242    pub(crate) fn cache_height(&mut self, widths: Vec<usize>) {
243        self.heights = Some(widths);
244    }
245
246    pub(crate) fn destroy_height_cache(&mut self) {
247        self.heights = None;
248    }
249
250    pub(crate) fn set_header_flag(&mut self, has_header: bool) {
251        self.has_header = has_header;
252    }
253}
254
255impl<R> Table<R>
256where
257    R: Records,
258{
259    /// Returns a table shape (count rows, count columns).
260    pub fn shape(&self) -> (usize, usize) {
261        let records = self.get_records();
262        (records.count_rows(), records.count_columns())
263    }
264
265    /// Returns an amount of rows in the table.
266    pub fn count_rows(&self) -> usize {
267        self.get_records().count_rows()
268    }
269
270    /// Returns an amount of columns in the table.
271    pub fn count_columns(&self) -> usize {
272        self.get_records().count_columns()
273    }
274
275    /// Returns a table shape (count rows, count columns).
276    pub fn is_empty(&self) -> bool {
277        let (count_rows, count_cols) = self.shape();
278        count_rows == 0 || count_cols == 0
279    }
280
281    /// Returns total widths of a table, including margin and vertical lines.
282    pub fn total_width(&self) -> usize {
283        let ctrl = self.get_width_ctrl();
284        get_table_total_width(&self.records, &self.cfg, &ctrl)
285    }
286
287    /// Returns total widths of a table, including margin and horizontal lines.
288    pub fn total_height(&self) -> usize {
289        let ctrl = self.get_height_ctrl();
290        get_table_total_height(&self.records, &self.cfg, &ctrl)
291    }
292
293    fn get_width_ctrl(&self) -> CachedEstimator<'_, WidthEstimator> {
294        match &self.widths {
295            Some(widths) => CachedEstimator::Cached(widths),
296            None => {
297                let mut w = WidthEstimator::default();
298                w.estimate(&self.records, &self.cfg);
299                CachedEstimator::Ctrl(w)
300            }
301        }
302    }
303
304    fn get_height_ctrl(&self) -> CachedEstimator<'_, HeightEstimator> {
305        match &self.heights {
306            Some(heights) => CachedEstimator::Cached(heights),
307            None => {
308                let mut w = HeightEstimator::default();
309                w.estimate(&self.records, &self.cfg);
310                CachedEstimator::Ctrl(w)
311            }
312        }
313    }
314}
315
316impl<R> Table<R>
317where
318    R: Records + RecordsMut<String>,
319{
320    pub(crate) fn update_records(&mut self) {
321        let ctrl = CfgWidthFunction::from_cfg(self.get_config());
322
323        for row in 0..self.get_records().count_rows() {
324            for col in 0..self.get_records().count_columns() {
325                let records = self.get_records_mut();
326                records.update((row, col), &ctrl);
327            }
328        }
329    }
330}
331
332impl<R> fmt::Display for Table<R>
333where
334    R: Records,
335{
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        let mut cfg = Cow::Borrowed(&self.cfg);
338        set_align_table(f, &mut cfg);
339        set_width_table(f, &mut cfg, self);
340
341        let width = self.get_width_ctrl();
342        let height = self.get_height_ctrl();
343
344        let grid = Grid::new(&self.records, &cfg, &width, &height);
345
346        write!(f, "{}", grid)
347    }
348}
349
350impl<R> From<R> for Table<R>
351where
352    R: Records,
353{
354    fn from(records: R) -> Self {
355        Self {
356            records,
357            cfg: GridConfig::default(),
358            has_header: false,
359            widths: None,
360            heights: None,
361        }
362    }
363}
364
365impl<'a, T> FromIterator<&'a T> for Table<VecRecords<CellInfo<'a>>>
366where
367    T: Tabled + 'a,
368{
369    fn from_iter<I>(iter: I) -> Self
370    where
371        I: IntoIterator<Item = &'a T>,
372    {
373        let ctrl = CfgWidthFunction::new(4);
374
375        let mut header = vec![CellInfo::default(); T::LENGTH];
376        for (text, cell) in T::headers().into_iter().zip(header.iter_mut()) {
377            CellMut::set(cell, text, &ctrl);
378        }
379
380        let mut records = vec![header];
381        for row in iter.into_iter() {
382            let mut list = vec![CellInfo::default(); T::LENGTH];
383            for (text, cell) in row.fields().into_iter().zip(list.iter_mut()) {
384                CellMut::set(cell, text, &ctrl);
385            }
386
387            records.push(list);
388        }
389
390        Builder::custom(VecRecords::from(records)).build()
391    }
392}
393
394#[derive(Debug)]
395enum CachedEstimator<'a, E> {
396    Cached(&'a [usize]),
397    Ctrl(E),
398}
399
400impl<R, E> Estimate<R> for CachedEstimator<'_, E>
401where
402    R: Records,
403    E: Estimate<R>,
404{
405    fn estimate(&mut self, _: R, _: &GridConfig) {}
406
407    fn get(&self, i: usize) -> Option<usize> {
408        match self {
409            Self::Cached(list) => list.get(i).copied(),
410            Self::Ctrl(e) => Estimate::<R>::get(e, i),
411        }
412    }
413
414    fn total(&self) -> usize {
415        match self {
416            Self::Cached(list) => list.iter().sum(),
417            Self::Ctrl(e) => Estimate::<R>::total(e),
418        }
419    }
420}
421
422fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut Cow<'_, GridConfig>) {
423    if let Some(alignment) = f.align() {
424        let alignment = convert_fmt_alignment(alignment);
425
426        match cfg {
427            Cow::Borrowed(c) => {
428                let mut new = c.clone();
429                new.set_alignment_horizontal(Entity::Global, alignment);
430                *cfg = Cow::Owned(new);
431            }
432            Cow::Owned(cfg) => {
433                cfg.set_alignment_horizontal(Entity::Global, alignment);
434            }
435        }
436    }
437}
438
439fn set_width_table<R>(f: &fmt::Formatter<'_>, cfg: &mut Cow<'_, GridConfig>, table: &Table<R>)
440where
441    R: Records,
442{
443    if let Some(width) = f.width() {
444        let total_width = table.total_width();
445        if total_width >= width {
446            return;
447        }
448
449        let mut fill = f.fill();
450        if fill == char::default() {
451            fill = ' ';
452        }
453
454        let available = width - total_width;
455        let alignment = f.align().unwrap_or(fmt::Alignment::Left);
456        let (left, right) = table_padding(alignment, available);
457
458        let mut margin = *cfg.get_margin();
459        margin.left.size += left;
460        margin.right.size += right;
461
462        if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default()
463        {
464            margin.left.fill = fill;
465        }
466
467        if (margin.right.size > 0 && margin.right.fill == char::default())
468            || fill != char::default()
469        {
470            margin.right.fill = fill;
471        }
472
473        match cfg {
474            Cow::Borrowed(c) => {
475                let mut new = c.clone();
476                new.set_margin(margin);
477                *cfg = Cow::Owned(new);
478            }
479            Cow::Owned(cfg) => cfg.set_margin(margin),
480        }
481    }
482}
483
484fn convert_fmt_alignment(alignment: fmt::Alignment) -> papergrid::AlignmentHorizontal {
485    match alignment {
486        fmt::Alignment::Left => papergrid::AlignmentHorizontal::Left,
487        fmt::Alignment::Right => papergrid::AlignmentHorizontal::Right,
488        fmt::Alignment::Center => papergrid::AlignmentHorizontal::Center,
489    }
490}
491
492fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) {
493    match alignment {
494        fmt::Alignment::Left => (available, 0),
495        fmt::Alignment::Right => (0, available),
496        fmt::Alignment::Center => {
497            let left = available / 2;
498            let right = available - left;
499            (left, right)
500        }
501    }
502}