Skip to content

Instantly share code, notes, and snippets.

@oatmealine
Created April 5, 2022 03:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oatmealine/ad5aba767d779fd1970fbab51fc6e7a1 to your computer and use it in GitHub Desktop.
Save oatmealine/ad5aba767d779fd1970fbab51fc6e7a1 to your computer and use it in GitHub Desktop.
smelte - a very, very dumb proof-of-concept Lua DSL HTML framework
-- smelte 1.0
-- a very, very dumb proof-of-concept Lua DSL HTML framework
--
-- smelte.declare 'name' (function() return span 'custom element' end)
-- -> void
-- declares a custom element
--
-- smelte.compile (function() return
-- {
-- head = {
-- title 'page title'
-- },
-- body = {
-- 'page content', br,
-- span 'content in a <span>'
-- }
-- }
-- end)
-- -> string
-- compiles a page to plain html
--
-- smelte.save 'index.html' ...
-- -> void
-- same syntax as svelte.compile, except saves it to the filename provided
--
-- licensed under [GPLv3.0](https://www.gnu.org/licenses/gpl-3.0.txt)
-- (c) 2022 Jill "oatmealine" Monoids
local self = {}
local old_G
local voidElements = {
area = true,
base = true,
br = true,
col = true,
embed = true,
hr = true,
img = true,
input = true,
link = true,
meta = true,
param = true,
source = true,
track = true,
wbr = true
}
local customElements = {}
local function escapeHTML(str)
local res = str:gsub('"', '&quot;'):gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;'):gsub('\'', '&#39;')
return res
end
local element = {}
function element:__call(input)
if type(input) == 'string' then
if voidElements[self._name] then
error('cannot append element to a void element (<' .. self._name .. '>' .. input .. '</' .. self._name .. '> would be invalid)', 2)
else
self._params[1] = input
end
elseif type(input) == 'table' then
for k, v in pairs(input) do
if type(k) == 'number' and voidElements[self._name] then
error('cannot append element to a void element (<' .. self._name .. '>' .. ((type(v) == 'table' and v._name) and ('<' .. v._name .. '/>') or v._name) .. '</' .. self._name .. '> would be invalid)', 2)
end
if type(v) == 'table' and getmetatable(v) ~= element then
local onlyIntegerValues = {}
for i, n in ipairs(v) do
onlyIntegerValues[i] = n
end
self(onlyIntegerValues)
else
self._params[k] = v
end
end
end
return self
end
function element:__tostring()
if customElements[self._name] then
return tostring(customElements[self._name](self._params))
else
local str = {}
table.insert(str, '<')
table.insert(str, self._name)
for k, v in pairs(self._params) do
if type(k) ~= 'number' then
table.insert(str, ' ')
table.insert(str, tostring(k))
table.insert(str, '="')
table.insert(str, escapeHTML(tostring(v)))
table.insert(str, '"')
end
end
if voidElements[self._name] then
table.insert(str, '>')
else
table.insert(str, '>')
for _, elem in ipairs(self._params) do
table.insert(str, tostring(elem))
end
table.insert(str, '</')
table.insert(str, self._name)
table.insert(str, '>')
end
return table.concat(str, '')
end
end
local function createElementTable(name)
return setmetatable({_name = name, _params = {}}, element)
end
local function applyGlobalTable()
old_G = getmetatable(_G)
_G = setmetatable(_G, {
__index = function(self, k)
if rawget(self, k) then
return rawget(self, k)
else
return createElementTable(k)
end
end
})
end
local function removeGlobalTable()
_G = setmetatable(_G, old_G)
end
function self.declare(name)
applyGlobalTable()
return function(func)
customElements[name] = func
removeGlobalTable()
end
end
function self.compile(func)
local obj = func()
local page = html {
head (obj.head or {}),
body (obj.body or {})
}
return tostring(page)
end
function self.save(filename)
print('saving page to ' .. filename)
applyGlobalTable()
return function(func)
local page = self.compile(func)
io.open(filename, 'w'):write(page):close()
removeGlobalTable()
end
end
return self
local smelte = require 'smelte'
local save = smelte.save
local declare = smelte.declare
declare 'box' (function(params)
return div {
h1 {
params.title
},
span {
params
},
style = [[
border: 2px solid #111;
background-color: #eee;
padding: 10px;
margin: 10px;
max-width: 600px;
]]
}
end)
save 'index.html' (function()
return {
head = {
title 'my website'
},
body = {
h1 'welcome to my website!', br,
a {'check out yugoslavia.best', href = 'https://yugoslavia.best'}, br,
box {
title = 'Did you know?',
'here is an image of my cat', br,
img {src = 'cat.png', alt = 'my cat'}
}
}
}
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment