From ca435c01a8c8d22ebec50f6166809ee44726d8c5 Mon Sep 17 00:00:00 2001 From: vx-clutch Date: Thu, 20 Nov 2025 16:57:46 -0500 Subject: [PATCH] alpha --- core/builtin.lua | 327 +++++++++++++++++++++++++++++++++++++++++++++- core/std.lua | 192 ++++++++++++++++++++++++++- doc/Fes.toml | 9 ++ doc/www/index.lua | 38 ++++++ main.go | 55 +++++--- 5 files changed, 601 insertions(+), 20 deletions(-) create mode 100644 doc/Fes.toml create mode 100644 doc/www/index.lua diff --git a/core/builtin.lua b/core/builtin.lua index b324680..05d73fa 100644 --- a/core/builtin.lua +++ b/core/builtin.lua @@ -15,9 +15,14 @@ function M.site_builder(header, footer) local self = { version = site_config.version or "", + title = site_config.title or "Document", header = header or [[ + + + + {{TITLE}} +
]], @@ -174,18 +321,194 @@ function M:note(content) return self end +function M:muted(content) + content = content or "" + self:custom('
' .. content .. '
') + return self +end + function M:a(link, str) - str = str or "" + link = link or "example.com" + str = str or link table.insert(self.parts, "" .. str .. "") return self end +function M:external(link, str) + link = link or "example.com" + str = str or link + table.insert(self.parts, "" .. str .. "") + return self +end + function M:version() return self.version end +function M:ul(items) + items = items or {} + local html = "" + self:custom(html) + return self +end + +function M:ol(items) + items = items or {} + local html = "
    " + for _, item in ipairs(items) do + html = html .. "
  1. " .. tostring(item) .. "
  2. " + end + html = html .. "
" + self:custom(html) + return self +end + +function M:li(str) + str = str or "" + self:custom("
  • " .. str .. "
  • ") + return self +end + +function M:code(str) + str = str or "" + self:custom("" .. str .. "") + return self +end + +function M:pre(str) + str = str or "" + self:custom("
    " .. str .. "
    ") + return self +end + +function M:blockquote(str) + str = str or "" + self:custom("
    " .. str .. "
    ") + return self +end + +function M:hr() + self:custom("
    ") + return self +end + +function M:divider() + self:custom('
    ') + return self +end + +function M:img(src, alt) + src = src or "" + alt = alt or "" + self:custom('' .. alt .. '') + return self +end + +function M:table(headers, rows) + headers = headers or {} + rows = rows or {} + + local html = "" + for _, header in ipairs(headers) do + html = html .. "" + end + html = html .. "" + + for _, row in ipairs(rows) do + html = html .. "" + for _, cell in ipairs(row) do + html = html .. "" + end + html = html .. "" + end + + html = html .. "
    " .. tostring(header) .. "
    " .. tostring(cell) .. "
    " + 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("" .. content .. "
    ") + return self +end + +function M:span(content, class) + content = content or "" + class = class or "" + local class_attr = class ~= "" and (' class="' .. class .. '"') or "" + self:custom("" .. content .. "") + return self +end + +function M:strong(str) + str = str or "" + self:custom("" .. str .. "") + return self +end + +function M:em(str) + str = str or "" + self:custom("" .. str .. "") + return self +end + +function M:br() + self:custom("
    ") + return self +end + +function M:section(content) + content = content or "" + self:custom('
    ' .. content .. '
    ') + return self +end + +function M:links(link_list) + link_list = link_list or {} + local html = '' + self:custom(html) + return self +end + +function M:lead(str) + str = str or "" + self:custom('

    ' .. str .. '

    ') + return self +end + +function M:small(str) + str = str or "" + self:custom('
    ' .. str .. '
    ') + return self +end + +function M:highlight(str) + str = str or "" + self:custom('' .. str .. '') + return self +end + function M:build() - return self.header .. table.concat(self.parts) .. self.footer + local header = self.header:gsub("{{TITLE}}", self.title or "Document") + return header .. table.concat(self.parts) .. self.footer end M.__tostring = function(self) diff --git a/core/std.lua b/core/std.lua index ed7e403..33f4879 100644 --- a/core/std.lua +++ b/core/std.lua @@ -16,4 +16,194 @@ function M.site_version() return "" end -return M \ No newline at end of file +function M.a(link, str) + return "" .. str .. "" +end + +function M.external(link, str) + return "" .. str .. "" +end + +function M.note(str) + return '
    ' .. str .. '
    ' +end + +function M.muted(str) + return '
    ' .. str .. '
    ' +end + +function M.h1(str) + return "

    " .. (str or "") .. "

    " +end + +function M.h2(str) + return "

    " .. (str or "") .. "

    " +end + +function M.h3(str) + return "

    " .. (str or "") .. "

    " +end + +function M.h4(str) + return "

    " .. (str or "") .. "

    " +end + +function M.h5(str) + return "
    " .. (str or "") .. "
    " +end + +function M.h6(str) + return "
    " .. (str or "") .. "
    " +end + +function M.p(str) + return "

    " .. (str or "") .. "

    " +end + +function M.code(str) + return "" .. (str or "") .. "" +end + +function M.pre(str) + return "
    " .. (str or "") .. "
    " +end + +function M.ul(items) + items = items or {} + local html = "" + return html +end + +function M.ol(items) + items = items or {} + local html = "
      " + for _, item in ipairs(items) do + html = html .. "
    1. " .. tostring(item) .. "
    2. " + end + html = html .. "
    " + return html +end + +function M.blockquote(str) + return "
    " .. (str or "") .. "
    " +end + +function M.hr() + return "
    " +end + +function M.img(src, alt) + src = src or "" + alt = alt or "" + return '' .. alt .. '' +end + +function M.strong(str) + return "" .. (str or "") .. "" +end + +function M.em(str) + return "" .. (str or "") .. "" +end + +function M.br() + return "
    " +end + +function M.div(content, class) + content = content or "" + class = class or "" + local class_attr = class ~= "" and (' class="' .. class .. '"') or "" + return "" .. content .. "" +end + +function M.span(content, class) + content = content or "" + class = class or "" + local class_attr = class ~= "" and (' class="' .. class .. '"') or "" + return "" .. content .. "" +end + +-- HTML escaping utility +function M.escape(str) + str = tostring(str or "") + str = str:gsub("&", "&") + str = str:gsub("<", "<") + str = str:gsub(">", ">") + str = str:gsub('"', """) + str = str:gsub("'", "'") + return str +end + +-- Get site name from config +function M.site_name() + local fes_mod = package.loaded.fes + if fes_mod and fes_mod.config and fes_mod.config.site and fes_mod.config.site.name then + return fes_mod.config.site.name + end + return "" +end + +-- Get site title from config +function M.site_title() + local fes_mod = package.loaded.fes + if fes_mod and fes_mod.config and fes_mod.config.site and fes_mod.config.site.title then + return fes_mod.config.site.title + end + return "" +end + +-- Get site authors from config +function M.site_authors() + local fes_mod = package.loaded.fes + if fes_mod and fes_mod.config and fes_mod.config.site and fes_mod.config.site.authors then + return fes_mod.config.site.authors + end + return {} +end + +-- Join array with separator +function M.join(arr, sep) + arr = arr or {} + sep = sep or ", " + local result = {} + for _, v in ipairs(arr) do + table.insert(result, tostring(v)) + end + return table.concat(result, sep) +end + +-- Trim whitespace +function M.trim(str) + str = tostring(str or "") + return str:match("^%s*(.-)%s*$") +end + +-- Table HTML generator +function M.table(headers, rows) + headers = headers or {} + rows = rows or {} + + local html = "" + for _, header in ipairs(headers) do + html = html .. "" + end + html = html .. "" + + for _, row in ipairs(rows) do + html = html .. "" + for _, cell in ipairs(row) do + html = html .. "" + end + html = html .. "" + end + + html = html .. "
    " .. tostring(header) .. "
    " .. tostring(cell) .. "
    " + return html +end + +return M diff --git a/doc/Fes.toml b/doc/Fes.toml new file mode 100644 index 0000000..fb06900 --- /dev/null +++ b/doc/Fes.toml @@ -0,0 +1,9 @@ +[site] + +name = "doc" +version = "0.0.1" +authors = ["vx-clutch"] + +[fes] +version = "1.0.0" +CUSTOM_CSS = diff --git a/doc/www/index.lua b/doc/www/index.lua new file mode 100644 index 0000000..219a1ac --- /dev/null +++ b/doc/www/index.lua @@ -0,0 +1,38 @@ +local fes = require("fes") +local site = fes.site_builder() + +site.title = "Fes Documentation" + +site:h1("Fes Documentation") +site:note([[ +This is the documentation for the Fes +Microframework. This documentation serves as +a starting point and in its current state is +not comprehensive. Furthermore, you should +note that Fes is not production grade or +stable, use at your own caution. +]]) + +site:muted("Before reading this you should consult the " .. fes.std.external("https://git.vxserver.dev/fSD/fes", "README")) + +local docs = {} + +local template = [[ +%s %s +]] +function docs:func(fn, desc) + table.insert(self, string.format(template, fn, desc)) + return self +end + +docs:func("fes.site_builder", "returns a site object, a required element for this framework.") +docs:func("site:h1", "adds a h1 to the site object.") +docs:func("site:h2", "adds a h2 to the site object.") +docs:func("site:h3", "adds a h3 to the site object.") +docs:func("site:h4", "adds a h4 to the site object.") +docs:func("site:h5", "adds a h5 to the site object.") +docs:func("site:h6", "adds a h6 to the site object.") + +site:note(table.concat(docs, "\n

    \n")) + +return site diff --git a/main.go b/main.go index 936c79e..9a3f13b 100644 --- a/main.go +++ b/main.go @@ -21,10 +21,8 @@ import ( //go:embed core/builtin.lua var builtinLua string - //go:embed core/markdown.lua var markdownLua string - //go:embed core/std.lua var stdLua string @@ -44,7 +42,6 @@ type MyConfig struct { var port = flag.Int("p", 3000, "set the server port") -// markdownToHTML converts markdown text to HTML func markdownToHTML(mdText string) string { extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock p := parser.NewWithExtensions(extensions) @@ -64,7 +61,6 @@ func loadLua(luaDir string, entry string, cfg *MyConfig) (string, error) { L.PreloadModule("fes", func(L *lua.LState) int { mod := L.NewTable() - // Load core modules from embedded files coreModules := map[string]string{ "builtin": builtinLua, "markdown": markdownLua, @@ -92,7 +88,7 @@ func loadLua(luaDir string, entry string, cfg *MyConfig) (string, error) { mod.RawSetString(modName, tbl) } } - // Pass config to Lua + if cfg != nil { configTable := L.NewTable() siteTable := L.NewTable() @@ -109,7 +105,7 @@ func loadLua(luaDir string, entry string, cfg *MyConfig) (string, error) { configTable.RawSetString("fes", fesTable) mod.RawSetString("config", configTable) } - // Register markdown_to_html function + mod.RawSetString("markdown_to_html", L.NewFunction(func(L *lua.LState) int { mdText := L.ToString(1) html := markdownToHTML(mdText) @@ -198,10 +194,8 @@ CUSTOM_CSS = } func fixMalformedToml(content string) string { - // Fix lines like "CUSTOM_CSS =" (with no value) to "CUSTOM_CSS = \"\"" re := regexp.MustCompile(`(?m)^(\s*\w+\s*=\s*)$`) return re.ReplaceAllStringFunc(content, func(match string) string { - // Extract the key name parts := strings.Split(strings.TrimSpace(match), "=") if len(parts) == 2 && strings.TrimSpace(parts[1]) == "" { key := strings.TrimSpace(parts[0]) @@ -217,7 +211,6 @@ func startServer(dir string) error { return err } - // Fix malformed TOML before parsing docStr := fixMalformedToml(string(doc)) var cfg MyConfig @@ -226,15 +219,43 @@ func startServer(dir string) error { return fmt.Errorf("failed to parse Fes.toml: %w", err) } - luaPath := filepath.Join(dir, "www", "index.lua") - data, err := loadLua(dir, luaPath, &cfg) + wwwDir := filepath.Join(dir, "www") + + entries, err := os.ReadDir(wwwDir) if err != nil { - return err + return fmt.Errorf("failed to read www directory: %w", err) } - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(data)) - }) - fmt.Printf("App running at:\n - Local: http://localhost:%d/\n", *port) + + routes := make(map[string]string) + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".lua") { + baseName := strings.TrimSuffix(entry.Name(), ".lua") + luaPath := filepath.Join(wwwDir, entry.Name()) + + if baseName == "index" { + routes["/"] = luaPath + routes["/index"] = luaPath + } else { + routes["/"+baseName] = luaPath + } + } + } + + for route, luaPath := range routes { + func(rt string, lp string) { + http.HandleFunc(rt, func(w http.ResponseWriter, r *http.Request) { + data, err := loadLua(dir, lp, &cfg) + if err != nil { + http.Error(w, fmt.Sprintf("Error loading page: %v", err), http.StatusInternalServerError) + return + } + w.Write([]byte(data)) + }) + }(route, luaPath) + } + + fmt.Printf("Server is running on http://localhost:%d\n", *port) + return http.ListenAndServe(fmt.Sprintf(":%d", *port), nil) } @@ -242,7 +263,7 @@ func main() { flag.Parse() if len(os.Args) < 3 { fmt.Println("Usage: fes ") - fmt.Println("Commands: new, serve") + fmt.Println("Commands: new, run") os.Exit(1) }