smart-format 0.2.0

Composable, zero-allocation Display formatting combinators for Rust.
Documentation
[![crates.io](https://img.shields.io/crates/v/smart-format.svg)](https://crates.io/crates/smart-format)
![](https://img.shields.io/crates/l/smart-format.svg)
![MSRV 1.85](https://img.shields.io/badge/rustc-1.85+-green.svg)

# Smart Format

Composable, zero-allocation `Display` formatting for Rust.

`smart-format` provides extension traits that add formatting operations to any type implementing
`Display`. Each operation returns a lightweight wrapper that itself implements `Display`, so
operations compose through `fmt::Formatter` without intermediate `String` allocations.

## Why this exists

Rust's `Display` trait is a streaming, zero-alloc formatting interface. But composing `Display`
values is awkward — there's no built-in vocabulary for "escape this, then truncate, then wrap in
tags." People end up calling `.to_string()` at every step, allocating intermediate `String`s that
only exist to be formatted again.

This crate makes the composition part zero-alloc too. Every operation is a struct wrapping
`T: Display` that implements `Display` with a transformation. Chain as many as you want.

## Features

Core functionality (always available):

| Trait | Methods |
|-------|---------|
| `SmartFormat` | `display_wrap()`, `display_prefix()`, `display_suffix()`, `display_if()`, `display_or_if()`, `display_truncate()`, `display_truncate_with()`, `display_pad_left()`, `display_pad_right()` |
| `SmartFormatCase` | `capitalize()`, `lowercase()`, `uppercase()` |
| `DisplayIterator` | `display_concat()`, `display_join(sep)` |

Feature-gated transforms:

| Feature | Trait | Methods |
|---------|-------|---------|
| `html` | `SmartFormatHtml` | `html_escape()`, `html_escape_strict()`, `html_unescape_basic()`, `html_attribute(name)` |
| `url` | `SmartFormatUrl` | `url_escape()` |
| `xml` | `SmartFormatXml` | `xml_c_data()` |
| `full` || enables all of the above |

## Usage

```toml
[dependencies]
smart-format = { version = "0.1", features = ["html"] }
```

```rust
use smart_format::prelude::*;

// Combinators chain without allocation
let output = "hello world"
    .display_truncate(5)
    .display_pad_right(10, '.')
    .display_wrap("[", "]")
    .to_string();
assert_eq!(output, "[hello.....]");

// Case transforms
let title = "hello world".capitalize().to_string();
assert_eq!(title, "Hello world");

// Iterator formatting
let joined = ["a", "b", "c"].iter().display_join(", ").to_string();
assert_eq!(joined, "a, b, c");

// Left-pad (zero-alloc, two-pass)
assert_eq!("007", 7.display_pad_left(3, '0').to_string());
```

With the `html` feature:

```rust
use smart_format::prelude::*;

// Escape, truncate, wrap — all composed, zero intermediate allocations
let safe = "<script>alert('xss')</script>"
    .html_escape()
    .display_truncate(20)
    .display_wrap("<span>", "</span>")
    .to_string();
```

## Design decisions

**Zero-alloc by construction.** Every combinator is a struct wrapping `T: Display` that implements
`Display`. No heap allocation happens until someone calls `.to_string()` or writes to a sink.
This is the same pattern used by `Iterator` adapters.

**Feature flags for domain-specific transforms.** HTML/URL/XML escaping adds dependencies and code
that many users don't need. Core text operations (case, iteration) are always available because
they have no extra dependencies and are universally useful.

**Unicode scalar values for width.** Width-sensitive operations (future: pad, truncate) count
Unicode scalar values (`char`), not bytes or grapheme clusters. This is documented as a known
limitation. Grapheme-aware and terminal-width-aware variants may be added behind a feature flag.

**Streaming escaping via bitset.** HTML escape uses a `u128` bitmask for O(1) "needs escaping?"
per ASCII byte. The bitmask is computed at compile time. Non-ASCII bytes pass through unchanged.

## MSRV

**Rust 1.85.0** (edition 2024).

## License

Licensed under either of

* Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT]LICENSE-MIT or http://opensource.org/licenses/MIT)
  at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license,
shall be dual licensed as above, without any additional terms or conditions.

## Development

Recommended (enable repo hooks once per clone):

```bash
git config core.hooksPath .githooks
```

Quality gates:

```bash
cargo +nightly fmt --all -- --check
cargo check --all-targets
cargo test --all-features
cargo clippy --all-targets -- -D warnings
```

See also: `CONTRIBUTING.md`.