| Crates.io | oxur-pretty |
| lib.rs | oxur-pretty |
| version | 0.2.0 |
| created_at | 2026-01-04 08:33:53.997112+00 |
| updated_at | 2026-01-17 00:36:16.511357+00 |
| description | Pretty-printer for S-expression formatted data |
| homepage | |
| repository | https://github.com/oxur/oxur |
| max_upload_size | |
| id | 2021582 |
| size | 155,325 |
Pretty-printer for S-expression formatted data with rustfmt-style CLI.
oxur-pretty provides tools for formatting S-expressions in a human-readable way, with support for various formatting strategies and a command-line tool that follows rustfmt conventions.
Key features:
format(format(x)) === format(x)oxurfmt command-line tool matching rustfmt interface--check flagoxurfmtThe oxurfmt command-line tool formats S-expression files following rustfmt conventions.
# From workspace root
make build
# Or directly
cargo build --release -p oxur-pretty
The binary will be at ./bin/oxurfmt (via Makefile) or target/release/oxurfmt.
# Format file in-place
oxurfmt file.sexp
# Check if formatted (CI/CD)
oxurfmt --check file.sexp
# Format to stdout
oxurfmt --emit stdout file.sexp
# Stdin to stdout
cat file.sexp | oxurfmt
echo "(Span :lo 0 :hi 10)" | oxurfmt
# With custom config
oxurfmt --config max_width=120,tab_spaces=4 file.sexp
Basic Usage:
oxurfmt [OPTIONS] <FILE>...
Options:
| Flag | Description |
|---|---|
--check |
Check if files are formatted (exit 1 if not) |
--emit <MODE> |
What data to emit: files (default) or stdout |
--backup |
Backup modified files (creates .bk files) |
--config <key=val,...> |
Set config from command line |
--color <MODE> |
Use colored output: always, never, or auto |
-l, --files-with-diff |
Print names of files needing formatting |
-v, --verbose |
Print verbose output |
-q, --quiet |
Print less output |
Configuration Keys:
max_width=<N> - Maximum line width (default: 100)tab_spaces=<N> - Spaces per indent level (default: 2)align_keywords=<bool> - Align keyword-value pairs (default: true)compact_simple_values=<bool> - Keep simple values compact (default: true)max_inline_items=<N> - Max items for inline formatting (default: 3)# Single file
oxurfmtmy-ast.sexp
# Multiple files
oxurfmtfile1.sexp file2.sexp file3.sexp
# With glob expansion
oxurfmt**/*.sexp
# Check if files are formatted
oxurfmt--check src/*.sexp
# Exit code 0 if formatted, 1 if not, 2 on error
if oxurfmt--check file.sexp; then
echo "Formatted correctly"
else
echo "Needs formatting"
fi
# List files needing formatting
oxurfmt--check -l **/*.sexp
# Read from stdin, write to stdout
cat input.sexp | oxurfmt> output.sexp
# Use '-' explicitly
oxurfmt- < input.sexp > output.sexp
# Format and pipe
echo "(Item :id 0 :name value)" | oxurfmt| less
# Format to stdout even with file input
oxurfmt--emit stdout my-ast.sexp > formatted.sexp
# Create backup before formatting
oxurfmt--backup important.sexp
# Creates important.sexp.bk before modifying important.sexp
# Single option
oxurfmt--config max_width=120 file.sexp
# Multiple options
oxurfmt--config max_width=80,tab_spaces=4,align_keywords=false file.sexp
# Disable keyword alignment
oxurfmt--config align_keywords=false file.sexp
# Show what's being formatted
oxurfmt-v file1.sexp file2.sexp
# Quiet mode (errors only)
oxurfmt-q **/*.sexp
oxurfmt automatically selects the best formatting strategy based on content:
Simple expressions that fit on one line:
(a b c)
(PathSegment :ident foo :id 0)
Type-like structures with keyword-value pairs that fit the max width:
(Span :lo 0 :hi 10)
(Ident :name "use" :span (Span :lo 0 :hi 3))
Multiline with aligned keywords for readability:
(Item
:attrs ()
:id 0
:span (Span :lo 0 :hi 0)
:vis (Inherited)
:ident (Ident :name "main" :span (Span :lo 0 :hi 0))
:kind (Fn ...))
General multiline with proper indentation:
(Crate
:attrs ()
:items ((Item ...)
(Item ...))
:spans (ModSpans
:inner-span (Span :lo 0 :hi 0)
:inject-use-span (Span :lo 0 :hi 0)))
--check mode)Use oxur-pretty as a library for programmatic formatting.
use oxur_pretty::format_sexp;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = "(Span :lo 0 :hi 10)";
let formatted = format_sexp(input)?;
println!("{}", formatted);
// Output: (Span :lo 0 :hi 10)
Ok(())
}
use oxur_pretty::{format_sexp_with_config, FormatConfig};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = "(VeryLongTypeName :key1 value1 :key2 value2 :key3 value3)";
let config = FormatConfig::default()
.with_max_width(40)?
.with_tab_spaces(4)?
.with_align_keywords(true);
let formatted = format_sexp_with_config(input, config)?;
println!("{}", formatted);
// Output will be multiline because it exceeds max_width
Ok(())
}
use oxur_pretty::FormatConfig;
// Default configuration
let config = FormatConfig::default();
// max_width: 100
// tab_spaces: 2
// align_keywords: true
// compact_simple_values: true
// max_inline_items: 3
// Custom configuration
let config = FormatConfig::default()
.with_max_width(120)?
.with_tab_spaces(4)?
.with_align_keywords(false)
.with_compact_simple_values(false)
.with_max_inline_items(5);
use oxur_pretty::{format_sexp, FormatterError};
match format_sexp("(unclosed") {
Ok(formatted) => println!("{}", formatted),
Err(FormatterError::UnmatchedOpen { pos }) => {
eprintln!("Unmatched opening parenthesis at position {}", pos);
}
Err(FormatterError::UnterminatedString { pos }) => {
eprintln!("Unterminated string at position {}", pos);
}
Err(e) => eprintln!("Error: {}", e),
}
use oxur_pretty::{parse, Formatter, FormatConfig};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse S-expression
let input = r#"(Ident :name "use" :span (Span :lo 0 :hi 3))"#;
let expr = parse(input)?;
// Format with custom config
let config = FormatConfig::default().with_max_width(80)?;
let formatter = Formatter::new(config);
let output = formatter.format(&expr);
println!("{}", output);
Ok(())
}
oxur-pretty is designed to work seamlessly with oxur-ast:
use oxur_ast::sexp::print_sexp; // Built-in S-expression printer
use oxur_pretty::format_sexp; // oxur-pretty formatter
// oxur-ast generates S-expressions (compact, no specific formatting)
let sexp = generate_sexp_from_ast(&ast);
let compact = print_sexp(&sexp);
// oxur-pretty formats for human readability
let formatted = format_sexp(&compact)?;
When to use which:
oxur-ast - For AST operations, round-trip preservation, programmatic S-expression generationoxur-pretty - For human-readable output, code formatting, documentation examplesRun all tests:
cargo test -p oxur-pretty
Run specific test suites:
# Unit tests
cargo test --lib
# Integration tests
cargo test --test cli_integration
The crate includes comprehensive tests:
Coverage target: 95%+
See design document ODD-0037 for implementation details:
# From workspace root
./bin/oxd show 37
oxur-pretty follows these principles:
The formatter is designed for speed:
Run benchmarks:
cargo bench -p oxur-pretty
# Format generated AST files
./bin/aster to-ast examples/hello.rs | oxurfmt
# Format test fixtures
oxurfmt test-data/**/*.sexp
# Check formatting in CI
oxurfmt --check src/**/*.sexp || exit 1
# Format with project-specific config
oxurfmt --config max_width=120,tab_spaces=4 src/*.sexp
| Feature | oxur-pretty | oxur-ast printer | rustfmt |
|---|---|---|---|
| S-expressions | ✓ Optimized | ✓ Basic | ✗ |
| Rust code | ✗ | ✗ | ✓ Optimized |
| Idempotent | ✓ | ✓ | ✓ |
| Check mode | ✓ | ✗ | ✓ |
| Config file | ✗ CLI only | ✗ | ✓ |
| Stdin/stdout | ✓ | ✓ | ✓ |
| Backup files | ✓ | ✗ | ✓ |
| Multiple strategies | ✓ 4 strategies | ✗ Simple | ✓ Many |
Check that all parentheses are balanced:
# Use verbose mode to see where parsing fails
oxurfmt-v file.sexp
Ensure config values are valid:
# Wrong
oxurfmt--config max_width=0 file.sexp
# Correct
oxurfmt--config max_width=80 file.sexp
If using --emit stdout, output goes to stdout, not file:
# This DOES NOT modify file.sexp
oxurfmt--emit stdout file.sexp
# This modifies file.sexp in-place
oxurfmtfile.sexp
Ensure write permissions:
chmod u+w file.sexp
oxurfmtfile.sexp
See the main repository for license information.
Contributions are welcome! Please see the main repository for guidelines.