An easy to use library for pretty printing tables of Rust struct
s and enum
s.
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.
- Usage
- Settings
- Derive
- Table types
- Tips & Tricks
std::fmt::*
options- Tuple combination
- Object
- Builder
- Macros
- Features
- Formats
- Notes
To print a list of structs or enums as a table, there is 2 ways.
- Using a builder method, to build table step by step
- Implement a
Tabled
trait for your type (or anotate 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 typped struct in cases where we know the data structure beforehand.
Notice that there are a lot of mods available for your tables. As well as the helpers such as derive macros and proc macros.
Bellow are shown both of these methods.
The example below is demontrates 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);
This section lists the set of settings you can apply to your table.
Most of the settings are laveraged by Table::with
and Table::modify
.
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.
+------+----------------+---------------+
| name | designed_by | invented_year |
+------+----------------+---------------+
| C | Dennis Ritchie | 1972 |
+------+----------------+---------------+
| Rust | Graydon Hoare | 2010 |
+------+----------------+---------------+
| Go | Rob Pike | 2009 |
+------+----------------+---------------+
โโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ name โ designed_by โ invented_year โ
โโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโค
โ C โ Dennis Ritchie โ 1972 โ
โโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโค
โ Rust โ Graydon Hoare โ 2010 โ
โโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโค
โ Go โ Rob Pike โ 2009 โ
โโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโ
โโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ name โ designed_by โ invented_year โ
โโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโค
โ C โ Dennis Ritchie โ 1972 โ
โ Rust โ Graydon Hoare โ 2010 โ
โ Go โ Rob Pike โ 2009 โ
โโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโ
โญโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฎ
โ name โ designed_by โ invented_year โ
โโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโค
โ C โ Dennis Ritchie โ 1972 โ
โ Rust โ Graydon Hoare โ 2010 โ
โ Go โ Rob Pike โ 2009 โ
โฐโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโฏ
โโโโโโโโฆโโโโโโโโโโโโโโโโโฆโโโโโโโโโโโโโโโโ
โ name โ designed_by โ invented_year โ
โ โโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฃ
โ C โ Dennis Ritchie โ 1972 โ
โ โโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฃ
โ Rust โ Graydon Hoare โ 2010 โ
โ โโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฃ
โ Go โ Rob Pike โ 2009 โ
โโโโโโโโฉโโโโโโโโโโโโโโโโโฉโโโโโโโโโโโโโโโโ
name | designed_by | invented_year
------+----------------+---------------
C | Dennis Ritchie | 1972
Rust | Graydon Hoare | 2010
Go | Rob Pike | 2009
| name | designed_by | invented_year |
|------|----------------|---------------|
| C | Dennis Ritchie | 1972 |
| Rust | Graydon Hoare | 2010 |
| Go | Rob Pike | 2009 |
====== ================ ===============
name designed_by invented_year
====== ================ ===============
C Dennis Ritchie 1972
Rust Graydon Hoare 2010
Go Rob Pike 2009
====== ================ ===============
.........................................
: name : designed_by : invented_year :
:......:................:...............:
: C : Dennis Ritchie : 1972 :
: Rust : Graydon Hoare : 2010 :
: Go : Rob Pike : 2009 :
:......:................:...............:
.---------------------------------------.
| name | designed_by | invented_year |
| C | Dennis Ritchie | 1972 |
| Rust | Graydon Hoare | 2010 |
| Go | Rob Pike | 2009 |
'---------------------------------------'
name designed_by invented_year
C Dennis Ritchie 1972
Rust Graydon Hoare 2010
Go Rob Pike 2009
name designed_by invented_year
C Dennis Ritchie 1972
Rust Graydon Hoare 2010
Go Rob Pike 2009
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 โ
โโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ C โ Dennis Ritchie 1972 โ
โ Go โ Rob Pike 2009 โ
โ Rust โ Graydon Hoare 2010 โ
โโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Also you can build your own one from scratch, and doing so not always possible at compile time,
so you may use Theme
object to do that.
Notice that the Theme
is quite powerfull on 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 +
+++++++++++++++++++++++++++++++++++++++
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::{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\
+-----+-----+"
);
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 | ! |"
);
You can set a 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 intividial cell by using BorderColored
.
use tabled::settings::{object::Columns, style::BorderColor, Color};
table.modify(Columns::single(2), BorderColor::new().set_top(Color::FG_GREEN));
It was considered that having a few atomic settings is better rather then have one but feature full.
But tabled::settings::themes::*
are a bit diverge 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 revers 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 must 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 |
'---------------------------------------------------------------------------'
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}");
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 โ
โโโโโโโโโโโดโโโโโโโโโโโ
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 |
+------+-----------+
The Format
function provides an interface for a 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 |
+----+----+----+----+----+----+----+----+----+-----+
The Padding
structure provides an interface for a 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.
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 |<<<<
>>>+------+----------------+---------------+<<<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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,
));
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 โโ
โโโโโดโโโโดโโโโโ
โโโโโโโโโโโโโ
Using the following structures you can configure a 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).
Bellow 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
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.
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 to keep words not splited (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.
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.
You can set a constant width for all columns using Justify
.
use tabled::settings::Width;
table.with(Width::justify(10));
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>());
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)));
You can increase a table or a specific cell height using Height
modifier.
Beware that we Height
controls only content,
so it can't make things smaller then a certain minimum.
Bellow 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 |
| | |
| | |
+-----------------------------------+------------------------------------------+
Increase a of a 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));
Truncation a of a 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));
You can rotate table using tabled::Rotate
.
Imagine you have a table already which 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 rotated;
use tabled::settings::Rotate;
table.with(Rotate::Left);
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ link โ https://getfedora.org/ โ https://www.opensuse.org/ โ https://endeavouros.com/ โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ destribution โ Fedora โ OpenSUSE โ Endeavouros โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ id โ 0 โ 2 โ 3 โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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 |
+------+----------------+
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 : |
+-------+-------------+-----------+
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 |
+---+------+----------------+---------------+
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 |
+---+---+---+
You can concatanate 2 tables using Concat
.
It will stick 2 tables together either vertically or horizontally.
The example below show the result of horizontal concat of primarily 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 primarily 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
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 *
โโโโโดโโโ*****
It's possible to set a horizontal(column) span and vertical(row) span to a cell.
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 |
+---+---+---+
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 |
+---+---+---+
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 boundry 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 โ โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
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()));
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 available when derive
feature in turned on.
And it is by default.
Most of the default types implement the corresponding traits too.
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,
}
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,
}
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,
}
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,
}
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 likeDisplayedOption<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.
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,
}
tabled
has a few representations of tables some differs from it's view some from it's implememtation details.
There are situations when you might better use one but not another. But sometimes some can be used interchangable.
Bellow you'll find a short list of existing ones. You can find a descriptive information about each at the documentation.
Main table of the library. It's implemenentation requires that all data be stored on heap.
It's simmilar 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 usefull when you can't fit all your data in memory.
Simmular 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 usefull in a very constrain environments.
It is the only table which supports no-std
.
Unlike Table
it does not nessarily requires columns be aligned.
It provides capabilities for a completely uterly 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 :
:...............:.................:
You can use ExpandedDisplay
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,
}
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
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 |##
#+-----+##
The library doesn't bind you in 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.
Tunning 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()));
You also can combine objects which implements 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 | | | | + ",
)
);
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
is a powerfull 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 | |
'-------------------------------------------------'
Utilities for dynamic Table
displays.
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 goint to see running it.
โญโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฎ
โ +---------+ โ +---------+ โ +---------+ โ
โ | table 0 | โ | table 1 | โ | table 2 | โ
โ +---------+ โ +---------+ โ +---------+ โ
โ | 0 | โ | world | โ โ
โ +---------+ โ +---------+ โ โ
โ | 1 | โ โ โ
โ +---------+ โ โ โ
โ | 2 | โ โ โ
โ +---------+ โ โ โ
โฐโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโฏ
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 be looking as foolows.
The library has a list of features.
std
- Used by default. If not its consideredno_std
with a limited set of functionality.derive
- Used by default. A support forTabled
derive macro.ansi
- A support for ANSI sequences.macros
- A support forrow!
,col!
macro.
You can convert some formats to a Table
using an utility library.
You can convert arbitrary json
to a Table
using json_to_table
library.
See the example.
You can convert arbitrary ron
to a Table
using ron_to_table
library.
See the example.
You can convert arbitrary csv
to a Table
using csv_to_table
library.
See the example.
You can convert arbitrary toml
to a Table
using toml_to_table
library.
See the example.
You can convert a Table
into HTML
<table>
using table_to_html
library.
See the example.
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
.
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"] }
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 handles it correctly.
+---------+----------------+---------------+
| name | designed_by | invented_year |
+---------+----------------+---------------+
| C ๐ | Dennis Ritchie | 1972 |
+---------+----------------+---------------+
| Rust ๐ | Graydon Hoare | 2010 |
+---------+----------------+---------------+
| Go ๐ง | Rob Pike | 2009 |
+---------+----------------+---------------+
It's a frequent case where it's nessary 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}");
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
Breaking MSRV considered to be a breaking change; but see semver-note
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 good, but at the end of the day you probably need to decide it yourself.
If you have any ideas for an enhancement or have a question about tabled
please file an issue.
Bellow 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 optionalcsv
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 if you feel about some crate being worth menthioned open an issue.