Giter Club home page Giter Club logo

tabled's Introduction

github crates.io docs.rs build status coverage dependency status

logo tabled

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

You can do a lot of things with the library.
If it doesn't do something which you feel it should or it's not clear how to, please file an issue.

This README contains a lot of information but it might be not complete,
you can find more examples in an examples folder.

Preview

Table of Contents

Usage

To print a list of structs or enums as a table, there are 2 ways.

  • Using a builder method, to build a table step by step
  • Implement a Tabled trait for your type (or annotate your type with a derive macro) and use a iterator of this type.

A builder method gets handy, when a data schema is unknown,
while a typed struct is useful in cases where we know the data structure beforehand.

Notice that there are a lot of mods available for your tables, as well as helpers such as derive macros and proc macros.

Below are shown both of these methods.
The example below is demonstrates a derive method.

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: "Go",
        designed_by: "Rob Pike",
        invented_year: 2009
    },
    Language{
        name: "Rust",
        designed_by: "Graydon Hoare",
        invented_year: 2010
    },
];

let table = Table::new(languages).to_string();

let expected = "+------+----------------+---------------+\n\
                | name | designed_by    | invented_year |\n\
                +------+----------------+---------------+\n\
                | C    | Dennis Ritchie | 1972          |\n\
                +------+----------------+---------------+\n\
                | Go   | Rob Pike       | 2009          |\n\
                +------+----------------+---------------+\n\
                | Rust | Graydon Hoare  | 2010          |\n\
                +------+----------------+---------------+";

assert_eq!(table, expected);

The next example shows a builder example.

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

let lyrics = r#"
    And the cat's in the cradle and the silver spoon
    Little boy blue and the man on the moon
    When you comin' home dad?
    I don't know when, but we'll get together then son
    You know we'll have a good time then
"#;

let mut builder = Builder::default();
for line in lyrics.lines() {
    let line = line.trim();
    if line.is_empty() {
        continue;
    }

    let words: Vec<_> = line.split_terminator(' ').collect();
    builder.push_record(words);
}

let columns = (0..builder.count_columns()).map(|i| i.to_string());
builder.insert_record(0, columns);

let mut table = builder.build();
table.with(Style::ascii_rounded());

let expected = concat!(
    ".------------------------------------------------------------------------------------.\n",
    "| 0      | 1     | 2      | 3     | 4    | 5      | 6    | 7        | 8      | 9     |\n",
    "| And    | the   | cat's  | in    | the  | cradle | and  | the      | silver | spoon |\n",
    "| Little | boy   | blue   | and   | the  | man    | on   | the      | moon   |       |\n",
    "| When   | you   | comin' | home  | dad? |        |      |          |        |       |\n",
    "| I      | don't | know   | when, | but  | we'll  | get  | together | then   | son   |\n",
    "| You    | know  | we'll  | have  | a    | good   | time | then     |        |       |\n",
    "'------------------------------------------------------------------------------------'",
);

assert_eq!(table, expected);

Settings

This section lists the set of settings you can apply to your table. Most of the settings are leveraged by Table::with and Table::modify.

Style

Styles

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 like this.

use tabled::{Table, Style};

let mut table = Table::new(&data);
table.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          |
+------+----------------+---------------+
modern
┌──────┬────────────────┬───────────────┐
│ name │ designed_by    │ invented_year │
├──────┼────────────────┼───────────────┤
│ C    │ Dennis Ritchie │ 1972          │
├──────┼────────────────┼───────────────┤
│ Rust │ Graydon Hoare  │ 2010          │
├──────┼────────────────┼───────────────┤
│ Go   │ Rob Pike       │ 2009          │
└──────┴────────────────┴───────────────┘
sharp
┌──────┬────────────────┬───────────────┐
│ 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          │
╰──────┴────────────────┴───────────────╯
extended
╔══════╦════════════════╦═══════════════╗
║ 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          
markdown
| name | designed_by    | invented_year |
|------|----------------|---------------|
| C    | Dennis Ritchie | 1972          |
| Rust | Graydon Hoare  | 2010          |
| Go   | Rob Pike       | 2009          |
re_structured_text
====== ================ ===============
 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          :
:......:................:...............:
ascii_rounded
.---------------------------------------.
| 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                 
empty
name designed_by    invented_year
C    Dennis Ritchie 1972         
Rust Graydon Hoare  2010         
Go   Rob Pike       2009         

Style customization

You can modify existing styles to fit your needs. Notice that all modifications are done at compile time.

Check the documentation for more customization options.

use tabled::settings::{Style, HorizontalLine, VerticalLine};

let style = Style::modern()
    .horizontals([(1, HorizontalLine::inherit(Style::modern()).horizontal('═'))])
    .verticals([(1, VerticalLine::inherit(Style::modern()))])
    .remove_horizontal()
    .remove_vertical();

The style will look like the following for the first example.

┌──────┬───────────────────────────────┐
│ name │ designed_by     invented_year │
├══════┼═══════════════════════════════┤
│ CDennis Ritchie  1972          │
│ GoRob Pike        2009          │
│ RustGraydon Hoare   2010          │
└──────┴───────────────────────────────┘

Also you can build your own one from scratch, and doing so is not always possible at compile time, so you may use Theme object to do that.

Notice that the Theme is quite powerful by itself, you can check it in the documentation.

use tabled::grid::config::{Border, HorizontalLine};
use tabled::settings::Theme;

let mut style = Theme::default();
style.set_lines_horizontal(HashMap::from_iter([(1, HorizontalLine::full('-', '-', '+', '+'))]));
style.set_border_frame(Border::filled('+'));

The style will look like the following.

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

Cell Border

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

For this purpose you can use Border.

use tabled::{settings::{object::Rows, Border, Style}, Table};

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

let table = Table::new(data)
    .with(Style::ascii())
    .modify(Rows::first(), Border::new().set_top('x'))
    .to_string();

assert_eq!(
    table,
    "+xxxxx+xxxxx+\n\
     | 0   | 1   |\n\
     +-----+-----+\n\
     | 123 | 456 |\n\
     +-----+-----+\n\
     | 789 | 000 |\n\
     +-----+-----+"
);

Text on borders

You can set a string to a horizontal border line.

use tabled::{settings::style::LineText, Table};
use tabled::settings::object::Rows;

let mut table = Table::new(["Hello World"]);
table.with(LineText::new("+-.table", Rows::first()));

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

Sometimes though it's not convenient to set a string. But rather necessary to set a custom char.

You can use LineChar to achieve this.

use tabled::{
    settings::{
        object::Columns,
        style::{LineChar, Offset},
        Modify, Style,
    },
    Table,
};

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

assert_eq!(
    table,
    "| 0     | 1     | 2 |\n\
     |:-----:|:-----:|:-:|\n\
     | Hello | World | ! |"
);

Colorize borders

You can set the colors of all borders using Color.

use tabled::settings::{style::BorderColor, Color};

table.with(BorderColor::new().set_top(Color::FG_GREEN));

You can also set a color border of an individual cell by using BorderColored.

use tabled::settings::{object::Columns, style::BorderColor, Color};

table.modify(Columns::single(2), BorderColor::new().set_top(Color::FG_GREEN));

Theme

It was considered that having a few atomic settings is better rather then have one but feature full.

But tabled::settings::themes::* diverges a bit from this idea. It contains a list of settings which do bigger changes to the table.

