Created
April 5, 2022 03:11
-
-
Save oatmealine/ad5aba767d779fd1970fbab51fc6e7a1 to your computer and use it in GitHub Desktop.
smelte - a very, very dumb proof-of-concept Lua DSL HTML framework
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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('"', '"'):gsub('&', '&'):gsub('<', '<'):gsub('>', '>'):gsub('\'', ''') | |
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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