Genearte .zig verions of templates to use in production.

This commit is contained in:
2026-01-28 17:01:28 +05:30
parent 4092e6ad8e
commit 8db2e0df37
1170 changed files with 10153 additions and 3722 deletions

89
examples/demo/README.md Normal file
View File

@@ -0,0 +1,89 @@
# Pugz Demo App
A comprehensive e-commerce demo showcasing Pugz template engine capabilities.
## Features
- Template inheritance (extends/block)
- Partial includes (header, footer)
- Mixins with parameters (product-card, rating, forms)
- Conditionals and loops
- Data binding
- Pretty printing
## Running the Demo
### Option 1: Runtime Templates (Default)
```bash
cd examples/demo
zig build run
```
Then visit `http://localhost:5882` in your browser.
### Option 2: Compiled Templates (Experimental)
Compiled templates offer maximum performance by pre-compiling templates to Zig functions at build time.
**Note:** Compiled templates currently have some code generation issues and are disabled by default.
To try compiled templates:
1. **Compile templates**:
```bash
# From demo directory
./compile_templates.sh
# Or manually from project root
cd ../..
zig build demo-compile-templates
```
This generates compiled templates in `generated/root.zig`
2. **Enable in code**:
- Open `src/main.zig`
- Set `USE_COMPILED_TEMPLATES = true`
3. **Build and run**:
```bash
zig build run
```
The `build.zig` automatically detects if `generated/` exists and includes the templates module.
## Template Structure
```
views/
├── layouts/ # Layout templates
│ └── base.pug
├── pages/ # Page templates
│ ├── home.pug
│ ├── products.pug
│ ├── cart.pug
│ └── ...
├── partials/ # Reusable partials
│ ├── header.pug
│ ├── footer.pug
│ └── head.pug
├── mixins/ # Reusable components
│ ├── product-card.pug
│ ├── buttons.pug
│ ├── forms.pug
│ └── ...
└── includes/ # Other includes
└── ...
```
## Known Issues with Compiled Templates
The template code generation (`src/tpl_compiler/zig_codegen.zig`) has some bugs:
1. `helpers.zig` import paths need to be relative
2. Double quotes being escaped incorrectly in string literals
3. Field names with dots causing syntax errors
4. Some undefined variables in generated code
These will be fixed in a future update. For now, runtime templates work perfectly!

View File

@@ -1,4 +1,5 @@
const std = @import("std");
const pugz = @import("pugz");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
@@ -14,22 +15,65 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
// Main executable
const pugz_mod = pugz_dep.module("pugz");
// ===========================================================================
// Template Compilation Step - OPTIONAL
// ===========================================================================
// This creates a "compile-templates" build step that users can run manually:
// zig build compile-templates
//
// Templates are compiled to generated/ and automatically used if they exist
const compile_templates = pugz.compile_tpls.addCompileStep(b, .{
.name = "compile-templates",
.source_dirs = &.{
"views/pages",
"views/partials",
},
.output_dir = "generated",
});
const compile_step = b.step("compile-templates", "Compile Pug templates");
compile_step.dependOn(&compile_templates.step);
// ===========================================================================
// Main Executable
// ===========================================================================
// Check if compiled templates exist
const has_templates = blk: {
var dir = std.fs.cwd().openDir("generated", .{}) catch break :blk false;
dir.close();
break :blk true;
};
// Build imports list
var imports: std.ArrayListUnmanaged(std.Build.Module.Import) = .{};
defer imports.deinit(b.allocator);
imports.append(b.allocator, .{ .name = "pugz", .module = pugz_mod }) catch @panic("OOM");
imports.append(b.allocator, .{ .name = "httpz", .module = httpz_dep.module("httpz") }) catch @panic("OOM");
// Only add templates module if they exist
if (has_templates) {
const templates_mod = b.createModule(.{
.root_source_file = b.path("generated/root.zig"),
});
imports.append(b.allocator, .{ .name = "templates", .module = templates_mod }) catch @panic("OOM");
}
const exe = b.addExecutable(.{
.name = "demo",
.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 = "httpz", .module = httpz_dep.module("httpz") },
},
.imports = imports.items,
}),
});
b.installArtifact(exe);
// Run step
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());