The first one is Theme itself. You can change layout, set style, colorize, config borders and even reverse the table with it.

use tabled::settings::{
    object::{Columns, Object},
    Alignment, Style, Theme,
};

let mut style = Theme::from_style(Style::ascii_rounded());
style.remove_border_horizontal();
style.remove_border_vertical();
style.align_columns(Alignment::left());
style.set_footer(true);

table.with(style);
table.modify(Columns::new(1..).not(Columns::last()), Alignment::center());
table.modify(Columns::last(), Alignment::right());

You'll see the following output when run against the first example.

.---------------------------------------------------------------------------.
| name                  C             Go          Rust                 name |
| designed_by     Dennis Ritchie   Rob Pike   Graydon Hoare     designed_by |
| invented_year        1972          2009         2010        invented_year |
'---------------------------------------------------------------------------'
Colorize content

You can colorize the content by the pattern or a specific cell.

use std::iter::FromIterator;

use tabled::{
    builder::Builder,
    settings::{object::Rows, style::Style, themes::Colorization, Color},
};

let data = vec![
    vec!["Word", "Translation", "Lang"],
    vec!["World", "le monde", "FR"],
    vec!["World", "Welt", "DE"],
];

let color_col1 = Color::BG_GREEN | Color::FG_BLACK;
let color_col2 = Color::BG_MAGENTA | Color::FG_BLACK;
let color_col3 = Color::BG_YELLOW | Color::FG_BLACK;
let color_head = Color::BG_WHITE | Color::FG_BLACK;
let color_head_text = Color::BG_BLUE | Color::FG_BLACK;

let mut table = Builder::from_iter(data).build();
table
    .with(Style::empty())
    .with(Colorization::columns([color_col1, color_col2, color_col3]))
    .with(Colorization::exact([color_head], Rows::first()))
    .modify(Rows::first(), color_head_text);

println!("{table}");
Preview
Column names

You can move the header right to the borders.

use tabled::{
    builder::Builder,
    settings::{style::Style, themes::ColumnNames},
};

let data = vec![
    vec![String::from("header 0"), String::from("header 1")],
    vec![String::from("Hello"), String::from("World")],
    vec![String::from("Bonjour"), String::from("le monde")],
    vec![String::from("Hallo"), String::from("Welt")],
];

let mut table = Builder::from(data).build();
table.with(Style::modern()).with(ColumnNames::default());

println!("{table}");
┌header 0─┬header 1──┐
│ Hello   │ World    │
├─────────┼──────────┤
│ Bonjour │ le monde │
├─────────┼──────────┤
│ Hallo   │ Welt     │
└─────────┴──────────┘

Alignment

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

use tabled::{
    settings::{object::Segment, Alignment, Settings},
    Table,
};

let data = [("Text", "Multiline\ntext"), ("text", "text")];
let mut table = Table::new(data);
table.modify(
    Segment::all(),
    Settings::new(Alignment::right(), Alignment::bottom()),
);

println!("{table}");

The output would be.

+------+-----------+
| &str |      &str |
+------+-----------+
|      | Multiline |
| Text | text      |
+------+-----------+
| text |      text |
+------+-----------+

Format

The Format function provides an interface for modification of cells.

use tabled::{
    settings::{format::Format, object::Rows},
    Table,
};

let data = vec![[0; 10]; 9];
let mut table = Table::new(data);
table.modify(
    Rows::new(..),
    Format::positioned(|_, (row, col)| ((row + 1) * (col + 1)).to_string()),
);

println!("{table}");

The result you must get will be.

+----+----+----+----+----+----+----+----+----+-----+
| 1  | 2  | 3  | 4  | 5  | 6  | 7  | 8  | 9  | 10  |
+----+----+----+----+----+----+----+----+----+-----+
| 2  | 4  | 6  | 8  | 10 | 12 | 14 | 16 | 18 | 20  |
+----+----+----+----+----+----+----+----+----+-----+
| 3  | 6  | 9  | 12 | 15 | 18 | 21 | 24 | 27 | 30  |
+----+----+----+----+----+----+----+----+----+-----+
| 4  | 8  | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40  |
+----+----+----+----+----+----+----+----+----+-----+
| 5  | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50  |
+----+----+----+----+----+----+----+----+----+-----+
| 6  | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 60  |
+----+----+----+----+----+----+----+----+----+-----+
| 7  | 14 | 21 | 28 | 35 | 42 | 49 | 56 | 63 | 70  |
+----+----+----+----+----+----+----+----+----+-----+
| 8  | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80  |
+----+----+----+----+----+----+----+----+----+-----+
| 9  | 18 | 27 | 36 | 45 | 54 | 63 | 72 | 81 | 90  |
+----+----+----+----+----+----+----+----+----+-----+
| 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100 |
+----+----+----+----+----+----+----+----+----+-----+

Padding

The Padding structure provides an interface for left, right, top and bottom padding of cells. You can set indent size and color of the padding.

use tabled::settings::{
    object::{Columns, Object, Rows},
    Color, Padding,
};

// Set a padding size for first column
table.modify(Columns::first().not((0, 0)), Padding::new(0, 10, 0, 0));

// Set a padding for a last column (except first row)
table.modify(Columns::last().not(Rows::first()), Padding::new(1, 1, 0, 0).fill('[', ']', ' ', ' '));

// Set a padding for a first row
table.modify(
    Rows::first(),
    Padding::new(2, 2, 0, 2).fill(' ', ' ', ' ', ' ').colorize(
        Color::BG_BLUE,
        Color::BG_BLUE,
        Color::BG_BLUE,
        Color::BG_BLUE,
    ),
);

Applying the last change to the first example will result in the following.

Preview

Margin

Margin sets extra space around the table (top, bottom, left, right). As for Padding you can set indent, size and color of the extra space.

use tabled::settings::Margin;

table.with(Margin::new(3, 4, 1, 2).fill('>', '<', 'v', '^'));

If you run it for a first example you'll get.

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

Margin Color

You can set a color for the characters.

use tabled::settings::{Margin, Color};

table.with(Margin::new(3, 4, 1, 2).fill('>', '<', 'v', '^').colorize(
    Color::BG_BRIGHT_BLUE,
    Color::BG_BRIGHT_CYAN,
    Color::BG_BLUE,
    Color::BG_RED,
));
Preview

Shadow

Shadow can be used to set a 'shadow' like margin.

use tabled::{settings::{Style, Shadow}, Table};

let data = vec![["A", "B", "C"]];
let table = Table::new(data)
    .with(Style::modern())
    .with(Shadow::new(1))
    .to_string();

println!("{}", table);

An output could look like the following.

┌───┬───┬───┐ 
│ 0 │ 1 │ 2 │▒
├───┼───┼───┤▒
│ A │ B │ C │▒
└───┴───┴───┘▒
 ▒▒▒▒▒▒▒▒▒▒▒▒▒

Width

Using the following structures you can configure the width of a table and a single cell.

BEWARE that Width controls only content, so it can't make things smaller then a certain minimum. BEWARE that it DOES NOT consider Padding when adjusting the width.

The functions preserves ansi color sequences (when ansi feature is on).

Below is an example of setting an exact table width.

use tabled::{
    settings::{
        peaker::{PriorityMax, PriorityMin},
        Settings, Width,
    },
    Table,
};

