290 lines
6.0 KiB
Markdown
290 lines
6.0 KiB
Markdown
|
|
# Pugz API Reference
|
||
|
|
|
||
|
|
## Compiled Mode
|
||
|
|
|
||
|
|
### Build Setup
|
||
|
|
|
||
|
|
In `build.zig`:
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const std = @import("std");
|
||
|
|
|
||
|
|
pub fn build(b: *std.Build) void {
|
||
|
|
const target = b.standardTargetOptions(.{});
|
||
|
|
const optimize = b.standardOptimizeOption(.{});
|
||
|
|
|
||
|
|
const pugz_dep = b.dependency("pugz", .{
|
||
|
|
.target = target,
|
||
|
|
.optimize = optimize,
|
||
|
|
});
|
||
|
|
|
||
|
|
const build_templates = @import("pugz").build_templates;
|
||
|
|
const compiled_templates = build_templates.compileTemplates(b, .{
|
||
|
|
.source_dir = "views", // Required: directory containing .pug files
|
||
|
|
.extension = ".pug", // Optional: default ".pug"
|
||
|
|
});
|
||
|
|
|
||
|
|
const exe = b.addExecutable(.{
|
||
|
|
.name = "myapp",
|
||
|
|
.root_module = b.createModule(.{
|
||
|
|
.root_source_file = b.path("src/main.zig"),
|
||
|
|
.target = target,
|
||
|
|
.optimize = optimize,
|
||
|
|
.imports = &.{
|
||
|
|
.{ .name = "pugz", .module = pugz_dep.module("pugz") },
|
||
|
|
.{ .name = "tpls", .module = compiled_templates },
|
||
|
|
},
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
b.installArtifact(exe);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Using Compiled Templates
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const std = @import("std");
|
||
|
|
const tpls = @import("tpls");
|
||
|
|
|
||
|
|
pub fn main() !void {
|
||
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||
|
|
defer arena.deinit();
|
||
|
|
|
||
|
|
// Template function name is derived from filename
|
||
|
|
// views/home.pug -> tpls.home()
|
||
|
|
// views/pages/about.pug -> tpls.pages_about()
|
||
|
|
const html = try tpls.home(arena.allocator(), .{
|
||
|
|
.title = "Welcome",
|
||
|
|
.items = &[_][]const u8{ "One", "Two" },
|
||
|
|
});
|
||
|
|
|
||
|
|
std.debug.print("{s}\n", .{html});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Template Names
|
||
|
|
|
||
|
|
File paths are converted to function names:
|
||
|
|
- `home.pug` → `home()`
|
||
|
|
- `pages/about.pug` → `pages_about()`
|
||
|
|
- `admin-panel.pug` → `admin_panel()`
|
||
|
|
|
||
|
|
List all available templates:
|
||
|
|
```zig
|
||
|
|
for (tpls.template_names) |name| {
|
||
|
|
std.debug.print("{s}\n", .{name});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Interpreted Mode
|
||
|
|
|
||
|
|
### ViewEngine
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const std = @import("std");
|
||
|
|
const pugz = @import("pugz");
|
||
|
|
|
||
|
|
pub fn main() !void {
|
||
|
|
// Initialize engine
|
||
|
|
var engine = pugz.ViewEngine.init(.{
|
||
|
|
.views_dir = "views", // Required: root views directory
|
||
|
|
.mixins_dir = "mixins", // Optional: default "mixins"
|
||
|
|
.extension = ".pug", // Optional: default ".pug"
|
||
|
|
.pretty = true, // Optional: default true
|
||
|
|
});
|
||
|
|
|
||
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||
|
|
defer arena.deinit();
|
||
|
|
|
||
|
|
// Render template (path relative to views_dir, no extension needed)
|
||
|
|
const html = try engine.render(arena.allocator(), "pages/home", .{
|
||
|
|
.title = "Hello",
|
||
|
|
.name = "World",
|
||
|
|
});
|
||
|
|
|
||
|
|
std.debug.print("{s}\n", .{html});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### renderTemplate
|
||
|
|
|
||
|
|
For inline template strings:
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const html = try pugz.renderTemplate(allocator,
|
||
|
|
\\h1 Hello, #{name}!
|
||
|
|
\\ul
|
||
|
|
\\ each item in items
|
||
|
|
\\ li= item
|
||
|
|
, .{
|
||
|
|
.name = "World",
|
||
|
|
.items = &[_][]const u8{ "one", "two", "three" },
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Data Types
|
||
|
|
|
||
|
|
Templates accept Zig structs as data. Supported field types:
|
||
|
|
|
||
|
|
| Zig Type | Template Usage |
|
||
|
|
|----------|----------------|
|
||
|
|
| `[]const u8` | `#{field}` |
|
||
|
|
| `i64`, `i32`, etc. | `#{field}` (converted to string) |
|
||
|
|
| `bool` | `if field` |
|
||
|
|
| `[]const T` | `each item in field` |
|
||
|
|
| `?T` (optional) | `if field` (null = false) |
|
||
|
|
| nested struct | `#{field.subfield}` |
|
||
|
|
|
||
|
|
### Example
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const data = .{
|
||
|
|
.title = "My Page",
|
||
|
|
.count = 42,
|
||
|
|
.show_header = true,
|
||
|
|
.items = &[_][]const u8{ "a", "b", "c" },
|
||
|
|
.user = .{
|
||
|
|
.name = "Alice",
|
||
|
|
.email = "alice@example.com",
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const html = try tpls.home(allocator, data);
|
||
|
|
```
|
||
|
|
|
||
|
|
Template:
|
||
|
|
```pug
|
||
|
|
h1= title
|
||
|
|
p Count: #{count}
|
||
|
|
if show_header
|
||
|
|
header Welcome
|
||
|
|
ul
|
||
|
|
each item in items
|
||
|
|
li= item
|
||
|
|
p #{user.name} (#{user.email})
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Directory Structure
|
||
|
|
|
||
|
|
Recommended project layout:
|
||
|
|
|
||
|
|
```
|
||
|
|
myproject/
|
||
|
|
├── build.zig
|
||
|
|
├── build.zig.zon
|
||
|
|
├── src/
|
||
|
|
│ └── main.zig
|
||
|
|
└── views/
|
||
|
|
├── mixins/
|
||
|
|
│ ├── buttons.pug
|
||
|
|
│ └── cards.pug
|
||
|
|
├── layouts/
|
||
|
|
│ └── base.pug
|
||
|
|
├── partials/
|
||
|
|
│ ├── header.pug
|
||
|
|
│ └── footer.pug
|
||
|
|
└── pages/
|
||
|
|
├── home.pug
|
||
|
|
└── about.pug
|
||
|
|
```
|
||
|
|
|
||
|
|
### Mixin Resolution
|
||
|
|
|
||
|
|
Mixins are resolved in order:
|
||
|
|
1. Defined in the current template
|
||
|
|
2. Loaded from `views/mixins/*.pug` (lazy-loaded on first use)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Web Framework Integration
|
||
|
|
|
||
|
|
### http.zig
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const std = @import("std");
|
||
|
|
const httpz = @import("httpz");
|
||
|
|
const tpls = @import("tpls");
|
||
|
|
|
||
|
|
const App = struct {
|
||
|
|
// app state
|
||
|
|
};
|
||
|
|
|
||
|
|
fn handler(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
|
||
|
|
_ = app;
|
||
|
|
_ = req;
|
||
|
|
|
||
|
|
const html = try tpls.home(res.arena, .{
|
||
|
|
.title = "Hello",
|
||
|
|
});
|
||
|
|
|
||
|
|
res.content_type = .HTML;
|
||
|
|
res.body = html;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Using ViewEngine with http.zig
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const App = struct {
|
||
|
|
engine: pugz.ViewEngine,
|
||
|
|
};
|
||
|
|
|
||
|
|
fn handler(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
|
||
|
|
_ = req;
|
||
|
|
|
||
|
|
const html = app.engine.render(res.arena, "home", .{
|
||
|
|
.title = "Hello",
|
||
|
|
}) catch |err| {
|
||
|
|
res.status = 500;
|
||
|
|
res.body = @errorName(err);
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
res.content_type = .HTML;
|
||
|
|
res.body = html;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Error Handling
|
||
|
|
|
||
|
|
```zig
|
||
|
|
const html = engine.render(allocator, "home", data) catch |err| {
|
||
|
|
switch (err) {
|
||
|
|
error.FileNotFound => // template file not found
|
||
|
|
error.ParseError => // invalid template syntax
|
||
|
|
error.OutOfMemory => // allocation failed
|
||
|
|
else => // other errors
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Memory Management
|
||
|
|
|
||
|
|
Always use `ArenaAllocator` for template rendering:
|
||
|
|
|
||
|
|
```zig
|
||
|
|
// Per-request pattern
|
||
|
|
fn handleRequest(allocator: std.mem.Allocator) ![]u8 {
|
||
|
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||
|
|
defer arena.deinit();
|
||
|
|
|
||
|
|
return try tpls.home(arena.allocator(), .{ .title = "Hello" });
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The arena pattern is efficient because:
|
||
|
|
- Template rendering creates many small allocations
|
||
|
|
- All allocations are freed at once with `arena.deinit()`
|
||
|
|
- No need to track individual allocations
|