blob: 23b0dcda13de9de20c215fef64f03f577adbc6cb [file] [log] [blame]
//! This module contains a [`Concat`] primitive which can be in order to combine 2 [`Table`]s into 1.
//!
//! # Example
//!
//! ```
//! use tabled::{Table, settings::Concat};
//! let table1 = Table::new([0, 1, 2, 3]);
//! let table2 = Table::new(["A", "B", "C", "D"]);
//!
//! let mut table3 = table1;
//! table3.with(Concat::horizontal(table2));
//! ```
use std::borrow::Cow;
use crate::{
grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut, Resizable},
settings::TableOption,
Table,
};
/// [`Concat`] concatenates tables along a particular axis [Horizontal | Vertical].
/// It doesn't do any key or column comparisons like SQL's join does.
///
/// When the tables has different sizes, empty cells will be created by default.
///
/// [`Concat`] in horizontal mode has similar behaviour to tuples `(a, b)`.
/// But it behaves on tables rather than on an actual data.
///
/// # Example
///
///
#[cfg_attr(feature = "derive", doc = "```")]
#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
/// use tabled::{Table, Tabled, settings::{Style, Concat}};
///
/// #[derive(Tabled)]
/// struct Message {
/// id: &'static str,
/// text: &'static str,
/// }
///
/// #[derive(Tabled)]
/// struct Department(#[tabled(rename = "department")] &'static str);
///
/// let messages = [
/// Message { id: "0", text: "Hello World" },
/// Message { id: "1", text: "Do do do something", },
/// ];
///
/// let departments = [
/// Department("Admins"),
/// Department("DevOps"),
/// Department("R&D"),
/// ];
///
/// let mut table = Table::new(messages);
/// table
/// .with(Concat::horizontal(Table::new(departments)))
/// .with(Style::extended());
///
/// assert_eq!(
/// table.to_string(),
/// concat!(
/// "╔════╦════════════════════╦════════════╗\n",
/// "║ id ║ text ║ department ║\n",
/// "╠════╬════════════════════╬════════════╣\n",
/// "║ 0 ║ Hello World ║ Admins ║\n",
/// "╠════╬════════════════════╬════════════╣\n",
/// "║ 1 ║ Do do do something ║ DevOps ║\n",
/// "╠════╬════════════════════╬════════════╣\n",
/// "║ ║ ║ R&D ║\n",
/// "╚════╩════════════════════╩════════════╝",
/// )
/// )
/// ```
#[derive(Debug)]
pub struct Concat {
table: Table,
mode: ConcatMode,
default_cell: Cow<'static, str>,
}
#[derive(Debug)]
enum ConcatMode {
Vertical,
Horizontal,
}
impl Concat {
fn new(table: Table, mode: ConcatMode) -> Self {
Self {
table,
mode,
default_cell: Cow::Borrowed(""),
}
}
/// Concatenate 2 tables horizontally (along axis=0)
pub fn vertical(table: Table) -> Self {
Self::new(table, ConcatMode::Vertical)
}
/// Concatenate 2 tables vertically (along axis=1)
pub fn horizontal(table: Table) -> Self {
Self::new(table, ConcatMode::Horizontal)
}
/// Sets a cell's content for cases where 2 tables has different sizes.
pub fn default_cell(mut self, cell: impl Into<Cow<'static, str>>) -> Self {
self.default_cell = cell.into();
self
}
}
impl<R, D, C> TableOption<R, D, C> for Concat
where
R: Records + ExactRecords + Resizable + PeekableRecords + RecordsMut<String>,
{
fn change(mut self, records: &mut R, _: &mut C, _: &mut D) {
let count_rows = records.count_rows();
let count_cols = records.count_columns();
let rhs = &mut self.table;
match self.mode {
ConcatMode::Horizontal => {
for _ in 0..rhs.count_columns() {
records.push_column();
}
for row in count_rows..rhs.count_rows() {
records.push_row();
for col in 0..records.count_columns() {
records.set((row, col), self.default_cell.to_string());
}
}
for row in 0..rhs.shape().0 {
for col in 0..rhs.shape().1 {
let text = rhs.get_records().get_text((row, col)).to_string();
let col = col + count_cols;
records.set((row, col), text);
}
}
}
ConcatMode::Vertical => {
for _ in 0..rhs.count_rows() {
records.push_row();
}
for col in count_cols..rhs.shape().1 {
records.push_column();
for row in 0..records.count_rows() {
records.set((row, col), self.default_cell.to_string());
}
}
for row in 0..rhs.shape().0 {
for col in 0..rhs.shape().1 {
let text = rhs.get_records().get_text((row, col)).to_string();
let row = row + count_rows;
records.set((row, col), text);
}
}
}
}
}
}