A high-performance ASCII table library for Rust with zero dependencies.
- Multiple table styles - Classic, Modern (Unicode), Minimal, Compact, Markdown
- Flexible alignment - Left, Center, Right per-cell and per-column
- Vertical alignment - Top, Middle, Bottom for multi-line cells
- Width constraints - Fixed, Min, Max, Proportional, Wrap
- Multi-line cells - Automatic word wrapping with configurable widths
- Cell spanning - Colspan support for merged cells
- Sorting - Sort by column (alphabetic or numeric, ascending or descending)
- Filtering - Filter rows by exact match, predicate, or substring
- Builder API - Fluent interface for table construction
- Zero dependencies - No external crates required (core library)
- WebAssembly support - Use in browsers and Node.js via
crabularnpm package - Safe Rust -
#![forbid(unsafe_code)] - High performance - Zero-allocation Display trait, allocation pooling for repeated renders
Crabular v0.2.0+ includes Rust 1.93 optimizations for significant performance improvements:
use crabular::Table;
let table = Table::new()
.header(["Name", "Age"])
.row(["Kata", "30"]);
// Zero-allocation printing (20-40% faster)
println!("{table}");
// For comparison, this allocates:
println!("{}", table.render());use std::io::{stdout, Write};
let mut buffer = Vec::with_capacity(4096);
for item in 0..10 {
buffer.clear();
table.render_into(&mut buffer)?;
stdout().write_all(&buffer)?;
}Benefits: 30-50% faster for repeated renders (pagination, filtering UI)
Add to your Cargo.toml:
[dependencies]
crabular = "0.7"For browser or Node.js usage, install the WebAssembly package:
npm install crabularimport init, { JsTable } from 'crabular';
await init();
const table = new JsTable();
table.style('modern');
table.header(['Name', 'Age']);
table.row(['Alice', '30']);
console.log(table.render());See crabular-wasm/examples for more JavaScript examples.
use crabular::{Table, TableStyle};
let mut table = Table::new();
table.set_style(TableStyle::Modern);
table.set_headers(["Name", "Age", "City"]);
table.add_row(["Kelana", "30", "Berlin"]);
table.add_row(["Kata", "25", "Yogyakarta"]);
println!("{}", table.render());Output:
┌────────┬─────┬────────────┐
│ Name │ Age │ City │
├────────┼─────┼────────────┤
│ Kelana │ 30 │ Berlin │
│ Kata │ 25 │ Yogyakarta │
└────────┴─────┴────────────┘
For a more fluent experience, use TableBuilder:
use crabular::{TableBuilder, TableStyle, Alignment, WidthConstraint};
let output = TableBuilder::new()
.style(TableStyle::Modern)
.header(["ID", "Name", "Score"])
.constrain(0, WidthConstraint::Fixed(5))
.constrain(1, WidthConstraint::Min(15))
.align(2, Alignment::Right)
.rows([
["1", "Kelana", "95.5"],
["2", "Kata", "87.2"],
["3", "Cherry Blossom", "92.0"],
])
.render();
print!("{output}"); // Or use .print() directly with std featureLimit cell content length with truncation:
use crabular::{TableBuilder, TableStyle};
let output = TableBuilder::new()
.style(TableStyle::Modern)
.header(["ID", "Name", "Description", "Score"])
.truncate(20) // Truncate to 20 characters with "..." suffix
.rows([
["1", "Kata", "A very long description that should be truncated", "95.5"],
["2", "Kelana", "Short desc", "87.2"],
["3", "Squidward", "Another extremely long description text here", "92.0"],
])
.render();
print!("{output}");Output:
┌─────┬────────────┬───────────────────────┬───────┐
│ ID │ Name │ Description │ Score │
├─────┼────────────┼───────────────────────┼───────┤
│ 1 │ Kata │ A very long descr... │ 95.5 │
│ 2 │ Kelana │ Short desc │ 87.2 │
│ 3 │ Squidward │ Another extremely... │ 92.0 │
└─────┴────────────┴───────────────────────┴───────┘
Note: Truncation is applied lazily during row insertion, so there's zero overhead when not used.
use crabular::TableStyle;
// Available styles:
let _ = TableStyle::Classic; // +---+---+ with | and -
let _ = TableStyle::Modern; // Unicode box-drawing characters
let _ = TableStyle::Minimal; // Header separator only
let _ = TableStyle::Compact; // No outer borders
let _ = TableStyle::Markdown; // GitHub-flavored markdown tables+-----------------+-----+---------------+
| Name | Age | City |
+-----------------+-----+---------------+
| Kelana | 30 | Berlin |
| Kata | 25 | Yogyakarta |
| Cherry Blossom | 35 | Bikini Bottom |
+-----------------+-----+---------------+
┌─────────────────┬─────┬───────────────┐
│ Name │ Age │ City │
├─────────────────┼─────┼───────────────┤
│ Kelana │ 30 │ Berlin │
│ Kata │ 25 │ Yogyakarta │
│ Cherry Blossom │ 35 │ Bikini Bottom │
└─────────────────┴─────┴───────────────┘
Name Age City
──────────────────────────────────────────
Kelana 30 Berlin
Kata 25 Yogyakarta
Cherry Blossom 35 Bikini Bottom
│ Name │ Age │ City │
──────────────────┼──────┼────────────────
│ Kelana │ 30 │ Berlin │
│ Kata │ 25 │ Yogyakarta │
│ Cherry Blossom │ 35 │ Bikini Bottom │
| Name | Age | City |
|----------------|-----|---------------|
| Kelana | 30 | Berlin |
| Kata | 25 | Yogyakarta |
| Cherry Blossom | 35 | Bikini Bottom |
Control column widths with various constraints:
use crabular::{Table, WidthConstraint};
let mut table = Table::new();
// Fixed width (exactly N characters)
table.constrain(WidthConstraint::Fixed(20));
// Minimum width (at least N characters)
table.constrain(WidthConstraint::Min(10));
// Maximum width (at most N characters, truncates if needed)
table.constrain(WidthConstraint::Max(30));
// Proportional (percentage of available width)
table.constrain(WidthConstraint::Proportional(50));
// Wrap (word wrap at N characters)
table.constrain(WidthConstraint::Wrap(25));use crabular::{Table, Row, Alignment};
let mut table = Table::new();
// Set column alignment
table.align(0, Alignment::Left);
table.align(1, Alignment::Center);
table.align(2, Alignment::Right);
// Per-row alignment via Row::with_alignment
let row = Row::with_alignment(["text"], Alignment::Center);
table.add_row(row);For multi-line cells:
use crabular::{Table, VerticalAlignment};
let mut table = Table::new();
table.valign(VerticalAlignment::Top); // Default
table.valign(VerticalAlignment::Middle);
table.valign(VerticalAlignment::Bottom);Create cells that span multiple columns:
use crabular::{Table, Cell, Row, Alignment};
let mut table = Table::new();
table.set_headers(["A", "B", "C"]);
let mut row = Row::new();
let mut merged = Cell::new("Spans two columns", Alignment::Center);
merged.set_span(2); // This cell spans 2 columns
row.push(merged);
row.push(Cell::new("Normal", Alignment::Left));
table.add_row(row);Note: Standard Markdown does not support colspan. When using
TableStyle::Markdownwith spanned cells, the output will render visually but won't be valid Markdown table syntax.
Sort table rows by any column:
use crabular::{Table, Row, Alignment};
let mut table = Table::new();
table.add_row(["Kelana", "30"]);
table.add_row(["Kata", "25"]);
// Alphabetic sorting
table.sort(0); // Ascending by column 0
table.sort_desc(0); // Descending by column 0
// Numeric sorting
table.sort_num(1); // Ascending numeric by column 1
table.sort_num_desc(1); // Descending numeric by column 1
// Custom sorting - compare by first column content
table.sort_by(|a, b| {
let a_content = a.cells().first().map_or("", |c| c.content());
let b_content = b.cells().first().map_or("", |c| c.content());
a_content.cmp(b_content)
});Filter rows based on conditions:
use crabular::{Table, Row, Alignment};
let mut table = Table::new();
table.add_row(["Kelana", "Active", "100"]);
table.add_row(["Kata", "Inactive", "50"]);
table.add_row(["Cherry Blossom", "Active", "75"]);
// Exact match - keeps rows where column 1 equals "Active"
table.filter_eq(1, "Active");
// Substring match - keeps rows where column 0 contains "Kelana"
// table.filter_has(0, "Kelana");
// Custom predicate on column - keeps rows where column 2 > 50
// table.filter_col(2, |val| val.parse::<i32>().unwrap_or(0) > 50);
// Full row predicate - keeps rows with more than 2 cells
let filtered = table.filtered(|row| row.len() > 2);
let _ = filtered;use crabular::{Table, Row, Alignment};
let mut table = Table::new();
table.set_headers(["A", "B"]);
table.add_row(["1", "2"]);
table.add_row(["3", "4"]);
// Add column at the end (first value is header, rest are row values)
table.add_column(&["C", "5", "6"], Alignment::Left);
// Insert column at position (first value is header, rest are row values)
table.insert_column(1, &["X", "a", "b"], Alignment::Center);
// Remove column
table.remove_column(2);A separate CLI tool is available at crabular-cli:
# Install
cargo install crabular-cli
# From CSV file (default: first row is header)
crabular-cli -i data.csv
# Truncate long cell content to 20 characters
crabular-cli -i data.csv --truncate 20
# Treat all rows as data (no header)
crabular-cli -i data.csv --no-header
# Skip first row, treat remaining as data
crabular-cli -i data.csv --skip-header
# From stdin
cat data.csv | crabular-cli -i -
# From JSON (supports nested objects)
echo '[{"name":"Kata","info":{"city":"NYC"}}]' | crabular-cli -i - --format json
# Different styles
crabular-cli -s modern -i data.csv
crabular-cli -s markdown -i data.csv| Option | Description |
|---|---|
-i, --input <FILE> |
Input file path (use - for stdin) |
-o, --output <FILE> |
Output file path |
-s, --style <STYLE> |
Table style: classic, modern, minimal, compact, markdown |
--format <FORMAT> |
Input format: csv, tsv, ssv, json, jsonl |
-S, --separator <CHAR> |
Field separator (default: auto-detect) |
--truncate N |
Truncate cell content to N characters with "..." suffix |
--no-header |
Treat all rows as data (no header row) |
--skip-header |
Skip first row, treat remaining as data |
| Method | Description |
|---|---|
new() |
Create empty table |
set_headers(row) |
Set header row |
add_row(row) |
Add data row |
truncate(limit) |
Set max cell content length |
render() |
Render to string |
print() |
Print to stdout |
set_style(style) |
Set table style |
align(col, alignment) |
Set column alignment |
valign(alignment) |
Set vertical alignment |
constrain(constraint) |
Add width constraint |
sort(col) |
Sort ascending |
sort_desc(col) |
Sort descending |
sort_num(col) |
Sort numeric ascending |
sort_num_desc(col) |
Sort numeric descending |
filter_eq(col, value) |
Filter by exact match |
filter_has(col, substr) |
Filter by substring |
filter_col(col, pred) |
Filter by predicate |
| Method | Description |
|---|---|
new() |
Create new builder |
style(style) |
Set table style |
header(cells) |
Set header row |
row(cells) |
Add data row |
rows(data) |
Add multiple rows |
truncate(limit) |
Set max cell content length |
align(col, alignment) |
Set column alignment |
valign(alignment) |
Set vertical alignment |
constrain(col, constraint) |
Set column constraint |
padding(padding) |
Set cell padding |
build() |
Build table |
render() |
Build and render |
print() |
Build and print |
MIT License - see LICENSE for details.
