frontmatter-gen 0.0.4

A Rust library for generating and parsing frontmatter in various formats.
Documentation

Frontmatter Gen (frontmatter-gen)

A high-performance Rust library for parsing and serialising frontmatter in YAML, TOML, and JSON formats. Built for safety, efficiency, and ease of use.

Made With Love Crates.io lib.rs Docs.rs Codecov Build Status GitHub

WebsiteDocumentationReport BugRequest FeatureContributing Guidelines

Overview 🚀

frontmatter-gen is a comprehensive Rust library that provides robust handling of frontmatter in content files. It delivers a type-safe, efficient solution for extracting, parsing, and serialising frontmatter in multiple formats. Whether you're building a static site generator, content management system, or any application requiring structured metadata, frontmatter-gen offers the tools you need.

Key Features 🎯

  • Zero-copy parsing for optimal memory efficiency
  • Multi-format support (YAML, TOML, JSON)
  • Type-safe operations with comprehensive error handling
  • Secure processing with input validation and size limits
  • Async support with the ssg feature flag
  • Command-line interface for direct manipulation

Available Features 🛠️

This crate provides several feature flags to customise its functionality:

  • default: Core frontmatter parsing functionality only
  • cli: Command-line interface tools for quick operations
  • ssg: Static Site Generator functionality (includes CLI features)

Configure features in your Cargo.toml:

[dependencies]

# Enable CLI support for validation and extraction
frontmatter-gen = { version = "0.0.4", features = ["cli"] }

# Enable all features (validation, extraction and static site generation)
frontmatter-gen = { version = "0.0.4", features = ["ssg"] }

Installation via cargo:

# Install with CLI support
cargo install frontmatter-gen --features="cli"

# Install with SSG support
cargo install frontmatter-gen --features="ssg"

Getting Started 📦

Library Usage

Add this to your Cargo.toml:

[dependencies]
# Core library with command-line interface and SSG support
frontmatter-gen = { version = "0.0.4", features = ["cli", "ssg"] }

Basic Usage 🔨

Extract and Parse Frontmatter

use frontmatter_gen::extract;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = r#"---
title: My Document
date: 2025-09-09
tags:
  - documentation
  - rust
---
# Content begins here"#;

    let (frontmatter, content) = extract(content)?;

    if let Some(title) = frontmatter.get("title").and_then(|v| v.as_str()) {
        println!("Title: {}", title);
    }

    println!("Content: {}", content);
    Ok(())
}

Format Conversion

use frontmatter_gen::{Frontmatter, Format, Value, to_format};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut frontmatter = Frontmatter::new();
    frontmatter.insert("title".to_string(), Value::String("My Document".to_string()));

    let json = to_format(&frontmatter, Format::Json)?;
    println!("JSON output: {}", json);
    assert!(json.contains(r#""title":"My Document""#));

    Ok(())
}

CLI Tool 🛠️

The fmg command provides comprehensive frontmatter operations:

# Extract frontmatter in various formats
fmg extract input.md --format yaml
fmg extract input.md --format toml
fmg extract input.md --format json

# Save extracted frontmatter
fmg extract input.md --format yaml --output frontmatter.yaml

# Validate frontmatter
fmg validate input.md --required title,date,author

You can also use the CLI directly from the source code:

# Extract frontmatter in various formats
cargo run --features="cli" extract input.md --format yaml
cargo run --features="cli" extract input.md --format toml
cargo run --features="cli" extract input.md --format json

# Save extracted frontmatter
cargo run --features="cli" extract input.md --format yaml --output frontmatter.yaml

Static Site Generation 🌐

Build and serve your static site:

# Generate a static site with the fmg CLI
fmg build \
    --content-dir content \
    --output-dir public \
    --template-dir templates

or from the source code:

# Generate a static site using cargo
cargo run --features="ssg" -- build \
    --content-dir content \
    --output-dir public \
    --template-dir templates

Serve locally (using Python for demonstration)

# Change to the output directory
cd public

# Serve the site
python -m http.server 8000 --bind 127.0.0.1

Then visit http://127.0.0.1:8000 in your favourite browser.

Error Handling 🚨

The library provides comprehensive error handling:

use frontmatter_gen::{extract, error::Error};

fn process_content(content: &str) -> Result<(), Error> {
    let (frontmatter, _) = extract(content)?;
    
    // Validate required fields
    for field in ["title", "date", "author"].iter() {
        if !frontmatter.contains_key(*field) {
            return Err(Error::ValidationError(
                format!("Missing required field: {}", field)
            ));
        }
    }
    
    Ok(())
}

Logging Support 📝

When the logging feature is enabled, the library integrates with Rust's log crate for detailed debug output. You can use any compatible logger implementation (e.g., env_logger, simple_logger).

Basic Logging Setup

use frontmatter_gen::extract;
use log::{debug, info, Level, Metadata, Record, set_logger, set_max_level};

struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Debug
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            println!(
                "{} [{}] - {}",
                record.target(),
                record.level(),
                record.args()
            );
        }
    }

    fn flush(&self) {}
}

