diff --git a/src/errors/writer.zig b/src/errors/writer.zig
new file mode 100644
index 0000000..e69de29
diff --git a/src/interfaces/node.zig b/src/interfaces/node.zig
index b653d9e..98dcc4f 100644
--- a/src/interfaces/node.zig
+++ b/src/interfaces/node.zig
@@ -2,6 +2,29 @@ const std = @import("std");
const assert = @import("std").debug.assert;
const renderErr = @import("../errors/render.zig").RenderError;
+pub fn Writer(comptime T: type, comptime initial: []const T, comptime new: []const T) type {
+ return struct {
+ const store: []const T = initial ++ new;
+
+ fn Write(comptime value: []const T) type {
+ return Writer(T, store, value);
+ }
+
+ fn write(comptime value: []const T) type {
+ return Writer(T, store, value);
+ }
+
+ pub fn Value() []const T {
+ return store;
+ }
+ };
+}
+
+test "writer" {
+ const writer = Writer(u8, "Hello", "World");
+ std.testing.expectEqualStrings(writer.Value(), "HelloWorld");
+}
+
pub const NodeType = enum { element, text, childless, attribute, nodes };
pub const Node = struct {
@@ -33,6 +56,66 @@ pub const Node = struct {
value: ?[]const u8,
};
+ pub fn render2(self: *const Node, _mem: type) type {
+ switch (self.data) {
+ .element => |elem| {
+ var mem = _mem.write("<");
+ mem = mem.write(elem.name);
+ if (elem.children) |children| {
+ for (children) |*child| {
+ if (child.node_type == .attribute) {
+ mem = child.render2(mem);
+ }
+ }
+ }
+ mem = mem.write(">");
+ if (elem.children) |children| {
+ for (children) |*child| {
+ if (child.node_type != .attribute) {
+ mem = child.render2(mem);
+ }
+ }
+ }
+ mem = mem.write("");
+ mem = mem.write(elem.name);
+ mem = mem.write(">");
+ return mem;
+ },
+ .text => |txt| {
+ return _mem.write(txt.content);
+ },
+ .childless => |childlessElem| {
+ var mem = _mem.write("<");
+ mem = mem.write(childlessElem.name);
+ if (childlessElem.children) |children| {
+ for (children) |*child| {
+ assert(child.node_type == .attribute);
+ mem = child.render2(mem);
+ }
+ }
+ return mem.write("/>");
+ },
+ .attribute => |attr| {
+ var mem = _mem.write(" ");
+ mem = mem.write(attr.name);
+ if (attr.value) |attrValue| {
+ mem = mem.write("=\"");
+ mem = mem.write(attrValue);
+ mem = mem.write("\"");
+ }
+ return mem;
+ },
+ .nodes => |nodes| {
+ var mem = _mem;
+ if (nodes.children) |children| {
+ for (children) |*child| {
+ mem = child.render2(mem);
+ }
+ }
+ return mem;
+ },
+ }
+ }
pub fn render(self: *const Node, mem: std.ArrayList(u8).Writer) renderErr!void {
switch (self.data) {
.element => |elem| {
diff --git a/src/main.zig b/src/main.zig
index 1064475..25f375c 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -7,18 +7,23 @@ const sample = @import("sample.zig");
const server = @import("server.zig");
pub fn main() !void {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- const allocator = gpa.allocator();
- defer {
- const deinit_status = gpa.deinit();
- if (deinit_status == .leak) std.testing.expect(false) catch @panic("Leak detected!");
- }
- var arr: std.ArrayList(u8) = .{}; //.init(allocator);
- defer arr.deinit(allocator);
- const writer = arr.writer(allocator);
- try sample.run(writer);
- const samplePage = arr.items;
- try server.runServer(samplePage);
+ // var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ // const allocator = gpa.allocator();
+ // defer {
+ // const deinit_status = gpa.deinit();
+ // if (deinit_status == .leak) std.testing.expect(false) catch @panic("Leak detected!");
+ // }
+ // var arr: std.ArrayList(u8) = .{}; //.init(allocator);
+ // defer arr.deinit(allocator);
+ // const writer = arr.writer(allocator);
+ // try sample.run(writer);
+ // const samplePage = arr.items;
+ // comptime {
+ // = sample.run2();
+ // }
+ @setEvalBranchQuota(5000);
+ const samplePage = comptime sample.run2();
+ try server.runServer(@constCast(samplePage));
}
fn Page(n: []const Node) Node {
@@ -34,7 +39,7 @@ test "components" {
const html = Page(&.{
el.Head(&.{
- el.Title(&.{el.Text("My Page")}),
+ el.Title("My Page"),
}),
el.Body(&.{el.Div(&.{el.Text("Hello world!")})}),
});
@@ -46,6 +51,31 @@ test "components" {
try std.testing.expectEqualStrings(res, "
My PageHello world!
");
}
+fn Writer(comptime T: type, comptime initial: []const T, comptime new: []const T) type {
+ return struct {
+ const store: []const T = initial ++ new;
+
+ fn Write(comptime value: []const T) type {
+ return Writer(T, store, value);
+ }
+
+ fn Value() []const T {
+ return store;
+ }
+ // store: []const T = initial ++ new,
+
+ // const Self = @This();
+
+ // fn Write(self: *Self, comptime value: []const T) type {
+ // return Writer(T, self.store, value);
+ // }
+
+ // fn Value(self: *Self) []const T {
+ // return self.store;
+ // }
+ };
+}
+
test "html5" {
const allocator = std.heap.page_allocator;
var arr: std.ArrayList(u8) = .{}; //.init(allocator);
@@ -57,7 +87,7 @@ test "html5" {
el.Head(&.{
el.Meta(&.{attr.Charset("UTF-8")}),
el.Meta(&.{ attr.Name("viewport"), attr.Content("width=device-width, initial-scale=1") }),
- el.Title(&.{el.Text("Document")}),
+ el.Title("Document"),
}),
el.Body(&.{el.Div(&.{
attr.Class("my-class"),
@@ -72,3 +102,23 @@ test "html5" {
try std.testing.expectEqualStrings(res, "DocumentHello world!
");
}
+
+test "comptime allocation" {
+ const a: []const u8 = "Hello";
+ const b: []const u8 = " World";
+ var c: []const u8 = "";
+ c = comptime blk: {
+ break :blk a ++ b;
+ };
+
+ // c = c ++ "!!";
+
+ std.debug.print("{d}", .{c.len});
+
+ try std.testing.expectEqualStrings(c, "Hello World");
+
+ const writer = Writer(u8, "Hello", " World");
+
+ const e = writer.Write("!!").Write("==").Value();
+ try std.testing.expectEqualStrings(e, "Hello World!!==");
+}
diff --git a/src/root.zig b/src/root.zig
index c3c5287..5da6a18 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -3,6 +3,7 @@ pub const el = @import("./components/elements.zig");
pub const attr = @import("./components/atributes.zig");
pub const wrappers = @import("./components/wrapper.zig");
pub const Node = @import("./interfaces/node.zig").Node;
+pub const Writer = @import("./interfaces/node.zig").Writer;
test {
std.testing.log_level = .warn;
diff --git a/src/sample.zig b/src/sample.zig
index a87858b..0bf750e 100644
--- a/src/sample.zig
+++ b/src/sample.zig
@@ -9,6 +9,7 @@ const mainExample = eg.mainExample;
const addDependency = eg.addDependency;
const importDependency = eg.importDependency;
const useComponents = eg.useComponents;
+const Writer = z.Writer;
fn NavLink(label: []const u8, href: []const u8) [3]z.Node {
return .{
@@ -405,3 +406,340 @@ pub fn run(writer: std.ArrayList(u8).Writer) !void {
try html.render(writer);
}
+
+pub fn run2() []const u8 {
+ const head = el.Head(&.{
+ el.Meta(&.{attr.Charset("UTF-8")}),
+ el.Meta(&.{ attr.Name("viewport"), attr.Content("width=device-width, initial-scale=1, maximum-scale=5, user-scalable=1") }),
+ el.Meta(&.{ attr.Name("description"), attr.Content("Zigomponent is a library for generating HTML in Zig") }),
+ el.Meta(&.{ attr.Name("keywords"), attr.Content("Zig, HTML, generation, library, Zigomponent") }),
+ el.Meta(&.{ attr.Name("author"), attr.Content("eltneg") }),
+ el.Title("Zigomponent - HTML Generation for Zig"),
+ el.Script(&.{attr.Src("https://cdn.tailwindcss.com")}),
+ el.Link(&.{
+ attr.Href("https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css"),
+ attr.Rel("stylesheet"),
+ }),
+ el.Link(&.{
+ attr.Rel("apple-touch-icon"),
+ attr.Sizes("180x180"),
+ attr.Href("/apple-touch-icon.png"),
+ }),
+ el.Link(&.{
+ attr.Rel("icon"),
+ attr.Type("image/png"),
+ attr.Sizes("32x32"),
+ attr.Href("/favicon-32x32.png"),
+ }),
+ el.Link(&.{
+ attr.Rel("icon"),
+ attr.Type("image/png"),
+ attr.Sizes("16x16"),
+ attr.Href("/favicon-16x16.png"),
+ }),
+ el.Link(&.{
+ attr.Rel("manifest"),
+ attr.Href("/site.webmanifest"),
+ }),
+ el.Link(&.{
+ attr.Rel("shortcut icon"),
+ attr.Href("/favicon.ico"),
+ }),
+ el.Link(&.{
+ attr.Rel("android-chrome-icon-192x192"),
+ attr.Sizes("192x192"),
+ attr.Href("/android-chrome-192x192.png"),
+ }),
+ el.Link(&.{
+ attr.Rel("android-chrome-icon-512x512"),
+ attr.Sizes("512x512"),
+ attr.Href("/android-chrome-512x512.png"),
+ }),
+ el.Script(&.{
+ attr.Src("https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"),
+ }),
+ el.Script(&.{
+ attr.Src("https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"),
+ }),
+ });
+
+ const header = el.Header(&.{
+ attr.Class("bg-white shadow-sm border-b"),
+ el.Div(&.{
+ attr.Class("max-w-6xl mx-auto px-4 py-6"),
+ el.Div(&.{
+ attr.Class("flex items-center justify-between"),
+ el.Div(&.{
+ attr.Class("flex items-center space-x-3 flex-shrink-0"),
+ el.Div(&.{
+ attr.Class("w-10 h-10 bg-orange-500 rounded-lg flex items-center justify-center"),
+ el.Span(&.{
+ attr.Class("text-white font-bold text-xl"),
+ el.Text("Z"),
+ }),
+ }),
+ el.H1(&.{
+ attr.Class("text-2xl font-bold text-gray-900"),
+ el.Text("zigomponent"),
+ }),
+ }),
+ el.Nav(&.{
+ attr.Class("hidden md:flex items-center space-x-6"),
+ el.A(&NavLink("Features", "#features")),
+ el.A(&NavLink("Examples", "#examples")),
+ el.A(&NavLink("Getting Started", "#getting-started")),
+ el.A(&.{
+ attr.Href("https://github.com/eltNEG/zigomponent"),
+ attr.Class("bg-orange-500 text-white px-4 py-2 rounded-lg hover:bg-orange-600 transition-colors"),
+ el.Text("GitHub"),
+ }),
+ }),
+ }),
+ }),
+ });
+
+ const hero = el.Section(&.{
+ attr.Class("bg-gradient-to-br from-orange-50 to-amber-50 py-20"),
+ el.Div(&.{
+ attr.Class("max-w-6xl mx-auto px-4 text-center"),
+ el.H2(&.{
+ attr.Class("text-4xl md:text-5xl font-bold text-gray-900 mb-6"),
+ el.Text("HTML Generation for "),
+ el.Span(&.{
+ attr.Class("text-orange-500"),
+ el.Text("Zig"),
+ }),
+ }),
+ el.P(&.{
+ attr.Class("text-lg md:text-xl text-gray-600 mb-8 max-w-3xl mx-auto px-4"),
+ el.Text("Zigomponent is a Zig library inspired by gomponents that allows you to create HTML elements using native Zig syntax. Build type-safe, composable HTML components with the power of Zig."),
+ }),
+ el.Div(&.{
+ attr.Class("flex flex-col md:flex-row justify-center space-y-4 md:space-y-0 md:space-x-4"),
+ el.A(&.{
+ attr.Href("#getting-started"),
+ attr.Class("bg-orange-500 text-white px-8 py-3 rounded-lg text-lg font-semibold hover:bg-orange-600 transition-colors"),
+ el.Text("Get Started"),
+ }),
+ el.A(&.{
+ attr.Href("#examples"),
+ attr.Class("border-2 border-orange-500 text-orange-500 px-8 py-3 rounded-lg text-lg font-semibold hover:bg-orange-50 transition-colors"),
+ el.Text("View Examples"),
+ }),
+ }),
+ }),
+ });
+
+ const features = el.Section(&.{
+ attr.ID("features"),
+ attr.Class("py-16 bg-gray-50"),
+ el.Div(&.{
+ attr.Class("max-w-6xl mx-auto px-4"),
+ el.Div(&.{
+ attr.Class("text-center mb-12"),
+ el.H3(&.{
+ attr.Class("text-3xl font-bold text-gray-900 mb-4"),
+ el.Text("Why Zigomponent?"),
+ }),
+ el.P(&.{
+ attr.Class("text-lg text-gray-600"),
+ el.Text("Powerful features for modern web development"),
+ }),
+ }),
+ el.Div(&.{
+ attr.Class("grid md:grid-cols-3 gap-8"),
+ el.Div(&Features("Type Safety", "Leverage Zig's compile-time type checking to catch HTML errors before runtime.").New()),
+ el.Div(&Features("Performance", "Zero-cost abstractions and compile-time optimizations for blazing fast HTML generation.").New()),
+ el.Div(&Features("Composable", "Build reusable components and compose them together for complex layouts.").New()),
+ }),
+ }),
+ });
+
+ const codeExample = el.Section(&.{
+ attr.Class("py-16 bg-white"),
+ el.Div(&.{
+ attr.Class("max-w-6xl mx-auto px-4"),
+ el.Div(&.{
+ attr.Class("text-center mb-12"),
+ el.H3(&.{
+ attr.Class("text-3xl font-bold text-gray-900 mb-4"),
+ el.Text("Simple and Intuitive"),
+ }),
+ el.P(&.{
+ attr.Class("text-lg text-gray-600"),
+ el.Text("Create HTML elements with native Zig syntax"),
+ }),
+ }),
+ Code(mainExample).New(),
+ }),
+ });
+
+ const script = el.JSScript(
+ \\document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
+ \\ anchor.addEventListener("click", function (e) {
+ \\ e.preventDefault();
+ \\ const target = document.querySelector(this.getAttribute("href"));
+ \\ if (target) {
+ \\ target.scrollIntoView({
+ \\ behavior: "smooth",
+ \\ block: "start",
+ \\ });
+ \\ }
+ \\ });
+ \\});
+ );
+
+ const footer = el.Footer(&.{
+ attr.Class("bg-gray-900 text-white py-12"),
+ el.Div(&.{
+ attr.Class("max-w-6xl mx-auto px-4"),
+ el.Div(&.{
+ attr.Class("grid md:grid-cols-4 gap-8"),
+ el.Div(&.{
+ attr.Class("md:col-span-2"),
+ el.Div(&.{
+ attr.Class("flex items-center space-x-3 mb-4"),
+ el.Div(&.{
+ attr.Class("w-8 h-8 bg-orange-500 rounded-lg flex items-center justify-center"),
+ el.Span(&.{
+ attr.Class("text-white font-bold"),
+ el.Text("Z"),
+ }),
+ }),
+ el.H5(&.{
+ attr.Class("text-xl font-bold"),
+ el.Text("zigomponent"),
+ }),
+ }),
+ el.P(&.{
+ attr.Class("text-gray-400 mb-4"),
+ el.Text("HTML generation for Zig, inspired by gomponents. Build type-safe, composable HTML components with the power of Zig."),
+ }),
+ }),
+ el.Div(&.{
+ el.H6(&.{
+ attr.Class("font-semibold mb-4"),
+ el.Text("Resources"),
+ }),
+ el.Ul(&.{
+ attr.Class("space-y-2 text-gray-400"),
+ el.Li(&.{
+ el.A(&.{
+ attr.Href("#"),
+ attr.Class("hover:text-white transition-colors"),
+ el.Text("Documentation"),
+ }),
+ }),
+ el.Li(&.{
+ el.A(&.{
+ attr.Href("https://github.com/eltNEG/zigomponent/blob/master/src/sample.zig"),
+ attr.Target("_blank"),
+ attr.Class("hover:text-white transition-colors"),
+ el.Text("Example"),
+ }),
+ }),
+ }),
+ }),
+ el.Div(&.{
+ el.H6(&.{
+ attr.Class("font-semibold mb-4"),
+ el.Text("Community"),
+ }),
+ el.Ul(&.{
+ attr.Class("space-y-2 text-gray-400"),
+ el.Li(&.{
+ el.A(&.{
+ attr.Href("https://github.com/eltNEG/zigomponent"),
+ attr.Target("_blank"),
+ attr.Class("hover:text-white transition-colors"),
+ el.Text("GitHub"),
+ }),
+ }),
+ }),
+ }),
+ }),
+ el.Div(&.{
+ attr.Class("border-t border-gray-800 mt-8 pt-8 text-center text-gray-400"),
+ el.P(&.{
+ el.Text("© 2025 Zigomponent. MIT License."),
+ }),
+ }),
+ }),
+ });
+
+ const moreExamples = el.Section(&.{
+ attr.ID("examples"),
+ attr.Class("py-16 bg-white"),
+ el.Div(&.{
+ attr.Class("max-w-6xl mx-auto px-4"),
+ el.Div(&.{
+ attr.Class("text-center mb-12"),
+ el.H3(&.{
+ attr.Class("text-3xl font-bold text-gray-900 mb-4"),
+ el.Text("More Examples"),
+ }),
+ el.P(&.{
+ attr.Class("text-lg text-gray-600"),
+ el.Text("See zigomponent in action"),
+ }),
+ }),
+ el.Div(&.{
+ attr.Class("grid grid-cols-1 lg:grid-cols-2 gap-8 px-4"),
+ Snippet("Creating a Form", formExample).New(),
+ Snippet("Custom Components", cardExample).New(),
+ }),
+ }),
+ });
+
+ const gettingStarted = el.Section(&.{
+ attr.ID("getting-started"),
+ attr.Class("py-16 bg-gray-50"),
+ el.Div(&.{
+ attr.Class("max-w-4xl mx-auto px-4 sm:px-6 lg:px-8"),
+ el.Div(&.{
+ attr.Class("text-center mb-12"),
+ el.H3(&.{
+ attr.Class("text-3xl font-bold text-gray-900 mb-4"),
+ el.Text("Getting Started"),
+ }),
+ el.P(&.{
+ attr.Class("text-lg text-gray-600"),
+ el.Text("Add zigomponent to your Zig project in minutes"),
+ }),
+ }),
+ el.Div(&.{
+ attr.Class("space-y-8"),
+ Snippet("1. Add zigomponent to your project", "$ zig fetch --save git+https://github.com/eltNEG/zigomponent;").New(),
+ Snippet("2. Update your build.zig", addDependency).New(),
+ Snippet("3. Import in your code", importDependency).New(),
+ Snippet("4. Use in your code", useComponents).New(),
+ }),
+ }),
+ });
+
+ const body = el.Body(&.{
+ attr.Class("bg-gray-50 text-gray-900"),
+ header,
+ hero,
+ codeExample,
+ features,
+ moreExamples,
+ gettingStarted,
+ script,
+ footer,
+ });
+
+ const html = el.ToNode(&.{
+ el.Raw(""),
+ el.Html(&.{
+ attr.Lang("en"),
+ head,
+ body,
+ }),
+ });
+
+ const writer = Writer(u8, "", "");
+ const rendered = html.render2(writer);
+
+ return rendered.Value();
+}