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}