Skip to content
/ tabled Public
forked from zhiburt/tabled

An easy to use library for pretty print tables of Rust structs and enums.

License

Notifications You must be signed in to change notification settings

kozmod/tabled

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Build Status Coverage Status Crate docs.rs license dependency status

tabled

An easy to use library for pretty printing tables of Rust structs and enums.

Table of Contents

Usage

To print a list of structs or enums as a table your types should implement the the Tabled trait or derive with a #[derive(Tabled)] macro.

use tabled::{Tabled, Table};

#[derive(Tabled)]
struct Language {
    name: &'static str,
    designed_by: &'static str,
    invented_year: usize,
}

let languages = vec![
    Language{
        name: "C",
        designed_by: "Dennis Ritchie",
        invented_year: 1972
    },
    Language{
        name: "Rust",
        designed_by: "Graydon Hoare",
        invented_year: 2010
    },
    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\
                +------+----------------+---------------+\n";

assert_eq!(table, expected);

Most of the default types implement the trait out of the box.

use tabled::TableIteratorExt;
let some_numbers = [1, 2, 3];
let table = some_numbers.table();

Settings

In this section is listened a set of settings you can apply for your table.

Style

There are a list of ready to use styles. Each style can be castomized.

A custom style also can be created from scratch.

A style can be used by passing it to .with method of Table.

let table = Table::new(&data).with(Style::psql());

Bellow rendered a list of pre configured styles.

If you think that there's some valuable style to be added, Please open an issue.

ASCII

+------+----------------+---------------+
| name |  designed_by   | invented_year |
+------+----------------+---------------+
|  C   | Dennis Ritchie |     1972      |
+------+----------------+---------------+
| Rust | Graydon Hoare  |     2010      |
+------+----------------+---------------+
|  Go  |    Rob Pike    |     2009      |
+------+----------------+---------------+

Psql

 name |  designed_by   | invented_year 
------+----------------+---------------
  C   | Dennis Ritchie |     1972      
 Rust | Graydon Hoare  |     2010      
  Go  |    Rob Pike    |     2009      

Github Markdown

| name |  designed_by   | invented_year |
|------+----------------+---------------|
|  C   | Dennis Ritchie |     1972      |
| Rust | Graydon Hoare  |     2010      |
|  Go  |    Rob Pike    |     2009      |

Modern

β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ name β”‚  designed_by   β”‚ invented_year β”‚
β”œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  C   β”‚ Dennis Ritchie β”‚     1972      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Rust β”‚ Graydon Hoare  β”‚     2010      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Go  β”‚    Rob Pike    β”‚     2009      β”‚
β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ReStructuredText

====== ================ ===============
 name    designed_by     invented_year 
====== ================ ===============
  C     Dennis Ritchie       1972      
 Rust   Graydon Hoare        2010      
  Go       Rob Pike          2009      
====== ================ ===============

Extended

╔══════╦════════════════╦═══════════════╗
β•‘ name β•‘  designed_by   β•‘ invented_year β•‘
╠══════╬════════════════╬═══════════════╣
β•‘  C   β•‘ Dennis Ritchie β•‘     1972      β•‘
╠══════╬════════════════╬═══════════════╣
β•‘ Rust β•‘ Graydon Hoare  β•‘     2010      β•‘
╠══════╬════════════════╬═══════════════╣
β•‘  Go  β•‘    Rob Pike    β•‘     2009      β•‘
β•šβ•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Dots

.........................................
: name :  designed_by   : invented_year :
:......:................:...............:
:  C   : Dennis Ritchie :     1972      :
: Rust : Graydon Hoare  :     2010      :
:  Go  :    Rob Pike    :     2009      :
:......:................:...............:

Blank

 name    designed_by     invented_year 
  C     Dennis Ritchie       1972      
  Rust   Graydon Hoare       2010      
  Go       Rob Pike          2009      

Custom

You can modify existing styles to fit your needs.

let style = Style::modern().header_off().horizontal_off();

The style will look like the following.

β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ name β”‚  designed_by   β”‚ invented_year β”‚
β”‚  C   β”‚ Dennis Ritchie β”‚     1972      β”‚
β”‚ Rust β”‚ Graydon Hoare  β”‚     2010      β”‚
β”‚  Go  β”‚    Rob Pike    β”‚     2009      β”‚
β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

You can find more methods which are available in the documentation

Alignment

You can set a horizontal and vertical alignment for a Header, Column, Row or Full set of cells.

Table::new(&data)
    .with(Modify::new(Full)
        .with(Alignment::left())
        .with(Alignment::top())
    );

Format

The Format function provides an interface for a modification of cells.

Table::new(&data)
    .with(Style::psql()),
    .with(Modify::new(Column(..)).with(Format(|s| format!("<< {} >>", s))))
    .with(Modify::new(Row(..1)).with(Format(|s| format!("Head {}", s))));

It's also possible to use functions with signature Fn(&str) -> String as a formatter.

Table::new(&data)
    .with(Style::psql()),
    .with(Modify::new(Column(..)).with(|s: &str| format!("<< {} >>", s)))
    .with(Modify::new(Row(..1)).with(str::to_lowercase));

IMPORTANT: you may need to specify type in your lambda otherwise compiler may be disagreed to work :)