fn gen_table(string_size: usize, width: usize) -> String {
    let data = vec![(string_size.to_string(), "x".repeat(string_size))];

    let mut table = Table::new(data);
    table.with(Settings::new(
        Width::wrap(width).priority::<PriorityMax>(),
        Width::increase(width).priority::<PriorityMin>(),
    ));

    table.to_string()
}

let table = gen_table(50, 40);
println!("{table}");

let table = gen_table(20, 40);
println!("{table}");

The result must be seen as following.

+--------+-----------------------------+
| String | String                      |
+--------+-----------------------------+
| 50     | xxxxxxxxxxxxxxxxxxxxxxxxxxx |
|        | xxxxxxxxxxxxxxxxxxxxxxx     |
+--------+-----------------------------+
+---------------+----------------------+
| String        | String               |
+---------------+----------------------+
| 20            | xxxxxxxxxxxxxxxxxxxx |
+---------------+----------------------+

Truncate

Truncate sets a maximum width of a cell by truncating its content.

use tabled::settings::{Width, object::Rows};

// Truncating content to 10 chars in case it's bigger than that
// in a first row.
table.modify(Rows::first(), Width::truncate(10));

// Truncating content to 7 chars and puts a suffix '...' after it
// in all rows except a first.
table.modify(Rows::new(1..), Width::truncate(10).suffix("..."));

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

use tabled::settings::Width;

// Tries to set table width to 22, in case it's bigger than that.
table.with(Width::truncate(22));

It can be used in combination with MinWidth to set an exact table size.

Wrapping

Wrap sets a maximum width of a cell by wrapping its content to new lines.

use tabled::settings::{Width, object::Rows};

// Wrap content to 10 chars in case it's bigger than that
// in a first row.
table.modify(Rows::first().with(Width::wrap(10)));

// Use a strategy where we try not to keep words split (where possible).
table.modify(Rows::new(1..).with(Width::wrap(10).keep_words()));

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

use tabled::settings::Width;

// Tries to set table width to 22, in case it's bigger than that.
table.with(Width::wrap(22));

It can be used in combination with MinWidth to set an exact table size.

Increase width

MinWidth sets a minimal width of an object.

use tabled::settings::{Width, object::Rows};

// increase the space used by cells in all rows except the header to be at least 10
table.modify(Rows::new(1..), Width::increase(10));

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

use tabled::settings::Width;

// increase width of a table in case it was lower than 10.
table.with(Width::increase(10));

It can be used in combination with Truncate and Wrap to set an exact table size.

Justify

You can set a constant width for all columns using Justify.

use tabled::settings::Width;

table.with(Width::justify(10));

Priority

You can tweak Truncate, Wrap, MinWidth logic by setting a priority by which a trim/inc be done.

use tabled::settings::{Width, peaker::PriorityMax};

table.with(Width::truncate(10).priority::<PriorityMax>());

Percent

By default you use usize int to set width settings, but you could do it also with tabled::width::Percent.

use tabled::settings::{Width, measurement::Percent};

table.with(Width::wrap(Percent(75)));

Height

You can increase a table or a specific cell height using the Height modifier.

Beware that Height controls only content, so it can't make things smaller then a certain minimum.

Below is an example of setting an exact table height and width.

use std::iter::FromIterator;
use tabled::{
    settings::{Height, Settings, Width},
    Table,
};

fn gen_data(width: usize, height: usize) -> Vec<Vec<String>> {
    let dims = format!("{}x{}", width, height);
    let string = vec!["x".repeat(width); height].join("\n");

    vec![
        vec![String::from("N"), String::from("string")],
        vec![dims, string],
    ]
}

fn gen_table(data: Vec<Vec<String>>, width: usize, height: usize) -> String {
    let mut table = Table::from_iter(data);

    table.with(
        Settings::empty()
            .with(Width::truncate(width))
            .with(Width::increase(width))
            .with(Height::increase(height))
            .with(Height::limit(height)),
    );

    table.to_string()
}

println!("{}", gen_table(gen_data(40, 10), 30, 8));
println!("{}", gen_table(gen_data(40, 4), 80, 12));
+-------+--------------------+
| N     | string             |
|       |                    |
|       |                    |
+-------+--------------------+
| 40x10 | xxxxxxxxxxxxxxxxxx |
|       |                    |
+-------+--------------------+
+-----------------------------------+------------------------------------------+
| N                                 | string                                   |
|                                   |                                          |
|                                   |                                          |
+-----------------------------------+------------------------------------------+
| 40x4                              | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|                                   | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|                                   | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|                                   | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|                                   |                                          |
|                                   |                                          |
+-----------------------------------+------------------------------------------+

Height increase

Increasing the height of a cell of a whole table could be done by Height::increase.

use tabled::settings::{Height, object::Rows};

// increase height of a table in case it was lower than 10.
table.with(Height::increase(10));

// increase height of cells in the last row on a table in case if some of them has it lower than 10.
table.modify(Rows::last(), Height::increase(10));

Height limit

Truncation of the height of a cell of a whole table could be done by Height::limit.

use tabled::settings::{Height, object::Rows};

// decrease height of a table to 10 in case it was bigger than that.
table.with(Height::limit(10));

// decrease height of cells in the last row on a table to 10 in case if some of them has it bigger than that.
table.modify(Rows::last(), Height::limit(10));

Rotate

You can rotate a table using tabled::Rotate.

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

┌────┬──────────────┬───────────────────────────┐
│ id │ distribution │ 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 rotated;

use tabled::settings::Rotate;

table.with(Rotate::Left);
┌──────────────┬────────────────────────┬───────────────────────────┬──────────────────────────┐
│ link         │ https://getfedora.org/ │ https://www.opensuse.org/ │ https://endeavouros.com/ │
├──────────────┼────────────────────────┼───────────────────────────┼──────────────────────────┤
│ distribution │ Fedora                 │ OpenSUSE                  │ Endeavouros              │
├──────────────┼────────────────────────┼───────────────────────────┼──────────────────────────┤
│ id           │ 0                      │ 2                         │ 3                        │
└──────────────┴────────────────────────┴───────────────────────────┴──────────────────────────┘

Disable

You can remove certain rows or columns from the table by Disable.

use tabled::settings::{
    object::{Columns, Rows},
    Disable,
};

table
    .with(Disable::row(Rows::first()))
    .with(Disable::column(Columns::single(2)));

If the above example be applied for a first example in a file it would look like this.

+------+----------------+
| C    | Dennis Ritchie |
+------+----------------+
| Go   | Rob Pike       |
+------+----------------+
| Rust | Graydon Hoare  |
+------+----------------+

Extract

You can Extract data segments of a table to focus on it closely.

use tabled::settings::Extract;

table.with(Extract::segment(1..3, 1..));
+-------+-------------+-----------+
|  i32  |    &str     |   bool    |
+-------+-------------+-----------+         +-------------+-----------+
| : 0 : | : Grodno :  | : true :  |         | : Grodno :  | : true :  |
+-------+-------------+-----------+    =    +-------------+-----------+
| : 1 : |  : Minsk :  | : true :  |         |  : Minsk :  | : true :  |
+-------+-------------+-----------+         +-------------+-----------+
| : 2 : | : Hamburg : | : false : |
+-------+-------------+-----------+
| : 3 : |  : Brest :  | : true :  |
+-------+-------------+-----------+

Header and Footer and Panel

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

use tabled::settings::Panel;

let count_elements = table.count_rows();

