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:
66
playground/benchmark.zig
Normal file
66
playground/benchmark.zig
Normal file
@@ -0,0 +1,66 @@
|
||||
// benchmark.zig - Benchmark for pugz (Zig Pug implementation)
|
||||
//
|
||||
// This benchmark matches the JavaScript pug benchmark for comparison
|
||||
// Uses exact same templates as packages/pug/support/benchmark.js
|
||||
|
||||
const std = @import("std");
|
||||
const pug = @import("../pug.zig");
|
||||
|
||||
const MIN_ITERATIONS: usize = 200;
|
||||
const MIN_TIME_NS: u64 = 200_000_000; // 200ms minimum
|
||||
|
||||
fn benchmark(comptime name: []const u8, template: []const u8, iterations: *usize, elapsed_ns: *u64) !void {
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
// Warmup
|
||||
for (0..10) |_| {
|
||||
var result = try pug.compile(allocator, template, .{});
|
||||
result.deinit(allocator);
|
||||
}
|
||||
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
var count: usize = 0;
|
||||
while (count < MIN_ITERATIONS or timer.read() < MIN_TIME_NS) {
|
||||
var result = try pug.compile(allocator, template, .{});
|
||||
result.deinit(allocator);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
const elapsed = timer.read();
|
||||
iterations.* = count;
|
||||
elapsed_ns.* = elapsed;
|
||||
|
||||
const ops_per_sec = @as(f64, @floatFromInt(count)) * 1_000_000_000.0 / @as(f64, @floatFromInt(elapsed));
|
||||
std.debug.print("{s}: {d:.0}\n", .{ name, ops_per_sec });
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var iterations: usize = 0;
|
||||
var elapsed_ns: u64 = 0;
|
||||
|
||||
// Tiny template - exact match to JS: 'html\n body\n h1 Title'
|
||||
const tiny = "html\n body\n h1 Title";
|
||||
try benchmark("tiny", tiny, &iterations, &elapsed_ns);
|
||||
|
||||
// Small template - exact match to JS (note trailing \n on each line)
|
||||
const small =
|
||||
"html\n" ++
|
||||
" body\n" ++
|
||||
" h1 Title\n" ++
|
||||
" ul#menu\n" ++
|
||||
" li: a(href=\"#\") Home\n" ++
|
||||
" li: a(href=\"#\") About Us\n" ++
|
||||
" li: a(href=\"#\") Store\n" ++
|
||||
" li: a(href=\"#\") FAQ\n" ++
|
||||
" li: a(href=\"#\") Contact\n";
|
||||
try benchmark("small", small, &iterations, &elapsed_ns);
|
||||
|
||||
// Medium template - Array(30).join(str) creates 29 copies in JS
|
||||
const medium = small ** 29;
|
||||
try benchmark("medium", medium, &iterations, &elapsed_ns);
|
||||
|
||||
// Large template - Array(100).join(str) creates 99 copies in JS
|
||||
const large = small ** 99;
|
||||
try benchmark("large", large, &iterations, &elapsed_ns);
|
||||
}
|
||||
274
playground/benchmark_examples.zig
Normal file
274
playground/benchmark_examples.zig
Normal file
@@ -0,0 +1,274 @@
|
||||
// benchmark_examples.zig - Benchmark pug example files
|
||||
//
|
||||
// Tests the same example files as the JS benchmark
|
||||
|
||||
const std = @import("std");
|
||||
const pug = @import("../pug.zig");
|
||||
|
||||
const Example = struct {
|
||||
name: []const u8,
|
||||
source: []const u8,
|
||||
};
|
||||
|
||||
// Example templates (matching JS pug examples that don't use includes/extends)
|
||||
const examples = [_]Example{
|
||||
.{
|
||||
.name = "attributes.pug",
|
||||
.source =
|
||||
\\div#id.left.container(class='user user-' + name)
|
||||
\\ h1.title= name
|
||||
\\ form
|
||||
\\ //- unbuffered comment :)
|
||||
\\ // An example of attributes.
|
||||
\\ input(type='text' name='user[name]' value=name)
|
||||
\\ input(checked, type='checkbox', name='user[blocked]')
|
||||
\\ input(type='submit', value='Update')
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "code.pug",
|
||||
.source =
|
||||
\\- var title = "Things"
|
||||
\\
|
||||
\\-
|
||||
\\ var subtitle = ["Really", "long",
|
||||
\\ "list", "of",
|
||||
\\ "words"]
|
||||
\\h1= title
|
||||
\\h2= subtitle.join(" ")
|
||||
\\
|
||||
\\ul#users
|
||||
\\ each user, name in users
|
||||
\\ // expands to if (user.isA == 'ferret')
|
||||
\\ if user.isA == 'ferret'
|
||||
\\ li(class='user-' + name) #{name} is just a ferret
|
||||
\\ else
|
||||
\\ li(class='user-' + name) #{name} #{user.email}
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "dynamicscript.pug",
|
||||
.source =
|
||||
\\html
|
||||
\\ head
|
||||
\\ title Dynamic Inline JavaScript
|
||||
\\ script.
|
||||
\\ var users = !{JSON.stringify(users).replace(/<\//g, "<\\/")}
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "each.pug",
|
||||
.source =
|
||||
\\ul#users
|
||||
\\ each user, name in users
|
||||
\\ li(class='user-' + name) #{name} #{user.email}
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "extend-layout.pug",
|
||||
.source =
|
||||
\\html
|
||||
\\ head
|
||||
\\ h1 My Site - #{title}
|
||||
\\ block scripts
|
||||
\\ script(src='/jquery.js')
|
||||
\\ body
|
||||
\\ block content
|
||||
\\ block foot
|
||||
\\ #footer
|
||||
\\ p some footer content
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "form.pug",
|
||||
.source =
|
||||
\\form(method="post")
|
||||
\\ fieldset
|
||||
\\ legend General
|
||||
\\ p
|
||||
\\ label(for="user[name]") Username:
|
||||
\\ input(type="text", name="user[name]", value=user.name)
|
||||
\\ p
|
||||
\\ label(for="user[email]") Email:
|
||||
\\ input(type="text", name="user[email]", value=user.email)
|
||||
\\ .tip.
|
||||
\\ Enter a valid
|
||||
\\ email address
|
||||
\\ such as <em>tj@vision-media.ca</em>.
|
||||
\\ fieldset
|
||||
\\ legend Location
|
||||
\\ p
|
||||
\\ label(for="user[city]") City:
|
||||
\\ input(type="text", name="user[city]", value=user.city)
|
||||
\\ p
|
||||
\\ select(name="user[province]")
|
||||
\\ option(value="") -- Select Province --
|
||||
\\ option(value="AB") Alberta
|
||||
\\ option(value="BC") British Columbia
|
||||
\\ option(value="SK") Saskatchewan
|
||||
\\ option(value="MB") Manitoba
|
||||
\\ option(value="ON") Ontario
|
||||
\\ option(value="QC") Quebec
|
||||
\\ p.buttons
|
||||
\\ input(type="submit", value="Save")
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "layout.pug",
|
||||
.source =
|
||||
\\doctype html
|
||||
\\html(lang="en")
|
||||
\\ head
|
||||
\\ title Example
|
||||
\\ script.
|
||||
\\ if (foo) {
|
||||
\\ bar();
|
||||
\\ }
|
||||
\\ body
|
||||
\\ h1 Pug - node template engine
|
||||
\\ #container
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "pet.pug",
|
||||
.source =
|
||||
\\.pet
|
||||
\\ h2= pet.name
|
||||
\\ p #{pet.name} is <em>#{pet.age}</em> year(s) old.
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "rss.pug",
|
||||
.source =
|
||||
\\doctype xml
|
||||
\\rss(version='2.0')
|
||||
\\channel
|
||||
\\ title RSS Title
|
||||
\\ description Some description here
|
||||
\\ link http://google.com
|
||||
\\ lastBuildDate Mon, 06 Sep 2010 00:01:00 +0000
|
||||
\\ pubDate Mon, 06 Sep 2009 16:45:00 +0000
|
||||
\\
|
||||
\\ each item in items
|
||||
\\ item
|
||||
\\ title= item.title
|
||||
\\ description= item.description
|
||||
\\ link= item.link
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "text.pug",
|
||||
.source =
|
||||
\\| An example of an
|
||||
\\a(href='#') inline
|
||||
\\| link.
|
||||
\\
|
||||
\\form
|
||||
\\ label Username:
|
||||
\\ input(type='text', name='user[name]')
|
||||
\\ p
|
||||
\\ | Just an example of some text usage.
|
||||
\\ | You can have <em>inline</em> html,
|
||||
\\ | as well as
|
||||
\\ strong tags
|
||||
\\ | .
|
||||
\\
|
||||
\\ | Interpolation is also supported. The
|
||||
\\ | username is currently "#{name}".
|
||||
\\
|
||||
\\ label Email:
|
||||
\\ input(type='text', name='user[email]')
|
||||
\\ p
|
||||
\\ | Email is currently
|
||||
\\ em= email
|
||||
\\ | .
|
||||
,
|
||||
},
|
||||
.{
|
||||
.name = "whitespace.pug",
|
||||
.source =
|
||||
\\- var js = '<script></script>'
|
||||
\\doctype html
|
||||
\\html
|
||||
\\
|
||||
\\ head
|
||||
\\ title= "Some " + "JavaScript"
|
||||
\\ != js
|
||||
\\
|
||||
\\
|
||||
\\
|
||||
\\ body
|
||||
,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
std.debug.print("=== Zig Pugz Example Benchmark ===\n\n", .{});
|
||||
|
||||
var passed: usize = 0;
|
||||
var failed: usize = 0;
|
||||
var total_time_ns: u64 = 0;
|
||||
var html_outputs: [examples.len]?[]const u8 = undefined;
|
||||
for (&html_outputs) |*h| h.* = null;
|
||||
|
||||
for (examples, 0..) |example, idx| {
|
||||
const iterations: usize = 100;
|
||||
var success = false;
|
||||
var time_ns: u64 = 0;
|
||||
|
||||
// Warmup
|
||||
for (0..5) |_| {
|
||||
var result = pug.compile(allocator, example.source, .{}) catch continue;
|
||||
result.deinit(allocator);
|
||||
}
|
||||
|
||||
// Benchmark
|
||||
var timer = try std.time.Timer.start();
|
||||
var i: usize = 0;
|
||||
while (i < iterations) : (i += 1) {
|
||||
var result = pug.compile(allocator, example.source, .{}) catch break;
|
||||
if (i == iterations - 1) {
|
||||
// Keep last HTML for output
|
||||
html_outputs[idx] = result.html;
|
||||
} else {
|
||||
result.deinit(allocator);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
time_ns = timer.read();
|
||||
|
||||
if (success and i == iterations) {
|
||||
const time_ms = @as(f64, @floatFromInt(time_ns)) / 1_000_000.0 / @as(f64, @floatFromInt(iterations));
|
||||
std.debug.print("{s}: OK ({d:.3} ms)\n", .{ example.name, time_ms });
|
||||
passed += 1;
|
||||
total_time_ns += time_ns;
|
||||
} else {
|
||||
std.debug.print("{s}: FAILED\n", .{example.name});
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("\n=== Summary ===\n", .{});
|
||||
std.debug.print("Passed: {d}/{d}\n", .{ passed, examples.len });
|
||||
std.debug.print("Failed: {d}/{d}\n", .{ failed, examples.len });
|
||||
|
||||
if (passed > 0) {
|
||||
const total_ms = @as(f64, @floatFromInt(total_time_ns)) / 1_000_000.0 / 100.0;
|
||||
std.debug.print("Total time (successful): {d:.3} ms\n", .{total_ms});
|
||||
std.debug.print("Average time: {d:.3} ms\n", .{total_ms / @as(f64, @floatFromInt(passed))});
|
||||
}
|
||||
|
||||
// Output HTML for comparison
|
||||
std.debug.print("\n=== HTML Output ===\n", .{});
|
||||
for (examples, 0..) |example, idx| {
|
||||
if (html_outputs[idx]) |html| {
|
||||
std.debug.print("\n--- {s} ---\n", .{example.name});
|
||||
const max_len = @min(html.len, 500);
|
||||
std.debug.print("{s}", .{html[0..max_len]});
|
||||
if (html.len > 500) std.debug.print("...", .{});
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
8
playground/examples/attributes.pug
Normal file
8
playground/examples/attributes.pug
Normal file
@@ -0,0 +1,8 @@
|
||||
div#id.left.container(class='user user-' + name)
|
||||
h1.title= name
|
||||
form
|
||||
//- unbuffered comment :)
|
||||
// An example of attributes.
|
||||
input(type='text' name='user[name]' value=name)
|
||||
input(checked, type='checkbox', name='user[blocked]')
|
||||
input(type='submit', value='Update')
|
||||
17
playground/examples/code.pug
Normal file
17
playground/examples/code.pug
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
- var title = "Things"
|
||||
|
||||
-
|
||||
var subtitle = ["Really", "long",
|
||||
"list", "of",
|
||||
"words"]
|
||||
h1= title
|
||||
h2= subtitle.join(" ")
|
||||
|
||||
ul#users
|
||||
each user, name in users
|
||||
// expands to if (user.isA == 'ferret')
|
||||
if user.isA == 'ferret'
|
||||
li(class='user-' + name) #{name} is just a ferret
|
||||
else
|
||||
li(class='user-' + name) #{name} #{user.email}
|
||||
5
playground/examples/dynamicscript.pug
Normal file
5
playground/examples/dynamicscript.pug
Normal file
@@ -0,0 +1,5 @@
|
||||
html
|
||||
head
|
||||
title Dynamic Inline JavaScript
|
||||
script.
|
||||
var users = !{JSON.stringify(users).replace(/<\//g, "<\\/")}
|
||||
3
playground/examples/each.pug
Normal file
3
playground/examples/each.pug
Normal file
@@ -0,0 +1,3 @@
|
||||
ul#users
|
||||
each user, name in users
|
||||
li(class='user-' + name) #{name} #{user.email}
|
||||
10
playground/examples/extend-layout.pug
Normal file
10
playground/examples/extend-layout.pug
Normal file
@@ -0,0 +1,10 @@
|
||||
html
|
||||
head
|
||||
h1 My Site - #{title}
|
||||
block scripts
|
||||
script(src='/jquery.js')
|
||||
body
|
||||
block content
|
||||
block foot
|
||||
#footer
|
||||
p some footer content
|
||||
11
playground/examples/extend.pug
Normal file
11
playground/examples/extend.pug
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
extends extend-layout.pug
|
||||
|
||||
block scripts
|
||||
script(src='/jquery.js')
|
||||
script(src='/pets.js')
|
||||
|
||||
block content
|
||||
h1= title
|
||||
each pet in pets
|
||||
include pet.pug
|
||||
29
playground/examples/form.pug
Normal file
29
playground/examples/form.pug
Normal file
@@ -0,0 +1,29 @@
|
||||
form(method="post")
|
||||
fieldset
|
||||
legend General
|
||||
p
|
||||
label(for="user[name]") Username:
|
||||
input(type="text", name="user[name]", value=user.name)
|
||||
p
|
||||
label(for="user[email]") Email:
|
||||
input(type="text", name="user[email]", value=user.email)
|
||||
.tip.
|
||||
Enter a valid
|
||||
email address
|
||||
such as <em>tj@vision-media.ca</em>.
|
||||
fieldset
|
||||
legend Location
|
||||
p
|
||||
label(for="user[city]") City:
|
||||
input(type="text", name="user[city]", value=user.city)
|
||||
p
|
||||
select(name="user[province]")
|
||||
option(value="") -- Select Province --
|
||||
option(value="AB") Alberta
|
||||
option(value="BC") British Columbia
|
||||
option(value="SK") Saskatchewan
|
||||
option(value="MB") Manitoba
|
||||
option(value="ON") Ontario
|
||||
option(value="QC") Quebec
|
||||
p.buttons
|
||||
input(type="submit", value="Save")
|
||||
7
playground/examples/includes.pug
Normal file
7
playground/examples/includes.pug
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
html
|
||||
include includes/head.pug
|
||||
body
|
||||
h1 My Site
|
||||
p Welcome to my super lame site.
|
||||
include includes/foot.pug
|
||||
14
playground/examples/layout.pug
Normal file
14
playground/examples/layout.pug
Normal file
@@ -0,0 +1,14 @@
|
||||
doctype html
|
||||
html(lang="en")
|
||||
head
|
||||
title Example
|
||||
script.
|
||||
if (foo) {
|
||||
bar();
|
||||
}
|
||||
body
|
||||
h1 Pug - node template engine
|
||||
#container
|
||||
:markdown-it
|
||||
Pug is a _high performance_ template engine for [node](http://nodejs.org),
|
||||
inspired by [haml](http://haml-lang.com/), and written by [TJ Holowaychuk](http://github.com/visionmedia).
|
||||
14
playground/examples/mixins.pug
Normal file
14
playground/examples/mixins.pug
Normal file
@@ -0,0 +1,14 @@
|
||||
include mixins/dialog.pug
|
||||
include mixins/profile.pug
|
||||
|
||||
.one
|
||||
+dialog
|
||||
|
||||
.two
|
||||
+dialog-title('Whoop')
|
||||
|
||||
.three
|
||||
+dialog-title-desc('Whoop', 'Just a mixin')
|
||||
|
||||
#profile
|
||||
+profile(user)
|
||||
3
playground/examples/pet.pug
Normal file
3
playground/examples/pet.pug
Normal file
@@ -0,0 +1,3 @@
|
||||
.pet
|
||||
h2= pet.name
|
||||
p #{pet.name} is <em>#{pet.age}</em> year(s) old.
|
||||
14
playground/examples/rss.pug
Normal file
14
playground/examples/rss.pug
Normal file
@@ -0,0 +1,14 @@
|
||||
doctype xml
|
||||
rss(version='2.0')
|
||||
channel
|
||||
title RSS Title
|
||||
description Some description here
|
||||
link http://google.com
|
||||
lastBuildDate Mon, 06 Sep 2010 00:01:00 +0000
|
||||
pubDate Mon, 06 Sep 2009 16:45:00 +0000
|
||||
|
||||
each item in items
|
||||
item
|
||||
title= item.title
|
||||
description= item.description
|
||||
link= item.link
|
||||
36
playground/examples/text.pug
Normal file
36
playground/examples/text.pug
Normal file
@@ -0,0 +1,36 @@
|
||||
| An example of an
|
||||
a(href='#') inline
|
||||
| link.
|
||||
|
||||
form
|
||||
label Username:
|
||||
input(type='text', name='user[name]')
|
||||
p
|
||||
| Just an example of some text usage.
|
||||
| You can have <em>inline</em> html,
|
||||
| as well as
|
||||
strong tags
|
||||
| .
|
||||
|
||||
| Interpolation is also supported. The
|
||||
| username is currently "#{name}".
|
||||
|
||||
label Email:
|
||||
input(type='text', name='user[email]')
|
||||
p
|
||||
| Email is currently
|
||||
em= email
|
||||
| .
|
||||
|
||||
// alternatively, if we plan on having only
|
||||
// text or inline-html, we can use a trailing
|
||||
// "." to let pug know we want to omit pipes
|
||||
|
||||
label Username:
|
||||
input(type='text')
|
||||
p.
|
||||
Just an example, like before
|
||||
however now we can omit those
|
||||
annoying pipes!.
|
||||
|
||||
Wahoo.
|
||||
11
playground/examples/whitespace.pug
Normal file
11
playground/examples/whitespace.pug
Normal file
@@ -0,0 +1,11 @@
|
||||
- var js = '<script></script>'
|
||||
doctype html
|
||||
html
|
||||
|
||||
head
|
||||
title= "Some " + "JavaScript"
|
||||
!= js
|
||||
|
||||
|
||||
|
||||
body
|
||||
70
playground/run_js.js
Normal file
70
playground/run_js.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* JS Pug - Process all .pug files in playground folder
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const pug = require('../../pug');
|
||||
|
||||
const dir = path.join(__dirname, 'examples');
|
||||
|
||||
// Get all .pug files
|
||||
const pugFiles = fs.readdirSync(dir)
|
||||
.filter(f => f.endsWith('.pug'))
|
||||
.sort();
|
||||
|
||||
console.log('=== JS Pug Playground ===\n');
|
||||
console.log(`Found ${pugFiles.length} .pug files\n`);
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
let totalTimeMs = 0;
|
||||
|
||||
for (const file of pugFiles) {
|
||||
const filePath = path.join(dir, file);
|
||||
const source = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
const iterations = 100;
|
||||
let success = false;
|
||||
let html = '';
|
||||
let error = '';
|
||||
let timeMs = 0;
|
||||
|
||||
try {
|
||||
const start = process.hrtime.bigint();
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
html = pug.render(source, {
|
||||
filename: filePath,
|
||||
basedir: dir
|
||||
});
|
||||
}
|
||||
|
||||
const end = process.hrtime.bigint();
|
||||
timeMs = Number(end - start) / 1_000_000 / iterations;
|
||||
success = true;
|
||||
passed++;
|
||||
totalTimeMs += timeMs;
|
||||
} catch (e) {
|
||||
error = e.message.split('\n')[0];
|
||||
failed++;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
console.log(`✓ ${file} (${timeMs.toFixed(3)} ms)`);
|
||||
// Show first 200 chars of output
|
||||
const preview = html.replace(/\s+/g, ' ').substring(0, 200);
|
||||
console.log(` → ${preview}${html.length > 200 ? '...' : ''}\n`);
|
||||
} else {
|
||||
console.log(`✗ ${file}`);
|
||||
console.log(` → ${error}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== Summary ===');
|
||||
console.log(`Passed: ${passed}/${pugFiles.length}`);
|
||||
console.log(`Failed: ${failed}/${pugFiles.length}`);
|
||||
if (passed > 0) {
|
||||
console.log(`Total time: ${totalTimeMs.toFixed(3)} ms`);
|
||||
console.log(`Average: ${(totalTimeMs / passed).toFixed(3)} ms per file`);
|
||||
}
|
||||
120
playground/run_zig.zig
Normal file
120
playground/run_zig.zig
Normal file
@@ -0,0 +1,120 @@
|
||||
// Zig Pugz - Process all .pug files in playground/examples folder
|
||||
|
||||
const std = @import("std");
|
||||
const pug = @import("../pug.zig");
|
||||
const fs = std.fs;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
std.debug.print("=== Zig Pugz Playground ===\n\n", .{});
|
||||
|
||||
// Open the examples directory relative to cwd
|
||||
var dir = fs.cwd().openDir("playground/examples", .{ .iterate = true }) catch |err| {
|
||||
// Try from playground directory
|
||||
dir = fs.cwd().openDir("examples", .{ .iterate = true }) catch {
|
||||
std.debug.print("Error opening examples directory: {}\n", .{err});
|
||||
return;
|
||||
};
|
||||
};
|
||||
defer dir.close();
|
||||
|
||||
// Collect .pug files
|
||||
var files = std.ArrayList([]const u8).init(allocator);
|
||||
defer {
|
||||
for (files.items) |f| allocator.free(f);
|
||||
files.deinit();
|
||||
}
|
||||
|
||||
var iter = dir.iterate();
|
||||
while (try iter.next()) |entry| {
|
||||
if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".pug")) {
|
||||
const name = try allocator.dupe(u8, entry.name);
|
||||
try files.append(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort files
|
||||
std.mem.sort([]const u8, files.items, {}, struct {
|
||||
fn lessThan(_: void, a: []const u8, b: []const u8) bool {
|
||||
return std.mem.lessThan(u8, a, b);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
std.debug.print("Found {d} .pug files\n\n", .{files.items.len});
|
||||
|
||||
var passed: usize = 0;
|
||||
var failed: usize = 0;
|
||||
var total_time_ns: u64 = 0;
|
||||
|
||||
for (files.items) |filename| {
|
||||
// Read file
|
||||
const file = dir.openFile(filename, .{}) catch {
|
||||
std.debug.print("✗ {s}\n → Could not open file\n\n", .{filename});
|
||||
failed += 1;
|
||||
continue;
|
||||
};
|
||||
defer file.close();
|
||||
|
||||
const source = file.readToEndAlloc(allocator, 1024 * 1024) catch {
|
||||
std.debug.print("✗ {s}\n → Could not read file\n\n", .{filename});
|
||||
failed += 1;
|
||||
continue;
|
||||
};
|
||||
defer allocator.free(source);
|
||||
|
||||
// Benchmark
|
||||
const iterations: usize = 100;
|
||||
var success = false;
|
||||
var last_html: ?[]const u8 = null;
|
||||
|
||||
// Warmup
|
||||
for (0..5) |_| {
|
||||
var result = pug.compile(allocator, source, .{}) catch continue;
|
||||
result.deinit(allocator);
|
||||
}
|
||||
|
||||
var timer = try std.time.Timer.start();
|
||||
var i: usize = 0;
|
||||
while (i < iterations) : (i += 1) {
|
||||
var result = pug.compile(allocator, source, .{}) catch break;
|
||||
if (i == iterations - 1) {
|
||||
last_html = result.html;
|
||||
} else {
|
||||
result.deinit(allocator);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
const elapsed_ns = timer.read();
|
||||
|
||||
if (success and i == iterations) {
|
||||
const time_ms = @as(f64, @floatFromInt(elapsed_ns)) / 1_000_000.0 / @as(f64, @floatFromInt(iterations));
|
||||
std.debug.print("✓ {s} ({d:.3} ms)\n", .{ filename, time_ms });
|
||||
|
||||
// Show preview
|
||||
if (last_html) |html| {
|
||||
const max_len = @min(html.len, 200);
|
||||
std.debug.print(" → {s}{s}\n\n", .{ html[0..max_len], if (html.len > 200) "..." else "" });
|
||||
allocator.free(html);
|
||||
}
|
||||
|
||||
passed += 1;
|
||||
total_time_ns += elapsed_ns;
|
||||
} else {
|
||||
std.debug.print("✗ {s}\n → Compilation failed\n\n", .{filename});
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("=== Summary ===\n", .{});
|
||||
std.debug.print("Passed: {d}/{d}\n", .{ passed, files.items.len });
|
||||
std.debug.print("Failed: {d}/{d}\n", .{ failed, files.items.len });
|
||||
|
||||
if (passed > 0) {
|
||||
const total_ms = @as(f64, @floatFromInt(total_time_ns)) / 1_000_000.0 / 100.0;
|
||||
std.debug.print("Total time: {d:.3} ms\n", .{total_ms});
|
||||
std.debug.print("Average: {d:.3} ms per file\n", .{total_ms / @as(f64, @floatFromInt(passed))});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user