| Crates.io | gullwing |
| lib.rs | gullwing |
| version | 1.0.0-rc.1 |
| created_at | 2025-12-31 18:25:45.873053+00 |
| updated_at | 2025-12-31 18:25:45.873053+00 |
| description | Runtime formatting and parsing with Python's Format Specification Mini-Language |
| homepage | |
| repository | https://github.com/freol35241/gullwing |
| max_upload_size | |
| id | 2015111 |
| size | 227,134 |
Runtime formatting and parsing with Python's Format Specification Mini-Language.
gullwing brings Python-style string formatting and parsing to Rust, enabling you to:
format() and str.format())parse package)Add this to your Cargo.toml:
[dependencies]
gullwing = "1.0.0-rc.1"
use gullwing::{Formatter, Value};
use std::collections::HashMap;
let formatter = Formatter::new("{name:>10} scored {score:05d} points")?;
let mut values = HashMap::new();
values.insert("name".to_string(), Value::from("Alice"));
values.insert("score".to_string(), Value::from(42));
let result = formatter.format_map(&values)?;
assert_eq!(result, " Alice scored 00042 points");
use gullwing::Parser;
let parser = Parser::new("{name} is {age:d} years old")?;
let result = parser.parse("Alice is 30 years old")?.unwrap();
assert_eq!(result.get("name").unwrap().as_str(), Some("Alice"));
assert_eq!(result.get("age").unwrap().as_int(), Some(30));
gullwing implements Python's format specification syntax:
[[fill]align][sign][z][#][0][width][grouping][.precision][type]
use gullwing::{Formatter, Value};
// Right-align in 10 characters
let f = Formatter::new("{:>10}")?;
// "> hello"
// Zero-pad integers to 5 digits
let f = Formatter::new("{:05d}")?;
// "00042"
// Format float with 2 decimal places
let f = Formatter::new("{:.2f}")?;
// "3.14"
// Hexadecimal with 0x prefix
let f = Formatter::new("{:#x}")?;
// "0xff"
// Thousands separator
let f = Formatter::new("{:,d}")?;
// "1,000,000"
// Center-align with custom fill
let f = Formatter::new("{:*^20}")?;
// "*******hello********"
| Type | Description | Example Input | Parsed As |
|---|---|---|---|
s |
String (default) | "hello" |
String |
d |
Decimal integer | "42", "-17" |
i64 |
b |
Binary integer | "1010", "0b1010" |
i64 |
o |
Octal integer | "755", "0o755" |
i64 |
x, X |
Hexadecimal | "ff", "0xFF" |
i64 |
f, F |
Fixed-point float | "3.14", "-2.5" |
f64 |
e, E |
Scientific notation | "1.5e10" |
f64 |
g, G |
General float | "3.14", "1e5" |
f64 |
% |
Percentage | "50%" |
f64 (0.50) |
c |
Character | "A" |
char |
use gullwing::{Parser, Formatter};
let parser = Parser::new("{timestamp} {level:>5} {message}")?;
let formatter = Formatter::new("[{level}] {message}")?;
let line = "2024-01-15T10:30:00 INFO Server started";
if let Some(parsed) = parser.parse(line)? {
let output = formatter.format_map(parsed.values())?;
println!("{}", output);
// Output: [INFO] Server started
}
use gullwing::{Parser, Formatter};
let parser = Parser::new("{id:d},{name},{score:f}")?;
let formatter = Formatter::new("ID: {id:03d} | Name: {name:<20} | Score: {score:. 1f}")?;
let csv_line = "5,Alice,95.7";
if let Some(parsed) = parser.parse(csv_line)? {
let output = formatter.format_map(parsed.values())?;
println!("{}", output);
// Output: ID: 005 | Name: Alice | Score: 95.7
}
gullwing includes a shuffle example that demonstrates text transformation capabilities:
# Build the example
cargo build --example shuffle --release
# Use it to transform log files
echo "2024-01-15 INFO Hello World" | \
target/release/examples/shuffle "{date} {level} {message}" "{level}: {message}"
# Output: INFO: Hello World
# Extract and reformat CSV data
echo "Alice,30,Engineer" | \
target/release/examples/shuffle "{name},{age:d},{job}" "{name} ({age}) - {job}"
# Output: Alice (30) - Engineer
use gullwing::Parser;
let parser = Parser::new("{number:d}")?;
// Search finds the first match
let result = parser.search("The answer is 42!")?.unwrap();
assert_eq!(result.get("number").unwrap().as_int(), Some(42));
// FindAll finds all matches
let results: Vec<_> = parser.findall("Numbers: 1, 2, 3")?.collect();
assert_eq!(results.len(), 3);
use gullwing::{Formatter, Value};
let formatter = Formatter::new("x={x}, y={y}, sum={z}")?;
let result = formatter.format_fn(|field| {
match field {
"x" => Some(Value::from(10)),
"y" => Some(Value::from(20)),
"z" => Some(Value::from(30)),
_ => None,
}
})?;
assert_eq!(result, "x=10, y=20, sum=30");
use gullwing::{Formatter, Value};
let formatter = Formatter::new("{0} + {1} = {2}")?;
let values = vec![Value::from(2), Value::from(3), Value::from(5)];
let result = formatter.format_positional(&values)?;
assert_eq!(result, "2 + 3 = 5");
Python:
"{name:>10} {value:05d}".format(name="Alice", value=42)
Rust with gullwing:
let formatter = Formatter::new("{name:>10} {value:05d}")?;
let mut values = HashMap::new();
values.insert("name".to_string(), Value::from("Alice"));
values.insert("value".to_string(), Value::from(42));
formatter.format_map(&values)?
Python:
import parse
result = parse.parse("{name} is {age:d} years old", "Alice is 30 years old")
print(result["name"], result["age"])
Rust with gullwing:
let parser = Parser::new("{name} is {age:d} years old")?;
let result = parser.parse("Alice is 30 years old")?.unwrap();
println!("{} {}",
result.get("name").unwrap().as_str().unwrap(),
result.get("age").unwrap().as_int().unwrap());
gullwing is built with:
Value enum for runtime value handlingBenchmarks run on an Intel x86_64 processor using criterion.rs. All operations are measured at runtime (no compile-time optimizations):
| Operation | Time |
|---|---|
Simple spec (>10) |
~26 ns |
Complex spec (0<+#20,.2f) |
~63 ns |
All features (*>+z#030_,.6e) |
~128 ns |
| Operation | Time |
|---|---|
| Simple string | ~104 ns |
| Integer | ~151 ns |
| Integer with grouping | ~337 ns |
| Float with precision | ~224 ns |
| Aligned/padded string | ~175 ns |
| Hex with prefix | ~160 ns |
| Complex pattern (3 fields) | ~895 ns |
| Multiple fields (10) | ~1.22 µs |
| Operation | Time |
|---|---|
| Simple pattern | ~38.5 µs |
| Integer | ~63.6 µs |
| Float | ~151.7 µs |
| Integer with grouping | ~32.0 µs |
| Multiple fields (3) | ~197.6 µs |
| Complex pattern | ~279 µs |
| Search (first match) | ~489 ns |
| FindAll (4 matches) | ~2.74 µs |
| Pattern creation | ~61.5 µs |
Key Takeaways:
std::fmt (nanoseconds)Run benchmarks yourself:
cargo bench
{obj.field})n type) falls back to default formattingContributions are welcome! Please feel free to submit issues or pull requests.
Licensed under the Apache License, Version 2.0. See LICENSE for details.
parse package