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

Overview

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.

Preview

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 it 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

This section lists the set of settings you can apply to your table.

Style

Themes

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

A custom style also can be created from scratch.

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

use tabled::{Table, Style};

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

Below is a rendered list of the preconfigured 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      │
└──────┴────────────────┴───────────────┘
Rounded
╭──────┬────────────────┬───────────────╮
│ 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 = tabled::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      │
└──────┴────────────────┴───────────────┘

Check the documentation for more customization options.

Cell Border

Sometimes tabled::Style settings are not enough. Sometimes it's nesessary to change a border of a particular cell.

For this purpose you can use Border.

use tabled::{TableIteratorExt, Modify, Border, object::Rows};

let data = [["123", "456"], ["789", "000"]];

let table = data.table()
    .with(Style::ascii())
    .with(Modify::new(Rows::single(0)).with(Border::default().top('x')));

let expected = "+xxxxx+xxxxx+\n\
                |  0  |  1  |\n\
                +-----+-----+\n\
                | 123 | 456 |\n\
                +-----+-----+\n\
                | 789 | 000 |\n\
                +-----+-----+\n";

assert_eq!(table.to_string(), expected);

Text in a top border

You can also have custom text as part of the top border of the table.

use tabled::{Table, style::BorderText};

let table = Table::new(["Hello World"])
    .with(BorderText::new(0, "+-.table"));

assert_eq!(
    table.to_string(),
    "+-.table------+\n\
     |    &str     |\n\
     +-------------+\n\
     | Hello World |\n\
     +-------------+\n"
);

Alignment

You can set a horizontal and vertical alignment for any Object (e.g Columns, Rows).

use tabled::{TableIteratorExt, Modify, Alignment, object::Segment};

data.table()
    .with(Modify::new(Segment::all()).with(Alignment::left()).with(Alignment::top()));

Format

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

use tabled::{Table, Modify, Format, object::{Rows, Columns}};

Table::new(&data)
    .with(Modify::new(Rows::first()).with(Format::new(|s| format!("Head {}", s))))
    .with(Modify::new(Columns::new(1..=2)).with(Format::new(|s| format!("<< {} >>", s))));

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

use tabled::{Table, Modify, object::{Rows, Columns}};

Table::new(&data)
    .with(Modify::new(Columns::single(3)).with(|s: &str| format!("<< {} >>", s)))
    .with(Modify::new(Rows::first()).with(str::to_lowercase));

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

Padding

The Padding structure provides an interface for a left, right, top and bottom padding of cells.

use tabled::{Table, Modify, Padding, object::Cell};

Table::new(&data)
    .with(Modify::new(Cell(0, 3)).with(Padding::new(1, 1, 0, 2)));

// It's possible to set a fill char for padding.
Table::new(&data)
    .with(Modify::new(Cell(0, 3)).with(Padding::new(1, 1, 0, 2).set_fill('>', '<', '^', 'V')));

Margin

Margin sets extra space around the border (top, bottom, left, right).

use tabled::{Table, Modify, Padding, object::Cell};

Table::new(&data)
    .with(Margin::new(3, 4, 1, 2).set_fill('>', '<', 'v', '^'));

An output would depend on the data. But it could look like the following.

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
>>>┌───────────┬───────────┐<<<<
>>>│  feature  │  released │<<<<
>>>│  margin   │   0.6.0   │<<<<
>>>└───────────┴───────────┘<<<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Max width

MaxWidth sets a maximum width of an object. This preserves the text color correctly.

use tabled::{TableIteratorExt, Modify, MaxWidth, object::Rows};

// Truncating content to 10 chars in all rows except a header.
data.table()
    .with(Modify::new(Rows::new(1..)).with(MaxWidth::truncating(10).suffix("...")));

// Wrapping content by new lines after 10 chars in a last row.
data.table()
    .with(Modify::new(Rows::last()).with(MaxWidth::wrapping(10)));

MaxWidth also can be used to set a maximum width of a whole table.

use tabled::{TableIteratorExt, MaxWidth};

data.table().with(MaxWidth::wrapping(10));

It can be used in combination with MinWidth.

Min width

MinWidth sets a minimal width of an object. This preserves the text color correctly.

