forme 0.1.1

Compile-time HTML template engine — plain HTML templates with tpl-* directives generate type-safe Rust rendering functions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# Template System Examples

This directory contains comprehensive examples demonstrating all features of the template system.

## Overview

The template system is a compile-time HTML template engine that generates Rust code from template files. It supports:

- Conditional rendering (`tpl-if`)
- Dynamic text content (`tpl-text`)
- Raw HTML content (`tpl-html`, `tpl-outer-html`)
- Dynamic attributes (`tpl-attr:*`)
- Optional attributes (`tpl-optional-attr:*`)
- Repeating elements (`tpl-repeat`)
- Nested templates with argument passing (`tpl-template`, `tpl-include`)
- Slot-based content passing (`tpl-slot:*`, `tpl-slot-items:*`)
- Complex Rust expressions
- Proper handling of void/self-closing elements

## Files

- **`templates/`** - Template source files (*.html)
  - `showcase.html` - Main template demonstrating all features
  - `card.html` - Reusable card component template
  - `header.html` - Header component with slot-items for top items and menu
  - `list.html` - List component with slot-items for children
  - `page.html` - Page layout composing header and list components
  - `simple.html` - Simple static HTML example
- **`showcase_example/`** - Multi-file example directory
  - `main.rs` - Example entry point
  - `types.rs` - Data structures (Item, User) used by templates
  - `generated.rs` - Auto-generated template rendering code
- **`formefile.ts`** - Config file for running the template compiler
- **`README.md`** - This file

## Quick Start

### Step 1: Generate Template Code

Run the template compiler to generate Rust code from the templates:

```bash
# Using the config file
cargo run -- --config examples/formefile.ts

# Or using explicit flags
cargo run -- --source examples/templates --output examples/showcase_example/generated.rs
```

This will:
- Read all `.html` files in the `examples/templates` directory
- Generate Rust functions for each template
- Save the code to `examples/showcase_example/generated.rs`

### Step 2: Run the Example

The example code shows how to use the generated templates:

```bash
cargo run --example showcase_example
```

### Step 3: View the Output

Open the generated HTML files in your browser:

```bash
# Linux
xdg-open examples/showcase_output.html

# macOS
open examples/showcase_output.html

# Or just open the files directly in your browser
```

## Template Features

All directive names start with `tpl-` prefix.

### 0. Template Arguments (`<tpl-arg>`)

Declare the arguments your template expects:

```html
<tpl-arg name="title" type="&str"/>
<tpl-arg name="show_header" type="bool"/>
<tpl-arg name="items" type="&[super::types::Item]"/>
<tpl-arg name="user" type="Option<&super::types::User>"/>

<!DOCTYPE html>
<html>
<!-- template content -->
</html>
```

These declarations generate the function signature with the specified parameters.

You can provide default values using the `default` attribute:

```html
<tpl-arg name="content" type="&str" default='""'/>
<tpl-arg name="content_html" type="&str" default='""'/>
```

When a default is provided, the argument becomes optional when calling the template via `tpl-template` or `tpl-include` -- if no value is passed, the default is used.

**Note:** When referencing custom types from your codebase, use proper module paths relative to the generated module. For example, if your types are in a `types` module at the same level, use `super::types::TypeName`.

### 1. Conditional Rendering (`tpl-if`)

Show or hide elements based on a boolean expression:

```html
<header tpl-if="show_header">
    <h1>This only renders if show_header is true</h1>
</header>

<div tpl-if="user.is_some()">
    User is logged in
</div>

<p tpl-if="items.len() == 0">No items available</p>
```

### 2. Text Content (`tpl-text`)

Insert dynamic text content from Rust expressions. The content is HTML-escaped:

```html
<p tpl-text="user_name">Fallback name</p>

<span tpl-text='format!("Hello {}!", name)'>Hello!</span>

<h1 tpl-text="title">Default Title</h1>
```

The text inside the element acts as a fallback and will be replaced by the expression result.

**Note:** Use single quotes for the attribute when the expression contains double quotes.

### 3. Raw HTML Content (`tpl-html` and `tpl-outer-html`)

Insert raw (unescaped) HTML content:

**`tpl-html`** replaces the element's children with raw HTML:

```html
<div tpl-html="content_html">Fallback content</div>
```

This renders `<div>` with `content_html` as its inner HTML (not escaped).

**`tpl-outer-html`** replaces the entire element (including its tags) with raw HTML:

```html
<div tpl-outer-html="content_html">Fallback content</div>
```

This renders only `content_html` -- the `<div>` wrapper is removed entirely.