There's 2 more Format modifiers. You can find more imformation about theire usage in the documentation.

  • FormatFrom - Uses Vec elements as new content.
  • FormatWithIndex - Like Format but with row and column index in lambda.

Indent

The Indent type provides an interface for a left, right, top and bottom indent of cells.

Table::new(&data).with(Modify::new(Row(1..)).with(Indent::new(1, 1, 0, 2)));

Max width

Using MaxWidth type its possible to set a max width of an object. While tinkering content we don't forget about its color.

// Truncating content to 10 chars in all rows except a header.
Table::new(&data).with(Modify::new(Row(1..)).with(MaxWidth::truncating(10, "...")));

// Wrapping content by new lines after 10 chars in a header row.
Table::new(&data).with(Modify::new(Row(..1)).with(MaxWidth::wrapping(10)));

Rotate

You can rotate table using Rotate.

Imagine you have a table already. And the output may look like this.

β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ id β”‚ destribution β”‚ link                      β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0  β”‚ Fedora       β”‚ https://getfedora.org/    β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 2  β”‚ OpenSUSE     β”‚ https://www.opensuse.org/ β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 3  β”‚ Endeavouros  β”‚ https://endeavouros.com/  β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Now we will add table.with(Rotate::Left) and the output will be;

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     link     β”‚ https://getfedora.org/ β”‚ https://www.opensuse.org/ β”‚ https://endeavouros.com/ β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ destribution β”‚         Fedora         β”‚         OpenSUSE          β”‚       Endeavouros        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚      id      β”‚           0            β”‚             2             β”‚            3             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Disable

You can remove certain rows or columns from the table.

Table::new(&data)
    .with(Disable::Row(..1))
    .with(Disable::Column(3..4));

Header and Footer

You can add a Header and Footer to display some information. By the way you can even add such line by using Panel

Table::new(&data)
    .with(Header("Tabled Name"))
    .with(Footer(format!("{} elements", data.len())))

A look will differ from a style you choose. But it's how it may look like.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       Tabled Name                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                            ...
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                        3 elements                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Concat

You can concatanate 2 tables using Concat. It will stick 2 tables together either vertically or horizontally.

let t1: Table = ...;
let t2: Table = ...;

let t3: Table = t1.with(Concat::vertical(t2));

Derive

To be able to use a Tabled macros each field must implement std::fmt::Display otherwise it will not work.

The following example will cause a error.

use tabled::Tabled;
#[derive(Tabled)]
struct SomeType {
    field1: SomeOtherType,
}

struct SomeOtherType;

Column name override

You can use a #[header("")] attribute to override a column name.

#[derive(Tabled)]
struct Person {
    #[header("Name")]
    first_name: &'static str,
    #[header("Surname")]
    last_name: &'static str,
}

Hide a column

You can mark filds as hidden in which case they fill be ignored and not be present on a sheet.

A similar affect could be achived by the means of a Disable setting.

struct Person {
   #[header(hidden = true)]
   id: u8,
   #[header("field 2", hidden)]
   number: &'static str,
   name: &'static str,
}

Custom field formatting

#[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:

  • Implement Tabled trait manually for a type.
  • Wrap Option to something like DisplayedOption<T>(Option<T>) and implement a Display trait for it.

Or to use an attribute #[field(display_with = "func")] for the field. To use it you must provide a function name in a display_with parameter.

fn display_option(o: &Option<bool>) -> String {
    match o {
        Some(s) => format!("is valid thing = {}", s), 
        None => format!("is not valid"),
    }
}

#[derive(Tabled)]
pub struct MyRecord {
    pub id: i64,
    #[field(display_with="display_option")]
    pub valid: Option<bool>
}

Inline

It's possible to inline internal data if it implements Tabled trait. Use #[header(inline)] or #[header(inline("prefix>>"))]. The string argument is a prefix which will be used for all inlined elements.

 #[derive(Tabled)]
 struct Person {
     id: u8,
     name: &'static str,
     #[header(inline)]
     ed: Education,
 }
 
#[derive(Tabled)]
struct Education {
    uni: &'static str,
    graduated: bool,
}

And it works for enums as well.

