const Self = @This(); pub const Content = union(enum) { elements: []const Self, string: []const u8, void: void, pub fn content(string: []const u8) @This() { return .{ .string = string }; } pub fn children(elements: []const Self) @This() { return .{ .elements = elements }; } }; pub const Attribute = struct { name: []const u8, value: []const u8, }; tag: []const u8, attributes: []const Attribute, content: Content, fn attributesFrom(attributes: anytype) []const Attribute { const T = @TypeOf(attributes); const info = @typeInfo(T); if (info != .@"struct") { @compileError("struct needed"); } const fields = info.@"struct".fields; var attrs: []const Attribute = &.{}; for (fields) |field| { const name = field.name; const value = @field(attributes, name); const next_attr: []const Attribute = &.{ .{ .name = name, .value = value } }; attrs = attrs ++ next_attr; } return attrs; } pub fn native(name: @EnumLiteral(), attrs: anytype, content: Content) Self { return .{ .tag = @tagName(name), .attributes = attributesFrom(attrs), .content = content, }; } pub fn transparent(content: Content) Self { return .{ .tag = "", .attributes = &.{}, .content = content, }; } pub fn toHtml(comptime self: Self) []const u8 { var string: []const u8 = string: { if (self.tag.len == 0) { break :string ""; } var string: []const u8 = "<" ++ self.tag; inline for (self.attributes) |attr| { string = string ++ " " ++ attr.name ++ "='" ++ attr.value ++ "'"; } string = string ++ ">"; break :string string; }; switch (self.content) { .void => {}, .string => |s| { string = string ++ s ++ ""; }, .elements => |els| { for (els) |el| { string = string ++ el.toHtml(); } string = string ++ ""; } } string = string ++ if (self.tag.len == 0) "" else ""; return string; } pub fn toDocument(comptime self: Self) []const u8 { return "" ++ self.toHtml(); }