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