diff --git a/build.zig.zon b/build.zig.zon index 8354247..d352ee4 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .pugz, - .version = "0.3.8", + .version = "0.3.9", .fingerprint = 0x822db0790e17621d, // Changing this has security and trust implications. .minimum_zig_version = "0.15.2", .dependencies = .{}, diff --git a/src/compile_tpls.zig b/src/compile_tpls.zig index 6552c29..6d6b3d9 100644 --- a/src/compile_tpls.zig +++ b/src/compile_tpls.zig @@ -207,14 +207,17 @@ fn compileSingleFile( // Parse template with full resolution (handles includes, extends, mixins) const final_ast = try engine.parseTemplate(arena_allocator, template_name, registry); + // Expand mixin calls into concrete AST nodes for codegen + const expanded_ast = try mixin.expandMixins(arena_allocator, final_ast, registry); + // Extract field names - const fields = try zig_codegen.extractFieldNames(arena_allocator, final_ast); + const fields = try zig_codegen.extractFieldNames(arena_allocator, expanded_ast); // Generate Zig code var codegen = zig_codegen.Codegen.init(arena_allocator); defer codegen.deinit(); - const zig_code = try codegen.generate(final_ast, "render", fields); + const zig_code = try codegen.generate(expanded_ast, "render", fields); // Create flat filename from views-relative path to avoid collisions // e.g., "pages/404.pug" → "pages_404.zig" diff --git a/src/tpl_compiler/main.zig b/src/tpl_compiler/main.zig index 29e3219..161d8a6 100644 --- a/src/tpl_compiler/main.zig +++ b/src/tpl_compiler/main.zig @@ -114,12 +114,15 @@ fn compileSingleFile(allocator: mem.Allocator, input_path: []const u8, output_pa // Parse template with full resolution (handles includes, extends, mixins) const final_ast = try engine.parseTemplate(allocator, template_name, ®istry); + + // Expand mixin calls into concrete AST nodes for codegen + const expanded_ast = try mixin.expandMixins(allocator, final_ast, ®istry); // Note: Don't free final_ast as it's managed by the ViewEngine // The normalized_source is intentionally leaked as AST strings point into it // Both will be cleaned up by the allocator when the CLI exits // Extract field names from final resolved AST - const fields = try zig_codegen.extractFieldNames(allocator, final_ast); + const fields = try zig_codegen.extractFieldNames(allocator, expanded_ast); defer { for (fields) |field| allocator.free(field); allocator.free(fields); @@ -139,7 +142,7 @@ fn compileSingleFile(allocator: mem.Allocator, input_path: []const u8, output_pa var codegen = Codegen.init(allocator); defer codegen.deinit(); - const zig_code = try codegen.generate(final_ast, function_name, fields); + const zig_code = try codegen.generate(expanded_ast, function_name, fields); defer allocator.free(zig_code); // Write output file diff --git a/src/view_engine.zig b/src/view_engine.zig index 07a7d46..3a62aca 100644 --- a/src/view_engine.zig +++ b/src/view_engine.zig @@ -280,8 +280,7 @@ pub const ViewEngine = struct { /// Resolves a path relative to the current file's directory. /// - Paths starting with "/" are absolute from views_dir root - /// - Paths starting with "./" or "../" are relative to current file's directory - /// - Other paths are relative to views_dir root (Pug convention) + /// - Other paths are relative to current file's directory (if provided) /// Returns a path relative to views_dir. fn resolveRelativePath(self: *const ViewEngine, allocator: std.mem.Allocator, path: []const u8, current_dir: ?[]const u8) ![]const u8 { _ = self; @@ -291,14 +290,6 @@ pub const ViewEngine = struct { return allocator.dupe(u8, path[1..]); } - // Check if path is explicitly relative (starts with "./" or "../") - const is_explicit_relative = std.mem.startsWith(u8, path, "./") or std.mem.startsWith(u8, path, "../"); - - // If not explicitly relative, treat as relative to views_dir root (Pug convention) - if (!is_explicit_relative) { - return allocator.dupe(u8, path); - } - // If no current directory (top-level call), path is already relative to views_dir const dir = current_dir orelse { return allocator.dupe(u8, path); @@ -455,15 +446,15 @@ test "resolveRelativePath - relative paths from subdirectory" { defer allocator.free(result3); try std.testing.expectEqualStrings("pages/utils", result3); - // From pages/, include header (no ./) -> header (relative to views root, Pug convention) + // From pages/, include header (no ./) -> pages/header (relative to current dir) const result4 = try engine.resolveRelativePath(allocator, "header", "pages"); defer allocator.free(result4); - try std.testing.expectEqualStrings("header", result4); + try std.testing.expectEqualStrings("pages/header", result4); - // From pages/, include includes/partial -> includes/partial (relative to views root) + // From pages/, include includes/partial -> pages/includes/partial (relative to current dir) const result5 = try engine.resolveRelativePath(allocator, "includes/partial", "pages"); defer allocator.free(result5); - try std.testing.expectEqualStrings("includes/partial", result5); + try std.testing.expectEqualStrings("pages/includes/partial", result5); } test "resolveRelativePath - absolute paths from views root" {