#[derive(Tabled)]
enum Vehicle {
    #[header(inline("Auto::"))]
    Auto {
        model: &'static str,
        engine: &'static str,
    },
    #[header(inline)]
    Bikecycle(#[header("name")] &'static str, #[header(inline)] Bike),
}
        
#[derive(Tabled)]
struct Bike {
    brand: &'static str,
    price: f32,
}

Features

Color

The library doesn't bind you in usage of any color library but to be able to work corectly with color input you should provide a --features color.

Table::new(&data)
    .with(Style::psql())
    .with(Modify::new(Column(..1)).with(Format(|s| s.red().to_string())))
    .with(Modify::new(Column(1..2)).with(Format(|s| s.blue().to_string())))
    .with(Modify::new(Column(2..)).with(Format(|s| s.green().to_string())));

carbon-2

Tuple combination

You also can combine objets which implements Tabled by means of tuples, you will get a combined columns of them.

use tabled::{Tabled, Table, Style};

#[derive(Tabled)]
enum Domain {
    Security,
    Embeded,
    Frontend,
    Unknown,
}

#[derive(Tabled)]
struct Developer(#[header("name")] &'static str);

let data = vec![
    (Developer("Terri Kshlerin"), Domain::Embeded),
    (Developer("Catalina Dicki"), Domain::Security),
    (Developer("Jennie Schmeler"), Domain::Frontend),
    (Developer("Maxim Zhiburt"), Domain::Unknown),
];

let table = Table::new(data).with(Style::psql()).to_string();

assert_eq!(
    table,
    concat!(
        "      name       | Security | Embeded | Frontend | Unknown \n",
        "-----------------+----------+---------+----------+---------\n",
        " Terri Kshlerin  |          |    +    |          |         \n",
        " Catalina Dicki  |    +     |         |          |         \n",
        " Jennie Schmeler |          |         |    +     |         \n",
        "  Maxim Zhiburt  |          |         |          |    +    \n"
    )
);

Object

You can peak your target for settings using and and not methods for an object.

Full.not(Row(..1)) // peak all cells except header
Head.and(Column(..1)).not(Cell(0, 0)) // peak a header and first column except a (0, 0) cell

Views

Tabled supports not only Table view!

Expanded display

You can use ExpanedDisplay if your data structure has a lot of fields.

Here's an example.

use tabled::{display::ExpandedDisplay, Tabled};

#[derive(Tabled)]
struct Distribution {
    name: &'static str,
    is_active: bool,
    is_cool: bool,
}

fn main() {
    let data = [
        Distribution {
            name: "Manjaro",
            is_cool: true,
            is_active: true,
        },
        Distribution {
            name: "Debian",
            is_cool: true,
            is_active: true,
        },
        Distribution {
            name: "Debian",
            is_cool: true,
            is_active: true,
        },
    ];

    let table = ExpandedDisplay::new(&data);

    println!("{}", table);
}

You'll see the following.

-[ RECORD 0 ]------
name      | Manjaro
is_active | true
is_cool   | true
-[ RECORD 1 ]------
name      | Debian
is_active | true
is_cool   | true
-[ RECORD 2 ]------
name      | Debian
is_active | true
is_cool   | true

Notes

ANSI escape codes

By default tabled doesn't handle ANSI escape codes. By default such things as hyperlinks, blinking and others things which can be achived via ANSI codes might not work correctly.

tabled support it by setting a color feature.

tabled = { version = "*", features = ["color"] }

Dynamic table

It might be hard to build a table using Tabled trait, if you have a data set which structure is determined at runtime. In such situation you can use a Builder.

use tabled::{builder::Builder, Style};

fn main() {
    let table = Builder::default()
        .set_header(["Index", "Language"])
        .add_row(["1", "English"])
        .add_row(["2", "Deutsch"])
        .build()
        .with(Style::psql());

    println!("{}", table);
}

Emoji

The library support emojies out of the box but be aware that some of the terminals and editors may not render them as you would expect.

Let's add emojies to an example from a Usage section.

 let languages = vec![
     Language {
         name: "C πŸ’•",
         designed_by: "Dennis Ritchie",
         invented_year: 1972,
     },
     Language {
         name: "Rust πŸ‘",
         designed_by: "Graydon Hoare",
         invented_year: 2010,
     },
     Language {
         name: "Go πŸ§‹",
         designed_by: "Rob Pike",
         invented_year: 2009,
     },
 ];

The resultant table will look like the following.

As you can see Github triks a bit a return table, but GNOME terminal and Alacritty terminal handles it correctly.

+---------+----------------+---------------+
|  name   |  designed_by   | invented_year |
+---------+----------------+---------------+
|  C πŸ’•   | Dennis Ritchie |     1972      |
+---------+----------------+---------------+
| Rust πŸ‘ | Graydon Hoare  |     2010      |
+---------+----------------+---------------+
|  Go πŸ§‹  |    Rob Pike    |     2009      |
+---------+----------------+---------------+

About

An easy to use library for pretty print tables of Rust structs and enums.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Rust 100.0%