Skip to content

Commit

Permalink
Add display trait impl for table struct (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
devashishdxt authored Apr 13, 2022
1 parent 9423899 commit 0b2b3f1
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 287 deletions.
149 changes: 2 additions & 147 deletions cli-table-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,154 +2,9 @@
#![cfg_attr(not(any(docsrs, feature = "doc")), forbid(unstable_features))]
#![deny(missing_docs)]
#![cfg_attr(any(docsrs, feature = "doc"), feature(doc_cfg))]
//! Rust crate for printing tables on command line.
//! Derive macros for `cli-table` crate.
//!
//! # Usage
//!
//! Add `cli-table` in your `Cargo.toms`'s `dependencies` section
//!
//! ```toml
//! [dependencies]
//! cli-table = "0.4"
//! ```
//!
//! ## Simple usage
//!
//! ```rust,ignore
//! use cli_table::{format::Justify, print_stdout, Cell, Style, Table};
//!
//! let table = vec![
//! vec!["Tom".cell(), 10.cell().justify(Justify::Right)],
//! vec!["Jerry".cell(), 15.cell().justify(Justify::Right)],
//! vec!["Scooby Doo".cell(), 20.cell().justify(Justify::Right)],
//! ]
//! .table()
//! .title(vec![
//! "Name".cell().bold(true),
//! "Age (in years)".cell().bold(true),
//! ])
//! .bold(true);
//!
//! assert!(print_stdout(table).is_ok());
//! ```
//!
//! Below is the output of the table we created just now:
//!
//! ```markdown
//! +------------+----------------+
//! | Name | Age (in years) | <-- This row and all the borders/separators
//! +------------+----------------+ will appear in bold
//! | Tom | 10 |
//! +------------+----------------+
//! | Jerry | 15 |
//! +------------+----------------+
//! | Scooby Doo | 25 |
//! +------------+----------------+
//! ```
//!
//! ## Derive macro
//!
//! `#[derive(Table)]` can also be used to print a `Vec` or slice of `struct`s as table.
//!
//! ```rust,ignore
//! use cli_table::{format::Justify, print_stdout, Table, WithTitle};
//!
//! #[derive(Table)]
//! struct User {
//! #[table(title = "ID", justify = "Justify::Right")]
//! id: u64,
//! #[table(title = "First Name")]
//! first_name: &'static str,
//! #[table(title = "Last Name")]
//! last_name: &'static str,
//! }
//!
//! let users = vec![
//! User {
//! id: 1,
//! first_name: "Scooby",
//! last_name: "Doo",
//! },
//! User {
//! id: 2,
//! first_name: "John",
//! last_name: "Cena",
//! },
//! ];
//!
//! assert!(print_stdout(users.with_title()).is_ok());
//! ```
//!
//! Below is the output of the table we created using derive macro:
//!
//! ```markdown
//! +----+------------+-----------+
//! | ID | First Name | Last Name | <-- This row will appear in bold
//! +----+------------+-----------+
//! | 1 | Scooby | Doo |
//! +----+------------+-----------+
//! | 2 | John | Cena |
//! +----+------------+-----------+
//! ```
//!
//! ### Field attributes
//!
//! - `title` | `name`: Used to specify title of a column. Usage: `#[table(title = "Title")]`
//! - `justify`: Used to horizontally justify the contents of a column. Usage: `#[table(justify = "Justify::Right")]`
//! - `align`: Used to vertically align the contents of a column. Usage: `#[table(align = "Align::Top")]`
//! - `color`: Used to specify color of contents of a column. Usage: `#[table(color = "Color::Red")]`
//! - `bold`: Used to specify boldness of contents of a column. Usage: `#[table(bold)]`
//! - `order`: Used to order columns in a table while printing. Usage: `#[table(order = <usize>)]`. Here, columns will
//! be sorted based on their order. For e.g., column with `order = 0` will be displayed on the left followed by
//! column with `order = 1` and so on.
//! - `display_fn`: Used to print types which do not implement `Display` trait. Usage `#[table(display_fn = "<func_name>")]`.
//! Signature of provided function should be `fn <func_name>(value: &<type>) -> impl Display`.
//! - `customize_fn`: Used to customize style of a cell. Usage `#[table(customize_fn = "<func_name>")]`. Signature of
//! provided function should be `fn <func_name>(cell: CellStruct, value: &<type>) -> CellStruct`. This attribute can
//! be used when you want to change the formatting/style of a cell based on its contents. Note that this will
//! overwrite all the style settings done by other attributes.
//! - `skip`: Used to skip a field from table. Usage: `#[table(skip)]`
//!
//! For more information on configurations available on derive macro, go to `cli-table/examples/struct.rs`.
//!
//! ## CSV
//!
//! This crate also integrates with [`csv`](https://crates.io/crates/csv) crate. On enabling `"csv"` feature, you can
//! use `TryFrom<&mut Reader> for TableStruct` trait implementation to convert `csv::Reader` to `TableStruct`.
//!
//! For more information on handling CSV values, go to `cli-table/examples/csv.rs`.
//!
//! # Styling
//!
//! Style of a table/cell can be modified by calling functions of [`Style`] trait. It is implementated by both
//! [`TableStruct`] and [`CellStruct`].
//!
//! For individually formatting each cell of a table, `justify`, `align` and `padding` functions can be used from
//! `CellStruct`.
//!
//! In addition to this, borders and separators of a table can be customized by calling `border` and `separator`
//! functions in `TableStruct`. For example, to create a borderless table:
//!
//! ```rust,ignore
//! use cli_table::{Cell, Table, TableStruct, format::{Justify, Border}, print_stdout};
//!
//! fn get_table() -> TableStruct {
//! vec![
//! vec!["Tom".cell(), 10.cell().justify(Justify::Right)],
//! vec!["Jerry".cell(), 15.cell().justify(Justify::Right)],
//! vec!["Scooby Doo".cell(), 20.cell().justify(Justify::Right)],
//! ]
//! .table()
//! }
//!
//! let table = get_table().border(Border::builder().build()); // Attaches an empty border to the table
//! assert!(print_stdout(table).is_ok());
//! ```
//!
//! # Features
//!
//! - `derive`: Enables derive macro for creating tables using structs. **Enabled** by default.
//! - `csv`: Enables support for printing tables using [`csv`](https://crates.io/crates/csv). **Enabled** by default.
//! For more details, see [`cli-table`](https://docs.rs/cli-table).
mod context;
mod table;
mod utils;
Expand Down
39 changes: 39 additions & 0 deletions cli-table/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,45 @@ Below is the output of the table we created just now:
+------------+----------------+
```

### `Display` trait implementation

To get a `Display` trait implementation of `TableStruct`, use `display()` function on the struct to get an instance
of `TableDisplay` which implements `Display` trait.

```rust
use cli_table::{format::Justify, print_stdout, Cell, Style, Table};

let table = vec![
vec!["Tom".cell(), 10.cell().justify(Justify::Right)],
vec!["Jerry".cell(), 15.cell().justify(Justify::Right)],
vec!["Scooby Doo".cell(), 20.cell().justify(Justify::Right)],
]
.table()
.title(vec![
"Name".cell().bold(true),
"Age (in years)".cell().bold(true),
])
.bold(true);

let table_display = table.display().unwrap();

println!("{}", table_display);
```

Below is the output of the table we created just now:

```markdown
+------------+----------------+
| Name | Age (in years) | <-- This row and all the borders/separators
+------------+----------------+ will appear in bold
| Tom | 10 |
+------------+----------------+
| Jerry | 15 |
+------------+----------------+
| Scooby Doo | 25 |
+------------+----------------+
```

### Derive macro

`#[derive(Table)]` can also be used to print a `Vec` or slice of `struct`s as table.
Expand Down
125 changes: 115 additions & 10 deletions cli-table/src/buffers.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,120 @@
use std::io::Result;
use std::io::{Result, Write};

use termcolor::BufferWriter;
use termcolor::{Buffer, BufferWriter, ColorSpec, WriteColor};

pub(crate) trait Buffers {
type Dimension;
pub struct Buffers<'a> {
pub writer: &'a BufferWriter,
buffers: Vec<Buffer>,
current_buffer: Option<Buffer>,
}

impl<'a> Buffers<'a> {
pub fn new(writer: &'a BufferWriter) -> Self {
Buffers {
writer,
buffers: Vec::new(),
current_buffer: None,
}
}

pub fn push(&mut self, buffer: Buffer) -> Result<()> {
if let Some(mut current_buffer) = self.current_buffer.take() {
current_buffer.reset()?;
self.buffers.push(current_buffer);
}

self.buffers.push(buffer);

Ok(())
}

pub fn append(&mut self, other: &mut Vec<Buffer>) -> Result<()> {
if let Some(mut current_buffer) = self.current_buffer.take() {
current_buffer.reset()?;
self.buffers.push(current_buffer);
}

self.buffers.append(other);

Ok(())
}

pub fn end(&mut self) -> Result<()> {
if let Some(mut current_buffer) = self.current_buffer.take() {
current_buffer.reset()?;
self.buffers.push(current_buffer);
}

Ok(())
}

pub fn into_vec(self) -> Result<Vec<Buffer>> {
let mut buffers = self.buffers;

if let Some(mut buffer) = self.current_buffer {
buffer.reset()?;
buffers.push(buffer);
}

Ok(buffers)
}
}

impl<'a> Write for Buffers<'a> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if let Some(ref mut current_buffer) = self.current_buffer {
current_buffer.write(buf)
} else {
let mut new_buffer = self.writer.buffer();
let num_bytes = new_buffer.write(buf)?;
self.current_buffer = Some(new_buffer);

Ok(num_bytes)
}
}

