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