@@ -59,6 +59,7 @@ It's designed for **DevOps/SRE workflows** like generating Kubernetes manifests,
59594 . ** Security by default** - Filesystem restricted to CWD; use ` --trust ` for full access
60605 . ** Output validation** - ` --validate json/yaml/toml ` ensures valid output format
61616 . ** Single binary** - No runtime dependencies, easy to deploy in containers
62+ 7 . ** IDE integration** - ` --ide-json ` outputs all function metadata (83 functions) for editor autocomplete/docs
6263
6364### CLI Quick Reference
6465``` bash
@@ -70,6 +71,7 @@ echo '{{ now() }}' | tmpltool # Pipe template from stdin
7071# With options
7172tmpltool --trust system.tmpl # Allow filesystem access outside CWD
7273tmpltool config.tmpl --validate json # Validate output is valid JSON
74+ tmpltool --ide-json # Output function metadata as JSON (for IDE integration)
7375
7476# With environment variables
7577DB_HOST=prod-db APP_ENV=production tmpltool config.tmpl
234236├── renderer.rs - Core template rendering logic (MiniJinja setup)
235237├── functions/ - Custom template functions (modular)
236238│ ├── mod.rs - Function registration with MiniJinja
239+ │ ├── metadata.rs - FunctionMetadata types for IDE integration
237240│ ├── environment.rs
238241│ ├── hash.rs
239242│ ├── filesystem.rs
@@ -288,26 +291,113 @@ main.rs → render_template() → read_template() → render() → write_output(
288291 Template parsing + rendering with context
289292```
290293
294+ ** 5. Trait-Based Metadata System**
295+ - All filter/is-functions implement traits with required ` METADATA ` constant
296+ - ` FilterFunction ` trait: dual function + filter syntax (e.g., ` md5 ` )
297+ - ` IsFunction ` / ` ContextIsFunction ` traits: dual function + is-test syntax (e.g., ` is_email ` )
298+ - Metadata includes: name, category, description, arguments, return type, examples, syntax variants
299+ - ` --ide-json ` collects metadata from all traits via ` get_all_metadata() ` in lib.rs
300+ - Ensures documentation stays in sync with implementation (single source of truth)
301+
291302### Adding New Functions
292303
293- When adding new template functions:
294-
295- 1 . ** Create function file** in ` src/functions/ ` (e.g., ` network.rs ` )
296- 2 . ** Implement function** using MiniJinja patterns:
297- ``` rust
298- use minijinja :: value :: Kwargs ;
299- use minijinja :: {Error , Value };
300-
301- pub fn my_function (kwargs : Kwargs ) -> Result <Value , Error > {
302- let arg : String = kwargs . get (" arg_name" )? ;
303- // Implementation
304- Ok (Value :: from (result ))
305- }
306- ```
307- 3 . ** Add module declaration** in ` src/functions/mod.rs ` : ` pub mod network; `
308- 4 . ** Register function** in ` register_all() ` : ` env.add_function("my_function", network::my_function); `
309- 5 . ** Write tests** in ` tests/test_my_function.rs ` . IMPORTANT: always in tests folder write tests. src folder should be clean from the tests.
310- 6 . ** Document** in README.md with examples
304+ Functions use a trait-based system with ** required metadata** for IDE integration. When implementing traits (` FilterFunction ` , ` IsFunction ` , ` ContextIsFunction ` ), you MUST provide a ` METADATA ` constant.
305+
306+ ** For filter functions (dual function + filter syntax):**
307+
308+ Add to ` src/filter_functions/ ` and implement ` FilterFunction ` trait:
309+ ``` rust
310+ use crate :: filter_functions :: FilterFunction ;
311+ use crate :: functions :: metadata :: {ArgumentMetadata , FunctionMetadata , SyntaxVariants };
312+ use minijinja :: value :: Kwargs ;
313+ use minijinja :: {Error , Value };
314+
315+ pub struct MyFilter ;
316+
317+ impl FilterFunction for MyFilter {
318+ const NAME : & 'static str = " my_filter" ;
319+ const METADATA : FunctionMetadata = FunctionMetadata {
320+ name : " my_filter" ,
321+ category : " string" ,
322+ description : " Description of what this does" ,
323+ arguments : & [ArgumentMetadata {
324+ name : " string" ,
325+ arg_type : " string" ,
326+ required : true ,
327+ default : None ,
328+ description : " Input string" ,
329+ }],
330+ return_type : " string" ,
331+ examples : & [
332+ " {{ my_filter(string=\ " hello\ " ) }}" ,
333+ " {{ \ " hello\ " | my_filter }}" ,
334+ ],
335+ syntax : SyntaxVariants :: FUNCTION_AND_FILTER ,
336+ };
337+
338+ fn call_as_function (kwargs : Kwargs ) -> Result <Value , Error > {
339+ let input : String = kwargs . get (" string" )? ;
340+ Ok (Value :: from (input . to_uppercase ()))
341+ }
342+
343+ fn call_as_filter (value : & Value , _kwargs : Kwargs ) -> Result <Value , Error > {
344+ let input = value . as_str (). unwrap_or_default ();
345+ Ok (Value :: from (input . to_uppercase ()))
346+ }
347+ }
348+ ```
349+
350+ Then register in ` src/filter_functions/mod.rs ` :
351+ ``` rust
352+ MyFilter :: register (env );
353+ ```
354+
355+ And add to ` get_all_metadata() ` :
356+ ``` rust
357+ & my_module :: MyFilter :: METADATA ,
358+ ```
359+
360+ ** For is-functions (dual function + is-test syntax):**
361+
362+ Add to ` src/is_functions/ ` and implement ` IsFunction ` or ` ContextIsFunction ` trait:
363+ ``` rust
364+ use crate :: is_functions :: IsFunction ;
365+ use crate :: functions :: metadata :: {ArgumentMetadata , FunctionMetadata , SyntaxVariants };
366+
367+ pub struct MyCheck ;
368+
369+ impl IsFunction for MyCheck {
370+ const FUNCTION_NAME : & 'static str = " is_my_check" ;
371+ const IS_NAME : & 'static str = " my_check" ;
372+ const METADATA : FunctionMetadata = FunctionMetadata {
373+ name : " is_my_check" ,
374+ category : " validation" ,
375+ description : " Check if value passes my_check" ,
376+ arguments : & [... ],
377+ return_type : " boolean" ,
378+ examples : & [
379+ " {{ is_my_check(string=\ " test\ " ) }}" ,
380+ " {% if \ " test\ " is my_check %}...{% endif %}" ,
381+ ],
382+ syntax : SyntaxVariants :: FUNCTION_AND_TEST ,
383+ };
384+
385+ fn call_as_function (kwargs : Kwargs ) -> Result <Value , Error > { ... }
386+ fn call_as_is (value : & Value ) -> bool { ... }
387+ }
388+ ```
389+
390+ ** For standalone functions in ` src/functions/ ` :**
391+
392+ These still use direct function registration:
393+ ``` rust
394+ pub fn my_function (kwargs : Kwargs ) -> Result <Value , Error > {
395+ let arg : String = kwargs . get (" arg_name" )? ;
396+ Ok (Value :: from (result ))
397+ }
398+ ```
399+
400+ Register in ` register_all() ` : ` env.add_function("my_function", my_module::my_function); `
311401
312402** For context-aware functions (filesystem access):**
313403``` rust
@@ -318,12 +408,13 @@ pub fn create_my_fn(context: Arc<TemplateContext>) -> impl Fn(Kwargs) -> Result<
318408 move | kwargs : Kwargs | {
319409 let path : String = kwargs . get (" path" )? ;
320410 let resolved = context . validate_and_resolve_path (& path )? ;
321- // Use resolved path
322411 Ok (Value :: from (result ))
323412 }
324413}
325414```
326415
416+ ** Testing:** Write tests in ` tests/test_my_function.rs ` (never in src folder)
417+
327418### Testing Philosophy
328419
329420- Unit tests in ` tests/ ` directory
@@ -366,6 +457,8 @@ cargo make all
366457
367458Husky pre-commit hooks validate commit message format.
368459
460+ ** Important:** Do not include Claude model references (e.g., "Co-Authored-By: Claude") in commit messages. Keep commits clean and professional.
461+
369462### Debugging Template Rendering
370463
371464When debugging template issues:
0 commit comments