diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-29 04:03:23 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-29 04:03:23 +0800 |
| commit | e2579e22812f0629706d1e81443fc512bf6add58 (patch) | |
| tree | c45c6aaa3a56c0bc4cfea4a6f626eb66568b34c0 | |
| parent | 7c20627194284f9c8ac87831917a113fb866900a (diff) | |
docs: add comprehensive module-level documentation
| -rw-r--r-- | just_template/src/expand.rs | 4 | ||||
| -rw-r--r-- | just_template/src/lib.rs | 163 | ||||
| -rw-r--r-- | just_template_macros/src/lib.rs | 17 |
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(); |