table
    .with(Panel::vertical(0, "A vertical panel").width(1))
    .with(Panel::header("Tabled Name"))
    .with(Panel::footer(format!("{} elements", count_elements)));

When applied to the main example of this file it will result in the following output.

+---+------+----------------+---------------+
| Tabled Name                               |
+---+------+----------------+---------------+
| A | name | designed_by    | invented_year |
|   |      |                |               |
| v |      |                |               |
| e |      |                |               |
+ r +------+----------------+---------------+
| t | C    | Dennis Ritchie | 1972          |
| i |      |                |               |
| c |      |                |               |
+ a +------+----------------+---------------+
| l | Go   | Rob Pike       | 2009          |
|   |      |                |               |
| p |      |                |               |
+ a +------+----------------+---------------+
| n | Rust | Graydon Hoare  | 2010          |
| e |      |                |               |
| l |      |                |               |
+---+------+----------------+---------------+
| 4 elements                                |
+---+------+----------------+---------------+

Merge

It's possible to create "Panel"s by combining the duplicates using Merge.

use tabled::{settings::merge::Merge, Table};

let data = [['A', 'B', 'B'], ['A', 'W', 'E'], ['Z', 'Z', 'Z']];

let mut table = Table::new(data);
table.with(Merge::horizontal()).with(Merge::vertical());

println!("{}", table);
+---+---+---+
| 0 | 1 | 2 |
+---+---+---+
| A | B     |
+   +---+---+
|   | W | E |
+---+---+---+
| Z         |
+---+---+---+

Concat

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

The example below shows the result of horizontal concat of the primary table of this file.

use tabled::settings::Concat;

table.with(Concat::horizontal(table.clone()));

The result.

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

The example below show the result of vertical concat of the primary table of this file.

use tabled::settings::Concat;

table.with(Concat::vertical(table.clone()));

The result.

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

Highlight

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

use tabled::{
    settings::{
        object::{Columns, Object, Rows},
        Border, Highlight, Style,
    },
    Table,
};

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

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

println!("{}", table);

The resulting table would be the following.

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

Span

It's possible to set a horizontal(column) span and vertical(row) span to a cell. For certain look and feel, this might cause visual artifacts on table borders (see #399). This can be fixed by using tabled::settings::style::BorderSpanCorrection.

Horizontal span

use tabled::{
    settings::{Alignment, Span},
    Table,
};

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

let mut table = Table::new(data);
table
    .modify((0, 0), Span::column(3))
    .modify((1, 0), Span::column(2))
    .with(Alignment::center());

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

Vertical span

use tabled::{
    settings::{Alignment, Span},
    Table,
};

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

let mut table = Table::new(data);
table
    .modify((0, 1), Span::row(3))
    .with(Alignment::center())
    .with(Alignment::center_vertical());

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

Split

You can Split a table on a row or column to redistribute the cells beyond that point into a new shape with the provided point acting as the new, upper boundary in the direction selected.

Adding this to a first example will result in the next table.

use tabled::settings::{Style, split::Split};

table.with(Style::modern());
table.with(Split::column(2).concat());

The result of the running example will be as follows.

┌───────────────┬────────────────┐
│ name          │ designed_by    │
├───────────────┼────────────────┤
│ C             │ Dennis Ritchie │
├───────────────┼────────────────┤
│ Go            │ Rob Pike       │
├───────────────┼────────────────┤
│ Rust          │ Graydon Hoare  │
├───────────────┼────────────────┤
│ invented_year │                │
├───────────────┼────────────────┤
│ 1972          │                │
├───────────────┼────────────────┤
│ 2009          │                │
├───────────────┼────────────────┤
│ 2010          │                │
└───────────────┴────────────────┘

Duplicate

It's possible to duplicate a given set of cell to another set.

use tabled::{Table, settings::{Dup, object::Rows}};

let mut table = Table::new(data);

// copy lastfirst line to the last line (last line gets erased).
table.with(Dup::new(Rows::last(), Rows::first()));

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;

The Tabled macro is available when derive feature in turned on. And it is by default.

Most of the default types implement the corresponding traits too.

Override a column name

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,
}

Format headers

Beside #[tabled(rename = "")] you can change a format of a column name using #[tabled(rename_all = "UPPERCASE")].

use tabled::Tabled;

#[derive(Tabled)]
#[tabled(rename_all = "CamelCase")]
struct Person {
    id: u8,
    number: &'static str,
    name: &'static str,
    #[tabled(rename_all = "snake_case")]
    middle_name: &'static str,
}

Hide a column

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

A similar effect 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,
}

Set column order

You can change the order in which they will be displayed in table.

use tabled::Tabled;

#[derive(Tabled)]
struct Person {
   id: u8,
   #[tabled(order = 0)]
   number: &'static str,
   #[tabled(order = 1)]
   name: &'static str,
}

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:

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

Alternatively, you can use the #[tabled(display_with = "func")] attribute for the field to specify a 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"),
    }
}

You can send an argument to a function like this (it also possible to use &self), using #[tabled(display_with("some_function", "arg1", 2, self))]

use tabled::Tabled;

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

impl MyRecord {
    fn display_valid(&self, arg: usize) -> String {
        match self.valid {
            Some(s) => format!("is valid thing = {} {}", s, arg),
            None => format!("is not valid {}", arg),
        }
    }
}

To reduce boilerplate code, one can also achieve this using the format attribute within #[derive(Tabled)].

use tabled::Tabled;

#[derive(Tabled)]
pub struct Motorcycle {
    weight: usize,
    #[tabled(format = "{} cc")]
    cc: usize,
}

In the above example, the cc field will be formatted using the specified format string "{} cc", where {} is replaced with the value of cc.

Just like with display_with attribute, you can pass arguments for more complex formatting scenarios:

use tabled::Tabled;

#[derive(Tabled)]
pub struct Motorcycle {
    weight: usize,
    #[tabled(format = "{}/{} cc/kg", self.cc, self.weight)]
    cc: usize,
}

In this case, the cc field will be formatted using the format string "{}/{} cc/kg", and {} will be replaced with the values of cc and weight, respectively.

Inline

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>>"))].

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(
        &'static str,
        #[tabled(inline)] Bike,
    ),
}

#[derive(Tabled)]
struct Bike {
    brand: &'static str,
    price: f32,
}

Table types

tabled has a few representations of tables. Some differ in view, and some differ in implementation details.

There are situations when you might better use one but not another. But sometimes some can be used interchangeably.

Below you'll find a short list of existing ones. You can find descriptive information about each in the documentation.

Table

Main table of the library. It's implemenentation requires that all data be stored on heap.

IterTable

It's similar to main Table, it's only difference is that it does not require a the whole buffer. It only requires a buffer for 1 row at a time.

It might be useful when you can't fit all your data in memory.

CompactTable

Similar to IterTable but it might not require any buffer. It also has capability for a sniffing logic, where we estimate data dimension on a small selection of data.

It might be useful in a very constrain environments. It is the only table which supports no-std.

PoolTable

Unlike Table it does not nessarily require columns to be aligned. It provides capabilities for a completely and utterly diverse table layout.

Example

use tabled::{
    settings::{Alignment, Style},
    tables::PoolTable,
};

let characters = [
    "Naruto Uzumaki",
    "Kakashi Hatake",
    "Minato Namikaze",
    "Jiraiya",
    "Orochimaru",
    "Itachi Uchiha",
];

