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:
45
CLAUDE.md
45
CLAUDE.md
@@ -52,11 +52,9 @@ Source → Lexer → Tokens → StripComments → Parser → AST → Linker →
|
|||||||
|
|
||||||
### Test Files
|
### Test Files
|
||||||
|
|
||||||
- **src/tests/general_test.zig** - Comprehensive integration tests
|
- **tests/general_test.zig** - Comprehensive integration tests
|
||||||
- **src/tests/doctype_test.zig** - Doctype-specific tests
|
- **tests/doctype_test.zig** - Doctype-specific tests
|
||||||
- **src/tests/check_list_test.zig** - Template output validation tests
|
- **tests/check_list_test.zig** - Template output validation tests
|
||||||
- **src/lexer_test.zig** - Lexer unit tests
|
|
||||||
- **src/parser_test.zig** - Parser unit tests
|
|
||||||
|
|
||||||
## API Usage
|
## API Usage
|
||||||
|
|
||||||
@@ -336,27 +334,26 @@ Uses error unions with detailed `PugError` context including line, column, and s
|
|||||||
## File Structure
|
## File Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
├── src/ # Source code
|
||||||
├── root.zig # Public library API
|
│ ├── root.zig # Public library API
|
||||||
├── view_engine.zig # High-level ViewEngine
|
│ ├── view_engine.zig # High-level ViewEngine
|
||||||
├── pug.zig # Main entry point (static compilation)
|
│ ├── pug.zig # Main entry point (static compilation)
|
||||||
├── template.zig # Data binding renderer
|
│ ├── template.zig # Data binding renderer
|
||||||
├── lexer.zig # Tokenizer
|
│ ├── lexer.zig # Tokenizer
|
||||||
├── lexer_test.zig # Lexer tests
|
│ ├── parser.zig # AST parser
|
||||||
├── parser.zig # AST parser
|
│ ├── runtime.zig # Shared utilities
|
||||||
├── parser_test.zig # Parser tests
|
│ ├── error.zig # Error formatting
|
||||||
├── runtime.zig # Shared utilities
|
│ ├── walk.zig # AST traversal
|
||||||
├── error.zig # Error formatting
|
│ ├── strip_comments.zig # Comment filtering
|
||||||
├── walk.zig # AST traversal
|
│ ├── load.zig # File loading
|
||||||
├── strip_comments.zig # Comment filtering
|
│ ├── linker.zig # Template inheritance
|
||||||
├── load.zig # File loading
|
│ └── codegen.zig # HTML generation
|
||||||
├── linker.zig # Template inheritance
|
|
||||||
├── codegen.zig # HTML generation
|
|
||||||
├── tests/ # Integration tests
|
├── tests/ # Integration tests
|
||||||
│ ├── general_test.zig
|
│ ├── general_test.zig
|
||||||
│ ├── doctype_test.zig
|
│ ├── doctype_test.zig
|
||||||
│ └── check_list_test.zig
|
│ └── check_list_test.zig
|
||||||
└── benchmarks/ # Performance benchmarks
|
├── benchmarks/ # Performance benchmarks
|
||||||
├── bench_v1.zig
|
├── docs/ # Documentation
|
||||||
└── bench_interpreted.zig
|
├── examples/ # Example templates
|
||||||
|
└── playground/ # Development playground
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -154,14 +154,14 @@ const html = try engine.render(arena.allocator(), "index", data);
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Template Syntax](src/docs/syntax.md) - Complete syntax reference
|
- [Template Syntax](docs/syntax.md) - Complete syntax reference
|
||||||
- [API Reference](src/docs/api.md) - Detailed API documentation
|
- [API Reference](docs/api.md) - Detailed API documentation
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
Same templates and data (`src/benchmarks/templates/`), MacBook Air M2, 2000 iterations, best of 5 runs.
|
Same templates and data (`benchmarks/templates/`), MacBook Air M2, 2000 iterations, best of 5 runs.
|
||||||
|
|
||||||
Both Pug.js and Pugz parse templates once, then measure render-only time.
|
Both Pug.js and Pugz parse templates once, then measure render-only time.
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ Run benchmarks:
|
|||||||
zig build bench
|
zig build bench
|
||||||
|
|
||||||
# Pug.js (for comparison)
|
# Pug.js (for comparison)
|
||||||
cd src/benchmarks/pugjs && npm install && npm run bench
|
cd benchmarks/pugjs && npm install && npm run bench
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const std = @import("std");
|
|||||||
const pugz = @import("pugz");
|
const pugz = @import("pugz");
|
||||||
|
|
||||||
const iterations: usize = 2000;
|
const iterations: usize = 2000;
|
||||||
const templates_dir = "src/benchmarks/templates";
|
const templates_dir = "benchmarks/templates";
|
||||||
|
|
||||||
// Data structures matching JSON files
|
// Data structures matching JSON files
|
||||||
const SubFriend = struct {
|
const SubFriend = struct {
|
||||||
@@ -54,8 +54,8 @@ console.log("Templates compiled. Starting benchmark...\n");
|
|||||||
|
|
||||||
console.log("╔═══════════════════════════════════════════════════════════════╗");
|
console.log("╔═══════════════════════════════════════════════════════════════╗");
|
||||||
console.log(`║ Pug.js Benchmark (${iterations} iterations) ║`);
|
console.log(`║ Pug.js Benchmark (${iterations} iterations) ║`);
|
||||||
console.log("║ Templates: src/benchmarks/templates/*.pug ║");
|
console.log("║ Templates: benchmarks/templates/*.pug ║");
|
||||||
console.log("║ Data: src/benchmarks/templates/*.json ║");
|
console.log("║ Data: benchmarks/templates/*.json ║");
|
||||||
console.log("╚═══════════════════════════════════════════════════════════════╝");
|
console.log("╚═══════════════════════════════════════════════════════════════╝");
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
10
build.zig
10
build.zig
@@ -55,7 +55,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
// Integration tests - general template tests
|
// Integration tests - general template tests
|
||||||
const general_tests = b.addTest(.{
|
const general_tests = b.addTest(.{
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/tests/general_test.zig"),
|
.root_source_file = b.path("tests/general_test.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
@@ -68,7 +68,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
// Integration tests - doctype tests
|
// Integration tests - doctype tests
|
||||||
const doctype_tests = b.addTest(.{
|
const doctype_tests = b.addTest(.{
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/tests/doctype_test.zig"),
|
.root_source_file = b.path("tests/doctype_test.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
@@ -81,7 +81,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
// Integration tests - check_list tests (pug files vs expected html output)
|
// Integration tests - check_list tests (pug files vs expected html output)
|
||||||
const check_list_tests = b.addTest(.{
|
const check_list_tests = b.addTest(.{
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/tests/check_list_test.zig"),
|
.root_source_file = b.path("tests/check_list_test.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
@@ -122,7 +122,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
const bench_exe = b.addExecutable(.{
|
const bench_exe = b.addExecutable(.{
|
||||||
.name = "bench",
|
.name = "bench",
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/benchmarks/bench.zig"),
|
.root_source_file = b.path("benchmarks/bench.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = .ReleaseFast,
|
.optimize = .ReleaseFast,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
@@ -141,7 +141,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
const test_includes_exe = b.addExecutable(.{
|
const test_includes_exe = b.addExecutable(.{
|
||||||
.name = "test-includes",
|
.name = "test-includes",
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/tests/test_includes.zig"),
|
.root_source_file = b.path("tests/test_includes.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.{
|
.{
|
||||||
.name = .pugz,
|
.name = .pugz,
|
||||||
.version = "0.3.0",
|
.version = "0.3.1",
|
||||||
.fingerprint = 0x822db0790e17621d, // Changing this has security and trust implications.
|
.fingerprint = 0x822db0790e17621d, // Changing this has security and trust implications.
|
||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.15.2",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.15.2",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.pugz = .{
|
.pugz = .{
|
||||||
.path = "../../..",
|
.path = "../..",
|
||||||
},
|
},
|
||||||
.httpz = .{
|
.httpz = .{
|
||||||
.url = "git+https://github.com/karlseguin/http.zig?ref=master#9ef2ffe8d611ff2e1081e5cf39cb4632c145c5b9",
|
.url = "git+https://github.com/karlseguin/http.zig?ref=master#9ef2ffe8d611ff2e1081e5cf39cb4632c145c5b9",
|
||||||
2
examples/demo/views/includes/other.pug
Normal file
2
examples/demo/views/includes/other.pug
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.p
|
||||||
|
| some other thing
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
//! Auto-generated by pugz.compileTemplates()
|
|
||||||
//! Do not edit manually - regenerate by running: zig build
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const ArrayList = std.ArrayList(u8);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Helpers
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const esc_lut: [256]?[]const u8 = blk: {
|
|
||||||
var t: [256]?[]const u8 = .{null} ** 256;
|
|
||||||
t['&'] = "&";
|
|
||||||
t['<'] = "<";
|
|
||||||
t['>'] = ">";
|
|
||||||
t['"'] = """;
|
|
||||||
t['\''] = "'";
|
|
||||||
break :blk t;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn esc(o: *ArrayList, a: Allocator, s: []const u8) Allocator.Error!void {
|
|
||||||
var i: usize = 0;
|
|
||||||
for (s, 0..) |c, j| {
|
|
||||||
if (esc_lut[c]) |e| {
|
|
||||||
if (j > i) try o.appendSlice(a, s[i..j]);
|
|
||||||
try o.appendSlice(a, e);
|
|
||||||
i = j + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i < s.len) try o.appendSlice(a, s[i..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn truthy(v: anytype) bool {
|
|
||||||
return switch (@typeInfo(@TypeOf(v))) {
|
|
||||||
.bool => v,
|
|
||||||
.optional => v != null,
|
|
||||||
.pointer => |p| if (p.size == .slice) v.len > 0 else true,
|
|
||||||
.int, .comptime_int => v != 0,
|
|
||||||
else => true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var int_buf: [32]u8 = undefined;
|
|
||||||
|
|
||||||
fn strVal(v: anytype) []const u8 {
|
|
||||||
const T = @TypeOf(v);
|
|
||||||
switch (@typeInfo(T)) {
|
|
||||||
.pointer => |p| switch (p.size) {
|
|
||||||
.slice => return v,
|
|
||||||
.one => {
|
|
||||||
// For pointer-to-array, slice it
|
|
||||||
const child_info = @typeInfo(p.child);
|
|
||||||
if (child_info == .array) {
|
|
||||||
const arr_info = child_info.array;
|
|
||||||
const ptr: [*]const arr_info.child = @ptrCast(v);
|
|
||||||
return ptr[0..arr_info.len];
|
|
||||||
}
|
|
||||||
return strVal(v.*);
|
|
||||||
},
|
|
||||||
else => @compileError("unsupported pointer type"),
|
|
||||||
},
|
|
||||||
.array => @compileError("arrays must be passed by pointer"),
|
|
||||||
.int, .comptime_int => return std.fmt.bufPrint(&int_buf, "{d}", .{v}) catch "0",
|
|
||||||
.optional => return if (v) |val| strVal(val) else "",
|
|
||||||
else => @compileError("strVal: unsupported type " ++ @typeName(T)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Templates
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
pub fn simple_2(a: Allocator, d: anytype) Allocator.Error![]u8 {
|
|
||||||
var o: ArrayList = .empty;
|
|
||||||
try o.appendSlice(a, "<div><h1 class=\"header\">");
|
|
||||||
try esc(&o, a, strVal(@field(d, "header")));
|
|
||||||
try o.appendSlice(a, "</h1><h2 class=\"header2\">");
|
|
||||||
try esc(&o, a, strVal(@field(d, "header2")));
|
|
||||||
try o.appendSlice(a, "</h2><h3 class=\"header3\">");
|
|
||||||
try esc(&o, a, strVal(@field(d, "header3")));
|
|
||||||
try o.appendSlice(a, "</h3><h4 class=\"header4\">");
|
|
||||||
try esc(&o, a, strVal(@field(d, "header4")));
|
|
||||||
try o.appendSlice(a, "</h4><h5 class=\"header5\">");
|
|
||||||
try esc(&o, a, strVal(@field(d, "header5")));
|
|
||||||
try o.appendSlice(a, "</h5><h6 class=\"header6\">");
|
|
||||||
try esc(&o, a, strVal(@field(d, "header6")));
|
|
||||||
try o.appendSlice(a, "</h6><ul class=\"list\">");
|
|
||||||
for (@field(d, "list")) |item| {
|
|
||||||
try o.appendSlice(a, "<li class=\"item\">");
|
|
||||||
try esc(&o, a, strVal(item));
|
|
||||||
try o.appendSlice(a, "</li>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</ul></div>");
|
|
||||||
return o.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_1(a: Allocator, d: anytype) Allocator.Error![]u8 {
|
|
||||||
var o: ArrayList = .empty;
|
|
||||||
try o.appendSlice(a, "<div class=\"simple-1\" style=\"background-color: blue; border: 1px solid black\"><div class=\"colors\"><span class=\"hello\">Hello ");
|
|
||||||
try esc(&o, a, strVal(@field(d, "name")));
|
|
||||||
try o.appendSlice(a, "!<strong>You have ");
|
|
||||||
try esc(&o, a, strVal(@field(d, "messageCount")));
|
|
||||||
try o.appendSlice(a, " messages!</strong></span>");
|
|
||||||
if (@hasField(@TypeOf(d), "colors") and truthy(@field(d, "colors"))) {
|
|
||||||
try o.appendSlice(a, "<ul>");
|
|
||||||
for (@field(d, "colors")) |color| {
|
|
||||||
try o.appendSlice(a, "<li class=\"color\">");
|
|
||||||
try esc(&o, a, strVal(color));
|
|
||||||
try o.appendSlice(a, "</li>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</ul>");
|
|
||||||
} else {
|
|
||||||
try o.appendSlice(a, "<div>No colors!</div>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</div>");
|
|
||||||
if (@hasField(@TypeOf(d), "primary") and truthy(@field(d, "primary"))) {
|
|
||||||
try o.appendSlice(a, "<button class=\"primary\" type=\"button\">Click me!</button>");
|
|
||||||
} else {
|
|
||||||
try o.appendSlice(a, "<button class=\"secondary\" type=\"button\">Click me!</button>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</div>");
|
|
||||||
return o.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_0(a: Allocator, d: anytype) Allocator.Error![]u8 {
|
|
||||||
var o: ArrayList = .empty;
|
|
||||||
try o.appendSlice(a, "<h1>Hello, ");
|
|
||||||
try esc(&o, a, strVal(@field(d, "name")));
|
|
||||||
try o.appendSlice(a, "</h1>");
|
|
||||||
return o.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn projects_escaped(a: Allocator, d: anytype) Allocator.Error![]u8 {
|
|
||||||
var o: ArrayList = .empty;
|
|
||||||
try o.appendSlice(a, "<!DOCTYPE html><html><head><title>");
|
|
||||||
try esc(&o, a, strVal(@field(d, "title")));
|
|
||||||
try o.appendSlice(a, "</title></head><body><p>");
|
|
||||||
try esc(&o, a, strVal(@field(d, "text")));
|
|
||||||
try o.appendSlice(a, "</p>");
|
|
||||||
if (@field(d, "projects").len > 0) {
|
|
||||||
for (@field(d, "projects")) |project| {
|
|
||||||
try o.appendSlice(a, "<a");
|
|
||||||
try o.appendSlice(a, " href=\"");
|
|
||||||
try o.appendSlice(a, strVal(project.url));
|
|
||||||
try o.appendSlice(a, "\"");
|
|
||||||
try o.appendSlice(a, "></a><project class=\"name\"></project><p>");
|
|
||||||
try esc(&o, a, strVal(project.description));
|
|
||||||
try o.appendSlice(a, "</p>");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try o.appendSlice(a, "<p>No projects</p>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</body></html>");
|
|
||||||
return o.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn friends(a: Allocator, d: anytype) Allocator.Error![]u8 {
|
|
||||||
var o: ArrayList = .empty;
|
|
||||||
try o.appendSlice(a, "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><title>Friends</title></head><body><div class=\"friends\">");
|
|
||||||
for (@field(d, "friends")) |friend| {
|
|
||||||
try o.appendSlice(a, "<div class=\"friend\"><ul><li>Name: ");
|
|
||||||
try esc(&o, a, strVal(friend.name));
|
|
||||||
try o.appendSlice(a, "</li><li>Balance: ");
|
|
||||||
try esc(&o, a, strVal(friend.balance));
|
|
||||||
try o.appendSlice(a, "</li><li>Age: ");
|
|
||||||
try esc(&o, a, strVal(friend.age));
|
|
||||||
try o.appendSlice(a, "</li><li>Address: ");
|
|
||||||
try esc(&o, a, strVal(friend.address));
|
|
||||||
try o.appendSlice(a, "</li><li>Image:<img");
|
|
||||||
try o.appendSlice(a, " src=\"");
|
|
||||||
try o.appendSlice(a, strVal(friend.picture));
|
|
||||||
try o.appendSlice(a, "\"");
|
|
||||||
try o.appendSlice(a, " /></li><li>Company: ");
|
|
||||||
try esc(&o, a, strVal(friend.company));
|
|
||||||
try o.appendSlice(a, "</li><li>Email:<a");
|
|
||||||
try o.appendSlice(a, " href=\"");
|
|
||||||
try o.appendSlice(a, strVal(friend.emailHref));
|
|
||||||
try o.appendSlice(a, "\"");
|
|
||||||
try o.appendSlice(a, "></a><friend class=\"email\"></friend></li><li>About: ");
|
|
||||||
try esc(&o, a, strVal(friend.about));
|
|
||||||
try o.appendSlice(a, "</li>");
|
|
||||||
if (truthy(friend.tags)) {
|
|
||||||
try o.appendSlice(a, "<li>Tags:<ul>");
|
|
||||||
for (if (@typeInfo(@TypeOf(friend.tags)) == .optional) (friend.tags orelse &.{}) else friend.tags) |tag| {
|
|
||||||
try o.appendSlice(a, "<li>");
|
|
||||||
try esc(&o, a, strVal(tag));
|
|
||||||
try o.appendSlice(a, "</li>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</ul></li>");
|
|
||||||
}
|
|
||||||
if (truthy(friend.friends)) {
|
|
||||||
try o.appendSlice(a, "<li>Friends:<ul>");
|
|
||||||
for (if (@typeInfo(@TypeOf(friend.friends)) == .optional) (friend.friends orelse &.{}) else friend.friends) |subFriend| {
|
|
||||||
try o.appendSlice(a, "<li>");
|
|
||||||
try esc(&o, a, strVal(subFriend.name));
|
|
||||||
try o.appendSlice(a, " (");
|
|
||||||
try esc(&o, a, strVal(subFriend.id));
|
|
||||||
try o.appendSlice(a, ")</li>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</ul></li>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</ul></div>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</div></body></html>");
|
|
||||||
return o.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search_results(a: Allocator, d: anytype) Allocator.Error![]u8 {
|
|
||||||
var o: ArrayList = .empty;
|
|
||||||
try o.appendSlice(a, "<div class=\"search-results view-gallery\">");
|
|
||||||
for (@field(d, "searchRecords")) |searchRecord| {
|
|
||||||
try o.appendSlice(a, "<div class=\"search-item\"><div class=\"search-item-container drop-shadow\"><div class=\"img-container\"><img");
|
|
||||||
try o.appendSlice(a, " src=\"");
|
|
||||||
try o.appendSlice(a, strVal(searchRecord.imgUrl));
|
|
||||||
try o.appendSlice(a, "\"");
|
|
||||||
try o.appendSlice(a, " /></div><h4 class=\"title\"><a");
|
|
||||||
try o.appendSlice(a, " href=\"");
|
|
||||||
try o.appendSlice(a, strVal(searchRecord.viewItemUrl));
|
|
||||||
try o.appendSlice(a, "\"");
|
|
||||||
try o.appendSlice(a, ">");
|
|
||||||
try esc(&o, a, strVal(searchRecord.title));
|
|
||||||
try o.appendSlice(a, "</a></h4>");
|
|
||||||
try esc(&o, a, strVal(searchRecord.description));
|
|
||||||
if (truthy(searchRecord.featured)) {
|
|
||||||
try o.appendSlice(a, "<div>Featured!</div>");
|
|
||||||
}
|
|
||||||
if (truthy(searchRecord.sizes)) {
|
|
||||||
try o.appendSlice(a, "<div>Sizes available:<ul>");
|
|
||||||
for (if (@typeInfo(@TypeOf(searchRecord.sizes)) == .optional) (searchRecord.sizes orelse &.{}) else searchRecord.sizes) |size| {
|
|
||||||
try o.appendSlice(a, "<li>");
|
|
||||||
try esc(&o, a, strVal(size));
|
|
||||||
try o.appendSlice(a, "</li>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</ul></div>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</div></div>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</div>");
|
|
||||||
return o.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn if_expression(a: Allocator, d: anytype) Allocator.Error![]u8 {
|
|
||||||
var o: ArrayList = .empty;
|
|
||||||
for (@field(d, "accounts")) |account| {
|
|
||||||
try o.appendSlice(a, "<div>");
|
|
||||||
if (std.mem.eql(u8, strVal(account.status), "closed")) {
|
|
||||||
try o.appendSlice(a, "<div>Your account has been closed!</div>");
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, strVal(account.status), "suspended")) {
|
|
||||||
try o.appendSlice(a, "<div>Your account has been temporarily suspended</div>");
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, strVal(account.status), "open")) {
|
|
||||||
try o.appendSlice(a, "<div>Bank balance:");
|
|
||||||
if (truthy(account.negative)) {
|
|
||||||
try o.appendSlice(a, "<span class=\"negative\">");
|
|
||||||
try esc(&o, a, strVal(account.balanceFormatted));
|
|
||||||
try o.appendSlice(a, "</span>");
|
|
||||||
} else {
|
|
||||||
try o.appendSlice(a, "<span class=\"positive\">");
|
|
||||||
try esc(&o, a, strVal(account.balanceFormatted));
|
|
||||||
try o.appendSlice(a, "</span>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</div>");
|
|
||||||
}
|
|
||||||
try o.appendSlice(a, "</div>");
|
|
||||||
}
|
|
||||||
return o.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const template_names = [_][]const u8{
|
|
||||||
"simple_2",
|
|
||||||
"simple_1",
|
|
||||||
"simple_0",
|
|
||||||
"projects_escaped",
|
|
||||||
"friends",
|
|
||||||
"search_results",
|
|
||||||
"if_expression",
|
|
||||||
};
|
|
||||||
@@ -2525,9 +2525,8 @@ pub const Lexer = struct {
|
|||||||
pub fn getTokens(self: *Lexer) ![]Token {
|
pub fn getTokens(self: *Lexer) ![]Token {
|
||||||
while (!self.ended) {
|
while (!self.ended) {
|
||||||
const advanced = self.advance();
|
const advanced = self.advance();
|
||||||
// Check for errors after every advance, regardless of return value
|
// Check for errors after every advance
|
||||||
if (self.last_error) |err| {
|
if (self.last_error != null) {
|
||||||
std.debug.print("Lexer error at {d}:{d}: {s}\n", .{ err.line, err.column, err.message });
|
|
||||||
return error.LexerError;
|
return error.LexerError;
|
||||||
}
|
}
|
||||||
if (!advanced) {
|
if (!advanced) {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const runtime = @import("runtime.zig");
|
|||||||
const mixin_mod = @import("mixin.zig");
|
const mixin_mod = @import("mixin.zig");
|
||||||
pub const MixinRegistry = mixin_mod.MixinRegistry;
|
pub const MixinRegistry = mixin_mod.MixinRegistry;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.pugz);
|
||||||
|
|
||||||
pub const TemplateError = error{
|
pub const TemplateError = error{
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
LexerError,
|
LexerError,
|
||||||
@@ -146,7 +148,12 @@ pub fn parseWithSource(allocator: Allocator, source: []const u8) !ParseResult {
|
|||||||
var lex = pug.lexer.Lexer.init(allocator, source, .{}) catch return error.OutOfMemory;
|
var lex = pug.lexer.Lexer.init(allocator, source, .{}) catch return error.OutOfMemory;
|
||||||
errdefer lex.deinit();
|
errdefer lex.deinit();
|
||||||
|
|
||||||
const tokens = lex.getTokens() catch return error.LexerError;
|
const tokens = lex.getTokens() catch {
|
||||||
|
if (lex.last_error) |err| {
|
||||||
|
log.err("{s} at line {d}, column {d}: {s}", .{ @tagName(err.code), err.line, err.column, err.message });
|
||||||
|
}
|
||||||
|
return error.LexerError;
|
||||||
|
};
|
||||||
|
|
||||||
// Strip comments
|
// Strip comments
|
||||||
var stripped = pug.strip_comments.stripComments(allocator, tokens, .{}) catch return error.OutOfMemory;
|
var stripped = pug.strip_comments.stripComments(allocator, tokens, .{}) catch return error.OutOfMemory;
|
||||||
@@ -157,6 +164,9 @@ pub fn parseWithSource(allocator: Allocator, source: []const u8) !ParseResult {
|
|||||||
defer pug_parser.deinit();
|
defer pug_parser.deinit();
|
||||||
|
|
||||||
const ast = pug_parser.parse() catch {
|
const ast = pug_parser.parse() catch {
|
||||||
|
if (pug_parser.getError()) |err| {
|
||||||
|
log.err("{s} at line {d}, column {d}: {s}", .{ @tagName(err.code), err.line, err.column, err.message });
|
||||||
|
}
|
||||||
return error.ParserError;
|
return error.ParserError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const cache = @import("cache");
|
|||||||
const Node = parser.Node;
|
const Node = parser.Node;
|
||||||
const MixinRegistry = mixin.MixinRegistry;
|
const MixinRegistry = mixin.MixinRegistry;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.pugz);
|
||||||
|
|
||||||
pub const ViewEngineError = error{
|
pub const ViewEngineError = error{
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
TemplateNotFound,
|
TemplateNotFound,
|
||||||
@@ -176,7 +178,8 @@ pub const ViewEngine = struct {
|
|||||||
defer self.cache_allocator.free(source);
|
defer self.cache_allocator.free(source);
|
||||||
|
|
||||||
// Parse template - returns AST and normalized source that AST strings point to
|
// Parse template - returns AST and normalized source that AST strings point to
|
||||||
var parse_result = template.parseWithSource(self.cache_allocator, source) catch {
|
var parse_result = template.parseWithSource(self.cache_allocator, source) catch |err| {
|
||||||
|
log.err("failed to parse template '{s}': {}", .{ full_path, err });
|
||||||
return ViewEngineError.ParseError;
|
return ViewEngineError.ParseError;
|
||||||
};
|
};
|
||||||
errdefer parse_result.deinit(self.cache_allocator);
|
errdefer parse_result.deinit(self.cache_allocator);
|
||||||
@@ -322,7 +325,8 @@ pub const ViewEngine = struct {
|
|||||||
};
|
};
|
||||||
defer self.cache_allocator.free(source);
|
defer self.cache_allocator.free(source);
|
||||||
|
|
||||||
const parse_result = template.parseWithSource(self.cache_allocator, source) catch {
|
const parse_result = template.parseWithSource(self.cache_allocator, source) catch |err| {
|
||||||
|
log.err("failed to parse template '{s}': {}", .{ full_path, err });
|
||||||
return ViewEngineError.ParseError;
|
return ViewEngineError.ParseError;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -494,7 +498,7 @@ test "ViewEngine - path escape protection" {
|
|||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
var engine = try ViewEngine.init(allocator, .{
|
var engine = try ViewEngine.init(allocator, .{
|
||||||
.views_dir = "src/tests/test_views",
|
.views_dir = "tests/test_views",
|
||||||
});
|
});
|
||||||
defer engine.deinit();
|
defer engine.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
h1 Include Test
|
|
||||||
include includes/some_partial.pug
|
|
||||||
p After include
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.info-box
|
|
||||||
h3 Included Partial
|
|
||||||
p This content comes from includes/some_partial.pug
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user