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 hashbrown::HashMap;
41
42    pub use crate::{compat::LazyLock, template::analysis::inject_enum_type_constants};
43
44    /// FNV-1a hash over raw bytes.
45    ///
46    /// Deterministic and stable across Rust versions (unlike
47    /// `DefaultHasher`).  Not suitable for cryptographic use.
48    #[must_use]
49    pub fn fnv1a_hash(data: &[u8]) -> u64 {
50        const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
51        const FNV_PRIME: u64 = 0x0100_0000_01b3;
52        let mut hash = FNV_OFFSET;
53        for &byte in data {
54            hash ^= u64::from(byte);
55            hash = hash.wrapping_mul(FNV_PRIME);
56        }
57        hash
58    }
59}
60
61#[cfg(feature = "std")]
62pub use cache::TemplateCache;
63pub use context::Context;
64pub use error::{SyntaxError, TemplateError};
65#[doc(hidden)]
66#[cfg(feature = "std")]
67pub use frontmatter::parse_frontmatter_with_base_dir;
68pub use frontmatter::{
69    Frontmatter, Import, ImportedNamespace, extract_template_stem, parse_frontmatter,
70    parse_frontmatter_with_env, parse_type_annotation, strip_frontmatter,
71};
72#[cfg(feature = "std")]
73pub use frontmatter::{resolve_imports, resolve_imports_with_consts};
74#[cfg(feature = "serde")]
75pub use serde_support::{DeError, SerError, from_value, to_value};
76#[cfg(feature = "std")]
77pub use template::load_template;
78pub use template::{CompileOptions, PrecompiledTemplateData, Template};
79pub use types::{
80    BUILTIN_TYPE_NAMES, TypeCheckError, VarDecl, VarType, VariantDecl, to_pascal_case,
81};
82pub use value::{Value, ValueTypeError};
83
84/// Construct a [`Context`] with JSON-like syntax.
85///
86/// Values are recursively converted:
87/// - `"string"` → `Value::Str`
88/// - `42_i64` → `Value::Int`
89/// - `true` / `false` → `Value::Bool`
90/// - `[a, b, c]` → `Value::List`
91/// - `{ key: val, ... }` → `Value::Struct`
92/// - `(expr)` → any expression via `Into<Value>`
93///
94/// # Examples
95///
96/// Simple values:
97/// ```
98/// use md_tmpl_core::{Template, ctx};
99///
100/// let tmpl = Template::from_source(
101///     "\
102/// ---
103/// params: [greeting = str, name = str]
104/// ---
105/// {{ greeting }}, {{ name }}!",
106/// )
107/// .unwrap();
108/// let output = tmpl
109///     .render_ctx(&ctx! {
110///         greeting: "Hello",
111///         name: "world",
112///     })
113///     .unwrap();
114/// assert_eq!(output, "Hello, world!");
115/// ```
116///
117/// Nested dicts and lists:
118/// ```
119/// use md_tmpl_core::{Template, ctx};
120///
121/// let tmpl = Template::from_source(
122///     "\
123/// ---
124/// params: [items = list(label = str)]
125/// ---
126/// > {% for item in items %}
127///
128/// {{ item.label }}
129///
130/// > {% /for %}",
131/// )
132/// .unwrap();
133/// let output = tmpl
134///     .render_ctx(&ctx! {
135///         items: [
136///             { label: "alpha" },
137///             { label: "beta" },
138///         ]
139///     })
140///     .unwrap();
141/// assert_eq!(output, "alpha\nbeta\n");
142/// ```
143#[macro_export]
144macro_rules! ctx {
145    ($($key:ident : $val:tt),* $(,)?) => {{
146        let mut ctx = $crate::Context::with_capacity($crate::__count!($($key)*));
147        $(
148            ctx.set(stringify!($key), $crate::__value!($val));
149        )*
150        ctx
151    }};
152}
153
154/// Internal token-counting helper — not part of the public API.
155#[macro_export]
156#[doc(hidden)]
157macro_rules! __count {
158    () => { 0_usize };
159    ($head:tt $($rest:tt)*) => { 1_usize + $crate::__count!($($rest)*) };
160}
161
162/// Internal recursive value builder — not part of the public API.
163///
164/// Converts token trees into [`Value`] instances:
165/// - `[...]` → `Value::List(...)`
166/// - `{...}` → `Value::Struct(...)`
167/// - `(expr)` → `Value::from(expr)` (for runtime expressions)
168/// - literal → `Value::from(literal)`
169#[macro_export]
170#[doc(hidden)]
171macro_rules! __value {
172    // Array → List
173    ([ $($item:tt),* $(,)? ]) => {
174        $crate::Value::List($crate::__private::Arc::new($crate::__private::vec![ $( $crate::__value!($item) ),* ]))
175    };
176    // Object → Struct
177    ({ $($key:ident : $val:tt),* $(,)? }) => {
178        $crate::Value::new_struct([
179            $( (stringify!($key), $crate::__value!($val)) ),*
180        ])
181    };
182    // Parenthesized expression → runtime value
183    (( $e:expr )) => {
184        $crate::Value::from($e)
185    };
186    // Any single literal or ident (strings, numbers, bools, parameter names)
187    ($other:expr) => {
188        $crate::Value::from($other)
189    };
190}