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}