static LOGGER: SimpleLogger = SimpleLogger;

fn init_logger() {
    // Explicitly handle logger initialization error
    if let Err(e) = set_logger(&LOGGER).map(|()| set_max_level(Level::Debug.to_level_filter())) {
        eprintln!("Failed to initialize logger: {}", e);
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize the custom logger
    init_logger();
    info!("Starting frontmatter extraction");

    let content = r#"---
title: My Document
date: 2025-09-09
---
# Content"#;

    // Extract frontmatter and remaining content
    let (frontmatter, content) = extract(content)?;
    debug!("Extracted frontmatter: {:?}", frontmatter);
    debug!("Remaining content: {:?}", content);

    Ok(())
}

Advanced Logging Configuration

For more control over logging:

use frontmatter_gen::{parser, Format};
use log::{debug, info, Level, Metadata, Record, set_logger, set_max_level};

struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Debug
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            println!("[{}] - {}", record.level(), record.args());
        }
    }

    fn flush(&self) {}
}

static LOGGER: SimpleLogger = SimpleLogger;

fn init_logger() {
    set_logger(&LOGGER).expect("Failed to set logger");
    set_max_level(Level::Debug.to_level_filter());
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize the custom logger
    init_logger();
    info!("Starting frontmatter processing");

    let yaml = r#"title: Test Document"#;
    let frontmatter = parser::parse(yaml, Format::Yaml)?;
    debug!("Parsed frontmatter: {:?}", frontmatter);

    Ok(())
}

CLI Logging 📝

When using the CLI with logging enabled:

# Set log level via environment variable
RUST_LOG=debug frontmatter_gen extract input.md --format yaml

# Or for more specific control
RUST_LOG=frontmatter_gen=debug,cli=info frontmatter_gen validate input.md

The library provides detailed error handling with context:

use frontmatter_gen::{extract, error::Error};

fn process_content(content: &str) -> Result<(), Error> {
    // Extract frontmatter and content
    let (frontmatter, _) = extract(content)?;
    
    // Validate required fields
    for field in ["title", "date", "author"].iter() {
        if !frontmatter.contains_key(*field) {
            return Err(Error::ValidationError(
                format!("Missing required field: {}", field)
            ));
        }
    }
    
    // Validate field types
    if let Some(date) = frontmatter.get("date") {
        if !date.is_string() {
            return Err(Error::ValidationError(
                "Date field must be a string".to_string()
            ));
        }
    }
    
    Ok(())
}

Documentation 📚

For comprehensive API documentation and examples, visit:

Contributing 🤝

We welcome contributions! Please see our Contributing Guidelines for details on:

  • Code of Conduct
  • Development Process
  • Submitting Pull Requests
  • Reporting Issues

Licence 📝

This project is dual-licensed under either:

at your option.

Acknowledgements 🙏

Special thanks to all contributors and the Rust community for their invaluable support and feedback.