4 Commits

Author SHA1 Message Date
e2a1271425 v0.1.3: Add scoped warnings for skipped errors
- Add scoped logger (pugz/runtime) for better log filtering
- Add warnings when mixin not found
- Add warnings for mixin attribute failures
- Add warnings for mixin directory/file lookup failures
- Add warnings for mixin parse/tokenize failures
- Use 'action first, then reason' pattern in all log messages
2026-01-19 19:31:39 +05:30
a498eea0bc v0.1.2: Bump version 2026-01-19 19:20:33 +05:30
c172009799 v0.1.1: Add warning log when mixin is not found 2026-01-19 19:19:28 +05:30
8ff473839c Bump version to 0.1.0 2026-01-19 19:11:36 +05:30
2 changed files with 46 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
.{ .{
.name = .pugz, .name = .pugz,
.version = "0.0.0", .version = "0.1.3",
.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 = .{

View File

@@ -26,6 +26,8 @@ const ast = @import("ast.zig");
const Lexer = @import("lexer.zig").Lexer; const Lexer = @import("lexer.zig").Lexer;
const Parser = @import("parser.zig").Parser; const Parser = @import("parser.zig").Parser;
const log = std.log.scoped(.@"pugz/runtime");
/// A value in the template context. /// A value in the template context.
pub const Value = union(enum) { pub const Value = union(enum) {
/// Null/undefined value. /// Null/undefined value.
@@ -765,8 +767,11 @@ pub const Runtime = struct {
} }
} }
// If still not found, skip this mixin call // If still not found, log warning and skip this mixin call
const mixin_def = mixin orelse return; const mixin_def = mixin orelse {
log.warn("skipping, mixin '{s}' not found", .{call.name});
return;
};
try self.context.pushScope(); try self.context.pushScope();
defer self.context.popScope(); defer self.context.popScope();
@@ -790,9 +795,13 @@ pub const Runtime = struct {
if (attr.value) |val| { if (attr.value) |val| {
// Strip quotes from attribute value for the object // Strip quotes from attribute value for the object
const clean_val = try self.evaluateString(val); const clean_val = try self.evaluateString(val);
attrs_obj.put(self.allocator, attr.name, Value.str(clean_val)) catch {}; attrs_obj.put(self.allocator, attr.name, Value.str(clean_val)) catch |err| {
log.warn("skipping attribute, failed to set '{s}': {}", .{ attr.name, err });
};
} else { } else {
attrs_obj.put(self.allocator, attr.name, Value.boolean(true)) catch {}; attrs_obj.put(self.allocator, attr.name, Value.boolean(true)) catch |err| {
log.warn("skipping attribute, failed to set '{s}': {}", .{ attr.name, err });
};
} }
} }
try self.context.set("attributes", .{ .object = attrs_obj }); try self.context.set("attributes", .{ .object = attrs_obj });
@@ -851,10 +860,16 @@ pub const Runtime = struct {
const resolver = self.file_resolver orelse return null; const resolver = self.file_resolver orelse return null;
// First try: look for a file named {name}.pug // First try: look for a file named {name}.pug
const specific_path = std.fs.path.join(self.allocator, &.{ self.mixins_dir, name }) catch return null; const specific_path = std.fs.path.join(self.allocator, &.{ self.mixins_dir, name }) catch |err| {
log.warn("skipping mixin lookup, failed to join path for '{s}': {}", .{ name, err });
return null;
};
defer self.allocator.free(specific_path); defer self.allocator.free(specific_path);
const with_ext = std.fmt.allocPrint(self.allocator, "{s}.pug", .{specific_path}) catch return null; const with_ext = std.fmt.allocPrint(self.allocator, "{s}.pug", .{specific_path}) catch |err| {
log.warn("skipping mixin lookup, failed to allocate path for '{s}': {}", .{ name, err });
return null;
};
defer self.allocator.free(with_ext); defer self.allocator.free(with_ext);
if (resolver(self.allocator, with_ext)) |source| { if (resolver(self.allocator, with_ext)) |source| {
@@ -869,17 +884,29 @@ pub const Runtime = struct {
// Second try: iterate through all .pug files in mixins directory // Second try: iterate through all .pug files in mixins directory
// Use cwd().openDir for relative paths, openDirAbsolute for absolute paths // Use cwd().openDir for relative paths, openDirAbsolute for absolute paths
var dir = if (std.fs.path.isAbsolute(self.mixins_dir)) var dir = if (std.fs.path.isAbsolute(self.mixins_dir))
std.fs.openDirAbsolute(self.mixins_dir, .{ .iterate = true }) catch return null std.fs.openDirAbsolute(self.mixins_dir, .{ .iterate = true }) catch |err| {
log.warn("skipping mixins directory scan, failed to open '{s}': {}", .{ self.mixins_dir, err });
return null;
}
else else
std.fs.cwd().openDir(self.mixins_dir, .{ .iterate = true }) catch return null; std.fs.cwd().openDir(self.mixins_dir, .{ .iterate = true }) catch |err| {
log.warn("skipping mixins directory scan, failed to open '{s}': {}", .{ self.mixins_dir, err });
return null;
};
defer dir.close(); defer dir.close();
var iter = dir.iterate(); var iter = dir.iterate();
while (iter.next() catch return null) |entry| { while (iter.next() catch |err| {
log.warn("skipping mixins directory scan, iteration failed: {}", .{err});
return null;
}) |entry| {
if (entry.kind != .file) continue; if (entry.kind != .file) continue;
if (!std.mem.endsWith(u8, entry.name, ".pug")) continue; if (!std.mem.endsWith(u8, entry.name, ".pug")) continue;
const file_path = std.fs.path.join(self.allocator, &.{ self.mixins_dir, entry.name }) catch continue; const file_path = std.fs.path.join(self.allocator, &.{ self.mixins_dir, entry.name }) catch |err| {
log.warn("skipping mixin file, failed to join path for '{s}': {}", .{ entry.name, err });
continue;
};
defer self.allocator.free(file_path); defer self.allocator.free(file_path);
if (resolver(self.allocator, file_path)) |source| { if (resolver(self.allocator, file_path)) |source| {
@@ -898,11 +925,17 @@ pub const Runtime = struct {
/// Parses a source file and extracts a mixin definition by name. /// Parses a source file and extracts a mixin definition by name.
fn parseMixinFromSource(self: *Runtime, source: []const u8, name: []const u8) ?ast.MixinDef { fn parseMixinFromSource(self: *Runtime, source: []const u8, name: []const u8) ?ast.MixinDef {
var lexer = Lexer.init(self.allocator, source); var lexer = Lexer.init(self.allocator, source);
const tokens = lexer.tokenize() catch return null; const tokens = lexer.tokenize() catch |err| {
log.warn("skipping mixin file, tokenize failed for '{s}': {}", .{ name, err });
return null;
};
// Note: lexer is not deinitialized - tokens contain slices into source // Note: lexer is not deinitialized - tokens contain slices into source
var parser = Parser.init(self.allocator, tokens); var parser = Parser.init(self.allocator, tokens);
const doc = parser.parse() catch return null; const doc = parser.parse() catch |err| {
log.warn("skipping mixin file, parse failed for '{s}': {}", .{ name, err });
return null;
};
// Find the mixin definition with the matching name // Find the mixin definition with the matching name
for (doc.nodes) |node| { for (doc.nodes) |node| {