Skip to content

Commit

Permalink
Merge a3951bd into 1a167a9
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiburt committed Jan 2, 2022
2 parents 1a167a9 + a3951bd commit c0f072e
Show file tree
Hide file tree
Showing 4 changed files with 405 additions and 43 deletions.
11 changes: 11 additions & 0 deletions examples/builder.rs
@@ -0,0 +1,11 @@
//! The example can be run by this command
//! `cargo run --example builder`

fn main() {
let table = tabled::builder::Builder::default()
.set_header(["Name", "Issue"])
.add_row(["sadbuttrueasfuck", "Complex dynamic table creation"])
.build();

println!("{}", table);
}
223 changes: 223 additions & 0 deletions src/builder.rs
@@ -0,0 +1,223 @@
//! Builder module provides a [Builder] type which helps building
//! a [Table] dynamically.

use std::{fmt::Display, iter::FromIterator};

use papergrid::{AlignmentHorizontal, Entity, Grid, Settings};

use crate::{Style, Table};

/// Builder creates a [Table] from dynamic data set.
///
/// It usefull when the amount of columns or rows is not known statically.
///
/// ```rust
/// use tabled::builder::Builder;
/// let table = Builder::default()
/// .set_header(["index", "measure", "value"])
/// .add_row(["0", "weight", "0.443"])
/// .build();
///
/// println!("{}", table);
/// ```
///
/// It may be usefull to use [FromIterator] for building.
///
/// ```rust
/// use tabled::builder::Builder;
/// use std::iter::FromIterator;
/// let data = vec![
/// ["column1", "column2"],
/// ["data1", "data2"],
/// ["data3", "data4"],
/// ];
///
/// let table = Builder::from_iter(data).build();
///
/// println!("{}", table);
/// ```
#[derive(Debug, Default, Clone)]
pub struct Builder {
/// A header row.
headers: Option<Vec<String>>,
/// A list of rows.
rows: Vec<Vec<String>>,
/// A number of columns.
size: usize,
/// A content of cells which are created in case rows has different length.
empty_cell_text: Option<String>,
}

impl Builder {
/// Creates a [Builder] instance.
pub fn new() -> Self {
Self::default()
}

/// Sets a [Table] header.
///
/// If not set a first row will be considered a header.
///
/// ```rust
/// use tabled::builder::Builder;
/// let builder = Builder::default()
/// .set_header(0..3)
/// .add_row(["i", "surname", "lastname"]);
/// ```
pub fn set_header<H, T>(mut self, header: H) -> Self
where
H: IntoIterator<Item = T>,
T: Display,
{
let header: Vec<String> = header.into_iter().map(|t| t.to_string()).collect();
self.update_size(header.len());
self.headers = Some(header);

self
}

/// Adds a row to a [Table].
///
/// If [Self::header] is not set the row will be considered a header.
///
/// ```rust
/// use tabled::builder::Builder;
/// let builder = Builder::default()
/// .add_row(0..3)
/// .add_row(["i", "surname", "lastname"]);
/// ```
pub fn add_row<R, T>(mut self, row: R) -> Self
where
R: IntoIterator<Item = T>,
T: Display,
{
let row: Vec<String> = row.into_iter().map(|t| t.to_string()).collect();
self.update_size(row.len());
self.rows.push(row);

self
}

/// Sets a content of cells which are created in case rows has different length.
///
///
/// ```rust
/// use tabled::builder::Builder;
/// let table = Builder::default()
/// .set_default_text("undefined")
/// .set_header(0..3)
/// .add_row(["i"])
/// .build();
/// ```
pub fn set_default_text<T: Into<String>>(mut self, text: T) -> Self {
self.empty_cell_text = Some(text.into());
self
}

/// Build creates a [Table] instance.
///
/// ```rust
/// use tabled::builder::Builder;
/// let table = Builder::default()
/// .set_header(["i", "column1", "column2"])
/// .add_row(["0", "value1", "value2"])
/// .build();
/// ```
pub fn build(mut self) -> Table {
if let Some(empty_cell_text) = self.empty_cell_text {
if let Some(header) = self.headers.as_mut() {
if self.size > header.len() {
append_vec(header, self.size - header.len(), empty_cell_text.clone());
}
}

for row in self.rows.iter_mut() {
if self.size > row.len() {
append_vec(row, self.size - row.len(), empty_cell_text.clone());
}
}
}

build_table(self.headers, self.rows, self.size)
}

fn update_size(&mut self, size: usize) {
if size > self.size {
self.size = size;
}
}
}

