Add documentation and interpreted benchmark
- Add docs/syntax.md: complete template syntax reference - Add docs/api.md: detailed API documentation - Add src/benchmarks/bench_interpreted.zig: runtime benchmark - Update build.zig: add bench-interpreted step - Update README.md: simplified with docs links and benchmark table
This commit is contained in:
197
README.md
197
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Pugz
|
# Pugz
|
||||||
|
|
||||||
A Pug template engine for Zig.
|
A Pug template engine for Zig, supporting both build-time compilation and runtime interpretation.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -23,27 +23,80 @@ zig fetch --save "git+https://github.com/ankitpatial/pugz#main"
|
|||||||
|
|
||||||
> **Note:** The primary repository is hosted at `code.patial.tech`. GitHub is a mirror. For dependencies, prefer the GitHub mirror for better availability.
|
> **Note:** The primary repository is hosted at `code.patial.tech`. GitHub is a mirror. For dependencies, prefer the GitHub mirror for better availability.
|
||||||
|
|
||||||
|
---
|
||||||
Then in your `build.zig`, add the `pugz` module as a dependency:
|
|
||||||
|
|
||||||
```zig
|
|
||||||
const pugz = b.dependency("pugz", .{
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
exe.root_module.addImport("pugz", pugz.module("pugz"));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
**Important:** Always use an arena allocator for rendering. The render function creates many small allocations that should be freed together. Using a general-purpose allocator without freeing will cause memory leaks.
|
### Compiled Mode (Build-Time)
|
||||||
|
|
||||||
|
Templates are converted to native Zig code at build time. No parsing happens at runtime.
|
||||||
|
|
||||||
|
**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",
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const std = @import("std");
|
||||||
|
const tpls = @import("tpls");
|
||||||
|
|
||||||
|
pub fn handleRequest(allocator: std.mem.Allocator) ![]u8 {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
return try tpls.home(arena.allocator(), .{
|
||||||
|
.title = "Welcome",
|
||||||
|
.user = .{ .name = "Alice" },
|
||||||
|
.items = &[_][]const u8{ "One", "Two", "Three" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Interpreted Mode (Runtime)
|
||||||
|
|
||||||
|
Templates are parsed and evaluated at runtime. Useful for development or dynamic templates.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const pugz = @import("pugz");
|
const pugz = @import("pugz");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
const engine = pugz.ViewEngine.init(.{
|
var engine = pugz.ViewEngine.init(.{
|
||||||
.views_dir = "views",
|
.views_dir = "views",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,29 +112,10 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### With http.zig
|
**Inline template strings:**
|
||||||
|
|
||||||
When using with http.zig, use `res.arena` which is automatically freed after each response:
|
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn handler(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
const html = try pugz.renderTemplate(allocator,
|
||||||
const html = app.view.render(res.arena, "index", .{
|
|
||||||
.title = "Hello",
|
|
||||||
}) catch |err| {
|
|
||||||
res.status = 500;
|
|
||||||
res.body = @errorName(err);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
res.content_type = .HTML;
|
|
||||||
res.body = html;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template String
|
|
||||||
|
|
||||||
```zig
|
|
||||||
const html = try engine.renderTpl(allocator,
|
|
||||||
\\h1 Hello, #{name}!
|
\\h1 Hello, #{name}!
|
||||||
\\ul
|
\\ul
|
||||||
\\ each item in items
|
\\ each item in items
|
||||||
@@ -92,67 +126,76 @@ const html = try engine.renderTpl(allocator,
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
---
|
||||||
|
|
||||||
### Run Tests
|
### With http.zig
|
||||||
|
|
||||||
```bash
|
```zig
|
||||||
zig build test
|
fn handler(_: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||||
|
// Compiled mode
|
||||||
|
const html = try tpls.home(res.arena, .{
|
||||||
|
.title = "Hello",
|
||||||
|
});
|
||||||
|
|
||||||
|
res.content_type = .HTML;
|
||||||
|
res.body = html;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run Benchmarks
|
---
|
||||||
|
|
||||||
```bash
|
## Memory Management
|
||||||
zig build bench # Run rendering benchmarks
|
|
||||||
zig build bench-2 # Run comparison benchmarks
|
Always use an `ArenaAllocator` for rendering. Template rendering creates many small allocations that should be freed together.
|
||||||
|
|
||||||
|
```zig
|
||||||
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const html = try engine.render(arena.allocator(), "index", data);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Template Syntax
|
---
|
||||||
|
|
||||||
```pug
|
## Documentation
|
||||||
doctype html
|
|
||||||
html
|
|
||||||
head
|
|
||||||
title= title
|
|
||||||
body
|
|
||||||
h1.header Hello, #{name}!
|
|
||||||
|
|
||||||
if authenticated
|
- [Template Syntax](docs/syntax.md) - Complete syntax reference
|
||||||
p Welcome back!
|
- [API Reference](docs/api.md) - Detailed API documentation
|
||||||
else
|
|
||||||
a(href="/login") Sign in
|
|
||||||
|
|
||||||
ul
|
---
|
||||||
each item in items
|
|
||||||
li= item
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
### Rendering Benchmarks (`zig build bench`)
|
Same templates and data (`src/benchmarks/templates/`), MacBook Air M2, 2000 iterations, best of 5 runs.
|
||||||
|
|
||||||
20,000 iterations on MacBook Air M2:
|
| Template | Pug.js | Pugz Compiled | Diff | Pugz Interpreted | Diff |
|
||||||
|
|----------|--------|---------------|------|------------------|------|
|
||||||
|
| simple-0 | 0.4ms | 0.1ms | +4x | 0.4ms | 1x |
|
||||||
|
| simple-1 | 1.3ms | 0.6ms | +2.2x | 5.8ms | -4.5x |
|
||||||
|
| simple-2 | 1.6ms | 0.5ms | +3.2x | 4.6ms | -2.9x |
|
||||||
|
| if-expression | 0.5ms | 0.2ms | +2.5x | 4.1ms | -8.2x |
|
||||||
|
| projects-escaped | 4.2ms | 0.6ms | +7x | 5.8ms | -1.4x |
|
||||||
|
| search-results | 14.7ms | 5.3ms | +2.8x | 50.7ms | -3.4x |
|
||||||
|
| friends | 145.5ms | 50.4ms | +2.9x | 450.8ms | -3.1x |
|
||||||
|
|
||||||
| Template | Avg | Renders/sec | Output |
|
- Pug.js and Pugz Compiled: render-only (pre-compiled)
|
||||||
|----------|-----|-------------|--------|
|
- Pugz Interpreted: parse + render on each iteration
|
||||||
| Simple | 11.81 us | 84,701 | 155 bytes |
|
- Diff: +Nx = N times faster, -Nx = N times slower
|
||||||
| Medium | 21.10 us | 47,404 | 1,211 bytes |
|
|
||||||
| Complex | 33.48 us | 29,872 | 4,852 bytes |
|
|
||||||
|
|
||||||
### Comparison Benchmarks (`zig build bench-2`)
|
---
|
||||||
ref: https://github.com/itsarnaud/template-engine-bench
|
|
||||||
|
|
||||||
2,000 iterations vs Pug.js:
|
## Development
|
||||||
|
|
||||||
| Template | Pugz | Pug.js | Speedup |
|
```bash
|
||||||
|----------|------|--------|---------|
|
zig build test # Run all tests
|
||||||
| simple-0 | 0.5ms | 2ms | 3.8x |
|
zig build bench-compiled # Benchmark compiled mode
|
||||||
| simple-1 | 6.7ms | 9ms | 1.3x |
|
zig build bench-interpreted # Benchmark interpreted mode
|
||||||
| simple-2 | 5.4ms | 9ms | 1.7x |
|
|
||||||
| if-expression | 4.4ms | 12ms | 2.7x |
|
# Pug.js benchmark (for comparison)
|
||||||
| projects-escaped | 7.3ms | 86ms | 11.7x |
|
cd src/benchmarks/pugjs && npm install && npm run bench
|
||||||
| search-results | 70.6ms | 41ms | 0.6x |
|
```
|
||||||
| friends | 682.1ms | 110ms | 0.2x |
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
99
build.zig
99
build.zig
@@ -1,11 +1,15 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
// Re-export build_templates for use by dependent packages
|
||||||
|
pub const build_templates = @import("src/build_templates.zig");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
const mod = b.addModule("pugz", .{
|
const mod = b.addModule("pugz", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creates an executable that will run `test` blocks from the provided module.
|
// Creates an executable that will run `test` blocks from the provided module.
|
||||||
@@ -78,101 +82,62 @@ pub fn build(b: *std.Build) void {
|
|||||||
test_unit_step.dependOn(&run_mod_tests.step);
|
test_unit_step.dependOn(&run_mod_tests.step);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
// Example: demo - Template Inheritance Demo with http.zig
|
// Compiled Templates Benchmark (compare with Pug.js bench.js)
|
||||||
|
// Uses auto-generated templates from src/benchmarks/templates/
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
const httpz_dep = b.dependency("httpz", .{
|
const mod_fast = b.addModule("pugz-fast", .{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = .ReleaseFast,
|
||||||
});
|
});
|
||||||
|
|
||||||
const demo = b.addExecutable(.{
|
const bench_templates = build_templates.compileTemplates(b, .{
|
||||||
.name = "demo",
|
.source_dir = "src/benchmarks/templates",
|
||||||
.root_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/examples/demo/main.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "pugz", .module = mod },
|
|
||||||
.{ .name = "httpz", .module = httpz_dep.module("httpz") },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
b.installArtifact(demo);
|
const bench_compiled = b.addExecutable(.{
|
||||||
|
.name = "bench-compiled",
|
||||||
const run_demo = b.addRunArtifact(demo);
|
|
||||||
run_demo.step.dependOn(b.getInstallStep());
|
|
||||||
|
|
||||||
const demo_step = b.step("demo", "Run the template inheritance demo web app");
|
|
||||||
demo_step.dependOn(&run_demo.step);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Benchmark executable
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
const bench = b.addExecutable(.{
|
|
||||||
.name = "bench",
|
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/benchmarks/benchmark.zig"),
|
.root_source_file = b.path("src/benchmarks/bench.zig"),
|
||||||
.target = target,
|
|
||||||
.optimize = .ReleaseFast, // Always use ReleaseFast for benchmarks
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "pugz", .module = mod },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
b.installArtifact(bench);
|
|
||||||
|
|
||||||
const run_bench = b.addRunArtifact(bench);
|
|
||||||
run_bench.step.dependOn(b.getInstallStep());
|
|
||||||
|
|
||||||
const bench_step = b.step("bench", "Run rendering benchmarks");
|
|
||||||
bench_step.dependOn(&run_bench.step);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Comparison Benchmark Tests (template-engine-bench templates)
|
|
||||||
// Run all: zig build test-bench
|
|
||||||
// Run one: zig build test-bench -- simple-0
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
const bench_tests = b.addTest(.{
|
|
||||||
.root_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/benchmarks/benchmark_2.zig"),
|
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = .ReleaseFast,
|
.optimize = .ReleaseFast,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "pugz", .module = mod },
|
.{ .name = "pugz", .module = mod_fast },
|
||||||
|
.{ .name = "tpls", .module = bench_templates },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
.filters = if (b.args) |args| args else &.{},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const run_bench_tests = b.addRunArtifact(bench_tests);
|
b.installArtifact(bench_compiled);
|
||||||
|
|
||||||
const bench_test_step = b.step("bench-2", "Run comparison benchmarks (template-engine-bench)");
|
const run_bench_compiled = b.addRunArtifact(bench_compiled);
|
||||||
bench_test_step.dependOn(&run_bench_tests.step);
|
run_bench_compiled.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
const bench_compiled_step = b.step("bench-compiled", "Benchmark compiled templates (compare with Pug.js)");
|
||||||
|
bench_compiled_step.dependOn(&run_bench_compiled.step);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
// Profile executable (for CPU profiling)
|
// Interpreted (Runtime) Benchmark
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
const profile = b.addExecutable(.{
|
const bench_interpreted = b.addExecutable(.{
|
||||||
.name = "profile",
|
.name = "bench-interpreted",
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/benchmarks/profile_friends.zig"),
|
.root_source_file = b.path("src/benchmarks/bench_interpreted.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = .ReleaseFast,
|
.optimize = .ReleaseFast,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "pugz", .module = mod },
|
.{ .name = "pugz", .module = mod_fast },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
b.installArtifact(profile);
|
b.installArtifact(bench_interpreted);
|
||||||
|
|
||||||
const run_profile = b.addRunArtifact(profile);
|
const run_bench_interpreted = b.addRunArtifact(bench_interpreted);
|
||||||
run_profile.step.dependOn(b.getInstallStep());
|
run_bench_interpreted.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
const profile_step = b.step("profile", "Run friends template for profiling");
|
const bench_interpreted_step = b.step("bench-interpreted", "Benchmark interpreted (runtime) templates");
|
||||||
profile_step.dependOn(&run_profile.step);
|
bench_interpreted_step.dependOn(&run_bench_interpreted.step);
|
||||||
|
|
||||||
// Just like flags, top level steps are also listed in the `--help` menu.
|
// Just like flags, top level steps are also listed in the `--help` menu.
|
||||||
//
|
//
|
||||||
|
|||||||
289
docs/api.md
Normal file
289
docs/api.md
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
# 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
|
||||||
340
docs/syntax.md
Normal file
340
docs/syntax.md
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
# Pugz Template Syntax
|
||||||
|
|
||||||
|
Complete reference for Pugz template syntax.
|
||||||
|
|
||||||
|
## Tags & Nesting
|
||||||
|
|
||||||
|
Indentation defines nesting. Default tag is `div`.
|
||||||
|
|
||||||
|
```pug
|
||||||
|
div
|
||||||
|
h1 Title
|
||||||
|
p Paragraph
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```html
|
||||||
|
<div><h1>Title</h1><p>Paragraph</p></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Classes & IDs
|
||||||
|
|
||||||
|
Shorthand syntax using `.` for classes and `#` for IDs.
|
||||||
|
|
||||||
|
```pug
|
||||||
|
div#main.container.active
|
||||||
|
.box
|
||||||
|
#sidebar
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```html
|
||||||
|
<div id="main" class="container active"></div>
|
||||||
|
<div class="box"></div>
|
||||||
|
<div id="sidebar"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Attributes
|
||||||
|
|
||||||
|
```pug
|
||||||
|
a(href="/link" target="_blank") Click
|
||||||
|
input(type="checkbox" checked)
|
||||||
|
button(disabled=false)
|
||||||
|
button(disabled=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```html
|
||||||
|
<a href="/link" target="_blank">Click</a>
|
||||||
|
<input type="checkbox" checked="checked" />
|
||||||
|
<button></button>
|
||||||
|
<button disabled="disabled"></button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Boolean attributes: `false` omits the attribute, `true` renders `attr="attr"`.
|
||||||
|
|
||||||
|
## Text Content
|
||||||
|
|
||||||
|
### Inline text
|
||||||
|
|
||||||
|
```pug
|
||||||
|
p Hello World
|
||||||
|
```
|
||||||
|
|
||||||
|
### Piped text
|
||||||
|
|
||||||
|
```pug
|
||||||
|
p
|
||||||
|
| Line one
|
||||||
|
| Line two
|
||||||
|
```
|
||||||
|
|
||||||
|
### Block text (dot syntax)
|
||||||
|
|
||||||
|
```pug
|
||||||
|
script.
|
||||||
|
console.log('hello');
|
||||||
|
console.log('world');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Literal HTML
|
||||||
|
|
||||||
|
```pug
|
||||||
|
<p>Passed through as-is</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interpolation
|
||||||
|
|
||||||
|
### Escaped (safe)
|
||||||
|
|
||||||
|
```pug
|
||||||
|
p Hello #{name}
|
||||||
|
p= variable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unescaped (raw HTML)
|
||||||
|
|
||||||
|
```pug
|
||||||
|
p Hello !{rawHtml}
|
||||||
|
p!= rawVariable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tag interpolation
|
||||||
|
|
||||||
|
```pug
|
||||||
|
p This is #[em emphasized] text
|
||||||
|
p Click #[a(href="/") here] to continue
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditionals
|
||||||
|
|
||||||
|
### if / else if / else
|
||||||
|
|
||||||
|
```pug
|
||||||
|
if condition
|
||||||
|
p Yes
|
||||||
|
else if other
|
||||||
|
p Maybe
|
||||||
|
else
|
||||||
|
p No
|
||||||
|
```
|
||||||
|
|
||||||
|
### unless
|
||||||
|
|
||||||
|
```pug
|
||||||
|
unless loggedIn
|
||||||
|
p Please login
|
||||||
|
```
|
||||||
|
|
||||||
|
### String comparison
|
||||||
|
|
||||||
|
```pug
|
||||||
|
if status == "active"
|
||||||
|
p Active
|
||||||
|
```
|
||||||
|
|
||||||
|
## Iteration
|
||||||
|
|
||||||
|
### each
|
||||||
|
|
||||||
|
```pug
|
||||||
|
each item in items
|
||||||
|
li= item
|
||||||
|
```
|
||||||
|
|
||||||
|
### with index
|
||||||
|
|
||||||
|
```pug
|
||||||
|
each val, index in list
|
||||||
|
li #{index}: #{val}
|
||||||
|
```
|
||||||
|
|
||||||
|
### with else (empty collection)
|
||||||
|
|
||||||
|
```pug
|
||||||
|
each item in items
|
||||||
|
li= item
|
||||||
|
else
|
||||||
|
li No items
|
||||||
|
```
|
||||||
|
|
||||||
|
### Objects
|
||||||
|
|
||||||
|
```pug
|
||||||
|
each val, key in object
|
||||||
|
p #{key}: #{val}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested iteration
|
||||||
|
|
||||||
|
```pug
|
||||||
|
each friend in friends
|
||||||
|
li #{friend.name}
|
||||||
|
each tag in friend.tags
|
||||||
|
span= tag
|
||||||
|
```
|
||||||
|
|
||||||
|
## Case / When
|
||||||
|
|
||||||
|
```pug
|
||||||
|
case status
|
||||||
|
when "active"
|
||||||
|
p Active
|
||||||
|
when "pending"
|
||||||
|
p Pending
|
||||||
|
default
|
||||||
|
p Unknown
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mixins
|
||||||
|
|
||||||
|
### Basic mixin
|
||||||
|
|
||||||
|
```pug
|
||||||
|
mixin button(text)
|
||||||
|
button= text
|
||||||
|
|
||||||
|
+button("Click me")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default parameters
|
||||||
|
|
||||||
|
```pug
|
||||||
|
mixin button(text, type="primary")
|
||||||
|
button(class="btn btn-" + type)= text
|
||||||
|
|
||||||
|
+button("Click me")
|
||||||
|
+button("Submit", "success")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Block content
|
||||||
|
|
||||||
|
```pug
|
||||||
|
mixin card(title)
|
||||||
|
.card
|
||||||
|
h3= title
|
||||||
|
block
|
||||||
|
|
||||||
|
+card("My Card")
|
||||||
|
p Card content here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rest arguments
|
||||||
|
|
||||||
|
```pug
|
||||||
|
mixin list(id, ...items)
|
||||||
|
ul(id=id)
|
||||||
|
each item in items
|
||||||
|
li= item
|
||||||
|
|
||||||
|
+list("mylist", "a", "b", "c")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attributes pass-through
|
||||||
|
|
||||||
|
```pug
|
||||||
|
mixin link(href, text)
|
||||||
|
a(href=href)&attributes(attributes)= text
|
||||||
|
|
||||||
|
+link("/home", "Home")(class="nav-link" data-id="1")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Template Inheritance
|
||||||
|
|
||||||
|
### Base layout (layout.pug)
|
||||||
|
|
||||||
|
```pug
|
||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title= title
|
||||||
|
block styles
|
||||||
|
body
|
||||||
|
block content
|
||||||
|
block scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Child template
|
||||||
|
|
||||||
|
```pug
|
||||||
|
extends layout.pug
|
||||||
|
|
||||||
|
block content
|
||||||
|
h1 Page Title
|
||||||
|
p Page content
|
||||||
|
```
|
||||||
|
|
||||||
|
### Block modes
|
||||||
|
|
||||||
|
```pug
|
||||||
|
block append scripts
|
||||||
|
script(src="extra.js")
|
||||||
|
|
||||||
|
block prepend styles
|
||||||
|
link(rel="stylesheet" href="extra.css")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Includes
|
||||||
|
|
||||||
|
```pug
|
||||||
|
include header.pug
|
||||||
|
include partials/footer.pug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
### HTML comment (rendered)
|
||||||
|
|
||||||
|
```pug
|
||||||
|
// This renders as HTML comment
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```html
|
||||||
|
<!-- This renders as HTML comment -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### Silent comment (not rendered)
|
||||||
|
|
||||||
|
```pug
|
||||||
|
//- This is a silent comment
|
||||||
|
```
|
||||||
|
|
||||||
|
## Block Expansion
|
||||||
|
|
||||||
|
Colon for inline nesting:
|
||||||
|
|
||||||
|
```pug
|
||||||
|
a: img(src="logo.png")
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```html
|
||||||
|
<a><img src="logo.png" /></a>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Self-Closing Tags
|
||||||
|
|
||||||
|
Explicit self-closing with `/`:
|
||||||
|
|
||||||
|
```pug
|
||||||
|
foo/
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```html
|
||||||
|
<foo />
|
||||||
|
```
|
||||||
|
|
||||||
|
Void elements (`br`, `hr`, `img`, `input`, `meta`, `link`, etc.) are automatically self-closing.
|
||||||
|
|
||||||
|
## Doctype
|
||||||
|
|
||||||
|
```pug
|
||||||
|
doctype html
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
```
|
||||||
154
src/benchmarks/bench_interpreted.zig
Normal file
154
src/benchmarks/bench_interpreted.zig
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
//! Pugz Benchmark - Interpreted (Runtime) Mode
|
||||||
|
//!
|
||||||
|
//! This benchmark uses the ViewEngine to render templates at runtime,
|
||||||
|
//! reading from the same template/data files as the compiled benchmark.
|
||||||
|
//!
|
||||||
|
//! Run: zig build bench-interpreted
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const pugz = @import("pugz");
|
||||||
|
|
||||||
|
const iterations: usize = 2000;
|
||||||
|
const templates_dir = "src/benchmarks/templates";
|
||||||
|
|
||||||
|
// Data structures matching JSON files
|
||||||
|
const SubFriend = struct {
|
||||||
|
id: i64,
|
||||||
|
name: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Friend = struct {
|
||||||
|
name: []const u8,
|
||||||
|
balance: []const u8,
|
||||||
|
age: i64,
|
||||||
|
address: []const u8,
|
||||||
|
picture: []const u8,
|
||||||
|
company: []const u8,
|
||||||
|
email: []const u8,
|
||||||
|
emailHref: []const u8,
|
||||||
|
about: []const u8,
|
||||||
|
tags: []const []const u8,
|
||||||
|
friends: []const SubFriend,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Account = struct {
|
||||||
|
balance: i64,
|
||||||
|
balanceFormatted: []const u8,
|
||||||
|
status: []const u8,
|
||||||
|
negative: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Project = struct {
|
||||||
|
name: []const u8,
|
||||||
|
url: []const u8,
|
||||||
|
description: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SearchRecord = struct {
|
||||||
|
imgUrl: []const u8,
|
||||||
|
viewItemUrl: []const u8,
|
||||||
|
title: []const u8,
|
||||||
|
description: []const u8,
|
||||||
|
featured: bool,
|
||||||
|
sizes: ?[]const []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
std.debug.print("╔═══════════════════════════════════════════════════════════════╗\n", .{});
|
||||||
|
std.debug.print("║ Interpreted (Runtime) Benchmark ({d} iterations) ║\n", .{iterations});
|
||||||
|
std.debug.print("║ Templates: {s}/*.pug ║\n", .{templates_dir});
|
||||||
|
std.debug.print("╚═══════════════════════════════════════════════════════════════╝\n", .{});
|
||||||
|
|
||||||
|
// Load JSON data
|
||||||
|
std.debug.print("\nLoading JSON data...\n", .{});
|
||||||
|
|
||||||
|
var data_arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer data_arena.deinit();
|
||||||
|
const data_alloc = data_arena.allocator();
|
||||||
|
|
||||||
|
const simple0 = try loadJson(struct { name: []const u8 }, data_alloc, "simple-0.json");
|
||||||
|
const simple1 = try loadJson(struct {
|
||||||
|
name: []const u8,
|
||||||
|
messageCount: i64,
|
||||||
|
colors: []const []const u8,
|
||||||
|
primary: bool,
|
||||||
|
}, data_alloc, "simple-1.json");
|
||||||
|
const simple2 = try loadJson(struct {
|
||||||
|
header: []const u8,
|
||||||
|
header2: []const u8,
|
||||||
|
header3: []const u8,
|
||||||
|
header4: []const u8,
|
||||||
|
header5: []const u8,
|
||||||
|
header6: []const u8,
|
||||||
|
list: []const []const u8,
|
||||||
|
}, data_alloc, "simple-2.json");
|
||||||
|
const if_expr = try loadJson(struct { accounts: []const Account }, data_alloc, "if-expression.json");
|
||||||
|
const projects = try loadJson(struct {
|
||||||
|
title: []const u8,
|
||||||
|
text: []const u8,
|
||||||
|
projects: []const Project,
|
||||||
|
}, data_alloc, "projects-escaped.json");
|
||||||
|
const search = try loadJson(struct { searchRecords: []const SearchRecord }, data_alloc, "search-results.json");
|
||||||
|
const friends_data = try loadJson(struct { friends: []const Friend }, data_alloc, "friends.json");
|
||||||
|
|
||||||
|
// Load template sources
|
||||||
|
const simple0_tpl = try loadTemplate(data_alloc, "simple-0.pug");
|
||||||
|
const simple1_tpl = try loadTemplate(data_alloc, "simple-1.pug");
|
||||||
|
const simple2_tpl = try loadTemplate(data_alloc, "simple-2.pug");
|
||||||
|
const if_expr_tpl = try loadTemplate(data_alloc, "if-expression.pug");
|
||||||
|
const projects_tpl = try loadTemplate(data_alloc, "projects-escaped.pug");
|
||||||
|
const search_tpl = try loadTemplate(data_alloc, "search-results.pug");
|
||||||
|
const friends_tpl = try loadTemplate(data_alloc, "friends.pug");
|
||||||
|
|
||||||
|
std.debug.print("Loaded. Starting benchmark...\n\n", .{});
|
||||||
|
|
||||||
|
var total: f64 = 0;
|
||||||
|
|
||||||
|
total += try bench("simple-0", allocator, simple0_tpl, simple0);
|
||||||
|
total += try bench("simple-1", allocator, simple1_tpl, simple1);
|
||||||
|
total += try bench("simple-2", allocator, simple2_tpl, simple2);
|
||||||
|
total += try bench("if-expression", allocator, if_expr_tpl, if_expr);
|
||||||
|
total += try bench("projects-escaped", allocator, projects_tpl, projects);
|
||||||
|
total += try bench("search-results", allocator, search_tpl, search);
|
||||||
|
total += try bench("friends", allocator, friends_tpl, friends_data);
|
||||||
|
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
std.debug.print(" {s:<20} => {d:>7.1}ms\n", .{ "TOTAL", total });
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadJson(comptime T: type, alloc: std.mem.Allocator, comptime filename: []const u8) !T {
|
||||||
|
const path = templates_dir ++ "/" ++ filename;
|
||||||
|
const content = try std.fs.cwd().readFileAlloc(alloc, path, 10 * 1024 * 1024);
|
||||||
|
const parsed = try std.json.parseFromSlice(T, alloc, content, .{});
|
||||||
|
return parsed.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadTemplate(alloc: std.mem.Allocator, comptime filename: []const u8) ![]const u8 {
|
||||||
|
const path = templates_dir ++ "/" ++ filename;
|
||||||
|
return try std.fs.cwd().readFileAlloc(alloc, path, 1 * 1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(
|
||||||
|
name: []const u8,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
template: []const u8,
|
||||||
|
data: anytype,
|
||||||
|
) !f64 {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
var timer = try std.time.Timer.start();
|
||||||
|
for (0..iterations) |_| {
|
||||||
|
_ = arena.reset(.retain_capacity);
|
||||||
|
_ = try pugz.renderTemplate(arena.allocator(), template, data);
|
||||||
|
}
|
||||||
|
const ms = @as(f64, @floatFromInt(timer.read())) / 1_000_000.0;
|
||||||
|
std.debug.print(" {s:<20} => {d:>7.1}ms\n", .{ name, ms });
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user