tomplate/lib.rs
1#![no_std]
2
3//! # Tomplate: TOML-Based Compile-Time Template Composition
4//!
5//! Tomplate is a powerful compile-time template engine for Rust that processes templates
6//! at compile time, resulting in zero runtime overhead. Templates are defined in TOML files
7//! and can use various template engines including Handlebars, Tera, and MiniJinja.
8//!
9//! ## Features
10//!
11//! - **Zero Runtime Overhead**: All template processing happens at compile time
12//! - **Multiple Template Engines**: Choose from Simple, Handlebars, Tera, or MiniJinja
13//! - **Composition Blocks**: Build complex templates from reusable parts
14//! - **Inline Templates**: Use template strings directly without registry
15//! - **Eager Evaluation**: Solve macro expansion order issues with `tomplate_eager!`
16//! - **File-Based Organization**: Store templates in `.tomplate.toml` files
17//!
18//! ## Getting Started
19//!
20//! ### Step 1: Add Dependencies
21//!
22//! Add to your `Cargo.toml`:
23//!
24//! ```toml
25//! [dependencies]
26//! tomplate = "0.1"
27//!
28//! [build-dependencies]
29//! tomplate-build = "0.1"
30//!
31//! # Optional: Enable additional template engines
32//! # [dependencies.tomplate]
33//! # version = "0.1"
34//! # features = ["handlebars", "tera"] # Add the engines you need
35//! ```
36//!
37//! ### Step 2: Create a Build Script
38//!
39//! Create `build.rs` in your project root:
40//!
41//! ```rust,ignore
42//! fn main() {
43//! tomplate_build::Builder::new()
44//! .add_patterns([
45//! "**/*.tomplate.toml", // Recursively find .tomplate.toml files
46//! "templates/*.toml", // Also check templates directory
47//! ])
48//! .build()
49//! .expect("Failed to build templates");
50//! }
51//! ```
52//!
53//! ### Step 3: Create Template Files
54//!
55//! Create a file like `templates/queries.tomplate.toml`:
56//!
57//! ```toml
58//! # Simple variable substitution (default engine)
59//! [user_query]
60//! template = "SELECT {fields} FROM users WHERE {condition}"
61//!
62//! # Using Handlebars for logic
63//! [conditional_query]
64//! template = """
65//! SELECT * FROM users
66//! {{#if status}}
67//! WHERE status = '{{status}}'
68//! {{/if}}
69//! """
70//! engine = "handlebars"
71//!
72//! # Template with default values
73//! [paginated_query]
74//! template = "SELECT * FROM {table} LIMIT {limit} OFFSET {offset}"
75//! ```
76//!
77//! ### Step 4: Use Templates in Your Code
78//!
79//! ```rust,ignore
80//! # use tomplate::tomplate;
81//! # fn main() {
82//! // Using templates from files
83//! const USER_QUERY: &str = tomplate!("user_query",
84//! fields = "id, name, email",
85//! condition = "active = true"
86//! );
87//! // Result: "SELECT id, name, email FROM users WHERE active = true"
88//!
89//! // Using inline templates (when not found in registry)
90//! const GREETING: &str = tomplate!(
91//! "Hello {name}, welcome to {place}!",
92//! name = "Alice",
93//! place = "Wonderland"
94//! );
95//! // Result: "Hello Alice, welcome to Wonderland!"
96//! # }
97//! ```
98//!
99//! ## Major Features Examples
100//!
101//! ### 1. File-Based vs Inline Templates
102//!
103//! ```rust,ignore
104//! use tomplate::tomplate;
105//!
106//! // File-based: Looks for "user_query" in your .tomplate.toml files
107//! const FROM_FILE: &str = tomplate!("user_query",
108//! fields = "id, name",
109//! condition = "active = true"
110//! );
111//!
112//! // Inline: If "Hello {name}!" isn't found in files, treats it as template
113//! const INLINE: &str = tomplate!(
114//! "Hello {name}!",
115//! name = "World"
116//! );
117//!
118//! // How it works:
119//! // 1. First checks if the string matches a template name in registry
120//! // 2. If not found, uses the string itself as an inline template
121//! ```
122//!
123//! ### 2. Nested Template Composition
124//!
125//! ```rust,ignore
126//! # use tomplate::tomplate;
127//! # fn main() {
128//! // Templates can use other templates as parameters
129//! const NESTED: &str = tomplate!("wrapper_template",
130//! header = tomplate!("header_template", title = "My App"),
131//! body = tomplate!("SELECT * FROM {table}", table = "users"),
132//! footer = tomplate!("footer_template", year = "2024")
133//! );
134//!
135//! // This enables building complex templates from simple parts
136//! # }
137//! ```
138//!
139//! ### 3. Composition Blocks with Scoped Variables
140//!
141//! ```rust,ignore
142//! use tomplate::tomplate;
143//!
144//! tomplate! {
145//! // Local variables - reusable within the block
146//! let base_fields = tomplate!("id, name, email");
147//! let active_condition = tomplate!("status = 'active'");
148//! let pagination = tomplate!("LIMIT {limit} OFFSET {offset}",
149//! limit = "10",
150//! offset = "0"
151//! );
152//!
153//! // Export constants - available outside the block
154//! const GET_ACTIVE_USERS = tomplate!(
155//! "SELECT {fields} FROM users WHERE {condition} {page}",
156//! fields = base_fields,
157//! condition = active_condition,
158//! page = pagination
159//! );
160//!
161//! const COUNT_ACTIVE = tomplate!(
162//! "SELECT COUNT(*) FROM users WHERE {condition}",
163//! condition = active_condition
164//! );
165//!
166//! // Can use both file templates and inline templates
167//! const MIXED = tomplate!("wrapper_template",
168//! content = tomplate!("Inline: {value}", value = base_fields)
169//! );
170//! }
171//!
172//! // The constants are now available for use
173//! fn main(){
174//! println!("{}", GET_ACTIVE_USERS);
175//! }
176//! // Output: "SELECT id, name, email FROM users WHERE status = 'active' LIMIT 10 OFFSET 0"
177//! ```
178//!
179//! ### 4. Multiple Template Engines
180//!
181//! ```rust,ignore
182//! // In your .tomplate.toml file:
183//! // [simple_template]
184//! // template = "Hello {name}"
185//! // engine = "simple" # Default - basic {var} substitution
186//! //
187//! // [handlebars_template]
188//! // template = "{{#if logged_in}}Welcome {{user}}{{else}}Please login{{/if}}"
189//! // engine = "handlebars"
190//! //
191//! // [tera_template]
192//! // template = "{% for item in items %}{{ item|upper }}{% endfor %}"
193//! // engine = "tera"
194//!
195//! // Use them the same way
196//! const SIMPLE: &str = tomplate!("simple_template", name = "Alice");
197//! const LOGIC: &str = tomplate!("handlebars_template",
198//! logged_in = "true",
199//! user = "Bob"
200//! );
201//! ```
202//!
203//! ### 5. Eager Evaluation for Nested Macros
204//!
205//! ```rust,ignore
206//! use tomplate::{tomplate, tomplate_eager};
207//!
208//! // Problem: This won't work with macros that expect string literals
209//! // sqlx::query!(tomplate!("select_user", id = "5")) // ❌ Fails
210//!
211//! // Solution: Use tomplate_eager! to expand inner macros first
212//! tomplate_eager! {
213//! sqlx::query!(tomplate!("select_user", id = "5")) // ✅ Works
214//! .fetch_one(&pool)
215//! .await?
216//! }
217//! ```
218//!
219//! ## Build Configuration
220//!
221//! In your `build.rs`:
222//!
223//! ```rust,ignore
224//! fn main() {
225//! tomplate::Builder::new()
226//! .add_patterns([
227//! "**/*.tomplate.toml",
228//! "templates/*.toml"
229//! ])
230//! .default_engine(tomplate::Engine::Simple)
231//! .build()
232//! .expect("Failed to build templates");
233//! }
234//! ```
235//!
236//! ## Template Files
237//!
238//! Create `.tomplate.toml` files in your project:
239//!
240//! ```toml
241//! [user_query]
242//! template = "SELECT {fields} FROM {table} WHERE {condition}"
243//! engine = "simple" # Optional, defaults to "simple"
244//!
245//! [complex_template]
246//! template = "{{#if condition}}{{value}}{{else}}default{{/if}}"
247//! engine = "handlebars"
248//! ```
249//!
250//! ## Feature Flags
251//!
252//! - `build`: Enables the build-time template discovery (enabled by default)
253//! - `handlebars`: Enables Handlebars template engine
254//! - `tera`: Enables Tera template engine
255//! - `minijinja`: Enables MiniJinja template engine
256
257/// The main template macro for compile-time template processing.
258///
259/// This macro can be used in two ways:
260/// 1. **Direct template invocation**: Process a single template
261/// 2. **Composition block**: Define multiple templates with local variables
262///
263/// # Direct Template Invocation
264///
265/// ```rust,ignore
266/// use tomplate::tomplate;
267///
268/// // From registry
269/// const QUERY: &str = tomplate!("user_query",
270/// fields = "id, name",
271/// table = "users"
272/// );
273///
274/// // Inline template
275/// const MSG: &str = tomplate!("Hello {name}!", name = "World");
276/// ```
277///
278/// # Composition Block
279///
280/// ```rust,ignore
281/// tomplate! {
282/// let helper = tomplate!("template_name");
283/// const OUTPUT = tomplate!("main_template", param = helper);
284/// }
285/// ```
286///
287/// # Parameters
288///
289/// Parameters can be:
290/// - String literals: `"value"`
291/// - Numbers: `42`, `3.14`
292/// - Booleans: `true`, `false`
293/// - Nested `tomplate!` calls for composition
294pub use tomplate_macros::tomplate;
295
296/// Eagerly evaluates `tomplate!` and `concat!` macros within a token stream.
297///
298/// This macro solves the problem where outer macros (like `sqlx::query!`) expect
299/// string literals but receive unexpanded macro calls. `tomplate_eager!` walks
300/// the token tree and expands inner macros first, allowing tomplate to work
301/// seamlessly with other macros.
302///
303/// # Examples
304///
305/// ```rust,ignore
306/// # use tomplate::{tomplate, tomplate_eager};
307/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
308/// # let pool = ();
309/// // Without tomplate_eager: ❌ Fails
310/// // sqlx::query!(tomplate!("select_user", id = "5"))
311/// // .fetch_one(&pool)
312/// // .await?;
313///
314/// // With tomplate_eager: ✅ Works
315/// tomplate_eager! {
316/// sqlx::query!(tomplate!("select_user", id = "5"))
317/// .fetch_one(&pool)
318/// .await?
319/// }
320///
321/// // Also works with concat!
322/// tomplate_eager! {
323/// const QUERY: &str = concat!(
324/// tomplate!("select_part1"),
325/// " UNION ALL ",
326/// tomplate!("select_part2")
327/// );
328/// }
329/// # Ok(())
330/// # }
331/// ```
332///
333/// # How It Works
334///
335/// The macro recursively walks through the provided token stream, finds any
336/// `tomplate!` or `concat!` invocations, evaluates them at compile time,
337/// and replaces them with their resulting string literals before passing
338/// the modified token stream to the compiler.
339pub use tomplate_macros::tomplate_eager;
340
341// Re-export builder utilities for use in build scripts
342#[cfg(feature = "build")]
343#[doc(cfg(feature = "build"))]
344pub use tomplate_build::Builder;
345
346// Re-export types for convenience
347#[cfg(feature = "build")]
348#[doc(cfg(feature = "build"))]
349pub use tomplate_build::{BuildMode, Engine, Error, Result, Template};