Skip to content

Commit f5025fb

Browse files
authored
Merge pull request #32 from bordeux/feature/ide-support
feat: add --ide-json flag for IDE integration
2 parents af42143 + 7c5a7eb commit f5025fb

82 files changed

Lines changed: 10029 additions & 9758 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 112 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ It's designed for **DevOps/SRE workflows** like generating Kubernetes manifests,
5959
4. **Security by default** - Filesystem restricted to CWD; use `--trust` for full access
6060
5. **Output validation** - `--validate json/yaml/toml` ensures valid output format
6161
6. **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
7172
tmpltool --trust system.tmpl # Allow filesystem access outside CWD
7273
tmpltool 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
7577
DB_HOST=prod-db APP_ENV=production tmpltool config.tmpl
@@ -234,6 +236,7 @@ src/
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

367458
Husky 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

371464
When debugging template issues:

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ urlencoding = "2"
3939

4040
[dev-dependencies]
4141
tempfile = "3.24.0"
42+
assert_cmd = "2"
43+
predicates = "3"
4244

4345
[package.metadata.deb]
4446
maintainer = "Chris Bednarczyk <tmpltool@bordeux.net>"

README.md

Lines changed: 141 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ A fast and simple command-line template rendering tool using [MiniJinja](https:/
3838
- [Web & URL Functions](#web--url-functions)
3939
- [Logic Functions](#logic-functions)
4040
- [Debugging & Development Functions](#debugging--development-functions)
41+
- [IDE Integration](#ide-integration)
4142
- [Advanced Examples](#advanced-examples)
4243
- [Error Handling](#error-handling)
4344
- [Development](#development)
@@ -50,18 +51,8 @@ A fast and simple command-line template rendering tool using [MiniJinja](https:/
5051
Get started in 30 seconds:
5152

5253
```bash
53-
# Download binary for your platform from releases
54-
# https://github.com/bordeux/tmpltool/releases
55-
56-
# Or use Docker to copy the binary (recommended for CI/CD):
57-
# Create a Dockerfile to extract the binary
58-
cat > Dockerfile << 'EOF'
59-
FROM alpine:latest
60-
COPY --from=ghcr.io/bordeux/tmpltool:latest /tmpltool /usr/local/bin/tmpltool
61-
EOF
62-
63-
docker build -t myapp .
64-
# Now tmpltool is available in your image at /usr/local/bin/tmpltool
54+
# Install tmpltool (works on macOS, Linux, and more)
55+
curl -fsSL https://raw.githubusercontent.com/bordeux/repo/master/install.sh | sh -s -- tmpltool
6556

6657
# Create and render template
6758
echo 'Hello {{ get_env(name="USER", default="World") }}!' > greeting.tmpl
@@ -94,6 +85,61 @@ tmpltool greeting.tmpl
9485

9586
## Installation
9687

88+
### Universal Installer (Recommended)
89+
90+
The easiest way to install tmpltool on any supported platform:
91+
92+
```bash
93+
curl -fsSL https://raw.githubusercontent.com/bordeux/repo/master/install.sh | sh -s -- tmpltool
94+
```
95+
96+
This script automatically detects your OS and installs using the appropriate package manager.
97+
98+
### macOS (Homebrew)
99+
100+
```bash
101+
brew tap bordeux/tap
102+
brew install tmpltool
103+
```
104+
105+
### Debian/Ubuntu (APT)
106+
107+
```bash
108+
# Add repository and install
109+
curl -fsSL https://raw.githubusercontent.com/bordeux/repo/master/install.sh | sh
110+
sudo apt update
111+
sudo apt install tmpltool
112+
```
113+
114+
Or manually:
115+
116+
```bash
117+
# Add GPG key
118+
curl -fsSL https://bordeux.github.io/apt-repo/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/bordeux-archive-keyring.gpg
119+
120+
# Add repository
121+
echo "deb [signed-by=/usr/share/keyrings/bordeux-archive-keyring.gpg] https://bordeux.github.io/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/bordeux.list
122+
123+
# Install
124+
sudo apt update
125+
sudo apt install tmpltool
126+
```
127+
128+
### Fedora/RHEL/CentOS (RPM)
129+
130+
```bash
131+
# Add repository and install
132+
curl -fsSL https://raw.githubusercontent.com/bordeux/repo/master/install.sh | sh
133+
sudo dnf install tmpltool # or yum on older systems
134+
```
135+
136+
Or manually:
137+
138+
```bash
139+
sudo curl -fsSL https://bordeux.github.io/rpm-repo/bordeux.repo -o /etc/yum.repos.d/bordeux.repo
140+
sudo dnf install tmpltool
141+
```
142+
97143
### From GitHub Releases
98144

99145
Download pre-built binaries for your platform from the [releases page](https://github.com/bordeux/tmpltool/releases):
@@ -179,6 +225,10 @@ cat template.txt | tmpltool [OPTIONS]
179225
- Validates the rendered output conforms to the specified format
180226
- Exits with error code 1 if validation fails
181227
- No output on success, error message only on validation failure
228+
- `--ide <FORMAT>` - Output function metadata for IDE integration (json, yaml, or toml)
229+
- Prints all available functions with descriptions, arguments, return types, and examples
230+
- Exits immediately after printing metadata (does not render templates)
231+
- Useful for building IDE plugins, autocomplete, and documentation generators
182232

183233
### Input/Output Patterns
184234

@@ -6159,6 +6209,85 @@ application:
61596209
- ✅ **Graceful Degradation**: Use `warn()` for non-critical issues
61606210
- ✅ **Fail Fast**: Use `abort()` for critical failures requiring immediate attention
61616211
6212+
## IDE Integration
6213+
6214+
The `--ide` flag outputs comprehensive metadata about all available functions, making it easy to build IDE plugins, autocomplete systems, and documentation generators.
6215+
6216+
### Output Formats
6217+
6218+
```bash
6219+
# JSON output (array of function metadata)
6220+
tmpltool --ide json > functions.json
6221+
6222+
# YAML output (list of function metadata)
6223+
tmpltool --ide yaml > functions.yaml
6224+
6225+
# TOML output (wrapped in [[functions]] array)
6226+
tmpltool --ide toml > functions.toml
6227+
```
6228+
6229+
### Metadata Structure
6230+
6231+
Each function includes:
6232+
6233+
| Field | Description |
6234+
|-------|-------------|
6235+
| `name` | Function name (e.g., `get_env`, `md5`) |
6236+
| `category` | Category grouping (e.g., `environment`, `hash`, `string`) |
6237+
| `description` | What the function does |
6238+
| `arguments` | Array of argument definitions with name, type, required flag, default value, and description |
6239+
| `return_type` | Type of value returned |
6240+
| `examples` | Usage examples showing both function and filter syntax where applicable |
6241+
| `syntax.function` | Whether callable as `func(arg=value)` |
6242+
| `syntax.filter` | Whether callable as `value \| filter` |
6243+
| `syntax.is_test` | Whether usable in `{% if value is test %}` |
6244+
6245+
### Example Output (JSON)
6246+
6247+
```json
6248+
[
6249+
{
6250+
"name": "get_env",
6251+
"category": "environment",
6252+
"description": "Get environment variable with optional default value",
6253+
"arguments": [
6254+
{
6255+
"name": "name",
6256+
"arg_type": "string",
6257+
"required": true,
6258+
"default": null,
6259+
"description": "Environment variable name"
6260+
},
6261+
{
6262+
"name": "default",
6263+
"arg_type": "string",
6264+
"required": false,
6265+
"default": null,
6266+
"description": "Default value if variable is not set"
6267+
}
6268+
],
6269+
"return_type": "string",
6270+
"examples": [
6271+
"{{ get_env(name=\"HOME\") }}",
6272+
"{{ get_env(name=\"PORT\", default=\"8080\") }}"
6273+
],
6274+
"syntax": {
6275+
"function": true,
6276+
"filter": false,
6277+
"is_test": false
6278+
}
6279+
}
6280+
]
6281+
```
6282+
6283+
### Use Cases
6284+
6285+
- **IDE Plugins**: Provide autocomplete suggestions with argument hints and documentation
6286+
- **Language Servers**: Power hover documentation and signature help
6287+
- **Documentation Generators**: Automatically generate function reference documentation
6288+
- **Validation Tools**: Verify template function usage against available functions
6289+
- **CI/CD Integration**: Generate function lists for pipeline documentation
6290+
61626291
## Advanced Examples
61636292
61646293
### Docker Compose Generator

0 commit comments

Comments
 (0)