use tabled::{TableIteratorExt, Modify, MinWidth, object::Rows};

data.table()
    .with(Modify::new(Rows::new(1..)).with(MinWidth::new(10)));

MinWidth also can be used to set a minimum width of a whole table.

use tabled::{TableIteratorExt, MinWidth};

data.table().with(MinWidth::new(10));

It can be used in combination with MaxWidth.

Justify

You can set a constant width for all columns using Justify. But be aware that it doesn't consider Padding.

use tabled::{TableIteratorExt, Justify};

data.table().with(Justify::new(10);

Rotate

You can rotate table using tabled::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 the following modificator and the output will be;

table.with(Rotate::Left)
┌──────────────┬────────────────────────┬───────────────────────────┬──────────────────────────┐
│     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.

use tabled::{TableIteratorExt, Disable};

data.table()
    .with(Disable::Row(..1))
    .with(Disable::Column(3..4));

Extract

You can Extract segments of a table to focus on a reduced number of rows and columns.

use tabled::{Table, Extract};

let rows = 1..3;
let columns = 1..;
Table::new(&data)
    .with(Extract::segment(rows, columns));
+-------+-------------+-----------+
|  i32  |    &str     |   bool    |
+-------+-------------+-----------+         +-------------+-----------+
| : 0 : | : Grodno :  | : true :  |         | : Grodno :  | : true :  |
+-------+-------------+-----------+    =    +-------------+-----------+
| : 1 : |  : Minsk :  | : true :  |         |  : Minsk :  | : true :  |
+-------+-------------+-----------+         +-------------+-----------+
| : 2 : | : Hamburg : | : false : |
+-------+-------------+-----------+
| : 3 : |  : Brest :  | : true :  |
+-------+-------------+-----------+

Refinishing

For styles with unique corner and edge textures it is possible to reapply a table style once a Table extract has been created.

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

let rows = 1..3;
let columns = 1..;
Table::new(&data)
    .with(Extract::segment(rows, columns))
    .with(Style::modern());
Raw extract
┼───────────────────────────┼──────────────────┼──────────────┤
│ The Dark Side of the Moon │ 01 March 1973    │ Unparalleled │
┼───────────────────────────┼──────────────────┼──────────────┤
│ Rumours                   │ 04 February 1977 │ Outstanding  │
┼───────────────────────────┼──────────────────┼──────────────┤

Refinished extract
┌───────────────────────────┬──────────────────┬───────────────┐
│ The Dark Side of the Moon │ 01 March 1973    │ Unparalleled  │
├───────────────────────────┼──────────────────┼───────────────┤
│ Rumours                   │ 04 February 1977 │  Outstanding  │
└───────────────────────────┴──────────────────┴───────────────┘

Header and Footer

You can add a Header and Footer to display some information.

use tabled::{Table, Header, Footer};

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

The look will depend on the style you choose but it may look something like this:

┌────────────────────────────────────────────────────────────┐
│                       Tabled Name                          │
├────────────────────────────────────────────────────────────┤
                            ...
├───────┼──────────────┼─────────┼───────────────────────────┤
│                        3 elements                          │
└────────────────────────────────────────────────────────────┘

You can also add a full row on any line using tabled::Panel.

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));

Highlight

Highlight can be used to change the borders of target region. Here's an example.

use tabled::{
    object::{Columns, Object, Rows},
    style::{Border, Style},
    Highlight, TableIteratorExt,
};

let data = vec![
    ["A", "B", "C"],
    ["D", "E", "F"]
];

let table = data.table()
    .with(Style::modern())
    .with(Highlight::new(
        Rows::first().and(Columns::single(2).and(Cell(1, 1))),
        Border::filled('*'),
    ));

The resulting table would be the following.

*************
* 0 │ 1 │ 2 *
*****───┼───*
│ A * B │ C *
├───*****───*
│ D │ E * F *
└───┴───*****

Column span

It's possible to have a horizontal (column) span of a cell.

The code example and the resulting table.

use tabled::{object::Cell, Modify, Span, TableIteratorExt};

