tabled/settings/concat/
mod.rs

1//! This module contains a [`Concat`] primitive which can be in order to combine 2 [`Table`]s into 1.
2//!
3//! # Example
4//!
5//! ```
6//! use tabled::{Table, settings::Concat};
7//! let table1 = Table::new([0, 1, 2, 3]);
8//! let table2 = Table::new(["A", "B", "C", "D"]);
9//!
10//! let mut table3 = table1;
11//! table3.with(Concat::horizontal(table2));
12//! ```
13
14use std::borrow::Cow;
15
16use crate::{
17    grid::config::Position,
18    grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut, Resizable},
19    settings::TableOption,
20    Table,
21};
22
23/// [`Concat`] concatenates tables along a particular axis [Horizontal | Vertical].
24/// It doesn't do any key or column comparisons like SQL's join does.
25///
26/// When the tables has different sizes, empty cells will be created by default.
27///
28/// [`Concat`] in horizontal mode has similar behaviour to tuples `(a, b)`.
29/// But it behaves on tables rather than on an actual data.
30///
31/// [`Concat`] DOES NOT handle style merge and other configuration of 2nd table,
32/// it just uses 1st one as a bases.
33///
34/// # Example
35///
36///
37#[cfg_attr(feature = "derive", doc = "```")]
38#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
39/// use tabled::{Table, Tabled, settings::{Style, Concat}};
40///
41/// #[derive(Tabled)]
42/// struct Message {
43///     id: &'static str,
44///     text: &'static str,
45/// }
46///
47/// #[derive(Tabled)]
48/// struct Department(#[tabled(rename = "department")] &'static str);
49///
50/// let messages = [
51///     Message { id: "0", text: "Hello World" },
52///     Message { id: "1", text: "Do do do something", },
53/// ];
54///
55/// let departments = [
56///     Department("Admins"),
57///     Department("DevOps"),
58///     Department("R&D"),
59/// ];
60///
61/// let mut table = Table::new(messages);
62/// table
63///     .with(Concat::horizontal(Table::new(departments)))
64///     .with(Style::extended());
65///
66/// assert_eq!(
67///     table.to_string(),
68///     concat!(
69///         "╔════╦════════════════════╦════════════╗\n",
70///         "║ id ║ text               ║ department ║\n",
71///         "╠════╬════════════════════╬════════════╣\n",
72///         "║ 0  ║ Hello World        ║ Admins     ║\n",
73///         "╠════╬════════════════════╬════════════╣\n",
74///         "║ 1  ║ Do do do something ║ DevOps     ║\n",
75///         "╠════╬════════════════════╬════════════╣\n",
76///         "║    ║                    ║ R&D        ║\n",
77///         "╚════╩════════════════════╩════════════╝",
78///     )
79/// )
80/// ```
81
82#[derive(Debug)]
83pub struct Concat {
84    table: Table,
85    mode: ConcatMode,
86    default_cell: Cow<'static, str>,
87}
88
89#[derive(Debug)]
90enum ConcatMode {
91    Vertical,
92    Horizontal,
93}
94
95impl Concat {
96    fn new(table: Table, mode: ConcatMode) -> Self {
97        Self {
98            table,
99            mode,
100            default_cell: Cow::Borrowed(""),
101        }
102    }
103
104    /// Concatenate 2 tables horizontally (along axis=0)
105    pub fn vertical(table: Table) -> Self {
106        Self::new(table, ConcatMode::Vertical)
107    }
108
109    /// Concatenate 2 tables vertically (along axis=1)
110    pub fn horizontal(table: Table) -> Self {
111        Self::new(table, ConcatMode::Horizontal)
112    }
113
114    /// Sets a cell's content for cases where 2 tables has different sizes.
115    pub fn default_cell(mut self, cell: impl Into<Cow<'static, str>>) -> Self {
116        self.default_cell = cell.into();
117        self
118    }
119}
120
121impl<R, D, C> TableOption<R, C, D> for Concat
122where
123    R: Records + ExactRecords + Resizable + PeekableRecords + RecordsMut<String>,
124{
125    fn change(mut self, records: &mut R, _: &mut C, _: &mut D) {
126        let count_rows = records.count_rows();
127        let count_cols = records.count_columns();
128
129        let rhs = &mut self.table;
130        match self.mode {
131            ConcatMode::Horizontal => {
132                for _ in 0..rhs.count_columns() {
133                    records.push_column();
134                }
135
136                for row in count_rows..rhs.count_rows() {
137                    records.push_row();
138
139                    for col in 0..records.count_columns() {
140                        let pos = Position::new(row, col);
141                        records.set(pos, self.default_cell.to_string());
142                    }
143                }
144
145                for row in 0..rhs.shape().0 {
146                    for col in 0..rhs.shape().1 {
147                        let pos = Position::new(row, col);
148                        let text = rhs.get_records().get_text(pos).to_string();
149                        let pos = pos + (0, count_cols);
150                        records.set(pos, text);
151                    }
152                }
153            }
154            ConcatMode::Vertical => {
155                for _ in 0..rhs.count_rows() {
156                    records.push_row();
157                }
158
159                for col in count_cols..rhs.shape().1 {
160                    records.push_column();
161
162                    for row in 0..records.count_rows() {
163                        let pos = Position::new(row, col);
164                        records.set(pos, self.default_cell.to_string());
165                    }
166                }
167
168                for row in 0..rhs.shape().0 {
169                    for col in 0..rhs.shape().1 {
170                        let pos = Position::new(row, col);
171                        let text = rhs.get_records().get_text(pos).to_string();
172                        let pos = pos + (count_rows, 0);
173                        records.set(pos, text);
174                    }
175                }
176            }
177        }
178    }
179}