View File

@@ -14,6 +14,10 @@ const pugz = @import("pugz");
const Allocator = std.mem.Allocator;
// Mode selection: set to true to use compiled templates
// Run `zig build compile-templates` to generate templates first
const USE_COMPILED_TEMPLATES = true;
// ============================================================================
// Data Types
// ============================================================================
@@ -163,6 +167,15 @@ const sample_cart_items = [_]CartItem{
.quantity = "1",
.total = "79.99",
},
.{
.id = "2",
.name = "Laptop",
.price = "500.00",
.image = "/images/keyboard.jpg",
.variant = "BLK",
.quantity = "1",
.total = "500.00",
},
.{
.id = "5",
.name = "Mechanical Keyboard",
@@ -224,11 +237,19 @@ const App = struct {
// ============================================================================
fn home(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
const html = app.view.render(res.arena, "pages/home", .{
const html = if (USE_COMPILED_TEMPLATES) blk: {
const templates = @import("templates");
break :blk try templates.pages_home.render(res.arena, .{
.title = "Home",
.cartCount = "2",
.authenticated = true,
.items = sample_products,
});
} else app.view.render(res.arena, "pages/home", .{
.title = "Home",
.cartCount = "2",
.authenticated = true,
.items = &[_][]const u8{ "Wireless Headphones", "Smart Watch", "Laptop Stand", "USB-C Hub" },
.items = sample_products,
}) catch |err| {
return renderError(res, err);
};
@@ -238,7 +259,14 @@ fn home(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
}
fn products(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
const html = app.view.render(res.arena, "pages/products", .{
const html = if (USE_COMPILED_TEMPLATES) blk: {
const templates = @import("templates");
break :blk try templates.pages_products.render(res.arena, .{
.title = "All Products",
.cartCount = "2",
.productCount = "6",
});
} else app.view.render(res.arena, "pages/products", .{
.title = "All Products",
.cartCount = "2",
.productCount = "6",
@@ -254,7 +282,17 @@ fn productDetail(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
const id = req.param("id") orelse "1";
_ = id;
const html = app.view.render(res.arena, "pages/product-detail", .{
const html = if (USE_COMPILED_TEMPLATES) blk: {
const templates = @import("templates");
break :blk try templates.pages_product_detail.render(res.arena, .{
.cartCount = "2",
.productName = "Wireless Headphones",
.category = "Electronics",
.price = "79.99",
.description = "Premium wireless headphones with active noise cancellation. Experience crystal-clear audio whether you're working, traveling, or relaxing at home.",
.sku = "WH-001-BLK",
});
} else app.view.render(res.arena, "pages/product-detail", .{
.cartCount = "2",
.productName = "Wireless Headphones",
.category = "Electronics",
@@ -270,7 +308,16 @@ fn productDetail(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
}
fn cart(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
const html = app.view.render(res.arena, "pages/cart", .{
const html = if (USE_COMPILED_TEMPLATES) blk: {
const templates = @import("templates");
break :blk try templates.pages_cart.render(res.arena, .{
.cartCount = "2",
.subtotal = sample_cart.subtotal,
.tax = sample_cart.tax,
.total = sample_cart.total,
.cartItems = &sample_cart_items,
});
} else app.view.render(res.arena, "pages/cart", .{
.title = "Shopping Cart",
.cartCount = "2",
.cartItems = &sample_cart_items,
@@ -287,7 +334,13 @@ fn cart(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
}
fn about(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
const html = app.view.render(res.arena, "pages/about", .{
const html = if (USE_COMPILED_TEMPLATES) blk: {
const templates = @import("templates");
break :blk try templates.pages_about.render(res.arena, .{
.title = "About",
.cartCount = "2",
});
} else app.view.render(res.arena, "pages/about", .{
.title = "About",
.cartCount = "2",
}) catch |err| {
@@ -299,7 +352,12 @@ fn about(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
}
fn includeDemo(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
const html = app.view.render(res.arena, "pages/include-demo", .{
const html = if (USE_COMPILED_TEMPLATES) blk: {
const templates = @import("templates");
break :blk try templates.pages_include_demo.render(res.arena, .{
.cartCount = "2",
});
} else app.view.render(res.arena, "pages/include-demo", .{
.title = "Include Demo",
.cartCount = "2",
}) catch |err| {
@@ -310,10 +368,39 @@ fn includeDemo(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
res.body = html;
}
fn simpleCompiled(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
if (USE_COMPILED_TEMPLATES) {
const templates = @import("templates");
const html = try templates.pages_simple.render(res.arena, .{
.title = "Compiled Template Demo",
.heading = "Hello from Compiled Templates!",
.siteName = "Pugz Demo",
});
res.content_type = .HTML;
res.body = html;
} else {
const html = app.view.render(res.arena, "pages/simple", .{
.title = "Simple Page",
.heading = "Hello from Runtime Templates!",
.siteName = "Pugz Demo",
}) catch |err| {
return renderError(res, err);
};
res.content_type = .HTML;
res.body = html;
}
}
fn notFound(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
res.status = 404;
const html = app.view.render(res.arena, "pages/404", .{
const html = if (USE_COMPILED_TEMPLATES) blk: {
const templates = @import("templates");
break :blk try templates.pages_404.render(res.arena, .{
.title = "Page Not Found",
.cartCount = "2",
});
} else app.view.render(res.arena, "pages/404", .{
.title = "Page Not Found",
.cartCount = "2",
}) catch |err| {
@@ -399,6 +486,7 @@ pub fn main() !void {
router.get("/cart", cart, .{});
router.get("/about", about, .{});
router.get("/include-demo", includeDemo, .{});
router.get("/simple", simpleCompiled, .{});
// Static files
router.get("/css/*", serveStatic, .{});
@@ -421,6 +509,7 @@ pub fn main() !void {
\\ GET /cart - Shopping cart
\\ GET /about - About page
\\ GET /include-demo - Include directive demo
\\ GET /simple - Simple compiled template demo
\\
\\ Press Ctrl+C to stop.
\\

View File

@@ -0,0 +1,8 @@
doctype html
html
head
title #{title}
body
h1 #{heading}
p Welcome to #{siteName}!
p This page was rendered using compiled Pug templates.

View File

@@ -0,0 +1,60 @@
// Example: Using compiled templates
//
// This demonstrates how to use templates compiled with pug-compile.
//
// Steps to generate templates:
// 1. Build: zig build
// 2. Compile templates: ./zig-out/bin/pug-compile --dir views --out generated pages
// 3. Run this example: zig build example-compiled
const std = @import("std");
const tpls = @import("generated");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const leaked = gpa.deinit();
if (leaked == .leak) {
std.debug.print("Memory leak detected!\n", .{});
}
}
const allocator = gpa.allocator();
std.debug.print("=== Compiled Templates Example ===\n\n", .{});
// Render home page
if (@hasDecl(tpls, "home")) {
const home_html = try tpls.home.render(allocator, .{
.title = "My Site",
.name = "Alice",
});
defer allocator.free(home_html);
std.debug.print("=== Home Page ===\n{s}\n\n", .{home_html});
}
// Render conditional page
if (@hasDecl(tpls, "conditional")) {
// Test logged in
{
const html = try tpls.conditional.render(allocator, .{
.isLoggedIn = "true",
.username = "Bob",
});
defer allocator.free(html);
std.debug.print("=== Conditional Page (Logged In) ===\n{s}\n\n", .{html});
}
// Test logged out
{
const html = try tpls.conditional.render(allocator, .{
.isLoggedIn = "",
.username = "",
});
defer allocator.free(html);
std.debug.print("=== Conditional Page (Logged Out) ===\n{s}\n\n", .{html});
}
}
std.debug.print("=== Example Complete ===\n", .{});
}