fn main() {
    let data = vec![
        ["A", "B", "C"],
        ["D", "E", "F"],
    ];

    let table = data
        .table()
        .with(Modify::new(Cell(0, 0)).with(Span::column(3)))
        .with(Modify::new(Cell(1, 0)).with(Span::column(2)));

    println!("{}", table);
+---+---+---+
|     0     |
+---+---+---+
|   A   | C |
+---+---+---+
| D | E | F |
+---+---+---+

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 #[tabled(rename = "")] attribute to override a column name.

use tabled::Tabled;

#[derive(Tabled)]
struct Person {
    #[tabled(rename = "Name")]
    first_name: &'static str,
    #[tabled(rename = "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 achieved by the means of a Disable setting.

use tabled::Tabled;

#[derive(Tabled)]
struct Person {
   id: u8,
   #[tabled(skip)]
   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.

Alternatively, use the #[tabled(display_with = "func")] attribute for the field to specify a custom display function.

use tabled::Tabled;

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

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

Inline

It's possible to inline internal data if it implements the Tabled trait. Use #[tabled(inline)] for it. You can also set a prefix which will be used for all inlined elements by using #[tabled(inline("prefix>>"))].

use tabled::Tabled;

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

#[derive(Tabled)]
struct Education {
    uni: &'static str,
    graduated: bool,
}

And it works for enums as well.

use tabled::Tabled;

#[derive(Tabled)]
enum Vehicle {
    #[tabled(inline("Auto::"))]
    Auto {
        model: &'static str,
        engine: &'static str,
    },
    #[tabled(inline)]
    Bikecycle(#[tabled(rename = "name")] &'static str, #[tabled(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 correctly with color input you should add the color feature of tabled to your Cargo.toml

use tabled::{Table, Modify, Style, Format, object::Columns};

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

carbon-2

Tuple combination

You also can combine objects 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(#[tabled("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 apply settings to subgroup of cells using and and not methods for an object.

use tabled::object::{Segment, Cell, Rows, Columns};

Segment::all().not(Rows::first()) // select all cells except header.
Columns::first().and(Columns::last()) // select cells from first and last columns.
Rows::first().and(Columns::single(0)).not(Cell(0, 0)) // select the header and first column except the (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 achieved via ANSI codes might not work correctly.

To enable this support, add the color feature to your Cargo.toml

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_columns(["Index", "Language"])
        .add_record(["1", "English"])
        .add_record(["2", "Deutsch"])
        .build()
        .with(Style::psql());

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

Index

You can use Builder::index to make a particular column an index, which will stay on the left.

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

fn main() {
    let table = Builder::default()
        .set_columns(["Index", "Language", "Status"])
        .add_record(["1", "English", "In progress"])
        .add_record(["2", "Deutsch", "Not ready"])
        .index()
        .set_index(1)
        .set_name(None)
        .build()
        .with(Style::rounded());

    println!("{}", table);
}
╭─────────┬───────┬─────────────╮
│         │ Index │   Status    │
├─────────┼───────┼─────────────┤
│ English │   1   │ In progress │
│ Deutsch │   2   │  Not ready  │
╰─────────┴───────┴─────────────╯

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 tricks 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      |
+---------+----------------+---------------+
Comments
  • would like to try this out in nushell

    would like to try this out in nushell

    hey @zhiburt, i'd love to try your tabled crate out in nushell just to see how it stacks up and if it could work for us, what the performance is like, how it handles emojis, etc. are you interested and available to help with this?

    if so, i'd see it initially working this way as a prototype. right now most commands automatically call table which creates the normal nushell tables. i figured we could create a new command called tabled and then just do ls | tabled and blah | tabled just to see how it works.

    what are your thoughts?

    opened by fdncred 156
  • v0.8.0 Fix wrapped filenames with hyperlinks

    v0.8.0 Fix wrapped filenames with hyperlinks

    Hi @zhiburt, this patch fixes nushell bug https://github.com/nushell/nushell/issues/6553 causing filenames in narrow tables to be incorrectly displayed, and fixes https://github.com/zhiburt/tabled/issues/203. The patch was made against tabled v0.8.0 (the version of tabled that nushell is currently using), but as you can see it's a tiny change, which can also be made against src/features/width/wrap.rs in your devel branch of tabled (here's a branch targeting your current master). I thought this PR targeting v0.8.0 might be more useful for getting the fix into nushell sooner. The crate it's using is here. It has unit tests and has been well tested by users, running in delta for a couple of years now.

    I've added one example: cargo run --example hyperlink_keep_words displays a table which is incorrect without the patch:

    On this branch

     〉cargo run --example hyperlink_keep_words
    .-----------------------------------------------.
    | name                           | is_hyperlink |
    | Debian                         | true         |
    | Debian                         | false        |
    | DebianDebianDebianDebianDebian | true         |
    | DebianDebianDebianDebianDebian |              |
    | DebianDebianDebianDebian       |              |
    | DebianDebianDebianDebianDebian | false        |
    | DebianDebianDebianDebianDebian |              |
    | DebianDebianDebianDebian       |              |
    '-----------------------------------------------'
    

    With the patch reverted

     〉git revert --no-edit HEAD
     .-----------------------------------------------.
    | name                           | is_hyperlink |
    | De | true         |
    | bian                      |              |
    | Debian                         | false        |
    | De | true         |
    | bianDebianDebianDebianDebianDe |              |
    | bianDebianDebianDebianDebianDe |              |
    | bianDebianDebianDebian    |              |
    | DebianDebianDebianDebianDebian | false        |
    | DebianDebianDebianDebianDebian |              |
    | DebianDebianDebianDebian       |              |
    '-----------------------------------------------'
    

    Currently it's simply removing the hyperlinks. I have the code to re-apply the hyperlinks to the wrapped segments, but I didn't want to disturb your split_keeping_words and split functions.

    opened by dandavison 33
  • Add a `join` method

    Add a `join` method

    let table3 = table1.join(table2);
    

    Notes:

    • ? Add an optional argument to define which stylies must be used
    • ? Add options to determine how to act if one table have a different amount of rows then the other
    good first issue 
    opened by zhiburt 22
  • Add `AlignmentHorizontal::No` in order to not change the content and print it as it is

    Add `AlignmentHorizontal::No` in order to not change the content and print it as it is

    The name may be not perfect but the idea is to use string as it is.

    Currently it's not possible to have a cell which starts from spaces.

    Example:

    #[test]
    fn table_value_starts_with_spaces() {
        let expected = "+------+\n\
                        | i32  |\n\
                        +------+\n\
                        |    1 |\n\
                        +------+\n";
    
        let table = Table::new(&["   1"]).to_string();
    
        assert_eq!(table, expected);
    }
    

    The test will fail because by default we use HorizontalAlignment::Center

    enhancement good first issue papergrid 
    opened by zhiburt 20
  • Add an option to avoid line-breaking in the middle of a word

    Add an option to avoid line-breaking in the middle of a word

    Currently I'm just putting some extra space in my strings so that the whole word gets wrapped to next line instead of line-wrapping in the middle of a word.

    It would be cool to have an option to automatically line-wrap at word boundaries.

    enhancement 
    opened by mrjones2014 18
  • Disable headers?

    Disable headers?

    Thanks for making this beautiful crate ❤️ . I am very much enjoying working with it.

    So, It there any way to disable/remove the headers completely. In my use case headers don't make that much sense.

    For example, I am printing a table like this

    • Code
    let details = [
        ("Username", "username"),
        ("Password", "password"),
        ("URL", "url"),
        ("Notes", "notes"),
    ];
    
    let table = table!(
        &details,
        Style::PseudoClean,
        HorizontalAlignment::new(Alignment::Left, AlignmentObject::Full),
        ChangeRing(Row(1..), vec![Box::new(|s| format!(" {} ", s))])
    );
    
    println!("{}", table);
    
    • Output
    ┌──────────┬──────────┐
    │&str      │&str      │
    ├──────────┼──────────┤
    │ Username │ username │
    │ Password │ password │
    │ URL      │ url      │
    │ Notes    │ notes    │
    └──────────┴──────────┘
    
    • Wanted
    ┌──────────┬──────────┐
    │ Username │ username │
    │ Password │ password │
    │ URL      │ url      │
    │ Notes    │ notes    │
    └──────────┴──────────┘
    

    Maybe there is already a way to disable headers but I wasn't able to find it in the docs.

    opened by numToStr 12
  • HTML escaping seems to be missing

    HTML escaping seems to be missing

    Does table_to_html do HTML-escaping? I.e. convert "<" to "&lt;" and so on?

    A simple tests suggests not - and I couldn't find anything in the code, specifically in the display function at impl Element for &str on table_to_html/src/lib.rs. I don't know the best idiomatic way to do this in Rust, but it's obviously fairly trivial: The string "<>&'\"" needs to be translated to "&lt;&gt;&amp;&apos;&quot;" (if I remember correctly).

    A related issue is how to customize the value-cell-to-html mapping, specifically for the NuShell Value type. Am I correct in thinking this should be done by defining an impl Element for Value? I'm fairly new to Rust, but assuming that's the right direction I can probably figure it out.

    opened by PerBothner 11
  • have HtmlTable emit td/thead/tbody elements

    have HtmlTable emit td/thead/tbody elements

    Could I request:

    • Group the <tr> row elements into <thead> and <tbody> groups.
    • Use <th> rather than <td> inside the header (<thead>).

    The latter is easy to do: In table_to_html/lib.rs just replace

    tag("td", attrs, text)
    

    by

    tag(if row > 0 {"td"} else {"th"}, attrs, text)
    

    However, do to my limited Rust-fluency I'm having problems with the former - which is actually more important for my needs.

    My application: I'm implementing emitting HTML when running nushell under DomTerm. I basically have this working: the table is displayed as an <table> and it looks nice. However, DomTerm has a nice feature when it sees a table with <thead> and <tbody>: As long as any part of the body is visible, it will show the header at the top of the window: the head stays static while the body scrolls. I think this is quite nice.

    opened by PerBothner 10
  • Table is not rendering correctly and is loosing some cells

    Table is not rendering correctly and is loosing some cells

    Thank you for making this wonderful crate ❤️

    I've been using tabled in my tool to render some data in tabular form but I am facing some issues regarding the rendering of the table. The data that I am trying to render is the following

    CMode                                                                   *CMode*
        Comment modes - Can be manual or computed in operator-pending phase
    
        Fields: ~
            {number}  (toggle)     Toggle action
            {number}  (comment)    Comment action
            {number}  (uncomment)  Uncomment action
    

    There is only one limitation that is the data has 80 character limit on a single line everything else should be wrapped. So I came up with the following

    Code
    let fields = Table::new([
        ["{number}", "(toggle)", "Toggle action"],
        ["{number}", "(comment)", "Comment action"],
        ["{number}", "(uncomment)", "Uncomment action"],
    ])
    .with(Disable::Row(..1))
    .with(Header("Fields: ~"))
    // .with(Style::blank())
    .with(Margin::new(3, 0, 0, 0))
    .with(Modify::new(Columns::new(..1)).with(Padding::new(4, 0, 0, 0)))
    .with(Modify::new(Cell(0, 0)).with(Padding::new(0, 0, 0, 0)))
    .with(Modify::new(Full).with(Alignment::left()));
    
    let head = Table::new([
        ["Cmode", "*CMode*"],
        [
            "Comment modes - Can be manual or computed in operator-pending phase",
            "",
        ],
        ["", ""],
        [fields.to_string().as_str(), ""],
    ])
    .with(Disable::Row(..1))
    .with(MaxWidth::wrapping(83))
    .with(MinWidth::new(83))
    // .with(Style::blank())
    .with(Modify::new(Cell(1, 0)).with(Padding::new(4, 0, 0, 0)))
    .with(Modify::new(Columns::new(1..=2)).with(Alignment::right()))
    .with(Modify::new(Columns::new(..1)).with(Alignment::left()))
    .with(Modify::new(Cell(1, 0)).with(Span::column(2)));
    
    Output
    +---------------------------------------------------------+--------------+
    | Cmode                                                   |     *CMode*  |
    +---------------------------------------------------------+--------------+
    |    Comment modes - Can be manual or computed in operator-pending phase |
    +---------------------------------------------------------+--------------+
    |                                                         |              |
    +---------------------------------------------------------+--------------+
    |     Fields: ~                                           |              |
    |    +------------+-------------+------------------+      |              |
    |    |    {number}| (toggle)    | Toggle action    |      |              |
    |    +------------+-------------+------------------+      |              |
    |    |    {number}| (comment)   | Comment action   |      |              |
    |    +------------+-------------+------------------+      |              |
    |    |    {number}| (uncomment) | Uncomment action |      |              |
    |    +------------+-------------+------------------+      |              |
    +---------------------------------------------------------+--------------+
    

    As you can see, the table doesn't respect (underflow) the width which I believe is because of Span because when I remove the last line i.e .with(Modify::new(Cell(1, 0)).with(Span::column(2))) from the above code then the width goes above the specified MaxWidth.

    Details
    +------------------------------------------------------------------------+----------+
    | Cmode                                                                  | *CMode*  |
    +------------------------------------------------------------------------+----------+
    |    Comment modes - Can be manual or computed in operator-pending phase |          |
    +------------------------------------------------------------------------+----------+
    |                                                                        |          |
    +------------------------------------------------------------------------+----------+
    |     Fields: ~                                                          |          |
    |    +------------+-------------+------------------+                     |          |
    |    |    {number}| (toggle)    | Toggle action    |                     |          |
    |    +------------+-------------+------------------+                     |          |
    |    |    {number}| (comment)   | Comment action   |                     |          |
    |    +------------+-------------+------------------+                     |          |
    |    |    {number}| (uncomment) | Uncomment action |                     |          |
    |    +------------+-------------+------------------+                     |          |
    +------------------------------------------------------------------------+----------+
    

    Data loss

    Now, If I extend the text in the second row then the cell at 0,1 loses its content.

    Code
    let fields = Table::new([
        ["{number}", "(toggle)", "Toggle action"],
        ["{number}", "(comment)", "Comment action"],
        ["{number}", "(uncomment)", "Uncomment action"],
    ])
    .with(Disable::Row(..1))
    .with(Header("Fields: ~"))
    // .with(Style::blank())
    .with(Margin::new(3, 0, 0, 0))
    .with(Modify::new(Columns::new(..1)).with(Padding::new(4, 0, 0, 0)))
    .with(Modify::new(Cell(0, 0)).with(Padding::new(0, 0, 0, 0)))
    .with(Modify::new(Full).with(Alignment::left()));
    
    let head = Table::new([
        ["Cmode", "*CMode*"],
        [
            "Comment modes - Can be manual or computed in operator-pending phase. I am going to extend this line and see what happens to the table.",
            "",
        ],
        ["", ""],
        [fields.to_string().as_str(), ""],
    ])
    .with(Disable::Row(..1))
    .with(MinWidth::new(83))
    .with(MaxWidth::wrapping(83))
    // .with(Style::blank())
    .with(Modify::new(Cell(1, 0)).with(Padding::new(4, 0, 0, 0)))
    .with(Modify::new(Columns::new(1..=2)).with(Alignment::right()))
    .with(Modify::new(Columns::new(..1)).with(Alignment::left()))
    .with(Modify::new(Cell(1, 0)).with(Span::column(2)));
    
    Details
    +-----------------------------------------------------------------+--------------+
    | Cmode                                                           |              |
    +-----------------------------------------------------------------+--------------+
    |    Comment modes - Can be manual or computed in operator-pending phase. I am go|
    |    ing to extend this line and see what happens to the table.                  |
    +-----------------------------------------------------------------+--------------+
    |                                                                 |              |
    +-----------------------------------------------------------------+--------------+
    |     Fields: ~                                                   |              |
    |    +------------+-------------+------------------+              |              |
    |    |    {number}| (toggle)    | Toggle action    |              |              |
    |    +------------+-------------+------------------+              |              |
    |    |    {number}| (comment)   | Comment action   |              |              |
    |    +------------+-------------+------------------+              |              |
    |    |    {number}| (uncomment) | Uncomment action |              |              |
    |    +------------+-------------+------------------+              |              |
    +-----------------------------------------------------------------+--------------+
    

    I might be doing something wrong here so if you can help me with this that will be very helpful.

    opened by numToStr 10
  • tabled_derive v0.1.9 update introduced a semver-incompatible change

    tabled_derive v0.1.9 update introduced a semver-incompatible change

    Now it implements the LENGTH associated constant which is missing from tabled v0.3.0. This results in broken builds for other projects (try cargo install cargo-bom). I'd suggest yanking tabled_derive v0.1.9 and releasing tabled_derive v0.2.0 with subsequent update to tabled.

    opened by Disasm 9
  • Add a way to print  a list of tables in line

    Add a way to print a list of tables in line

    I noticed that there's no easy way to print 2 tables in a row. Like this

    ┌───────────────┬─────────┬──────────┬───────────┐        ┌───────────────┬─────────┬──────────┬───────────┐
    │ temperature_c │ wind_ms │ latitude │ longitude │        │ temperature_c │ wind_ms │ latitude │ longitude │
    ├───────────────┼─────────┼──────────┼───────────┤        ├───────────────┼─────────┼──────────┼───────────┤
    │      16       │  3000   │ 111.111  │  333.333  │        │      16       │  3000   │ 111.111  │  333.333  │
    │      -20      │   300   │  5.111   │  7282.1   │        │      -20      │   300   │  5.111   │  7282.1   │ 
    │      40       │   100   │    0     │     0     │        │      40       │   100   │    0     │     0     │
    └───────────────┴─────────┴──────────┴───────────┘        └───────────────┴─────────┴──────────┴───────────┘
    

    We could create a different type for achieving it.

    Or we could create a type which implements Tabled and wraps Table. So essentially a 2 tables inside 1 would be printed. Like this

    let line = Table::new([ table1, table2 ])
    

    Actually it will not work because Table::new takes rows not columns

    If anyone have an idea according to the design please feel free to discuss.

    enhancement good first issue 
    opened by zhiburt 9
  • Create `Colorization` structure to colorize table.

    Create `Colorization` structure to colorize table.

    See #126 for a picture how output could look like after using the option.

    The idea is to create Colorization as structure for table colorization.

    table.with(Colorization::new(color1, color2).pattern(ColorizationPattern::Chess))
    
    enum ColorizationPattern {
      Columns,
      Rows,
      Chess,
    }
    
    enhancement good first issue 
    opened by zhiburt 0
  • Add `BorderChar` support on `Table` level so offset value will be used across all cells.

    Add `BorderChar` support on `Table` level so offset value will be used across all cells.

    Currently we can do the following.

    use tabled::{
        style::{BorderChar, Offset, Style},
        Modify, Table,
    };
    
        let table = Table::new([["Hello", "World", "!"]])
            .with(Style::markdown())
            .with(
                Modify::new(Columns::new(..))
                    .with(BorderChar::horizontal(':', Offset::Begin(0)))
                    .with(BorderChar::horizontal(':', Offset::End(0))),
            )
            .to_string();
    

    But sometimes we know exact position without any reference to a cell.

    So the idea that we could run the following.

    use tabled::{
        style::{BorderChar, Offset, Style},
        Modify, Table,
    };
    
        let table = Table::new([["Hello", "World", "!"]])
            .with(Style::markdown())
            .with(BorderChar::horizontal(':', Offset::Begin(0))
            .with(BorderChar::horizontal(':', Offset::End(0)),
            .to_string();
    
    enhancement good first issue 
    opened by zhiburt 0
  • Add `CSS` themes for `HtmlTable`

    Add `CSS` themes for `HtmlTable`

    Currently we only provide a raw HTML <table>.

    The idea is that we will able to supply custom CSS for tables. The CSS can match Styles.

    https://github.com/zhiburt/tabled/tree/master/table_to_html

    enhancement good first issue table_to_html 
    opened by zhiburt 0
Owner
Maxim Zhiburt
Maxim Zhiburt
Tiny Rust library to draw pretty line graphs using ascii characters.

rasciigraph Tiny Rust library to draw pretty line graphs using ascii characters. Usage Add this to your Cargo.toml [dependencies] rasciigraph = "0.1.1

Orhan Balci 54 Jan 6, 2023
Print pacman package files

Paccat Print pacman package files Usage paccat [options] <targets> -- <files> a target can be specified as <pkgname>, <repo>/<pkgname>, <url> or <file

Lulu 25 May 1, 2022
As-tree - Print a list of paths as a tree of paths 🌳

as-tree Print a list of paths as a tree of paths. For example, given: dir1/foo.txt dir1/bar.txt dir2/qux.txt it will print: . ├── dir1 │ ├── foo.tx

Jake Zimmerman 396 Dec 10, 2022
Print your git contributions in your terminal, blazingly fast

Takoyaki Blazingly fast git contribution graph in your terminal Features ✔️ Customizable ✔️ Plugins to support a bunch of cloud based git repositories

kyeboard 13 Feb 6, 2023
Macro to print variable(s) with values nicely (stripped from release builds)

log_macro Macro to print variable(s) with values nicely (stripped from release builds) Install cargo add log_macro Use Add this to top of file: #[mac

Nikita 3 Aug 22, 2023
Pure-Rust implementation of Fast Static Symbol Tables string compression

fsst-rs A pure-Rust, zero-dependency implementation of the FSST string compression algorithm. FSST is a string compression algorithm meant for use in

Spiral 78 Sep 29, 2024
:large_orange_diamond: Build beautiful terminal tables with automatic content wrapping

Comfy-table Comfy-table tries to provide utility for building beautiful tables, while being easy to use. Features: Dynamic arrangement of content to a

Arne Beer 525 Jan 8, 2023
Solving context limits when working with AI LLM models by implementing a "chunkable" attribute on your prompt structs.

Promptize Promptize attempts to solve the issues with context limits when working with AI systems. It allows a user to add an attribute to their struc

Dan Nelson 5 Jul 18, 2023
Derive forms from structs.

leptos_form: Derive leptos forms from rust structs Documentation Docs GitHub repository Cargo package Minimum supported Rust version: 1.75.0 or later

null 10 Nov 25, 2023
App to collect ram/cpu usage from OS and show it in pretty graphs

System info collector This is simple app to collect data about system cpu and memory usage over time. After collecting results into csv file, html fil

Rafał Mikrut 3 Jul 11, 2023
A small CLI tool to query ArcGIS REST API services, implemented in Rust. The server response is returned as pretty JSON.

A small CLI tool to query ArcGIS REST API services, implemented in Rust. The server response is returned as pretty JSON.

Andrew Vitale 2 Apr 25, 2022
😎 Pretty way of writing regular expressions in Rust

?? Write readable regular expressions The crate provides a clean and readable way of writing your regex in the Rust programming language: Without pret

Adi Salimgereyev 7 Aug 12, 2023
An easy-to-use SocketCAN library for Python and C++, built in Rust.

JCAN An easy-to-use SocketCAN library for Python and C++, built in Rust, using cxx-rs and pyo3. Warning: I have never used Rust before and I don't kno

Leigh Oliver 4 Feb 9, 2023
📺(tv) Tidy Viewer is a cross-platform CLI csv pretty printer that uses column styling to maximize viewer enjoyment.

??(tv) Tidy Viewer is a cross-platform CLI csv pretty printer that uses column styling to maximize viewer enjoyment.

Alex Hallam 1.8k Jan 2, 2023
A pretty (simple) alternative to strace

lurk lurk is a simple and pretty alternative to strace. It allows the user to trace system calls of a process or of a command. In contrast to strace,

Jakob Waibel 476 Dec 30, 2022
Designed as successor to Pretty-Good-Video for improved codec structure, API design & performance

Pretty Fast Video Minimal video codec designed as a successor to Pretty Good Video Goals are to improve: Quality API design Codec structure (Hopefully

Hazel Stagner 36 Jun 5, 2023
💫 Easy to use, robust Rust library for displaying spinners in the terminal

spinoff an easy to use, robust library for displaying spinners in the terminal ?? Install Add as a dependency to your Cargo.toml: [dependencies] spino

ad4m 401 Jun 24, 2023
Rustato: A powerful, thread-safe global state management library for Rust applications, offering type-safe, reactive state handling with an easy-to-use macro-based API.

Rustato State Manager A generical thread-safe global state manager for Rust Introduction • Features • Installation • Usage • Advanced Usage • Api Refe

BiteCraft 8 Sep 16, 2024
Nodium is an easy-to-use data analysis and automation platform built using Rust, designed to be versatile and modular.

Nodium is an easy-to-use data analysis and automation platform built using Rust, designed to be versatile and modular. Nodium aims to provide a user-friendly visual node-based interface for various tasks.

roggen 19 May 2, 2023