Skip to main content

md_tmpl/
lib.rs

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