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