diff --git a/tabled/src/derive/mod.rs b/tabled/src/derive/mod.rs index 3209a4ba..f55e2678 100644 --- a/tabled/src/derive/mod.rs +++ b/tabled/src/derive/mod.rs @@ -6,26 +6,36 @@ pub mod display; /// /// The macro available only when `derive` feature in turned on (and it is by default). /// -/// To be able to use the derive each field must implement `std::fmt::Display`. +/// ``` +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct SomeType { +/// field0: &'static str, +/// field1: String, +/// field2: usize, +/// } +/// ``` +/// +/// To be able to use the derive each field must implement `std::fmt::Display`.\ /// The following example will cause an error because of that. /// -/// ```rust,compile_fail +/// ```,compile_fail /// use tabled::Tabled; +/// /// #[derive(Tabled)] /// struct SomeType { -/// field1: SomeOtherType, +/// field1: Vec, /// } -/// -/// struct SomeOtherType; /// ``` /// /// Bellow you'll find available options for it. /// -/// ### Override a column name +/// ## Rename a column name /// /// You can use a `#[tabled(rename = "")]` attribute to override a column name. /// -/// ```rust,no_run +/// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] @@ -37,13 +47,11 @@ pub mod display; /// } /// ``` /// -/// ### Hide a column +/// ## Hide a column /// /// You can mark fields as hidden in which case they fill be ignored and not be present on a sheet. /// -/// A similar affect could be achieved by the means of a `Remove`. -/// -/// ```rust,no_run +/// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] @@ -55,11 +63,11 @@ pub mod display; /// } /// ``` /// -/// ### Set column order +/// ## Set column order /// /// You can change the order in which they will be displayed in table. /// -/// ```rust,no_run +/// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] @@ -72,59 +80,59 @@ pub mod display; /// } /// ``` /// -/// ### Format fields +/// ## Format fields /// -/// As was said already, using `#[derive(Tabled)]` is possible only when all fields implement a `Display` trait. -/// However, this may be often not the case for example when a field uses the `Option` type. There's 2 common ways how to solve this: +/// Using `#[derive(Tabled)]` is possible only when all fields implement a `Display` trait.\ +/// However, this may be not convinient for example when a field uses the `Option` type.\ +/// There's 2 common ways how to solve this: /// /// - Implement `Tabled` trait manually for a type. /// - Wrap `Option` to something like `DisplayedOption(Option)` and implement a Display trait for it. /// -/// Alternatively, you can use the `#[tabled(display = "func")]` attribute for the field to specify a display function. +/// But, it's not quite convient either. /// -/// ```rust,no_run -/// use tabled::Tabled; +/// So alternatively, we provide the next solutions. /// -/// #[derive(Tabled)] -/// pub struct MyRecord { -/// pub id: i64, -/// #[tabled(display = "display_option")] -/// pub valid: Option -/// } +/// - Use the `#[tabled(display = "func")]` - attribute to set a display function. +/// - Use the `#[tabled(format = "{}")]` - attribute to format field. /// -/// fn display_option(o: &Option) -> String { -/// match o { -/// Some(s) => format!("is valid thing = {}", s), -/// None => format!("is not valid"), -/// } -/// } -/// ``` +/// ### `#[tabled(display)]` +/// +/// A poverfull helper, the set function must have a first argument as a reference to a field.\ +/// It supports custom arguments as well (including `self`). /// -/// It's also possible to change function argument to be `&self`, -/// using `#[tabled(display("some_function", self))]` +/// You can set it right on the whole type,\ +/// In which case all fields which are matching a set type will be using the given function. /// -/// ```rust,no_run +/// We also provide a set of commonly used function for your types.\ +/// You can find them in [`tabled::derive::display`]. +/// +/// ``` /// use tabled::Tabled; +/// use tabled::derive::display; /// /// #[derive(Tabled)] -/// pub struct MyRecord { +/// #[tabled(display(i64, "display_i64"))] +/// pub struct Record { /// pub id: i64, -/// #[tabled(display("Self::display_valid", self))] -/// pub valid: Option +/// #[tabled(display("display::option", "unvalidated"))] +/// pub valid: Option, +/// #[tabled(display("display_private", self))] +/// pub private: (), /// } /// -/// impl MyRecord { -/// fn display_valid(_: &Option, s: &Self) -> String { -/// match s.valid { -/// Some(s) => format!("is valid thing = {}", s), -/// None => format!("is not valid"), -/// } -/// } +/// fn display_private(_: &(), rec: &Record) -> String { +/// todo!() +/// } +/// +/// fn display_i64(val: &i64) -> String { +/// todo!() /// } /// ``` /// -/// There's also a probably more suitable way for formatting, if your format is constant. -/// Using `#[tabled(format = "{}")]` and `#[tabled(format("{}"))]` and proving a general formatting string. +/// ### `#[tabled(format)]` +/// +/// An analogue to [`format!`], which can be used right on the field.\ /// /// ``` /// use tabled::Tabled; @@ -133,17 +141,17 @@ pub mod display; /// struct Record { /// #[tabled(skip)] /// id: u8, -/// #[tabled(format("{}.{}", self.id, self.name))] +/// #[tabled(format("{}.{}-{}", self.id, self.name, 123))] /// name: String, /// } /// ``` /// -/// ### Format headers +/// ## Format headers /// -/// Beside `#[tabled(rename = "")]` you can change a format of a column name using +/// Beside `#[tabled(rename = "")]` you can change a format of a column name using\ /// `#[tabled(rename_all = "UPPERCASE")]`. /// -/// ```rust,no_run +/// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] @@ -157,12 +165,14 @@ pub mod display; /// } /// ``` /// -/// ### Inline +/// ## Embeding a field /// -/// It's possible to inline internal data if it implements the `Tabled` trait using `#[tabled(inline)]`. -/// You can also set a prefix which will be used for all inlined elements by `#[tabled(inline("prefix>>"))]`. +/// You can inline a field or a variant if it implements `Tabled` trait\ +/// using `#[tabled(inline)]`. +/// You can also set a prefix for inlined elements by given it as a argument\ +/// `#[tabled(inline("::::"))]`. /// -/// ```rust,no_run +/// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] @@ -182,7 +192,7 @@ pub mod display; /// /// And it works for enums as well. /// -/// ```rust,no_run +/// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] @@ -205,4 +215,6 @@ pub mod display; /// price: f32, /// } /// ``` +/// +/// [`tabled::derive::display`]: crate::tabled::derive::display pub use tabled_derive::Tabled; diff --git a/tabled/src/lib.rs b/tabled/src/lib.rs index a3af2f0f..315baa3a 100644 --- a/tabled/src/lib.rs +++ b/tabled/src/lib.rs @@ -1,19 +1,25 @@ //! An easy to use library for pretty print tables of Rust `struct`s and `enum`s. //! -//! The library supports different approaches of table building. -//! You can use [`Tabled`] trait if the data type is known. -//! Or you can use [`Builder`] to construct the table from scratch. +//! There's two approaches to construct a table. //! -//! ## Derive +//! 1. When the type of data is known. +//! 2. When it's unknown. //! -//! If you want to build a table for your custom type. -//! A starting point is to a annotate your type with `#[derive(Tabled)]`. +//! Here you can work with both.\ +//! For first approach you shall find [`derive::Tabled`] macros being very helpfull.\ +//! For a later one you shall take a look at [`Builder`]. //! -//! Then to provide your collection to [`Table::new`] and you will be set to render table. +//! There are a number of [`settings`] you can use\ +//! to change table appearance, layout and data itself. +//! +//! Beside a default [`Table`] type there are more,\ +//! more specific table which works best when there are some constraints. //! #![cfg_attr(all(feature = "derive", feature = "std"), doc = "```")] #![cfg_attr(not(all(feature = "derive", feature = "std")), doc = "```ignore")] //! use tabled::{Tabled, Table}; +//! use tabled::settings::{Style, Alignment, object::Columns}; +//! use testing_table::assert_table; //! //! #[derive(Tabled)] //! struct Language { @@ -28,112 +34,175 @@ //! Language{ name: "Go", designed_by: "Rob Pike", invented_year: 2009 }, //! ]; //! -//! let table = Table::new(languages).to_string(); -//! -//! let expected = "+------+----------------+---------------+\n\ -//! | name | designed_by | invented_year |\n\ -//! +------+----------------+---------------+\n\ -//! | C | Dennis Ritchie | 1972 |\n\ -//! +------+----------------+---------------+\n\ -//! | Rust | Graydon Hoare | 2010 |\n\ -//! +------+----------------+---------------+\n\ -//! | Go | Rob Pike | 2009 |\n\ -//! +------+----------------+---------------+"; +//! let mut table = Table::new(languages); +//! table.with(Style::modern()); +//! table.modify(Columns::first(), Alignment::right()); //! -//! assert_eq!(table, expected); +//! assert_table!( +//! table, +//! "┌──────┬────────────────┬───────────────┐" +//! "│ name │ designed_by │ invented_year │" +//! "├──────┼────────────────┼───────────────┤" +//! "│ C │ Dennis Ritchie │ 1972 │" +//! "├──────┼────────────────┼───────────────┤" +//! "│ Rust │ Graydon Hoare │ 2010 │" +//! "├──────┼────────────────┼───────────────┤" +//! "│ Go │ Rob Pike │ 2009 │" +//! "└──────┴────────────────┴───────────────┘" +//! ); //! ``` //! -//! BEWARE not all types can derive [`Tabled`] trait. -//! The example below can't be compiled. +//! ## Building table step by step +//! +//! When you data scheme is not known at compile time.\ +//! You most likely will not able to relay on [`Table`].\ +//! One option would be is to use [`Builder`]. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use std::iter::once; +//! use tabled::{builder::Builder, settings::Style}; +//! use testing_table::assert_table; +//! +//! const X: usize = 3; +//! const Y: usize = 5; //! -//! Because `tabled` must know what we're up to print as a field, so -//! each field must implement [`std::fmt::Display`]. +//! let mut builder = Builder::default(); +//! +//! for i in 0..X { +//! let row = (0..Y).map(|j| (i * j).to_string()); +//! builder.push_record(row); +//! } +//! +//! builder.insert_record(0, (0..Y).map(|i| i.to_string())); +//! builder.insert_column(0, once(String::new()).chain((0..X).map(|i| i.to_string()))); //! -//! ```rust,compile_fail -//! # use tabled::Tabled; -//! #[derive(Tabled)] -//! struct SomeType { -//! field1: SomeOtherType, -//! } +//! let mut table = builder.build(); +//! table.with(Style::rounded()); //! -//! struct SomeOtherType; +//! assert_table!( +//! table, +//! "╭───┬───┬───┬───┬───┬───╮" +//! "│ │ 0 │ 1 │ 2 │ 3 │ 4 │" +//! "├───┼───┼───┼───┼───┼───┤" +//! "│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │" +//! "│ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │" +//! "│ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │" +//! "╰───┴───┴───┴───┴───┴───╯" +//! ); //! ``` //! -//! You can tweak it by derive options. +//! ## Settings //! -//! ### Default implementations +//! You can find lots of settings in [`tabled::settings`]. //! -//! [`Table`] can be build from vast majority of Rust's standard types. +//! ## Hints +//! +//! [`Table`] can be build from vast majority of Rust's standard types.\ //! This allows you to run the following code. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] -//! use tabled::{Tabled, Table}; -//! let table = Table::new(&[1, 2, 3]); -//! # let expected = "+-----+\n\ -//! # | i32 |\n\ -//! # +-----+\n\ -//! # | 1 |\n\ -//! # +-----+\n\ -//! # | 2 |\n\ -//! # +-----+\n\ -//! # | 3 |\n\ -//! # +-----+"; -//! # assert_eq!(table.to_string(), expected); -//! ``` +//! use tabled::Table; +//! use testing_table::assert_table; //! -//! ### Builder +//! let table = Table::new(&[1, 2, 3]); //! -//! When you data scheme is not known at compile time. -//! You most likely will not able to relay on [`Tabled`] trait. +//! assert_table!( +//! table, +//! "+-----+" +//! "| i32 |" +//! "+-----+" +//! "| 1 |" +//! "+-----+" +//! "| 2 |" +//! "+-----+" +//! "| 3 |" +//! "+-----+" +//! ); +//! ``` //! -//! So one option would be is to use [`Builder`]. +//! You can compine types, and settings together using a tupples.\ +//! And achive magical results. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] -//! use std::iter; +//! use tabled::Table; +//! use tabled::settings::{style::{Style, HorizontalLine}, Alignment, Padding}; +//! use testing_table::assert_table; //! -//! use tabled::{ -//! builder::Builder, -//! settings::{Modify, object::Rows, Alignment, Style} -//! }; +//! let data = &[(1, 2, "Hello"), (1, 3, "World")]; //! -//! let (x, y) = (3, 10); +//! let mut table = Table::new(data); +//! table.with( +//! Style::modern() +//! .remove_horizontal() +//! .horizontals([(1, HorizontalLine::inherit(Style::modern()))]) +//! ); +//! table.with((Alignment::right(), Padding::new(2, 0, 2, 1))); //! -//! let mut builder = Builder::default(); +//! assert_table!( +//! table, +//! "┌─────┬─────┬───────┐" +//! "│ │ │ │" +//! "│ │ │ │" +//! "│ i32│ i32│ &str│" +//! "│ │ │ │" +//! "├─────┼─────┼───────┤" +//! "│ │ │ │" +//! "│ │ │ │" +//! "│ 1│ 2│ Hello│" +//! "│ │ │ │" +//! "│ │ │ │" +//! "│ │ │ │" +//! "│ 1│ 3│ World│" +//! "│ │ │ │" +//! "└─────┴─────┴───────┘" +//! ); +//! ``` //! -//! let header = iter::once(String::from("i")).chain((0..y).map(|i| i.to_string())); -//! builder.push_record(header); +//! Be ware you don't obligated to `collect` your data before building. //! -//! for i in 0..x { -//! let row = iter::once(i).chain((0..y).map(|j| i * j)).map(|i| i.to_string()); -//! builder.push_record(row); -//! } +#![cfg_attr(all(feature = "derive", feature = "std"), doc = "```")] +#![cfg_attr(not(all(feature = "derive", feature = "std")), doc = "```ignore")] +//! use tabled::{Tabled, Table}; +//! use testing_table::assert_table; +//! use std::iter::once; +//! +//! #[derive(Tabled)] +//! struct Data( +//! #[tabled(rename = "word")] +//! String, +//! #[tabled(rename = "id")] +//! usize, +//! ); +//! +//! let data = once(Data(String::from("Hello"), 0)) +//! .chain(once(Data(String::from("World"), 1))) +//! .chain(once(Data(String::from("!!!"), 2))); //! -//! let table = builder.build() -//! .with(Style::rounded()) -//! .modify(Rows::new(1..), Alignment::left()) -//! .to_string(); +//! let mut table = Table::new(data); //! -//! assert_eq!( +//! assert_table!( //! table, -//! concat!( -//! "╭───┬───┬───┬───┬───┬───┬────┬────┬────┬────┬────╮\n", -//! "│ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", -//! "├───┼───┼───┼───┼───┼───┼────┼────┼────┼────┼────┤\n", -//! "│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │\n", -//! "│ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", -//! "│ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │\n", -//! "╰───┴───┴───┴───┴───┴───┴────┴────┴────┴────┴────╯", -//! ) +//! "+-------+----+" +//! "| word | id |" +//! "+-------+----+" +//! "| Hello | 0 |" +//! "+-------+----+" +//! "| World | 1 |" +//! "+-------+----+" +//! "| !!! | 2 |" +//! "+-------+----+" //! ); //! ``` //! -//! ### Build table using [`row!`] and [`col!`] macros. +//! Build table using [`row!`] and [`col!`] macros. //! #![cfg_attr(all(feature = "macros", feature = "std"), doc = "```")] #![cfg_attr(not(all(feature = "macros", feature = "std")), doc = "```ignore")] //! use tabled::{row, col}; +//! use testing_table::assert_table; //! //! let table = row![ //! col!["Hello", "World", "!"], @@ -141,25 +210,23 @@ //! col!["World"; 3], //! ]; //! -//! assert_eq!( -//! table.to_string(), -//! concat!( -//! "+-----------+-----------+-----------+\n", -//! "| +-------+ | +-------+ | +-------+ |\n", -//! "| | Hello | | | Hello | | | World | |\n", -//! "| +-------+ | +-------+ | +-------+ |\n", -//! "| | World | | | Hello | | | World | |\n", -//! "| +-------+ | +-------+ | +-------+ |\n", -//! "| | ! | | | Hello | | | World | |\n", -//! "| +-------+ | +-------+ | +-------+ |\n", -//! "+-----------+-----------+-----------+", -//! ) +//! assert_table!( +//! table, +//! "+-----------+-----------+-----------+" +//! "| +-------+ | +-------+ | +-------+ |" +//! "| | Hello | | | Hello | | | World | |" +//! "| +-------+ | +-------+ | +-------+ |" +//! "| | World | | | Hello | | | World | |" +//! "| +-------+ | +-------+ | +-------+ |" +//! "| | ! | | | Hello | | | World | |" +//! "| +-------+ | +-------+ | +-------+ |" +//! "+-----------+-----------+-----------+" //! ); //! ``` //! -//! ### Settings +//! # `no_std` //! -//! You can use many settings which is found in [`tabled::settings`] module. +//! Only [`CompactTable`] can be used in `no_std` context. //! //! # Features //! @@ -168,76 +235,6 @@ //! - `ansi` - A support for ANSI sequences. //! - `macros` - A support for `row!`, `col!` macro. //! -//! # Advanced -//! -//! ## Table types -//! -//! [`Table`] keeps data buffered, which sometimes not ideal choice. -//! For such reason there is [`IterTable`] and [`CompactTable`]. -//! -//! ### [`IterTable`] -//! -//! [`IterTable`] stands on a middle ground between [`Table`] and [`CompactTable`]. -//! -//! It does allocate memory but in a much smaller chunks that a [`Table`] does. -//! The benefit is that it can be used interchangeably with [`Table`]. -//! -#![cfg_attr(feature = "std", doc = "```")] -#![cfg_attr(not(feature = "std"), doc = "```ignore")] -//! use tabled::tables::IterTable; -//! -//! let iterator = (0..3).map(|row| (0..4).map(move |col| format!("{}-{}", row, col))); -//! -//! let table = IterTable::new(iterator).to_string(); -//! -//! assert_eq!( -//! table, -//! "+-----+-----+-----+-----+\n\ -//! | 0-0 | 0-1 | 0-2 | 0-3 |\n\ -//! +-----+-----+-----+-----+\n\ -//! | 1-0 | 1-1 | 1-2 | 1-3 |\n\ -//! +-----+-----+-----+-----+\n\ -//! | 2-0 | 2-1 | 2-2 | 2-3 |\n\ -//! +-----+-----+-----+-----+", -//! ); -//! ``` -//! -//! ### [`CompactTable`] -//! -//! Alloc free can be configured ('1) to not make any allocations. -//! But the price is that the set of settings which can be applied to it is limited. -//! -//! It also can be printed directly to [`fmt::Write`] to not have any intermidiaries. -//! -//! '1. It does not make any allocations in case you provide it with `width` and `count_rows`. -//! -//! ``` -//! use tabled::{settings::Style, tables::CompactTable}; -//! use core::fmt::{Write, Result}; -//! -//! struct StubWriter; -//! -//! impl Write for StubWriter { -//! fn write_str(&mut self, _: &str) -> Result { -//! Ok(()) -//! } -//! } -//! -//! let data = [ -//! ["FreeBSD", "1993", "William and Lynne Jolitz", "?"], -//! ["OpenBSD", "1995", "Theo de Raadt", ""], -//! ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""], -//! ]; -//! -//! let table = CompactTable::from(data).with(Style::psql()); -//! -//! table.fmt(StubWriter); -//! ``` -//! -//! ## `no_std` -//! -//! [`CompactTable`] can be used in `no_std` context. -//! //! ## More information //! //! You can find more examples of settings and attributes in diff --git a/tabled/src/tabled.rs b/tabled/src/tabled.rs index a27da5d8..2ce3b05c 100644 --- a/tabled/src/tabled.rs +++ b/tabled/src/tabled.rs @@ -16,6 +16,7 @@ pub trait Tabled { /// /// The cells will be placed in the same row, preserving the order. fn fields(&self) -> Vec>; + /// Headers must return a list of column names. fn headers() -> Vec>; } @@ -29,6 +30,7 @@ where fn fields(&self) -> Vec> { T::fields(self) } + fn headers() -> Vec> { T::headers() } @@ -43,6 +45,7 @@ where fn fields(&self) -> Vec> { T::fields(self) } + fn headers() -> Vec> { T::headers() }