aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--just_template/src/expand.rs4
-rw-r--r--just_template/src/lib.rs163
-rw-r--r--just_template_macros/src/lib.rs17
3 files changed, 165 insertions, 19 deletions
diff --git a/just_template/src/expand.rs b/just_template/src/expand.rs
index b609113..954d903 100644
--- a/just_template/src/expand.rs
+++ b/just_template/src/expand.rs
@@ -56,8 +56,8 @@ fn read_impl_areas(content: String) -> Option<(String, HashMap<String, String>)>
// Implementation block start
if trimmed_line.starts_with(IMPL_AREA_BEGIN) {
- // If the current ImplArea name length is greater than 0, it means we are already inside a block,
- // since nesting is not allowed, matching fails, exit early
+ // If the current ImplArea name is not empty, a block is already active.
+ // Nesting is not allowed, so matching fails and exits early.
if !current_area_name.is_empty() {
return None;
}
diff --git a/just_template/src/lib.rs b/just_template/src/lib.rs
index 2b4f986..21a122e 100644
--- a/just_template/src/lib.rs
+++ b/just_template/src/lib.rs
@@ -1,11 +1,168 @@
-//! Template struct for storing template strings and their parameters.
+//! A template engine for code generation.
+//!
+//! `just_template` provides a template system with three layers of substitution,
+//! designed for generating repetitive source code while keeping the template
+//! readable.
+//!
+//! # Template syntax
+//!
+//! ## Simple parameters — `<<<key>>>`
+//!
+//! The most basic substitution. Replaced with the value set via
+//! [`Template::insert_param`] or the [`tmpl!`] macro.
+//!
+//! ```rust
+//! # use just_template::Template;
+//! let mut t = Template::from("Hello, <<<name>>>!".to_string());
+//! t.insert_param("name".to_string(), "World".to_string());
+//! assert_eq!(t.expand().unwrap(), "Hello, World!");
+//! ```
+//!
+//! ## Implementation blocks
+//!
+//! **Syntax:** `>>>>>>>>>> name` / `@@@ >>> name ... @@@ <<<`
+//!
+//! Template sections that can be instantiated multiple times with different
+//! parameter sets. Each instantiation is called an "arm".
+//!
+//! ```rust
+//! # use just_template::Template;
+//! let mut t = Template::from(r#"
+//! >>>>>>>>>> match_arms
+//! @@@ >>> match_arms
+//! <<<value>>> => println!("<<<value>>>"),
+//! @@@ <<<
+//! "#.trim().to_string());
+//!
+//! let arms = t.add_impl("match_arms".to_string());
+//! arms.push(std::collections::HashMap::from([
+//! ("value".to_string(), "a".to_string()),
+//! ]));
+//! arms.push(std::collections::HashMap::from([
+//! ("value".to_string(), "b".to_string()),
+//! ]));
+//!
+//! let out = t.expand().unwrap();
+//! assert!(out.contains(r#"a => println!("a")"#));
+//! assert!(out.contains(r#"b => println!("b")"#));
+//! ```
+//!
+//! ## Display blocks
+//!
+//! **Syntax:** `??? >>> name` / `??? <<<`
+//!
+//! Conditionally included sections. Hidden by default; shown when a parameter
+//! with the same name exists in the parameter map.
+//!
+//! ```rust
+//! # use just_template::Template;
+//! // Without enabling, the block is omitted
+//! let t = Template::from(r#"
+//! visible
+//! ??? >>> debug
+//! hidden by default
+//! ??? <<<
+//! "#.trim().to_string());
+//! assert_eq!(t.expand().unwrap(), "visible");
+//!
+//! // Enable by inserting a param with the block name
+//! let mut t = Template::from(r#"
+//! visible
+//! ??? >>> debug
+//! hidden by default
+//! ??? <<<
+//! "#.trim().to_string());
+//! t.insert_param("debug".to_string(), "".to_string());
+//! let out = t.expand().unwrap();
+//! assert!(out.contains("hidden by default"));
+//! ```
+//!
+//! Display blocks also work inside implementation blocks, and can be controlled
+//! per arm by including the block name in that arm's parameter map:
+//!
+//! ```rust
+//! # use just_template::Template;
+//! let mut t = Template::from(r#"
+//! >>>>>>>>>> arms
+//! @@@ >>> arms
+//! <<<name>>>
+//! ??? >>> extra
+//! <<<name>>>.extra()
+//! ??? <<<
+//! @@@ <<<
+//! "#.trim().to_string());
+//!
+//! let arms = t.add_impl("arms".to_string());
+//! // Arm 1: no "extra" → display block hidden
+//! arms.push(std::collections::HashMap::from([
+//! ("name".to_string(), "foo".to_string()),
+//! ]));
+//! // Arm 2: has "extra" → display block shown for this arm only
+//! arms.push(std::collections::HashMap::from([
+//! ("name".to_string(), "bar".to_string()),
+//! ("extra".to_string(), "".to_string()),
+//! ]));
+//!
+//! let out = t.expand().unwrap();
+//! assert!(out.contains("foo"));
+//! assert!(!out.contains("foo.extra()"));
+//! assert!(out.contains("bar"));
+//! assert!(out.contains("bar.extra()"));
+//! ```
+//!
+//! # The `tmpl!` macro
+//!
+//! The [`tmpl!`](macro.tmpl.html) proc macro provides a concise syntax for
+//! setting both simple parameters and implementation blocks.
+//!
+//! ```rust
+//! # use just_template::{Template, tmpl};
+//! let mut t = Template::from(r#"
+//! >>>>>>>>>> arms
+//! @@@ >>> arms
+//! <<<crate_name>>> => Some(<<<crate_name>>>::exec(data, params).await),
+//! @@@ <<<
+//! "#.trim().to_string());
+//!
+//! # let param: i32 = 1; // dummy
+//! tmpl!(t,
+//! func_name = "my_func",
+//! arms {
+//! crate_name = "my",
+//! {
+//! crate_name = "you",
+//! extra = ""
+//! }
+//! }
+//! );
+//! ```
+//!
+//! When the template variable is named `tmpl`, it can be omitted:
+//!
+//! ```rust
+//! # use just_template::{Template, tmpl};
+//! let mut tmpl = Template::from("<<<a>>> + <<<b>>> = <<<c>>>".to_string());
+//!
+//! tmpl! {
+//! a = "1",
+//! b = "2",
+//! c = "3",
+//! };
+//!
+//! assert_eq!(tmpl.expand().unwrap(), "1 + 2 = 3");
+//! ```
mod expand;
mod template;
-pub use template::*; // Re-export template
+pub use template::*;
-pub use just_template_macros::*; // Re-export macros
+/// Re-exports the `tmpl!` macro from `just_template_macros`.
+///
+/// This macro provides a concise syntax for setting template parameters
+/// and implementation blocks. See the [module-level documentation](index.html#the-tmpl-macro)
+/// for usage examples.
+pub use just_template_macros::tmpl;
#[cfg(test)]
pub mod test_expand;
diff --git a/just_template_macros/src/lib.rs b/just_template_macros/src/lib.rs
index 3f0b4fb..7eec692 100644
--- a/just_template_macros/src/lib.rs
+++ b/just_template_macros/src/lib.rs
@@ -3,8 +3,6 @@ use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Expr, Ident, Token, braced};
-// ── Parsing ─────────────────────────────────────────────────────────────────
-
/// Top-level item inside `tmpl! { }` or after the template ident in `tmpl!(tmpl, ...)`
enum TopItem {
/// `key = value` — simple parameter
@@ -21,8 +19,6 @@ enum Arm {
Multi(Vec<(Ident, Expr)>),
}
-// ── Parse implementations ──────────────────────────────────────────────────
-
impl Parse for TopItem {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
@@ -71,8 +67,6 @@ impl Arm {
}
}
-// ── Macro entry point ──────────────────────────────────────────────────────
-
fn split_input(input: ParseStream) -> syn::Result<(Ident, Vec<TopItem>)> {
// Try to parse: `ident , items` (explicit template variable)
// If the first token is an ident followed by `,`, it's explicit.
@@ -88,12 +82,9 @@ fn split_input(input: ParseStream) -> syn::Result<(Ident, Vec<TopItem>)> {
Ok((first, items))
} else {
// The first ident is actually the start of the first item.
- // We need to re-parse. Use `syn::Result` to signal this.
- // Tricky: we've already consumed `first`. Since syn::parse uses
- // ParseStream which is a cursor, we actually can't rewind easily.
- //
- // Solution: use a custom approach — treat the first ident as
- // the start of an item, and parse the rest as items.
+ // It must be re-parsed. Since `first` has already been consumed,
+ // the first item is parsed manually using `first` as the starting ident,
+ // and then any remaining items are parsed from the input stream.
let items = {
// Re-build: first ident + rest
let mut items: Vec<TopItem> = Vec::new();
@@ -144,8 +135,6 @@ pub fn tmpl(input: TokenStream) -> TokenStream {
}
}
-// ── Code generation ────────────────────────────────────────────────────────
-
fn generate(template_var: &Ident, items: &[TopItem]) -> Vec<proc_macro2::TokenStream> {
let mut stmts: Vec<proc_macro2::TokenStream> = Vec::new();