Skip to main content

md_tmpl_core/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
3
4#[macro_use]
5extern crate alloc;
6
7#[cfg(feature = "std")]
8mod cache;
9pub(crate) mod compat;
10#[doc(hidden)]
11pub mod compiled;
12/// Template grammar constants, syntax characters, and utility functions.
13///
14/// Contains the canonical definitions of expression delimiters, tag markers,
15/// type names, and other tokens used by the template engine.
16pub mod consts;
17mod context;
18mod error;
19mod filter;
20mod frontmatter;
21#[cfg(feature = "std")]
22mod include;
23mod include_core;
24mod parser;
25mod scope;
26#[cfg(feature = "serde")]
27mod serde_support;
28mod template;
29mod types;
30mod value;
31
32/// Hidden re-exports for use by proc-macro generated code.
33///
34/// These are not part of the public API — generated code references them
35/// via `::md_tmpl::__private::*`.
36#[doc(hidden)]
37pub mod __private {
38    pub use alloc::{borrow::Cow, boxed::Box, format, string::String, sync::Arc, vec, vec::Vec};
39
40    pub use crate::compat::LazyLock;
41
42    /// FNV-1a hash over raw bytes.
43    ///
44    /// Deterministic and stable across Rust versions (unlike
45    /// `DefaultHasher`).  Not suitable for cryptographic use.
46    #[must_use]
47    pub fn fnv1a_hash(data: &[u8]) -> u64 {
48        const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
49        const FNV_PRIME: u64 = 0x0100_0000_01b3;
50        let mut hash = FNV_OFFSET;
51        for &byte in data {
52            hash ^= u64::from(byte);
53            hash = hash.wrapping_mul(FNV_PRIME);
54        }
55        hash
56    }
57}
58
59#[cfg(feature = "std")]
60pub use cache::TemplateCache;
61pub use context::Context;
62pub use error::{SyntaxError, TemplateError};
63#[doc(hidden)]
64#[cfg(feature = "std")]
65pub use frontmatter::parse_frontmatter_with_base_dir;
66pub use frontmatter::{
67    Frontmatter, Import, ImportedNamespace, extract_template_stem, parse_frontmatter,
68    parse_frontmatter_with_env, parse_type_annotation, strip_frontmatter,
69};
70#[cfg(feature = "std")]
71pub use frontmatter::{resolve_imports, resolve_imports_with_consts};
72#[cfg(feature = "serde")]
73pub use serde_support::{DeError, SerError, from_value, to_value};
74#[cfg(feature = "std")]
75pub use template::load_template;
76pub use template::{CompileOptions, PrecompiledTemplateData, Template};
77pub use types::{
78    BUILTIN_TYPE_NAMES, TypeCheckError, VarDecl, VarType, VariantDecl, to_pascal_case,
79};
80pub use value::{Value, ValueTypeError};
81
82/// Construct a [`Context`] with JSON-like syntax.
83///
84/// Values are recursively converted:
85/// - `"string"` → `Value::Str`
86/// - `42_i64` → `Value::Int`
87/// - `true` / `false` → `Value::Bool`
88/// - `[a, b, c]` → `Value::List`
89/// - `{ key: val, ... }` → `Value::Struct`
90/// - `(expr)` → any expression via `Into<Value>`
91///
92/// # Examples
93///
94/// Simple values:
95/// ```
96/// use md_tmpl_core::{Template, ctx};
97///
98/// let tmpl = Template::from_source(
99///     "\
100/// ---
101/// params: [greeting = str, name = str]
102/// ---
103/// {{ greeting }}, {{ name }}!",
104/// )
105/// .unwrap();
106/// let output = tmpl
107///     .render_ctx(&ctx! {
108///         greeting: "Hello",
109///         name: "world",
110///     })
111///     .unwrap();
112/// assert_eq!(output, "Hello, world!");
113/// ```
114///
115/// Nested dicts and lists:
116/// ```
117/// use md_tmpl_core::{Template, ctx};
118///
119/// let tmpl = Template::from_source(
120///     "\
121/// ---
122/// params: [items = list(label = str)]
123/// ---
124/// > {% for item in items %}
125///
126/// {{ item.label }}
127///
128/// > {% /for %}",
129/// )
130/// .unwrap();
131/// let output = tmpl
132///     .render_ctx(&ctx! {
133///         items: [
134///             { label: "alpha" },
135///             { label: "beta" },
136///         ]
137///     })
138///     .unwrap();
139/// assert_eq!(output, "alpha\nbeta\n");
140/// ```
141#[macro_export]
142macro_rules! ctx {
143    ($($key:ident : $val:tt),* $(,)?) => {{
144        let mut ctx = $crate::Context::with_capacity($crate::__count!($($key)*));
145        $(
146            ctx.set(stringify!($key), $crate::__value!($val));
147        )*
148        ctx
149    }};
150}
151
152/// Internal token-counting helper — not part of the public API.
153#[macro_export]
154#[doc(hidden)]
155macro_rules! __count {
156    () => { 0_usize };
157    ($head:tt $($rest:tt)*) => { 1_usize + $crate::__count!($($rest)*) };
158}
159
160/// Internal recursive value builder — not part of the public API.
161///
162/// Converts token trees into [`Value`] instances:
163/// - `[...]` → `Value::List(...)`
164/// - `{...}` → `Value::Struct(...)`
165/// - `(expr)` → `Value::from(expr)` (for runtime expressions)
166/// - literal → `Value::from(literal)`
167#[macro_export]
168#[doc(hidden)]
169macro_rules! __value {
170    // Array → List
171    ([ $($item:tt),* $(,)? ]) => {
172        $crate::Value::List($crate::__private::Arc::new($crate::__private::vec![ $( $crate::__value!($item) ),* ]))
173    };
174    // Object → Struct
175    ({ $($key:ident : $val:tt),* $(,)? }) => {
176        $crate::Value::new_struct([
177            $( (stringify!($key), $crate::__value!($val)) ),*
178        ])
179    };
180    // Parenthesized expression → runtime value
181    (( $e:expr )) => {
182        $crate::Value::from($e)
183    };
184    // Any single literal or ident (strings, numbers, bools, parameter names)
185    ($other:expr) => {
186        $crate::Value::from($other)
187    };
188}