Skip to main content

Crate crabular

Crate crabular 

Source
Expand description

CI Crates.io Documentation License: MIT

Dall-E generated crabular image

§Crabular

A high-performance ASCII table library for Rust with zero dependencies.

§Features

  • 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 crabular npm package
  • Safe Rust - #![forbid(unsafe_code)]
  • High performance - Zero-allocation Display trait, allocation pooling for repeated renders

§Performance

Crabular v0.2.0+ includes Rust 1.93 optimizations for significant performance improvements:

§Zero-Allocation Display

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

§Allocation Pooling for Repeated Renders

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)

§Installation

Add to your Cargo.toml:

[dependencies]
crabular = "0.7"

§WebAssembly (JavaScript/TypeScript)

For browser or Node.js usage, install the WebAssembly package:

npm install crabular
import 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.

§Quick Start

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 │
└────────┴─────┴────────────┘

§Builder API

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 feature

§Truncation

Limit 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.

§Table Styles

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

§Classic

+-----------------+-----+---------------+
| Name            | Age | City          |
+-----------------+-----+---------------+
| Kelana          | 30  | Berlin        |
| Kata            | 25  | Yogyakarta    |
| Cherry Blossom  | 35  | Bikini Bottom |
+-----------------+-----+---------------+

§Modern

┌─────────────────┬─────┬───────────────┐
│ Name            │ Age │ City          │
├─────────────────┼─────┼───────────────┤
│ Kelana          │ 30  │ Berlin        │
│ Kata            │ 25  │ Yogyakarta    │
│ Cherry Blossom  │ 35  │ Bikini Bottom │
└─────────────────┴─────┴───────────────┘

§Minimal

  Name              Age    City           
──────────────────────────────────────────
  Kelana            30     Berlin         
  Kata              25     Yogyakarta     
  Cherry Blossom    35     Bikini Bottom  

§Compact

│ Name            │ Age  │ City          │
──────────────────┼──────┼────────────────
│ Kelana          │ 30   │ Berlin        │
│ Kata            │ 25   │ Yogyakarta    │
│ Cherry Blossom  │ 35   │ Bikini Bottom │

§Markdown

| Name           | Age | City          |
|----------------|-----|---------------|
| Kelana         | 30  | Berlin        |
| Kata           | 25  | Yogyakarta    |
| Cherry Blossom | 35  | Bikini Bottom |

§Width Constraints

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

§Alignment

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

§Vertical Alignment

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

§Cell Spanning (Colspan)

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::Markdown with spanned cells, the output will render visually but won’t be valid Markdown table syntax.

§Sorting

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

§Filtering

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;

§Column Operations

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

§CLI Tool

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

§CLI Options

OptionDescription
-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 NTruncate cell content to N characters with “…” suffix
--no-headerTreat all rows as data (no header row)
--skip-headerSkip first row, treat remaining as data

§API Reference

§Table

MethodDescription
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

§TableBuilder

MethodDescription
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

§License

MIT License - see LICENSE for details.

Re-exports§

pub use alignment::Alignment;
pub use builder::TableBuilder;
pub use cell::Cell;
pub use constraint::WidthConstraint;
pub use padding::Padding;
pub use row::Row;
pub use style::TableStyle;
pub use table::Table;
pub use vertical_alignment::VerticalAlignment;

Modules§

alignment
builder
cell
constraint
padding
row
style
table
vertical_alignment