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}