impl<R, V> FromIterator<R> for Builder
where
R: IntoIterator<Item = V>,
V: Display,
{
fn from_iter<T: IntoIterator<Item = R>>(iter: T) -> Self {
let mut builder = Self::default();
for row in iter {
builder = builder.add_row(row);
}

builder
}
}

/// Building [Table] from ordinary data.
fn build_table(header: Option<Vec<String>>, rows: Vec<Vec<String>>, count_columns: usize) -> Table {
let grid = build_grid(header, rows, count_columns);
create_table_from_grid(grid)
}

/// Building [Grid] from ordinary data.
fn build_grid(header: Option<Vec<String>>, rows: Vec<Vec<String>>, count_columns: usize) -> Grid {
let mut count_rows = rows.len();

if header.is_some() {
count_rows += 1;
}

let mut grid = Grid::new(count_rows, count_columns);

let mut row = 0;
if let Some(headers) = header {
for (i, text) in headers.into_iter().enumerate() {
grid.set(&Entity::Cell(0, i), Settings::new().text(text));
}

row = 1;
}

for fields in rows.into_iter() {
// don't show off a empty data array
if fields.is_empty() {
continue;
}

for (column, field) in fields.into_iter().enumerate() {
grid.set(&Entity::Cell(row, column), Settings::new().text(field));
}

row += 1;
}

grid
}

fn create_table_from_grid(mut grid: Grid) -> Table {
// it's crusial to set a global setting rather than a setting for an each cell
// as it will be hard to override that since how Grid::style method works
grid.set(
&Entity::Global,
Settings::new()
.indent(1, 1, 0, 0)
.alignment(AlignmentHorizontal::Center),
);

let table = Table { grid };
table.with(Style::ASCII)
}

fn append_vec(v: &mut Vec<String>, n: usize, value: String) {
v.extend((0..n).map(|_| value.clone()));
}
49 changes: 6 additions & 43 deletions src/lib.rs
Expand Up @@ -151,8 +151,9 @@
//! [README.md](https://github.com/zhiburt/tabled/blob/master/README.md)
//!

use papergrid::{Entity, Grid, Settings};
use std::fmt;
use builder::Builder;
use papergrid::Grid;
use std::{fmt, iter::FromIterator};

mod alignment;
mod concat;
Expand All @@ -165,6 +166,7 @@ mod panel;
mod rotate;
mod width;

pub mod builder;
pub mod display;
pub mod style;

Expand Down Expand Up @@ -264,10 +266,8 @@ pub struct Table {
impl Table {
/// New creates a Table instance.
pub fn new<T: Tabled>(iter: impl IntoIterator<Item = T>) -> Self {
let grid = build_grid(iter);

let table = Self { grid };
table.with(Style::ASCII)
let rows = iter.into_iter().map(|t| t.fields());
Builder::from_iter(rows).set_header(T::headers()).build()
}

/// Returns a table shape (count rows, count columns).
Expand Down Expand Up @@ -364,43 +364,6 @@ where
}
}

/// Building [Grid] from a data.
/// You must prefer [Table] over this function.
fn build_grid<T: Tabled>(iter: impl IntoIterator<Item = T>) -> Grid {
let headers = T::headers();
let obj: Vec<Vec<String>> = iter.into_iter().map(|t| t.fields()).collect();

let mut grid = Grid::new(obj.len() + 1, headers.len());

// it's crusial to set a global setting rather than a setting for an each cell
// as it will be hard to override that since how Grid::style method works
grid.set(
&Entity::Global,
Settings::new()
.indent(1, 1, 0, 0)
.alignment(AlignmentHorizontal::Center),
);

for (i, h) in headers.iter().enumerate() {
grid.set(&Entity::Cell(0, i), Settings::new().text(h));
}

let mut row = 1;
for fields in &obj {
for (column, field) in fields.iter().enumerate() {
grid.set(&Entity::Cell(row, column), Settings::new().text(field));
}

// don't show off a empty data array
// currently it's possible when `#[header(hidden)]` attribute used for a enum
if !fields.is_empty() {
row += 1;
}
}

grid
}

macro_rules! tuple_table {
( $($name:ident)+ ) => {
impl<$($name: Tabled),+> Tabled for ($($name,)+){
Expand Down

0 comments on commit c0f072e

Please sign in to comment.