let data = characters.chunks(2);

let table = PoolTable::new(data)
    .with(Style::dots())
    .with(Alignment::center())
    .to_string();

println!("{table}");

The output would look like the following.

...................................
: Naruto Uzumaki : Kakashi Hatake :
:................:................:
:  Minato Namikaze   :  Jiraiya   :
:....................:............:
:  Orochimaru   :  Itachi Uchiha  :
:...............:.................:

ExtendedTable

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

Here's an example.

use tabled::{tables::ExtendedTable, Tabled};

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

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 = ExtendedTable::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

Tips and Tricks

std::fmt::* options

You use formatting(std::fmt::*) options to apply certain settings.

use tabled::Table;

let numbers = [1, 2, 3];
let table = Table::new(numbers);

println!("{:#^10}", table);

The result will be as follows.

#+-----+##
#| i32 |##
#+-----+##
#|  1  |##
#+-----+##
#|  2  |##
#+-----+##
#|  3  |##
#+-----+##

ANSI

The library doesn't bind you in the usage of any color library but to be able to work correctly with colored input (with ANSI sequences), and avoid miscalculation of string width because of embedded ansi sequences, you should add the ansi feature to your Cargo.toml:

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

Then you can use colored strings as values and table will be properly rendered.

Tuning our favorite example will result in the following:

use tabled::{format::Format, object::Columns, Style, Table};
use owo_colors::OwoColorize;

let mut table = Table::new(&data);
table
    .with(Style::psql())
    .modify(Columns::single(0), Format::new(|s| s.red().to_string()))
    .modify(Columns::single(1), Format::new(|s| s.blue().to_string()))
    .modify(Columns::new(2..), Format::new(|s| s.green().to_string()));

carbon-2

Tuple combination

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

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

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

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

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

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

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

Object

You can apply settings to a subgroup of cells using and and not methods for an object.

use tabled::settings::object::{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.

Also you can target a column via its name using ByColumnName.

use tabled::{location::ByColumnName, Alignment, Modify};

table.with(Modify::new(ByColumnName::new("name")).with(Alignment::center()));

Builder

Builder is a powerful tool you shall be aware of.

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

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

let mut builder = Builder::default();
builder.push_record(["Index", "Language", "Status"]);
builder.push_record(["1", "English", "In progress"]);
builder.push_record(["2", "Deutsch", "Not ready"]);

let builder = builder.index().column(1).name(None);

let mut table = builder.build();
table.with(Style::rounded());

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

For example you can use transpose() method to change the layout.

// A dynamic table example
// ...

let mut builder = builder.index().transpose();
.-------------------------------------------------.
|   | 0      | 1      | 2      | 3        | 4     |
| 0 | And    | Little | When   | I        | You   |
| 1 | the    | boy    | you    | don't    | know  |
| 2 | cat's  | blue   | comin' | know     | we'll |
| 3 | in     | and    | home   | when,    | have  |
| 4 | the    | the    | dad?   | but      | a     |
| 5 | cradle | man    |        | we'll    | good  |
| 6 | and    | on     |        | get      | time  |
| 7 | the    | the    |        | together | then  |
| 8 | silver | moon   |        | then     |       |
| 9 | spoon  |        |        | son      |       |
'-------------------------------------------------'

Macros

Utilities for dynamic Table displays.

Col and Row

col! creates a single column table, with a given set of cells. row! creates a single row table, with a given set of cells.

Combine col! and row! to create flexible table visualizations.

use tabled::{col, row, settings::Style};

let mut table = row![
    col!["table 0", "0", "1", "2"],
    col!["table 1", "world"],
    col!["table 2"],
];
table.with(Style::modern_rounded());

println!("{table}");

The output you're going to see running it.

╭─────────────┬─────────────┬─────────────╮
│ +---------+ │ +---------+ │ +---------+ │
│ | table 0 | │ | table 1 | │ | table 2 | │
│ +---------+ │ +---------+ │ +---------+ │
│ | 0       | │ | world   | │             │
│ +---------+ │ +---------+ │             │
│ | 1       | │             │             │
│ +---------+ │             │             │
│ | 2       | │             │             │
│ +---------+ │             │             │
╰─────────────┴─────────────┴─────────────╯

static_table

It's possible to construct a table at compile time, via static_table. You'd need to include a different crate to use it.

static_table = "*"
let table = static_table::static_table!(
    [
        ["x", "y", "op", "result"],
        ["1", '2', '*', '2'],
        ["2", '2', '*', '4']
    ],
    THEME = "ROUNDED",
);

assert_eq!(
    table,
    "╭───┬───┬────┬────────╮\n\
     │ x │ y │ op │ result │\n\
     ├───┼───┼────┼────────┤\n\
     │ 1 │ 2 │ *  │ 2      │\n\
     │ 2 │ 2 │ *  │ 4      │\n\
     ╰───┴───┴────┴────────╯",
);

Notice that you can even use it in documentation.

/// Multiply 2 integers together.
///
#[doc = static_table::static_table!([
    ["x", "y", "result"],
    ["1", '0', '0'],
    ["1", '2', '2'],
    ["2", '2', '4']
])]
pub fn mul(left: usize, right: usize) -> usize {
    left + right
}

It will look as follows.

Preview

Features

The library has a list of features.

  • std - Used by default. If not its considered no_std with a limited set of functionality.
  • derive - Used by default. Adds support for Tabled derive macro.
  • ansi - A support for ANSI sequences.
  • macros - A support for row!, col! macro.

Formats

You can convert some formats to a Table using a utility library.

json format

You can convert arbitrary json to a Table using json_to_table library. See the example.

ron format

You can convert arbitrary ron to a Table using ron_to_table library. See the example.

csv format

You can convert arbitrary csv to a Table using csv_to_table library. See the example.

toml format

You can convert arbitrary toml to a Table using toml_to_table library. See the example.

html format

You can convert a Table into HTML <table> using table_to_html library. See the example.

Notes

Charset

Since version 0.11 we no longer have special treatment for symbols which WILL break your terminal output such as \t and \r. So if your content might contain them you shall either handle it yourself, or call tabled::settings::formatting::Charset::clean and tabled::settings::formatting::Tabsize.

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 ansi feature to your Cargo.toml

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

Emoji

The library support emojies out of the box (but sometimes ansi feature is required). 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 handle it correctly.

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

Terminal size

It's a frequent case where it's necessary to align a table to a terminal width or height. You can achieve that by using Width and Height. You can peak a strategy by which a column/row truncation/widening will be done by using Priority.

This example uses terminal_size crate to determine ones size, but it's possible to use anything.

use tabled::{
    builder::Builder,
    settings::{peaker::PriorityMax, Height, Settings, Width},
    Table,
};
use terminal_size::{terminal_size, Height as TerminalHeight, Width as TerminalWidth};

fn get_terminal_size() -> (usize, usize) {
    let (TerminalWidth(width), TerminalHeight(height)) =
        terminal_size().expect("failed to obtain a terminal size");

    (width as usize, height as usize)
}

let (width, height) = get_terminal_size();

let data = [
    ["0.2.1", "2021-06-23", "true", "#[header(inline)] attribute"],
    ["0.2.0", "2021-06-19", "false", "API changes"],
    ["0.1.4", "2021-06-07", "false", "display_with attribute"],
];

