aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-09-19 23:40:53 -0400
committerfrosty <gabriel@bwaaa.monster>2026-09-19 23:42:41 -0400
commit4e5b81c1688080ea770184f54c66ebe82482b692 (patch)
tree56d8d3a2d5d0c9feb776ff084a4b2b9c68e8956e
parentca4732f7c6e3d0c735cb0977c505db52b99ff688 (diff)
feat: add html.append helper
-rw-r--r--examples/site/build.lua4
-rw-r--r--src/Dom/Dom.c121
2 files changed, 111 insertions, 14 deletions
diff --git a/examples/site/build.lua b/examples/site/build.lua
index e18124e..7a9dfd4 100644
--- a/examples/site/build.lua
+++ b/examples/site/build.lua
@@ -11,7 +11,7 @@ local features = {
local feature_list = assert(html.find_by_class(page, "feature-list"))
for _, feature in ipairs(features) do
- table.insert(feature_list.children, html.element("li", nil, feature))
+ feature_list:append(html.element("li", nil, feature))
end
local entries = {}
@@ -25,7 +25,7 @@ summary.text = tostring(#entries) .. " entries"
local file_list = assert(html.find_by_class(page, "file-list"))
for _, entry in ipairs(entries) do
- table.insert(file_list.children, html.element("li", nil,
+ html.append(file_list, html.element("li", nil,
html.element("code", nil, entry.path),
" (", entry.type, ")"))
end
diff --git a/src/Dom/Dom.c b/src/Dom/Dom.c
index 4eb3ad4..67817aa 100644
--- a/src/Dom/Dom.c
+++ b/src/Dom/Dom.c
@@ -6,10 +6,18 @@
#include <string.h>
#include <strings.h>
+#define HTML_NODE_METATABLE "yapssg.html_node"
+
static int absolute_index(lua_State *lua, int index) {
return index < 0 ? lua_gettop(lua) + index + 1 : index;
}
+static void set_html_node_metatable(lua_State *lua, int table_index) {
+ table_index = absolute_index(lua, table_index);
+ luaL_getmetatable(lua, HTML_NODE_METATABLE);
+ lua_setmetatable(lua, table_index);
+}
+
static int table_is_node(lua_State *lua, int table_index) {
int is_node;
@@ -25,15 +33,16 @@ static int table_is_node(lua_State *lua, int table_index) {
return is_node;
}
-static void append_element_child(lua_State *lua, int children_index,
- int child_index, size_t *child_count,
- unsigned int depth) {
+static void append_normalized_child(lua_State *lua, int children_index,
+ int child_index, size_t *child_count,
+ unsigned int depth,
+ const char *function_name) {
size_t array_length;
size_t index;
int child_type;
if (!lua_checkstack(lua, 2)) {
- luaL_error(lua, "html.element children are nested too deeply");
+ luaL_error(lua, "%s children are nested too deeply", function_name);
}
children_index = absolute_index(lua, children_index);
child_index = absolute_index(lua, child_index);
@@ -45,7 +54,7 @@ static void append_element_child(lua_State *lua, int children_index,
if (child_type == LUA_TSTRING ||
(child_type == LUA_TTABLE && table_is_node(lua, child_index))) {
if (*child_count == INT_MAX) {
- luaL_error(lua, "html.element has too many children");
+ luaL_error(lua, "%s has too many children", function_name);
}
++*child_count;
lua_pushvalue(lua, child_index);
@@ -54,21 +63,22 @@ static void append_element_child(lua_State *lua, int children_index,
}
if (child_type != LUA_TTABLE) {
luaL_error(lua,
- "html.element child must be a string, node, child array, or "
- "nil (got %s)",
- lua_typename(lua, child_type));
+ "%s child must be a string, node, child array, or nil (got "
+ "%s)",
+ function_name, lua_typename(lua, child_type));
}
if (depth == 100) {
- luaL_error(lua, "html.element children are nested too deeply");
+ luaL_error(lua, "%s children are nested too deeply", function_name);
}
array_length = lua_objlen(lua, child_index);
if (array_length > INT_MAX) {
- luaL_error(lua, "html.element child array is too large");
+ luaL_error(lua, "%s child array is too large", function_name);
}
for (index = 1; index <= array_length; ++index) {
lua_rawgeti(lua, child_index, (int)index);
- append_element_child(lua, children_index, -1, child_count, depth + 1);
+ append_normalized_child(lua, children_index, -1, child_count, depth + 1,
+ function_name);
lua_pop(lua, 1);
}
}
@@ -104,10 +114,84 @@ static int html_element(lua_State *lua) {
lua_newtable(lua);
children_index = lua_gettop(lua);
for (index = 3; index <= argument_count; ++index) {
- append_element_child(lua, children_index, index, &child_count, 0);
+ append_normalized_child(lua, children_index, index, &child_count, 0,
+ "html.element");
}
lua_setfield(lua, -2, "children");
+ set_html_node_metatable(lua, -1);
+
+ return 1;
+}
+
+static int html_append(lua_State *lua) {
+ size_t new_child_count = 0;
+ size_t existing_child_count;
+ size_t index;
+ int argument_count = lua_gettop(lua);
+ int new_children_index;
+ int children_index;
+ const char *tag;
+
+ luaL_checktype(lua, 1, LUA_TTABLE);
+ lua_getfield(lua, 1, "tag");
+ tag = lua_tostring(lua, -1);
+ if (tag == NULL || tag[0] == '\0') {
+ return luaL_argerror(lua, 1, "element expected");
+ }
+ lua_pop(lua, 1);
+
+ lua_newtable(lua);
+ new_children_index = lua_gettop(lua);
+ for (index = 2; index <= (size_t)argument_count; ++index) {
+ append_normalized_child(lua, new_children_index, (int)index,
+ &new_child_count, 0, "html.append");
+ }
+ if (new_child_count == 0) {
+ lua_pushvalue(lua, 1);
+ return 1;
+ }
+ lua_getfield(lua, 1, "children");
+ if (lua_isnil(lua, -1)) {
+ lua_pop(lua, 1);
+ lua_newtable(lua);
+ lua_pushvalue(lua, -1);
+ lua_setfield(lua, 1, "children");
+ } else if (!lua_istable(lua, -1)) {
+ return luaL_error(lua, "html.append element children must be a table");
+ }
+ children_index = lua_gettop(lua);
+ existing_child_count = lua_objlen(lua, children_index);
+ if (existing_child_count > INT_MAX) {
+ return luaL_error(lua, "html.append element has too many children");
+ }
+
+ if (existing_child_count == 0) {
+ size_t text_length;
+
+ lua_getfield(lua, 1, "text");
+ if (lua_type(lua, -1) == LUA_TSTRING) {
+ lua_tolstring(lua, -1, &text_length);
+ if (text_length > 0) {
+ lua_pushvalue(lua, -1);
+ lua_rawseti(lua, children_index, 1);
+ existing_child_count = 1;
+ }
+ }
+ lua_pop(lua, 1);
+ }
+
+ if (new_child_count > INT_MAX - existing_child_count) {
+ return luaL_error(lua, "html.append element has too many children");
+ }
+ for (index = 1; index <= new_child_count; ++index) {
+ lua_rawgeti(lua, new_children_index, (int)index);
+ lua_rawseti(lua, children_index, (int)(existing_child_count + index));
+ }
+ lua_pushnil(lua);
+ lua_setfield(lua, 1, "text");
+
+ lua_pushvalue(lua, 1);
return 1;
}
@@ -222,9 +306,21 @@ static int html_find_by_class(lua_State *lua) {
}
void dom_register_html(lua_State *lua) {
+ if (luaL_newmetatable(lua, HTML_NODE_METATABLE)) {
+ lua_newtable(lua);
+ lua_pushcfunction(lua, html_append);
+ lua_setfield(lua, -2, "append");
+ lua_setfield(lua, -2, "__index");
+ lua_pushboolean(lua, 0);
+ lua_setfield(lua, -2, "__metatable");
+ }
+ lua_pop(lua, 1);
+
lua_newtable(lua);
lua_pushcfunction(lua, html_element);
lua_setfield(lua, -2, "element");
+ lua_pushcfunction(lua, html_append);
+ lua_setfield(lua, -2, "append");
lua_pushcfunction(lua, html_find_by_class);
lua_setfield(lua, -2, "find_by_class");
lua_setglobal(lua, "html");
@@ -357,6 +453,7 @@ static void push_element(lua_State *lua, const xmlNode *node) {
lua_newtable(lua);
element_index = lua_gettop(lua);
+ set_html_node_metatable(lua, element_index);
set_string_field(lua, element_index, "tag", (const char *)node->name);
push_attributes(lua, node, element_index);
push_children(lua, node, element_index, structured_children);