### 4. Dynamic Attributes (`tpl-attr:*`)

Generate attribute values dynamically:

```html
<div tpl-attr:class='format!("user-{}", user_id)'
     tpl-attr:id="element_id">
    Content
</div>

<img tpl-attr:src="image_url"
     tpl-attr:alt="image_description">

<a tpl-attr:href='format!("/user/{}", user.id)'>Profile</a>
```

Each `tpl-attr:name` creates a dynamic `name` attribute.

### 5. Optional Attributes (`tpl-optional-attr:*`)

Add attributes conditionally based on boolean expressions:

```html
<button tpl-optional-attr:disabled="!user.is_active">
    Click me
</button>

<input type="text"
       tpl-optional-attr:readonly="is_locked"
       tpl-optional-attr:required="is_mandatory">

<input type="checkbox"
       tpl-optional-attr:checked="user.is_admin">
```

The attribute is only rendered if the expression evaluates to `true`.

### 6. Repeating Elements (`tpl-repeat`)

Loop over collections using the syntax `tpl-repeat="variable in iterator"`:

```html
<ul>
    <li tpl-repeat="item in items.iter()">
        <span tpl-text="item.name">Item</span>
    </li>
</ul>
```

With enumeration:

```html
<li tpl-repeat="(idx, item) in items.iter().enumerate()">
    <span tpl-text='format!("{}. {}", idx + 1, item.name)'>Item</span>
</li>
```

With filtering:

```html
<div tpl-repeat="item in items.iter().filter(|i| i.in_stock)">
    <p tpl-text="item.name">Product</p>
</div>
```

### 7. Nested Templates (`tpl-template` and `tpl-include`)

Use other templates as components:

```html
<div tpl-template="card.html" tpl-arg:title='&"Card Title"' tpl-arg:content='&"This is the content"'></div>

<!-- Or using tpl-include (same syntax, replaced inline instead of wrapping) -->
<div tpl-include="button.html" tpl-arg:text="&user_name"></div>
```

- Include the `.html` extension in the template name
- Pass arguments using `tpl-arg:name` attributes where `name` matches the template's argument
- Use `&` prefix for string literals or owned values to pass as references
- `tpl-template` replaces the element's children with the rendered template
- `tpl-include` replaces the entire element with the rendered template

### 8. Slot Content Passing (`tpl-slot:*` and `tpl-slot-items:*`)

For more complex argument passing to nested templates, you can use slots. Slots let you pass rendered HTML content as template arguments instead of simple expressions.

**`tpl-slot:name`** renders all children into a single `&str` argument:

```html
<div tpl-template="card.html" tpl-arg:title="&u.name">
    <template tpl-slot:content_html>
        <p><strong>Username:</strong> <span tpl-text="u.name">Unknown</span></p>
        <p><strong>Email:</strong> <span tpl-text="u.email">no-email</span></p>
    </template>
</div>
```

The children of the `<template tpl-slot:content_html>` element are rendered to a single HTML string and passed as the `content_html: &str` argument to `card.html`.

**`tpl-slot-items:name`** renders each direct child element as a separate string in a `Vec<String>`:

```html
<template tpl-include="header.html">
    <template tpl-slot-items:top_items>
        <a href="/index.html">Home</a>
        <a href="/account.html">Account</a>
    </template>
    <template tpl-slot-items:menu_items>
        <a href="/index.html">Home</a>
        <a href="/account.html">Account</a>
    </template>
</template>
```

Each direct child element inside the slot is rendered to its own `String` and collected into a `Vec<String>`, which is passed as the argument. This is useful for list-type parameters.

Children inside `tpl-slot-items:` can use directives like `tpl-repeat`, `tpl-if`, `tpl-text`, etc.

### 9. Complex Expressions

Use any valid Rust expression in directives:

```html
<p tpl-text='format!("Total: {}", items.len())'>0 items</p>

<div tpl-attr:class='format!("status-{}", if active { "on" } else { "off" })'>
    Status indicator
</div>

<span tpl-text='user.as_ref().map(|u| u.name.as_str()).unwrap_or("Guest")'>
    Guest
</span>
```

**Important:** Use single quotes for attributes containing double quotes inside expressions.

### 10. Combining Features

You can combine multiple directives on the same element:

```html
<div tpl-if="user.is_some()">
    <div tpl-repeat="u in user.iter()">
        <p tpl-text='format!("Welcome {}!", u.name)'>Guest</p>
        <div tpl-if="u.is_admin">
            <span class="badge">Admin</span>
        </div>
    </div>
</div>
```

With attributes:

```html
<button tpl-optional-attr:disabled="!is_enabled"
        tpl-attr:class='format!("btn-{}", button_style)'
        tpl-text="button_label">
    Click
</button>
```

## Template Structure

Templates are standard HTML files with directives:

```html
<!-- Optional comment describing the template -->
<tpl-arg name="title" type="&str"/>
<tpl-arg name="items" type="&[Item]"/>
<tpl-arg name="user" type="Option<&User>"/>

<!DOCTYPE html>
<html>
<head>
    <title tpl-text="title">Default Title</title>
</head>
<body>
    <!-- Your template content with directives -->
    <h1 tpl-text="title">Fallback Title</h1>

    <div tpl-if="items.len() > 0">
        <ul>
            <li tpl-repeat="item in items.iter()">
                <span tpl-text="item.name">Item</span>
            </li>
        </ul>
    </div>
</body>
</html>
```

## Generated Code

The template compiler generates Rust functions with parameters based on `<tpl-arg>` declarations:

```rust
pub fn render_card(
    out: &mut impl std::fmt::Write,
    title: &str,
    content: &str,
    content_html: &str,
) -> std::fmt::Result {
    // Generated rendering code
}
```

Function naming:
- `showcase.html` -> `render_showcase(out, ...args...)`
- `card.html` -> `render_card(out, ...args...)`
- `simple.html` -> `render_simple(out)` (if no tpl-arg)

The generated file includes `#![cfg_attr(rustfmt, rustfmt::skip)]` at the top so `cargo fmt` skips it.

## Project Structure

The example uses a clean directory-based structure:

```
examples/
├── formefile.ts           # Config file for the template compiler
├── templates/           # Template source files
│   ├── showcase.html    # Main showcase template
│   ├── card.html        # Reusable card component
│   ├── header.html      # Header with slot-items
│   ├── list.html        # List with slot-items
│   ├── page.html        # Page layout composing header and list
│   └── simple.html      # Simple static template
└── showcase_example/    # Multi-file example
    ├── main.rs          # Example entry point
    ├── types.rs         # Data structures (Item, User)
    └── generated.rs     # Auto-generated template code
```

### Data Structures Module

Data structures are defined in `types.rs`:

```rust
// examples/showcase_example/types.rs
#[derive(Debug)]
pub struct Item {
    pub id: u32,
    pub name: String,
    pub price: f64,
    pub in_stock: bool,
    pub is_featured: bool,
}

#[derive(Debug)]
pub struct User {
    pub id: u32,
    pub name: String,
    pub email: String,
    pub is_admin: bool,
}
```

Templates reference these types using proper module paths:

```html
<!-- In templates/showcase.html -->
<tpl-arg name="items" type="&[super::types::Item]"/>
<tpl-arg name="user" type="Option<&super::types::User>"/>
```

The `super::types::` path is relative to the generated module, allowing the generated code to properly reference your custom types.

## Usage in Your Code

After generating the templates, use them as standalone modules:

```rust
// examples/showcase_example/main.rs

// Import types from types module
mod types;

// Import generated template code as a standalone module
// The generated code references types using super::types::Item paths
mod generated;

// Re-export the render functions for convenience
use generated::{render_card, render_page, render_showcase, render_simple};
// Re-export types for convenience
use types::{Item, User};

fn main() {
    // Create a String to collect output
    let mut html = String::new();

    // Call the generated render function
    // Note: the function writes to the output, doesn't return HTML
    render_simple(&mut html).expect("Failed to render");

    // Use the HTML
    std::fs::write("output.html", html).unwrap();
}
```

With data:

```rust
// Pass the template arguments explicitly
let title = "My Page";
let show_header = true;
let items = vec![
    Item {
        id: 1,
        name: "Widget".into(),
        price: 9.99,
        in_stock: true,
        is_featured: false,
    },
];
let user = Some(User {
    id: 1,
    name: "Alice".into(),
    email: "alice@example.com".into(),
    is_admin: true,
});

let mut html = String::new();
render_showcase(
    &mut html,
    title,
    show_header,
    &items,
    user.as_ref(),
    // ... other arguments
).expect("Failed to render");
```

### Why This Structure?

1. **Directory-Based Organization**: Example is a proper multi-file project
2. **Separation of Concerns**: Types are in their own module (`types.rs`)
3. **Proper Module Paths**: Templates use explicit type paths like `super::types::Item`
4. **Clean Interface**: Re-export functions and types for easier use
5. **No Manual Edits**: `generated.rs` stays untouched and can be regenerated
6. **Type Safety**: Full type checking with proper module resolution
7. **Pure Module Declaration**: Uses standard `mod generated;` without any `include!()` macro
8. **Scalable**: Easy to add more modules or examples following the same pattern