let table_settings = Settings::default()
    .with(Width::wrap(width).priority::<PriorityMax>())
    .with(Width::increase(width))
    .with(Height::limit(height))
    .with(Height::increase(height));

let mut table = Table::from_iter(data);
table.with(table_settings);

println!("{table}");

Semver

When you need to release a breaking change—any breaking change—you do it in a major version. Period. No excuses.

We still do it. We often do break change on minor version bump. So you probably shall not depend on minor version (like 0.7). It's likely better to depend on constant version e.g. =0.8.0

MSRV

Breaking MSRV considered to be a breaking change; but see semver-note

Comparison

Nowadays there's a few libraries for pretty tables. Some may wonder why tabled is better or worse than others libraries?

I hope tabled does it's job well, but at the end of the day you probably need to decide for yourself. If you have any ideas for an enhancement or have a question about tabled please file an issue.

Below you will find a list of crates which do similar things or do something which tabled doesn't.

You can find performance comparison benchmarks here.

The description is taken from the author's quotes.

  • cli-table tends to keep the compile time and crate size low and support all the platforms. It has an optional csv support.

  • comfy-table focuses on providing a minimalistic, but rock-solid library for building text-based tables with focus on safety and dynamic-length content arrangement.

  • term-table-rs main focus is on a good set of tools for rendering CLI tables, while allowing users to bring their own tools for things like colors. It has an ability to have different number of columns in each row of the table.

Please open an issue if you feel another crate is worth mentioning.

tabled's People

Contributors

akiomik avatar alexdelia avatar andeya avatar cgmossa avatar dandavison avatar danieleades avatar dependabot[bot] avatar fa993 avatar fbonin avatar isaaccloos avatar jondot avatar jvanbuel avatar kianmeng avatar kobzol avatar kozmod avatar michel-slm avatar mroci avatar nithinmuthukumar avatar nskobelevs avatar obi1kenobi avatar oliashish avatar panarch avatar pjhades avatar senk8 avatar thomassimmer avatar vojta7 avatar wfxr avatar zhiburt avatar zjp-cn avatar zxch3n avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tabled's Issues

Support psql-like 'expanded display'

psql has an 'expanded display' mode, which converts the format to two columns, with the header in the first column and value in the second, and one row printed at a time:

peter@localhost testdb=> \a \t \x
Output format is aligned.
Tuples only is off.
Expanded display is on.
peter@localhost testdb=> SELECT * FROM my_table;
-[ RECORD 1 ]-
first  | 1
second | one
-[ RECORD 2 ]-
first  | 2
second | two
-[ RECORD 3 ]-
first  | 3
second | three
-[ RECORD 4 ]-
first  | 4
second | four

(example taken from the bottom of https://www.postgresql.org/docs/current/app-psql.html).

It would be great if something similar could be supported by this crate, as it helps a lot when rows have so many columns that they span multiple lines.

Thanks for the crate by the way, it's working wonderfully!

Create `MinWidth` component

MinWidth constrain cells to the given width if they have not meet a necessary length.

An example

#[test]
fn min_width() {
    let data = create_vector::<3, 3>();
    let table = Table::new(&data)
        .with(Style::GITHUB_MARKDOWN)
        .with(Modify::new(Column(..1)).with(MinWidth(5)))
        .to_string();

    let expected = concat!(
        "|  N  | column 0 |\n",
        "|-----+----------|\n",
        "|  0  |   0-1    |\n",
        "|  1  |   1-1    |\n",
        "|  2  |   2-1    |\n",
    );

    assert_eq!(table, expected);
}

It's necessary to determine what is the proper behavior in case it's used together with MaxWidth.
And if these are needed to be combined as 1 type Width.

An implementation can be inspired by MaxWidth component.
But I would change the actually content via format by adding the necessary amount of spaces.
A space character may worth to be a constant.

Add a trait for `table()` on `Iterator`

It seems possible to create a trait which would be implemented for any Iterator.
So the following calls could be done.

let people: &[Person] = ...
let table = people.table()
                  .with(Style::psql());
let people: Vec<Person> = ...
let table = people.table()
                  .with(Style::psql());

Is it seem like a good idea?

The idea of implementation can be taken from https://youtu.be/bnnacleqg6k

Support table name in derive

The idea is that we could set a table name on a type derived with Tabled

#[derive(Tabled)
#[tabled(name = "Users")]
struct User {
  id: i64,
  surname: String,
  lastname: String,
}

Using such a type would build a header with a table name.
Which may look like the following.

┌───────────────────────────┬
│           Users           │
├───────────────────────────┼
│ id │ surname   │ lastname │
             ....
└────┴───────────┴──────────┘

It requires changes in Tabled trait.
Which I would not do.

Probably a different trait will must to be created.
Which would extend a Tabled trait and would have a default implementation for type implements it.

The name of argument #[tabled] may be not ideal though.

Recognize formatting options in Display implementation

std::fmt::Formatter has a bunch of options which can be determined.
We could change the table according to these options.

The changes may be done here.

tabled/src/lib.rs

Lines 304 to 308 in 0dbb426

impl fmt::Display for Table {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.grid)
}
}

  • align
    Align could apply an Alignment for each cell.
  • width
    Width ideally would need to set a width of the whole table including length of separators.
    But it's not clear then how to behave in cases where minimum possible length is 5 but 3 was specified.
    Print nothing?

There's more options out there but I don't see there use case yet.
But it might be there.

colided `span`ed columns cause incorrect rendering

It doesn't affect tabled but it must be resolved to have the same algorithm for tabled::Panel in #77.
*Actually not for #77 but for exposure of #71 certainly

The correct view is not defined yet.
But the below test shows an issue.

#[test]
fn render_2_colided_row_span_3x3() {
    let mut grid = Grid::new(3, 3);
    grid.set_cell_borders(DEFAULT_CELL_STYLE.clone());

    grid.set(&Entity::Cell(0, 0), Settings::new().text("0-01111111").span(2));
    grid.set(&Entity::Cell(1, 0), Settings::new().text("1-0"));
    grid.set(&Entity::Cell(1, 1), Settings::new().text("1-1").span(2));
    grid.set(&Entity::Cell(1, 2), Settings::new().text("1-1").span(0));
    grid.set(&Entity::Cell(2, 0), Settings::new().text("2-0"));
    grid.set(&Entity::Cell(2, 1), Settings::new().text("2-1"));
    grid.set(&Entity::Cell(2, 2), Settings::new().text("2-2"));

    let grid = grid.to_string();

    let expected = concat!(
        "+---+------+---+\n",
        "|0-01111111|   |\n",
        "+---+------+---+\n",
        "|1-0|1-1       |\n",
        "+---+------+---+\n",
        "|2-0|  2-1 |2-2|\n",
        "+---+------+---+\n",
    );

    println!("{}", grid);

    assert_eq!(grid, expected);

    let mut grid = Grid::new(3, 3);
    grid.set_cell_borders(DEFAULT_CELL_STYLE.clone());

    grid.set(&Entity::Cell(0, 0), Settings::new().text("0-0").span(2));
    grid.set(&Entity::Cell(1, 0), Settings::new().text("1-0"));
    grid.set(&Entity::Cell(1, 1), Settings::new().text("1-11111111").span(2));
    grid.set(&Entity::Cell(1, 2), Settings::new().text("1-1").span(0));
    grid.set(&Entity::Cell(2, 0), Settings::new().text("2-0"));
    grid.set(&Entity::Cell(2, 1), Settings::new().text("2-1"));
    grid.set(&Entity::Cell(2, 2), Settings::new().text("2-2"));

    let grid = grid.to_string();


    let expected = concat!(
        "+---+------+---+\n",
        "|0-0       |   |\n",
        "+---+------+---+\n",
        "|1-0|1-11111111|\n",
        "+---+------+---+\n",
        "|2-0|  2-1 |2-2|\n",
        "+---+------+---+\n",
    );


    println!("{}", grid);

    assert_eq!(grid, expected);
}

