refactor: move docs and examples to src folder, update README with accurate benchmarks
This commit is contained in:
289
docs/api.md
289
docs/api.md
@@ -1,289 +0,0 @@
|
||||
# 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
340
docs/syntax.md
@@ -1,340 +0,0 @@
|
||||
# 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>
|
||||
```
|
||||
Reference in New Issue
Block a user