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