Why not insert a method to change the headers ?

Hello everyone, my use-case is to map a lot of different struct in one struct that implement the tabled trait; my problem is that I cannot easily change the headers since every struct need different header names.

Now I have this structure:
println!("{}", Table::new(testtable).to_string());

but I need something similar to this (the headers object could be a Vec of String):
println!("{}", Table::new(testtable).with_headers(headers).to_string());

is anyone interested in implement this ?

How to set table width to be terminal width

Hi, I'm experimenting with this library, and I'm wondering if it's possible to set the table width automatically so that it's the same as the terminal's width. I can get the terminal width using the terminal_size crate or similar, but I'm unsure how to do actually use that to configure the table. This would be very useful for me if possible. Thanks!

Using emojis breaks layout or panics

String length does not seem to be properly calculated when format strings contain emojis, leading to broken table layout or panic.

Sample code:

use tabled::{Tabled, table};

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

fn main() {
    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
        },
    ];

    print!("{}", table!(&languages));
}

Crashes with:

thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', /home/nicoulaj/.cargo/registry/src/github.com-1ecc6299db9ec823/papergrid-0.1.9/src/lib.rs:343:33
stack backtrace:
   0: rust_begin_unwind
             at /rustc/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/library/std/src/panicking.rs:493:5
   1: core::panicking::panic_fmt
             at /rustc/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/library/core/src/panicking.rs:92:14
   2: core::panicking::panic_bounds_check
             at /rustc/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/library/core/src/panicking.rs:69:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at /home/nicoulaj/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:182:10
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /home/nicoulaj/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:15:9
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at /home/nicoulaj/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:2381:9
   6: papergrid::Grid::build_row
             at /home/nicoulaj/.cargo/registry/src/github.com-1ecc6299db9ec823/papergrid-0.1.9/src/lib.rs:343:33
   7: <papergrid::Grid as core::fmt::Display>::fmt
             at /home/nicoulaj/.cargo/registry/src/github.com-1ecc6299db9ec823/papergrid-0.1.9/src/lib.rs:599:13
   8: <&T as core::fmt::Display>::fmt
             at /home/nicoulaj/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:2010:62
   9: core::fmt::write
             at /rustc/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/library/core/src/fmt/mod.rs:1092:17
  10: core::fmt::Write::write_fmt
             at /home/nicoulaj/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:182:9
  11: <T as alloc::string::ToString>::to_string
             at /home/nicoulaj/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/string.rs:2270:9

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.

is there a correct way to format `HashMap`

It is hard for me to understand how a HashMap could be made into a well-formatted
table. Can we start some discussion here? Because either it needs to be implemented or better
documented, so it is an issue regardless.

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

Color rendering error

Cargo.toml:

[dependencies]
colored = "2.0.0"
[dependencies.tabled]
version = "0.3.0"
default-features = false
features = ["color"]

main.rs

use colored::Colorize;
use tabled::{Column, Format, Full, MaxWidth, Modify, Style, Table, Tabled};

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

impl Language {
    fn new(name: &'static str, designed_by: &'static str, invented_year: usize) -> Self {
        Self {
            name,
            designed_by,
            invented_year,
        }
    }
}

fn main() {
    let languages = vec![
        Language::new("C 💕", "Dennis  Ritchie", 1972),
        Language::new("Rust 👍", "Graydon Hoare", 2010),
        Language::new("Go", "Pob Pike", 2009),
    ];

    let table = Table::new(languages)
        .with(Modify::new(Full).with(MaxWidth::wrapping(8)))
        .with(Modify::new(Column(..1)).with(Format(|s| s.red().to_string())))
        .with(Modify::new(Column(1..2)).with(Format(|s| s.green().to_string())))
        .with(Modify::new(Column(2..)).with(Format(|s| s.blue().to_string())))
        .with(Style::pseudo());
    println!("{}", table);
}

2021-10-18 11 28 37

Why is "_by" blue and not green?

Also, why is "Ritchie" white and not green?

2021-10-18 11 29 35

I used a small terminal, so I limited the width of the table.

Add a `Margin` modifier

Margins

Margins can be set outside the grid (top, bottom, left, right). Margins and the character used for rendering the margin can be set separately.

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
>>>┌───────────┬───────────┐<<<<
>>>│row 1 col 1│row 1 col 2│<<<<
>>>├───────────┼───────────┤<<<<
>>>│row 2 col 1│row 2 col 2│<<<<
>>>└───────────┴───────────┘<<<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

There's a list of steps necessary to take.

  • A new margin field must be added to papergrid::Grid.
  • fmt::Display of papergrid::Grid must be updated.
  • margin.rs with Margin modifier must be created in tabled which will set the table margin.

The possible implementation of tabled::Margin

struct Margin {
  top: Option<Indent>,
  bottom: Option<Indent>,
  left: Option<Indent>,
  right: Option<Indent>,
}

struct Indent {
  size: usize,
  symbol: char,
}

Inspired by the same functionality in https://github.com/vdmeer/asciitable

Add a support for colored borders and split lines

The goal is to be able to colorize borders.

It is also necessary to add a support for colored customization of a header

#[test]
#[cfg(feature = "color")]
fn top_border_override_with_colored_string_test() {
    use owo_colors::OwoColorize;

    let data = create_vector::<2, 2>();
    let table = Table::new(&data)
        .with(Style::ASCII)
        .with(TopBorderText::new("-Table".on_black().green().to_string()))
        .to_string();

    let expected = concat!(
        "\u{1b}[32m\u{1b}[40m-Table\u{1b}[0m\u{1b}[0m---------+----------+\n",
        "| N | column 0 | column 1 |\n",
        "+---+----------+----------+\n",
        "| 0 |   0-0    |   0-1    |\n",
        "+---+----------+----------+\n",
        "| 1 |   1-0    |   1-1    |\n",
        "+---+----------+----------+\n",
    );

    assert_eq!(table, expected);
}

Provide a way to deal with types that don't implement Display

For instance:

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

I can't implement Display for Option<bool>, so I'm stuck here ([E0117]: only traits defined in the current crate can be implemented for arbitrary types). I cannot use another type either.

Some possible ideas:

  • allow to switch to Debug instead of Display for the whole struct ?
  • same, on a per field basis ?
  • allow me to provide my own display trait, so I can implement it on arbitrary types ?

Create a Colored table?

We could create a method to output colored table.

The question only in which way we may colorize it.

Also ExtandedDisplay header pattern could be colorized. To bring more attention on separtion.

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.

Hidden fields shouldn't need to implement Display

I have a field of a struct which I do not plan to show in the table output. However, despite adding:

    #[header(hidden = true)]
    foo: SomeType

I still have an error compiling the struct, saying SomeType doesn't implement display. Obviously, I don't want to go through the trouble of implementing Display for a type I don't plan to list in the table. If possible, there should be a way to completely disable parsing for this field so that not having Display implemented for a field which is hidden doesn't error out.

