figura 0.0.2

A flexible string template formatting crate
Documentation

Figura - template

A flexible and extensible template formatting engine for Rust that supports custom delimiters, alignment options, and pluggable directive parsers.

Features

  • Flexible Delimiters: Use any characters as opening and closing delimiters (default: { and })
  • Alignment Support: Built-in support for left (<), right (>), and center (^) alignment
  • Extensible Parser System: Create custom directive parsers for domain-specific templating needs
  • Built-in Directives: Variable replacement and pattern repetition out of the box
  • Escape Sequences: Support for escaping delimiters when needed
  • Type Safety: Strong typing with clear error handling

Quick Start

Add this to your Cargo.toml:

[dependencies]
figura = "0.0.1"

Basic Usage

use figura::{Template, Context, Value, DefaultParser};
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a template
    let template = Template::<'{', '}'}>::parse::<DefaultParser>("Hello, {name}! You are {age} years old.")?;

    // Create context with values
    let mut context: Context = HashMap::new();
    context.insert("name", Value::String("Alice".to_string()));
    context.insert("age", Value::Int(30));

    // Format the template
    let result = template.format(&context)?;
    println!("{}", result); // Output: Hello, Alice! You are 30 years old.

    Ok(())
}

Core Concepts

Values

The engine supports three basic value types:

use figura::Value;

let string_val = Value::String("Hello".to_string());
let int_val = Value::Int(42);
let bool_val = Value::Bool(true);

Context

Context is a key-value map that provides data for template rendering:

use figura::{Context, Value};
use std::collections::HashMap;

let mut ctx: Context = HashMap::new();
ctx.insert("user", Value::String("John".to_string()));
ctx.insert("score", Value::Int(95));
ctx.insert("active", Value::Bool(true));

Built-in Directives

Variable Replacement

Replace placeholders with context values:

let template = Template::<'{', '}'>::parse::<DefaultParser>("Welcome, {username}!")?;

Pattern Repetition

Repeat patterns a specified number of times:

// Repeat literal pattern
let template = Template::<'{', '}'>::parse::<DefaultParser>("{*:5}")?; // Outputs: *****

// Repeat using context values
let mut ctx = HashMap::new();
ctx.insert("char", Value::String("-".to_string()));
ctx.insert("count", Value::Int(3));
let template = Template::<'{', '}'>::parse::<DefaultParser>("{char:count}")?; // Outputs: ---

Alignment

Control text alignment using alignment specifiers:

// Left alignment (default)
let template = Template::<'{', '}'>::parse::<DefaultParser>("{name<}")?;

// Right alignment
let template = Template::<'{', '}'>::parse::<DefaultParser>("{name>}")?;

// Center alignment
let template = Template::<'{', '}'>::parse::<DefaultParser>("{name^}")?;

// Check detected alignment
println!("Alignment: {:?}", template.alignment());

Custom Delimiters

Use any characters as delimiters:

// Square brackets
let template = Template::<'[', ']'>::parse::<DefaultParser>("Hello [name]!")?;

// Same character for both (useful for LaTeX-style)
let template = Template::<'|', '|'>::parse::<DefaultParser>("Value: |variable|")?;

Escape Sequences

Escape delimiters when you need literal characters:

// Double delimiters for escaping
let template = Template::<'{', '}'>::parse::<DefaultParser>("{{not a directive}} but {this_is}")?;
// Outputs: {not a directive} but [value of this_is]

Custom Parsers

Create your own directive parsers for specialized templating needs:

use figura::{Parser, Directive, Token, Context, TemplateError};

#[derive(Debug)]
struct UppercaseDirective(String);

impl Directive for UppercaseDirective {
    fn execute(&self, ctx: &Context) -> Result<String, TemplateError> {
        if let Some(value) = ctx.get(&self.0) {
            Ok(value.to_string().to_uppercase())
        } else {
            Err(TemplateError::NoValueFound(self.0.clone()))
        }
    }
}

struct CustomParser;

impl Parser for CustomParser {
    fn parse(tokens: &[Token], content: &str) -> Option<Box<dyn Directive>> {
        match tokens {
            // {variable:upper}
            [Token::Literal(var), Token::Symbol(':'), Token::Literal(cmd)]
                if cmd == "upper" => {
                Some(Box::new(UppercaseDirective(var.clone())))
            }

            // Fallback to default parsing
            _ => DefaultParser::parse(tokens, content)
        }
    }
}

// Usage
let template = Template::<'{', '}'>::parse::<CustomParser>("{name:upper}")?;

Advanced Examples

Complex Template with Multiple Features

use figura::{Template, Context, Value, DefaultParser};
use std::collections::HashMap;

let template_str = r#"
=== User Report ===
Name: {name^}
Score: {stars:score} ({score}/5)
Status: {status}
{{Note: This is a literal brace}}
"#;

let template = Template::<'{', '}'>::parse::<DefaultParser>(template_str)?;

let mut context = HashMap::new();
context.insert("name", Value::String("Alice Smith".to_string()));
context.insert("stars", Value::String("".to_string()));
context.insert("score", Value::Int(4));
context.insert("status", Value::String("Active".to_string()));

let result = template.format(&context)?;
println!("{}", result);

Working with Different Data Types

// The engine automatically converts values to strings
let mut ctx = HashMap::new();
ctx.insert("count", Value::Int(42));
ctx.insert("is_valid", Value::Bool(true));
ctx.insert("message", Value::String("Hello".to_string()));

let template = Template::<'{', '}'>::parse::<DefaultParser>(
    "Count: {count}, Valid: {is_valid}, Message: {message}"
)?;

// Output: Count: 42, Valid: true, Message: Hello

Performance Considerations

  • Templates are parsed once and can be reused multiple times with different contexts
  • The engine uses efficient string building and minimal allocations during formatting
  • Consider caching parsed templates for frequently used patterns

Contributing

Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.