quillmark-typst 0.44.0

Typst backend for Quillmark
Documentation
//! # Quillmark Helper Package Generator
//!
//! This module generates the virtual `@local/quillmark-helper:0.1.0` package
//! that provides document data and helper functions to Typst plates.
//!
//! ## Package Contents
//!
//! The generated package exports:
//! - `data` - A dictionary containing all document fields as JSON
//! - `eval-markup(string)` - Evaluates pre-converted Typst markup strings
//! - `parse-date(string)` - Parses ISO 8601 date strings to Typst datetime
//!
//! ## Usage in Plates
//!
//! ```typst
//! #import "@local/quillmark-helper:0.1.0": data, eval-markup, parse-date
//!
//! #data.title
//! #eval-markup(data.BODY)
//! #parse-date(data.date)
//! ```

use crate::convert::escape_string;

/// Helper function to inject JSON into Typst code.
/// Exposed for fuzzing tests.
#[doc(hidden)]
pub fn inject_json(bytes: &str) -> String {
    format!("json(bytes(\"{}\"))", escape_string(bytes))
}

/// Helper package version
pub const HELPER_VERSION: &str = "0.1.0";

/// Helper package namespace
pub const HELPER_NAMESPACE: &str = "local";

/// Helper package name
pub const HELPER_NAME: &str = "quillmark-helper";

/// Template for the `lib.typ` file, loaded at compile time
const LIB_TYP_TEMPLATE: &str = include_str!("lib.typ.template");

/// Generate the `lib.typ` content for the quillmark-helper package.
///
/// The generated file contains:
/// - Embedded JSON data as `#let data = json(bytes("..."))`
/// - `#let eval-markup(s) = eval(s, mode: "markup")` helper
/// - `#let parse-date(s) = { ... }` helper for ISO 8601 dates
pub fn generate_lib_typ(json_data: &str) -> String {
    let escaped_json = escape_string(json_data);

    LIB_TYP_TEMPLATE
        .replace("{version}", HELPER_VERSION)
        .replace("{escaped_json}", &escaped_json)
}

/// Generate the `typst.toml` content for the quillmark-helper package.
pub fn generate_typst_toml() -> String {
    format!(
        r#"[package]
name = "{name}"
version = "{version}"
namespace = "{namespace}"
entrypoint = "lib.typ"
"#,
        name = HELPER_NAME,
        version = HELPER_VERSION,
        namespace = HELPER_NAMESPACE
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_lib_typ_basic() {
        let json = r#"{"title": "Test", "BODY": "Hello"}"#;
        let lib = generate_lib_typ(json);

        // Should contain the version comment
        assert!(lib.contains("Version: 0.1.0"));

        // Should contain the data binding
        assert!(lib.contains("#let data = json(bytes("));

        // Should contain the eval-markup helper
        assert!(lib.contains("#let eval-markup(s) = eval(s, mode: \"markup\")"));

        // Should contain the parse-date helper
        assert!(lib.contains("#let parse-date(s)"));
    }

    #[test]
    fn test_generate_lib_typ_escapes_json() {
        // JSON with special characters that need escaping
        let json = r#"{"title": "Test \"quoted\""}"#;
        let lib = generate_lib_typ(json);

        // The quotes in JSON should be escaped for Typst string literal
        assert!(lib.contains("\\\""));
    }

    #[test]
    fn test_generate_lib_typ_handles_newlines() {
        let json = "{\n\"title\": \"Test\"\n}";
        let lib = generate_lib_typ(json);

        // Newlines should be escaped
        assert!(lib.contains("\\n"));
    }

    #[test]
    fn test_generate_typst_toml() {
        let toml = generate_typst_toml();

        assert!(toml.contains("name = \"quillmark-helper\""));
        assert!(toml.contains("version = \"0.1.0\""));
        assert!(toml.contains("namespace = \"local\""));
        assert!(toml.contains("entrypoint = \"lib.typ\""));
    }

    #[test]
    fn test_helper_constants() {
        assert_eq!(HELPER_VERSION, "0.1.0");
        assert_eq!(HELPER_NAMESPACE, "local");
        assert_eq!(HELPER_NAME, "quillmark-helper");
    }
}