summaryrefslogtreecommitdiff
path: root/.zs/components/html.lua
diff options
context:
space:
mode:
authordelta <darkussdelta@gmail.com>2026-01-27 04:18:15 +0100
committerdelta <darkussdelta@gmail.com>2026-01-27 04:18:15 +0100
commit3026a05a2a2be91aa160de230a838acd5c2b3536 (patch)
tree141ea424f7c4149c27e6749a73fec7dd4fd354ab /.zs/components/html.lua
parentce0a6c0388b4684d8f15a7ff1049e42e2d8cc63c (diff)
woohoo no more bash componentsHEADmaster
Diffstat (limited to '.zs/components/html.lua')
-rw-r--r--.zs/components/html.lua152
1 files changed, 152 insertions, 0 deletions
diff --git a/.zs/components/html.lua b/.zs/components/html.lua
new file mode 100644
index 0000000..1355905
--- /dev/null
+++ b/.zs/components/html.lua
@@ -0,0 +1,152 @@
+-- This is taken from http://lua-users.org/wiki/SortedIteration
+-- This version is stripped of comments and empty lines + some stuff is renamed
+
+local function cmp(op1, op2)
+ local type1, type2 = type(op1), type(op2)
+ if type1 ~= type2 then --cmp by type
+ return type1 < type2
+ elseif type1 == "number" or type1 == "string" then --type2 is equal to type1
+ return op1 < op2 --comp by default
+ elseif type1 == "boolean" then
+ return op1 == true
+ else
+ return tostring(op1) < tostring(op2) --cmp by address
+ end
+end
+
+local function __gen_oindex(t)
+ local oindex = {}
+ for key in pairs(t) do
+ table.insert(oindex, key)
+ end
+ table.sort(oindex, cmp)
+ return oindex
+end
+
+local function onext(t, state)
+ local key = nil
+ if state == nil then
+ t.__oindex = __gen_oindex(t)
+ key = t.__oindex[1]
+ else
+ for i = 1, #t.__oindex do
+ if t.__oindex[i] == state then
+ key = t.__oindex[i + 1]
+ end
+ end
+ end
+ if key then
+ return key, t[key]
+ end
+ t.__oindex = nil
+end
+
+local function opairs(t)
+ return onext, t, nil
+end
+
+local html = {}
+
+---@alias Node { tag: string?, [string|number]: any }
+
+local entities = {
+ { "&", "&amp;" },
+ { "<", "&lt;" },
+ { ">", "&gt;" },
+ { [["]], "&quot;" },
+ { "'", "&#x27;" },
+}
+
+local void_tags = {
+ area = true,
+ base = true,
+ br = true,
+ col = true,
+ embed = true,
+ hr = true,
+ img = true,
+ input = true,
+ link = true,
+ meta = true,
+ source = true,
+ track = true,
+ wbr = true,
+}
+
+local function key_length(t)
+ local n = 0
+ for k in pairs(t) do
+ if type(k) == "string" then
+ n = n + 1
+ end
+ end
+ return n
+end
+
+---@param content string
+function html.sanitize(content)
+ for _, pair in ipairs(entities) do
+ content = content:gsub(pair[1], pair[2])
+ end
+
+ return content
+end
+
+---@param node Node
+function html.render(node)
+ local state = {}
+
+ local function push(value)
+ table.insert(state, tostring(value))
+ end
+
+ if node.tag then
+ push "<"
+ push(node.tag)
+
+ for attr, value in opairs(node) do
+ if type(attr) == "string" and attr ~= "tag" then
+ push " "
+ push(attr)
+ if value ~= "" then
+ push [[="]]
+ push(html.sanitize(type(value) == "string" and value or tostring(value)))
+ push [["]]
+ end
+ end
+ end
+
+ push ">"
+ elseif key_length(node) > 0 then
+ error("cannot set attributes on a list of values", 2)
+ end
+
+ if void_tags[node.tag] and #node > 0 then
+ error("'" .. node.tag .. "' is a void tag and cannot have children", 2)
+ end
+
+ for _, value in ipairs(node) do
+ local type = type(value)
+ if type == "string" then
+ push(html.sanitize(value))
+ elseif type == "table" then
+ push(html.render(value))
+ else
+ push(html.sanitize(tostring(value)))
+ end
+ end
+
+ if not void_tags[node.tag] and node.tag then
+ push "</"
+ push(node.tag)
+ push ">"
+ end
+
+ return table.concat(state, "")
+end
+
+return setmetatable(html, {
+ __call = function(_, ...)
+ return html.render(...)
+ end,
+})