fes

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 8f16e257bba8c83ab285bafda445074c7eef6bf4
parent 705911ac9d9006479597fd62b4f69c8b91068981
Author: vx-clutch <[email protected]>
Date:   Fri, 28 Nov 2025 21:12:57 -0500

alpha p2

Diffstat:
Mcore/builtin.lua | 240++-----------------------------------------------------------------------------
Dcore/log.lua | 7-------
Acore/middleware.lua | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/std.lua | 23+++++++----------------
Acore/symbol.lua | 7+++++++
Acore/util.lua | 14++++++++++++++
Mexamples/canonical/www/index.lua | 2+-
Mexamples/hello-world/www/index.lua | 2+-
Aexamples/json/Fes.toml | 6++++++
Aexamples/json/www/index.lua | 34++++++++++++++++++++++++++++++++++
Mexamples/multi-page/www/index.lua | 2+-
Mexamples/multi-page/www/page1.lua | 2+-
Mexamples/multi-page/www/page2.lua | 2+-
Aexamples/simple/Fes.toml | 5+++++
Aexamples/simple/www/index.lua | 1+
Msrc/new/new.go | 6++++--
Msrc/server/server.go | 12++++++++++++
17 files changed, 270 insertions(+), 264 deletions(-)

diff --git a/core/builtin.lua b/core/builtin.lua @@ -398,242 +398,14 @@ function M:custom(str) return self end -function M:h1(str) - str = str or "" - self:custom(std.h1(str)) - return self -end - -function M:h2(str) - str = str or "" - self:custom(std.h2(str)) - return self -end - -function M:h3(str) - str = str or "" - self:custom(std.h3(str)) - return self -end - -function M:h4(str) - str = str or "" - self:custom(std.h4(str)) - return self -end - -function M:h5(str) - str = str or "" - self:custom(std.h5(str)) - return self -end - -function M:h6(str) - str = str or "" - self:custom(std.h6(str)) - return self -end - -function M:p(str) - str = str or "" - table.insert(self.parts, "<p>" .. str .. "</p>") - return self -end - -function M:note(content) - content = content or "" - self:custom('<div class="note">' .. content .. '</div>') - return self -end - -function M:muted(content) - content = content or "" - self:custom('<div class="muted quiet">' .. content .. '</div>') - return self -end - -function M:a(link, str) - link = link or "example.com" - str = str or link - table.insert(self.parts, "<a href=\"" .. link .. "\">" .. str .. "</a>") - return self -end - -function M:ha(link, str) - link = link or "example.com" - str = str or link - table.insert(self.parts, "<a class=\"hidden\" href=\"" .. link .. "\">" .. str .. "</a>") - return self -end - -function M:external(link, str) - link = link or "example.com" - str = str or link - table.insert(self.parts, "<a target=\"_blank\" href=\"" .. link .. "\">" .. str .. "</a>") - return self -end - -function M:version() - return self.version -end - -function M:ul(items) - items = items or {} - local html = "<ul>" - for _, item in ipairs(items) do - html = html .. "<li>" .. tostring(item) .. "</li>" - end - html = html .. "</ul>" - self:custom(html) - return self -end - -function M:ol(items) - items = items or {} - local html = "<ol>" - for _, item in ipairs(items) do - html = html .. "<li>" .. tostring(item) .. "</li>" - end - html = html .. "</ol>" - self:custom(html) - return self -end - -function M:li(str) - str = str or "" - self:custom("<li>" .. str .. "</li>") - return self -end - -function M:code(str) - str = str or "" - self:custom("<pre><code>" .. str .. "</code></pre>") - return self -end - -function M:blockquote(str) - str = str or "" - self:custom("<blockquote>" .. str .. "</blockquote>") - return self -end - -function M:hr() - self:custom("<hr>") - return self -end - -function M:divider() - self:custom('<div class="divider"></div>') - return self -end - -function M:img(src, alt) - src = src or "" - alt = alt or "" - self:custom('<img src="' .. src .. '" alt="' .. alt .. '">') - return self -end - -function M:table(headers, rows) - headers = headers or {} - rows = rows or {} - - local html = "<table><thead><tr>" - for _, header in ipairs(headers) do - html = html .. "<th>" .. tostring(header) .. "</th>" - end - html = html .. "</tr></thead><tbody>" - - for _, row in ipairs(rows) do - html = html .. "<tr>" - for _, cell in ipairs(row) do - html = html .. "<td>" .. tostring(cell) .. "</td>" - end - html = html .. "</tr>" - end - - html = html .. "</tbody></table>" - self:custom(html) - return self -end - -function M:div(content, class) - content = content or "" - class = class or "" - local class_attr = class ~= "" and (' class="' .. class .. '"') or "" - self:custom("<div" .. class_attr .. ">" .. content .. "</div>") - return self -end - -function M:span(content, class) - content = content or "" - class = class or "" - local class_attr = class ~= "" and (' class="' .. class .. '"') or "" - self:custom("<span" .. class_attr .. ">" .. content .. "</span>") - return self -end - -function M:strong(str) - str = str or "" - self:custom("<strong>" .. str .. "</strong>") - return self -end - -function M:em(str) - str = str or "" - self:custom("<em>" .. str .. "</em>") - return self -end - -function M:br() - self:custom("<br>") - return self -end - -function M:section(content) - content = content or "" - self:custom('<div class="section">' .. content .. '</div>') - return self -end - -function M:links(link_list) - link_list = link_list or {} - local html = '<div class="links">' - for _, link_data in ipairs(link_list) do - local link = link_data.link or link_data[1] or "#" - local text = link_data.text or link_data[2] or link - local external = link_data.external or false - if external then - html = html .. '<a target="_blank" href="' .. link .. '">' .. text .. '</a>' - else - html = html .. '<a href="' .. link .. '">' .. text .. '</a>' +for name, func in pairs(std) do + if type(func) == "function" then + M[name] = function(self, ...) + local result = func(...) + table.insert(self.parts, result) + return self end end - html = html .. '</div>' - self:custom(html) - return self -end - -function M:lead(str) - str = str or "" - self:custom(std.small(str)) - return self -end - -function M:small(str) - str = str or "" - self:custom(std.small(str)) - return self -end - -function M:highlight(str) - str = str or "" - self:custom(std.highlight(str)) - return self -end - -function M:banner(str) - self:custom(std.banner(str)) end function M:build() diff --git a/core/log.lua b/core/log.lua @@ -1,7 +0,0 @@ -local M = {} - -function M.printl(fmt, ...) - print(string.format(fmt, ...)) -end - -return M diff --git a/core/middleware.lua b/core/middleware.lua @@ -0,0 +1,169 @@ +local M = {} + +function M.json_decode(json) + if type(json) ~= "string" then + return nil, "input must be a string" + end + + local pos = 1 + local len = #json + + local function skip_ws() + while pos <= len and json:sub(pos,pos):match("%s") do + pos = pos + 1 + end + end + + local function parse_value() + skip_ws() + local c = json:sub(pos,pos) + if c == "{" then return parse_object() + elseif c == "[" then return parse_array() + elseif c == '"' then return parse_string() + elseif c:match("[%d%-]") then return parse_number() + elseif json:sub(pos,pos+3) == "true" then pos=pos+4; return true + elseif json:sub(pos,pos+4) == "false" then pos=pos+5; return false + elseif json:sub(pos,pos+3) == "null" then pos=pos+4; return nil + else return nil, "invalid value at position "..pos + end + end + + function parse_string() + pos = pos + 1 + local start_pos = pos + local str = "" + while pos <= len do + local c = json:sub(pos,pos) + if c == '"' then + str = str .. json:sub(start_pos,pos-1) + pos = pos + 1 + return str + elseif c == "\\" then + str = str .. json:sub(start_pos,pos-1) + pos = pos + 1 + local esc = json:sub(pos,pos) + local map = {b="\b", f="\f", n="\n", r="\r", t="\t", ['"']='"', ["\\"]="\\", ["/"]="/"} + str = str .. (map[esc] or esc) + pos = pos + 1 + start_pos = pos + else + pos = pos + 1 + end + end + return nil, "unterminated string" + end + + function parse_number() + local start_pos = pos + while pos <= len and json:sub(pos,pos):match("[%d%+%-eE%.]") do + pos = pos + 1 + end + local n = tonumber(json:sub(start_pos,pos-1)) + if not n then return nil, "invalid number at position "..start_pos end + return n + end + + function parse_array() + pos = pos + 1 + local arr = {} + skip_ws() + if json:sub(pos,pos) == "]" then pos=pos+1; return arr end + while true do + local val, err = parse_value() + if err then return nil, err end + table.insert(arr,val) + skip_ws() + local c = json:sub(pos,pos) + if c == "]" then pos=pos+1; break + elseif c == "," then pos=pos+1 + else return nil, "expected ',' or ']' at position "..pos + end + end + return arr + end + + function parse_object() + pos = pos + 1 + local obj = {} + skip_ws() + if json:sub(pos,pos) == "}" then pos=pos+1; return obj end + while true do + skip_ws() + if json:sub(pos,pos) ~= '"' then return nil, "expected string key at "..pos end + local key, err = parse_string() + if err then return nil, err end + skip_ws() + if json:sub(pos,pos) ~= ":" then return nil, "expected ':' at "..pos end + pos = pos + 1 + local val, err = parse_value() + if err then return nil, err end + obj[key] = val + skip_ws() + local c = json:sub(pos,pos) + if c == "}" then pos=pos+1; break + elseif c == "," then pos=pos+1 + else return nil, "expected ',' or '}' at "..pos + end + end + return obj + end + + local result, err = parse_value() + if err then return nil, err end + skip_ws() + if pos <= len then return nil, "trailing characters at "..pos end + return result +end + +function M.json_encode(value) + local t = type(value) + if t == "nil" then + return "null" + elseif t == "boolean" then + return tostring(value) + elseif t == "number" then + return tostring(value) + elseif t == "string" then + return '"' .. value:gsub('[%z\1-\31\\"]', { + ['\\'] = '\\\\', + ['"'] = '\\"', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t' + }):gsub("[%z\1-\31]", function(c) + return string.format("\\u%04x", c:byte()) + end) .. '"' + elseif t == "table" then + local is_array = true + local max_index = 0 + for k,v in pairs(value) do + if type(k) ~= "number" then + is_array = false + else + if k > max_index then max_index = k end + end + end + + local items = {} + if is_array then + for i = 1, max_index do + table.insert(items, M.json_encode(value[i])) + end + return "[" .. table.concat(items,",") .. "]" + else + for k,v in pairs(value) do + if type(k) ~= "string" then + return nil, "object keys must be strings" + end + table.insert(items, M.json_encode(k) .. ":" .. M.json_encode(v)) + end + return "{" .. table.concat(items,",") .. "}" + end + else + return nil, "unsupported type: " .. t + end +end + +return M diff --git a/core/std.lua b/core/std.lua @@ -55,9 +55,7 @@ end function M.h3(str) return "<h3>" .. (str or "") .. "</h3>" end - -function M.h4(str) - return "<h4>" .. (str or "") .. "</h4>" +function M.h4(str) return "<h4>" .. (str or "") .. "</h4>" end function M.h5(str) @@ -72,11 +70,11 @@ function M.p(str) return "<p>" .. (str or "") .. "</p>" end -function M.code(str) - return "<code>" .. (str or "") .. "</code>" +function M.pre(str) + return "<pre>" .. (str or "") .. "</pre>" end -function M.pre(str) +function M.code(str) return "<pre><code>" .. (str or "") .. "</code></pre>" end @@ -228,23 +226,16 @@ function M.table(headers, rows) return html end -function M.copyright() - return "&#169;" -end - function M.highlight(str) - str = str or "" - return '<span class="highlight">' .. str .. "</span>" + return '<span class="highlight">' .. (str or "") .. "</span>" end function M.banner(str) - str = str or "" - return '<div class="banner">' .. str .. "</div>" + return '<div class="banner">' .. (str or "") .. "</div>" end function M.center(str) - str = str or "" - return '<div class="center">' .. str .. "</div>" + return '<div class="center">' .. (str or "") .. "</div>" end function M.nav(link, str) diff --git a/core/symbol.lua b/core/symbol.lua @@ -0,0 +1,7 @@ +local M = {} + +M.copyright = "&#169;" +M.registered_trademark = "&#174;" +M.trademark = "&#8482;" + +return M diff --git a/core/util.lua b/core/util.lua @@ -0,0 +1,14 @@ +local std = require("core.std") +local symbol = require("core.symbol") + +local M = {} + +function M.cc(tbl) + return table.concat(tbl) +end + +function M.copyright(link, holder) + return symbol.copyright .. " " .. std.external(link, holder) +end + +return M diff --git a/examples/canonical/www/index.lua b/examples/canonical/www/index.lua @@ -4,7 +4,7 @@ local std = fes.std local site = fes.fes() site.title = "Canonical" -site.copyright = std.copyright() .. " " .. std.external("https://git.vxserver.dev/fSD", "fSD") +site.copyright = fes.util.copyright("https://git.vxserver.dev/fSD", "fSD") site:banner(fes.app.header.render(std)) diff --git a/examples/hello-world/www/index.lua b/examples/hello-world/www/index.lua @@ -2,7 +2,7 @@ local fes = require("fes") local site = fes.fes() site.title = "Hello, World!" -site.copyright = fes.std.copyright() .. " " .. fes.std.external("https://git.vxserver.dev/fSD", "fSD") +site.copyright = fes.util.copyright("https://git.vxserver.dev/fSD", "fSD") site:h1("Hello, World!") diff --git a/examples/json/Fes.toml b/examples/json/Fes.toml @@ -0,0 +1,5 @@ +[app] + +name = "json" +version = "0.0.1" +authors = ["vx-clutch"] +\ No newline at end of file diff --git a/examples/json/www/index.lua b/examples/json/www/index.lua @@ -0,0 +1,34 @@ +local fes = require("fes") +local site = fes.fes() + +site.title = "JSON" +site.copyright = fes.util.copyright("https://git.vxserver.dev/fSD", "fSD") + +local json_pre = [[ +{ + "userId": 1, + "id": 1, + "title": "delectus aut autem", + "completed": false +} +]] + +local json = fes.middleware.json_decode(json_pre) + +site:h1("JSON") + +site:note(fes.util.cc({ + fes.std.h2("Before"), + fes.std.code(json_pre), +})) +site:note(fes.util.cc({ + fes.std.h2("After"), + fes.std.ul({ + json["userId"], + json["id"], + json["title"], + json["completed"], + }) +})) + +return site diff --git a/examples/multi-page/www/index.lua b/examples/multi-page/www/index.lua @@ -2,7 +2,7 @@ local fes = require("fes") local site = fes.fes() site.title = "Home" -site.copyright = fes.std.copyright() .. " " .. fes.std.external("https://git.vxserver.dev/fSD", "fSD") +site.copyright = fes.util.copyright("https://git.vxserver.dev/fSD", "fSD") site:h1("Home") site:note( diff --git a/examples/multi-page/www/page1.lua b/examples/multi-page/www/page1.lua @@ -2,7 +2,7 @@ local fes = require("fes") local site = fes.fes() site.title = "Page 1" -site.copyright = fes.std.copyright() .. " " .. fes.std.external("https://git.vxserver.dev/fSD", "fSD") +site.copyright = fes.util.copyright("https://git.vxserver.dev/fSD", "fSD") site:h1("Page 1") site:note( diff --git a/examples/multi-page/www/page2.lua b/examples/multi-page/www/page2.lua @@ -2,7 +2,7 @@ local fes = require("fes") local site = fes.fes() site.title = "Page 2" -site.copyright = fes.std.copyright() .. " " .. fes.std.external("https://git.vxserver.dev/fSD", "fSD") +site.copyright = fes.util.copyright("https://git.vxserver.dev/fSD", "fSD") site:h1("Page 2") site:note( diff --git a/examples/simple/Fes.toml b/examples/simple/Fes.toml @@ -0,0 +1,5 @@ +[app] + +name = "simple" +version = "0.0.1" +authors = ["vx-clutch"] diff --git a/examples/simple/www/index.lua b/examples/simple/www/index.lua @@ -0,0 +1 @@ +return "Hello, World!" diff --git a/src/new/new.go b/src/new/new.go @@ -30,13 +30,15 @@ func Project(dir string) error { } indexLua := filepath.Join(dir, "www", "index.lua") if _, err := os.Stat(indexLua); os.IsNotExist(err) { - content := `local fes = require("fes") + content := fmt.Sprintf(`local fes = require("fes") local site = fes.fes() +site.title = "%s" + site:h1("Hello, World!") return site -` +`, dir) if err := os.WriteFile(indexLua, []byte(content), 0644); err != nil { return err } diff --git a/src/server/server.go b/src/server/server.go @@ -108,6 +108,18 @@ func loadLua(luaDir string, entry string, cfg *config.MyConfig) (string, error) return 1 }) + L.PreloadModule("core.symbol", func(L *lua.LState) int { + data, err := config.Core.ReadFile("core/symbol.lua") + if err != nil { + panic(err) + } + if err := L.DoString(string(data)); err != nil { + panic(err) + } + L.Push(L.Get(-1)) + return 1 + }) + L.PreloadModule("fes", func(L *lua.LState) int { mod := L.NewTable() coreModules := []string{}