**Note:** The `generated.rs` file is auto-generated and should not be manually edited. Templates reference custom types using proper module paths (e.g., `super::types::TypeName`) relative to the generated module. Since all files are in the `showcase_example` directory, Rust's module system automatically finds them when you declare `mod types;` and `mod generated;`.

**Important:** All variables used in template expressions must be passed as arguments or be in scope.

## Directive Reference

| Directive | Syntax | Purpose |
|-----------|--------|---------|
| `<tpl-arg>` | `<tpl-arg name="var" type="Type"/>` | Declare template arguments |
| `<tpl-arg>` | `<tpl-arg name="var" type="Type" default='expr'/>` | Declare with default value |
| `tpl-if` | `tpl-if="condition"` | Conditional rendering |
| `tpl-repeat` | `tpl-repeat="var in iterator"` | Loop over collections |
| `tpl-text` | `tpl-text="expression"` | Dynamic text content (HTML-escaped) |
| `tpl-html` | `tpl-html="expression"` | Raw HTML content replacing children |
| `tpl-outer-html` | `tpl-outer-html="expression"` | Raw HTML replacing entire element |
| `tpl-attr:name` | `tpl-attr:class="expr"` | Dynamic attribute value |
| `tpl-optional-attr:name` | `tpl-optional-attr:disabled="bool_expr"` | Conditional attribute |
| `tpl-template` | `tpl-template="name.html"` | Include nested template (wrapping) |
| `tpl-include` | `tpl-include="name.html"` | Include nested template (inline) |
| `tpl-arg:name` | `tpl-arg:title="expr"` | Pass argument to nested template |
| `tpl-slot:name` | `tpl-slot:content_html` (on `<template>` child) | Pass slot content as `&str` argument |
| `tpl-slot-items:name` | `tpl-slot-items:children` (on `<template>` child) | Pass slot items as `Vec<String>` argument |

## Tips

1. **Quote Handling**: Use single quotes for attributes containing double quotes
   ```html
   <p tpl-text='format!("Hello {}", name)'>Hi</p>
   ```

2. **Type Safety**: The Rust compiler validates your template expressions

3. **Fallback Content**: Text inside elements with `tpl-text` serves as fallback

4. **Template Arguments**: Declare all arguments with `<tpl-arg>` at the top of the template

5. **Default Values**: Use the `default` attribute on `<tpl-arg>` for optional arguments

6. **Reusable Components**: Create small templates and compose with `tpl-template`

7. **Reference Passing**: Use `&` when passing string literals or owned values to nested templates
   ```html
   <div tpl-template="card.html" tpl-arg:title='&"Title"' tpl-arg:content='&format!("Content: {}", val)'></div>
   ```

8. **Slot Content**: Use `tpl-slot:` for passing rendered HTML as `&str`, and `tpl-slot-items:` for passing a list of rendered items as `Vec<String>`

9. **Type Paths**: Reference custom types in templates using proper module paths relative to the generated module:
   ```html
   <tpl-arg name="items" type="&[super::types::Item]"/>
   ```

10. **Debug**: Check `generated.rs` to see the actual Rust code produced

## Troubleshooting

### Template not found

**Problem:** `Found 0 template file(s)`

**Solution:** Ensure files have `.html` or `.htm` extension (not `.tpl`)

### Type errors in generated code

**Problem:** Compilation errors about undefined variables

**Solution:**
- Pass all required arguments to the render function
- Check that argument types match the `<tpl-arg>` declarations
- Ensure types match what expressions expect
- Variables used in expressions must be passed as arguments or available in scope
- Verify type paths are correct (e.g., `super::types::Item` not `super::showcase_types::Item`)

### Attributes not rendering

**Problem:** Attributes don't appear in output

**Verification:**
- Correct syntax: `tpl-attr:name="value"` (not `attr-name`)
- For optional attrs: `tpl-optional-attr:name="bool_expr"`
- Check `generated.rs` to see what code was produced

### Quote/escaping issues

**Problem:** Invalid Rust syntax in generated code

**Solution:**
- Use single quotes for the attribute when expression contains double quotes
- Avoid inline `<style>` tags with CSS (use external stylesheets instead)
- Braces `{}` in CSS conflict with Rust format strings

## Learn More

- See `showcase.html` for a complete example demonstrating all features
- See `card.html` for a simple reusable component with slot support
- See `page.html` for a layout template composing header and list components
- See `simple.html` for a basic static template
- Run `cargo run --example showcase_example` to see it in action