fn flush(&mut self) -> std::io::Result<()> {
if let Some(ref mut current_buffer) = self.current_buffer {
current_buffer.flush()
} else {
Ok(())
}
}
}

impl<'a> WriteColor for Buffers<'a> {
fn supports_color(&self) -> bool {
match self.current_buffer {
Some(ref buffer) => buffer.supports_color(),
None => self.writer.buffer().supports_color(),
}
}

fn set_color(&mut self, color: &ColorSpec) -> Result<()> {
if let Some(ref mut current_buffer) = self.current_buffer {
current_buffer.set_color(color)
} else {
let mut new_buffer = self.writer.buffer();
new_buffer.set_color(color)?;
self.current_buffer = Some(new_buffer);

Ok(())
}
}

type Buffers;
fn reset(&mut self) -> Result<()> {
if let Some(ref mut current_buffer) = self.current_buffer {
current_buffer.reset()
} else {
Ok(())
}
}

fn buffers(
&self,
writer: &BufferWriter,
available_dimension: Self::Dimension,
) -> Result<Self::Buffers>;
fn is_synchronous(&self) -> bool {
if let Some(ref buffer) = self.current_buffer {
buffer.is_synchronous()
} else {
self.writer.buffer().is_synchronous()
}
}
}
Loading

0 comments on commit 0b2b3f1

Please sign in to comment.