fix: add scoped error logging for lexer/parser errors

- Add std.log.scoped(.pugz) to template.zig and view_engine.zig
- Log detailed error info (code, line, column, message) when parsing fails
- Log template path context in ViewEngine on parse errors
- Remove debug print from lexer, use proper scoped logging instead
- Move benchmarks, docs, examples, playground, tests out of src/ to project root
- Update build.zig and documentation paths accordingly
- Bump version to 0.3.1
This commit is contained in:
2026-01-25 17:10:02 +05:30
parent 9d3b729c6c
commit aaf6a1af2d
1148 changed files with 57 additions and 330 deletions

289
docs/api.md Normal file
View 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
View 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>
```