Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,46 +25,45 @@ path = "src/cli/main.rs"
required-features = ["cli"]

[features]
default = ["cli"]
cli = [
"dep:clap",
"dep:anyhow",
"dep:tiktoken-rs",
"dep:comfy-table",
"dep:ratatui",
"dep:crossterm",
"dep:tui-textarea",
"dep:arboard",
"dep:syntect",
"dep:unicode-width",
"dep:chrono",
]
default = ["cli", "cli-stats", "tui", "tui-clipboard", "tui-time", "parallel"]
cli = ["dep:clap", "dep:anyhow"]
cli-stats = ["cli", "dep:tiktoken-rs", "dep:comfy-table"]
tui = ["dep:anyhow", "dep:ratatui", "dep:crossterm", "dep:tui-textarea"]
tui-clipboard = ["tui", "dep:arboard"]
tui-time = ["tui", "dep:chrono"]
parallel = ["dep:rayon"]

[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
indexmap = "2.0"
serde_json = { version = "1.0.145", features = ["preserve_order"] }
thiserror = "2.0.17"
itoa = "1.0"
ryu = "1.0"
rayon = { version = "1.10", optional = true }

# CLI dependencies (gated behind "cli" feature)
# CLI dependencies (gated behind "cli"/"cli-stats" features)
clap = { version = "4.5.11", features = ["derive"], optional = true }
anyhow = { version = "1.0.86", optional = true }
tiktoken-rs = { version = "0.9.1", optional = true }
comfy-table = { version = "7.1", optional = true }

# TUI dependencies (gated behind "cli" feature)
# TUI dependencies (gated behind "tui" feature)
ratatui = { version = "0.29", optional = true }
crossterm = { version = "0.28", optional = true }
tui-textarea = { version = "0.7", optional = true }
arboard = { version = "3.4", optional = true }
syntect = { version = "5.2", optional = true }
unicode-width = { version = "0.2", optional = true }
chrono = { version = "0.4", optional = true }

[dev-dependencies]
datatest-stable = "0.3.3"
glob = "0.3"
criterion = "0.5"

[[test]]
name = "spec_fixtures"
harness = false

[[bench]]
name = "encode_decode"
harness = false
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ users[2]{id,name}:
- **Strict Validation**: Enforces all spec rules (configurable)
- **Well-Tested**: Comprehensive test suite with unit tests, spec fixtures, and real-world scenarios

## Performance Snapshot (Criterion)

Snapshot from commit `f5b1b7e` using:
`cargo bench --bench encode_decode -- --save-baseline current --noplot`

| Benchmark | Median |
| --- | --- |
| `tabular/encode/128` | 145.81 us |
| `tabular/decode/128` | 115.51 us |
| `tabular/encode/1024` | 1.2059 ms |
| `tabular/decode/1024` | 949.65 us |
| `deep_object/encode/32` | 11.766 us |
| `deep_object/decode/32` | 10.930 us |
| `deep_object/encode/128` | 46.867 us |
| `deep_object/decode/128` | 49.468 us |
| `decode_long_unquoted` | 10.554 us |

Numbers vary by machine; use Criterion baselines to compare before/after changes.

## Installation

### As a Library
Expand All @@ -53,6 +72,22 @@ cargo add toon-format
cargo install toon-format
```

### Feature Flags

By default, all CLI/TUI features are enabled. You can opt in to only what you need:

```toml
toon-format = { version = "0.4", default-features = false }
```

```bash
cargo install toon-format --no-default-features --features cli
cargo install toon-format --no-default-features --features cli,cli-stats
cargo install toon-format --no-default-features --features cli,tui,tui-clipboard,tui-time
```

Feature summary: `cli`, `cli-stats`, `tui`, `tui-clipboard`, `tui-time`, `parallel`.

---

## Library Usage
Expand Down
92 changes: 92 additions & 0 deletions benches/encode_decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use serde_json::{json, Value};
use toon_format::{decode_default, encode_default};

fn make_tabular(rows: usize) -> Value {
let mut items = Vec::with_capacity(rows);
for i in 0..rows {
items.push(json!({
"id": i,
"name": format!("User_{i}"),
"score": i * 2,
"active": i % 2 == 0,
"tag": format!("tag{i}"),
}));
}
Value::Array(items)
}

fn make_deep_object(depth: usize) -> Value {
let mut value = json!({
"leaf": "value",
"count": 1,
});

for i in 0..depth {
value = json!({
format!("level_{i}"): value,
});
}

value
}

fn make_long_unquoted(words: usize) -> String {
let mut parts = Vec::with_capacity(words);
for i in 0..words {
parts.push(format!("word{i}"));
}
parts.join(" ")
}

fn bench_tabular(c: &mut Criterion) {
let mut group = c.benchmark_group("tabular");
for rows in [128_usize, 1024] {
let value = make_tabular(rows);
let toon = encode_default(&value).expect("encode tabular");

group.bench_with_input(BenchmarkId::new("encode", rows), &value, |b, val| {
b.iter(|| encode_default(black_box(val)).expect("encode tabular"));
});

group.bench_with_input(BenchmarkId::new("decode", rows), &toon, |b, input| {
b.iter(|| decode_default::<Value>(black_box(input)).expect("decode tabular"));
});
}
group.finish();
}

fn bench_deep_object(c: &mut Criterion) {
let mut group = c.benchmark_group("deep_object");
for depth in [32_usize, 128] {
let value = make_deep_object(depth);
let toon = encode_default(&value).expect("encode deep object");

group.bench_with_input(BenchmarkId::new("encode", depth), &value, |b, val| {
b.iter(|| encode_default(black_box(val)).expect("encode deep object"));
});

group.bench_with_input(BenchmarkId::new("decode", depth), &toon, |b, input| {
b.iter(|| decode_default::<Value>(black_box(input)).expect("decode deep object"));
});
}
group.finish();
}

fn bench_long_unquoted(c: &mut Criterion) {
let words = 512;
let long_value = make_long_unquoted(words);
let toon = format!("value: {long_value}");

c.bench_function("decode_long_unquoted", |b| {
b.iter(|| decode_default::<Value>(black_box(&toon)).expect("decode long unquoted"));
});
}

criterion_group!(
benches,
bench_tabular,
bench_deep_object,
bench_long_unquoted
);
criterion_main!(benches);
5 changes: 1 addition & 4 deletions examples/parts/arrays.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use toon_format::encode_default;

Expand Down
5 changes: 1 addition & 4 deletions examples/parts/arrays_of_arrays.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use toon_format::encode_default;

Expand Down
5 changes: 1 addition & 4 deletions examples/parts/decode_strict.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use serde_json::Value;
use toon_format::{
decode,
DecodeOptions,
};
use toon_format::{decode, DecodeOptions};

pub fn decode_strict() {
// Malformed: header says 2 rows, but only 1 provided
Expand Down
6 changes: 1 addition & 5 deletions examples/parts/delimiters.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use serde_json::json;
use toon_format::{
encode,
Delimiter,
EncodeOptions,
};
use toon_format::{encode, Delimiter, EncodeOptions};

pub fn delimiters() {
let data = json!({
Expand Down
5 changes: 1 addition & 4 deletions examples/parts/mixed_arrays.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use toon_format::encode_default;

Expand Down
5 changes: 1 addition & 4 deletions examples/parts/objects.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use toon_format::encode_default;

Expand Down
15 changes: 3 additions & 12 deletions examples/parts/round_trip.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
use serde::{
Deserialize,
Serialize,
};
use serde_json::{
json,
Value,
};
use toon_format::{
decode_default,
encode_default,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use toon_format::{decode_default, encode_default};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Product {
Expand Down
10 changes: 2 additions & 8 deletions examples/parts/structs.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
use serde::{
Deserialize,
Serialize,
};
use toon_format::{
decode_default,
encode_default,
};
use serde::{Deserialize, Serialize};
use toon_format::{decode_default, encode_default};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct User {
Expand Down
5 changes: 1 addition & 4 deletions examples/parts/tabular.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use toon_format::encode_default;

Expand Down
Loading
Loading