Allow completely custom border

It could be cool to have an ability to have a completely custom border with text squeezed in etc.

Example

---Hello World---
...

Unable to compile

I ran cargo upgrade and papergrid got updated to 0.1.11 and now I am unable to compile tabled because somehow papergrid contains a breaking change.

  • Error
error[E0432]: unresolved import `papergrid::Alignment`
 --> /home/hello/.cargo/registry/src/github.com-1ecc6299db9ec823/tabled-0.1.3/src/alignment.rs:1:17
  |
1 | use papergrid::{Alignment, Entity, Grid, Settings};
  |                 ^^^^^^^^^ no `Alignment` in the root

error[E0432]: unresolved import `papergrid::Alignment`
   --> /home/hello/.cargo/registry/src/github.com-1ecc6299db9ec823/tabled-0.1.3/src/lib.rs:141:9
    |
141 | pub use papergrid::Alignment;
    |         ^^^^^^^^^^^^^^^^^^^^ no `Alignment` in the root

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0432`.
error: could not compile `tabled`

To learn more, run the command again with --verbose.
  • Crates details
tabled: "0.1.3"

# These are inside Cargo.lock
tabled_derive: "0.1.5"
papergrid: "0.1.11"

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.

Add option to wrap when `MaxWidth` is configured

It would be really nice to be able to wrap wide columns onto newlines, rather than truncating them. Something like:

    let expected = concat!(
        "| id | destribution |    link    |\n",
        "|----+--------------+------------|\n",
        "| 0  |    Fedora    | https://ge |\n",
        "|    |              | tfedora.or |\n",
        "|    |              |     g/     |\n",
        "| 2  |   OpenSUSE   | https://ww |\n",
        "|    |              | w.opensuse |\n",
        "|    |              |   .org/    |\n",
        "| 3  |  Endeavouro  | https://en |\n",
        "|    |      s       | deavouros. |\n",
        "|    |              |    com/    |\n",
    );

when the max width is set to 10.


I had a go at this, and have the above test passing, but it required changing the public API of MaxWidth and didn't work great with type inference. Happy to push it up so you can take a look though.

Add an option to set the character for the padding

Currently the default character we use is a space character .

The idea is to set this character to a custom character.

let indent = tabled::Indent::new(0, 0, 1, 1).set_padding_char('V', '^', '<', '>');
│vvvvvvvvvvvvvvv│vvvvvvvvvvvvvvv│
│> row 1 col 1 <│> row 1 col 2 <│
│^^^^^^^^^^^^^^^│^^^^^^^^^^^^^^^│

The name of the function is not perfect.
Please if you have a better idea use it.

The changes must be done in Indent structure and in papergrid::Settings.


inspired by the same logic in https://github.com/vdmeer/asciitable

Fix `Panel` style rendering on tables with more than 2 columns

Running this code will result in the following.

use tabled::{Style, Table};

fn main() {
    let table = Table::new(&[(0, 1)])
        .with(tabled::Panel("Numbers", 0))
        .with(Style::modern());

    println!("{}", table);
}
┌───────────┬
│Numbers    │
├───────────┼
│ i32i32 │
├─────┼─────┤
│  01  │
└─────┴─────┘

As you may see the styles on the right side is not exactly correct.
It would be good to be fixed.

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

Fix `Panel` rendering when it 2 `Panel`s are used

This is an example

use tabled::{Footer, Header, MaxWidth, Modify, Row, Style, Cell};

fn main() {
    let message = r#"The terms "the ocean" or "the sea" used without specification refer to the interconnected body of salt water covering the majority of the Earth's surface"#;
    let link = r#"https://en.wikipedia.org/wiki/Ocean"#;

    let oceans = ["Atlantic", "Pacific", "Indian", "Southern", "Arctic"];

    let mut builder = tabled::builder::Builder::default().set_header(["#", "Ocean"]);
    for (i, ocean) in oceans.iter().enumerate() {
        builder = builder.add_row([i.to_string(), ocean.to_string()]);
    }

    let table = builder
        .build()
        .with(Header(message))
        .with(Footer(link))
        .with(Modify::new(Cell(0, 0)).with(MaxWidth::wrapping(5)))
        .with(Style::GITHUB_MARKDOWN);

    println!("{}", table);
}
|The t         |
|erms          |
|"the          |
|--------------+
|      #       |       Ocean        |

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.

Add an option like `display_with` but only which takes a `&self`

It may be useful as sometimes we would like to print a content on the basis of some internal information not only on the type information itself.

#[derive(Tabled)]
struct House {
   #[field(display_with_self = "foo")]
   key: String,
}

fn foo(&House) -> String { ... }

Create a style from pattern

We could create a style from pattern.

Like having a pattern for line.
And repeat it corresponding of table length.

I am note sure now how it may look like but here's a general idea.

PatternStyle::new("=+")

=+=+=+=+=
* 1  23 *
=+=+=+=+=

originally seen this approach here https://github.com/Nukesor/comfy-table

Is it valuable to support ordering in derive macro?

We could support something like the following.

#[derive(Tabled)]
struct Picture {
  #[tabled(order = 2)];
  author: String,
  title: String,
  created_at_year: u32,
}

Which would result in a table where the author column would be the 2nd not the first like by default.

A use case I can picture is the type which is marked by #[repl(C)] probably.
But that's it.

Is it valuable?

Add new styles

I am not sure what the appropriate names for these styles.
Please be free to chose the name for it.

To create a style is necessary to create a new constant and function in style.rs file.
Look at the existing styles.

tabled/src/style.rs

Lines 210 to 220 in 6aa713c

const _ASCII: StyleSettings = StyleSettings::new(
Frame {
bottom: Some(Line::bordered('-', '+', '+', '+')),
top: Some(Line::bordered('-', '+', '+', '+')),
left: Some('|'),
right: Some('|'),
},
Some(Line::bordered('-', '+', '+', '+')),
Some(Line::bordered('-', '+', '+', '+')),
Some('|'),
);

Bellow there's a list of styles which could be added.

┌           ┐
 rc 11 rc 12

 rc 21 rc 22 
└           ┘
┌           ┐
 rc 11 rc 12
├     ┼     ┤       
 rc 21 rc 22  
└           ┘
rc 11│rc 12
─────┼─────
rc 21│rc 22
│rc 11 rc 12 
│
│rc 21 rc 22 

Inspired by the same functionality in https://github.com/vdmeer/asciitable

Hyperlinks break width detection

Following instructions in this gist, the way to print a hyperlink in the terminal with rust is something like format!("\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\", display, url) which displays in the terminal as having width display.len(), but tabled appears to think its display width is approximately 14+display.len()+url.len(). This and others are listed in the operating system command section of the relevant wikipedia page.

An example of the results

┌─────────────────────────────────────────────────────────────────────────────┬────────────────────┐
│                                    name                                     │     similarity     │
├─────────────────────────────────────────────────────────────────────────────┼────────────────────┤
│ Person1 │        100         │
│         Person2         │ 65.38461538461539  │
│      Person3      │ 62.962962962962955 │
│    Person4    │ 62.962962962962955 │
│       Person5       │ 60.714285714285715 │
└─────────────────────────────────────────────────────────────────────────────┴────────────────────┘

after glancing at some docs, I think the underlying issue is with ansi-parser

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.