diff --git a/TODO b/TODO
index a1372fd..9b9fd43 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,2 @@
Make this static ( cannot find core.std everywhere )
-Add hotreloading
Add an interval element
-Compare bin version to Fes.toml
-Add ways to interact with left and right
diff --git a/core/builtin.lua b/core/builtin.lua
index 38dd63f..41b1f0f 100644
--- a/core/builtin.lua
+++ b/core/builtin.lua
@@ -27,23 +27,85 @@ function M.fes(header, footer)
{{TITLE}}
@@ -304,6 +459,13 @@ function M:a(link, str)
return self
end
+function M:ha(link, str)
+ 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
@@ -454,26 +616,30 @@ end
function M:lead(str)
str = str or ""
- self:custom('' .. str .. '
')
+ self:custom(std.small(str))
return self
end
function M:small(str)
str = str or ""
- self:custom('' .. str .. '
')
+ self:custom(std.small(str))
return self
end
function M:highlight(str)
str = str or ""
- self:custom('' .. str .. '')
+ self:custom(std.highlight(str))
return self
end
+function M:banner(str)
+ self:custom(std.banner(str))
+end
+
function M:build()
local header = self.header:gsub("{{TITLE}}", self.title or "Document")
local footer = self.footer:gsub("{{COPYRIGHT}}", self.copyright or "© The Copyright Holder")
- return header .. table.concat(self.parts) .. footer
+ return header .. table.concat(self.parts, "\n") .. footer
end
M.__tostring = function(self)
diff --git a/core/console.lua b/core/console.lua
deleted file mode 100644
index 992dceb..0000000
--- a/core/console.lua
+++ /dev/null
@@ -1,19 +0,0 @@
-local M = {}
-
-function M.log(fmt, ...)
- print(string.format("[%12f] ", os.clock()) .. string.format(fmt, ...))
-end
-
-function M.info(fmt, ...)
- print("INFO: " .. string.format(fmt, ...))
-end
-
-function M.warn(fmt, ...)
- print("WARN: " .. string.format(fmt, ...))
-end
-
-function M.error(fmt, ...)
- print("ERROR: " .. string.format(fmt, ...))
-end
-
-return M
diff --git a/core/log.lua b/core/log.lua
new file mode 100644
index 0000000..0d3c011
--- /dev/null
+++ b/core/log.lua
@@ -0,0 +1,7 @@
+local M = {}
+
+function M.printl(fmt, ...)
+ print(string.format(fmt, ...))
+end
+
+return M
diff --git a/core/markdown.lua b/core/markdown.lua
deleted file mode 100644
index 07da252..0000000
--- a/core/markdown.lua
+++ /dev/null
@@ -1,19 +0,0 @@
-local M = {}
-
--- Markdown to HTML conversion function
--- Uses the Go backend markdown parser
-function M.to_html(markdown_text)
- markdown_text = markdown_text or ""
-
- -- Get the fes module
- local fes_mod = package.loaded.fes
- if fes_mod and fes_mod.markdown_to_html then
- return fes_mod.markdown_to_html(markdown_text)
- end
-
- -- Fallback: return error message if Go function not available
- return "Error: markdown_to_html function not available
"
-end
-
-return M
-
diff --git a/core/std.lua b/core/std.lua
index 3b4ad45..c7462d7 100644
--- a/core/std.lua
+++ b/core/std.lua
@@ -17,9 +17,17 @@ function M.site_version()
end
function M.a(link, str)
+ link = link or "https://example.com"
+ str = str or link
return "" .. str .. ""
end
+function M.ha(link, str)
+ link = link or "https://example.com"
+ str = str or link
+ return "" .. str .. ""
+end
+
function M.external(link, str)
return "" .. str .. ""
end
@@ -92,6 +100,16 @@ function M.ol(items)
return html
end
+function M.tl(items)
+ items = items or {}
+ local html = ''
+ for _, item in ipairs(items) do
+ html = html .. "- " .. tostring(item) .. "
"
+ end
+ html = html .. "
"
+ return html
+end
+
function M.blockquote(str)
return "" .. (str or "") .. "
"
end
@@ -191,13 +209,13 @@ end
function M.table(headers, rows)
headers = headers or {}
rows = rows or {}
-
+
local html = ""
for _, header in ipairs(headers) do
html = html .. "| " .. tostring(header) .. " | "
end
html = html .. "
"
-
+
for _, row in ipairs(rows) do
html = html .. ""
for _, cell in ipairs(row) do
@@ -205,7 +223,7 @@ function M.table(headers, rows)
end
html = html .. "
"
end
-
+
html = html .. "
"
return html
end
@@ -215,7 +233,24 @@ function M.copyright()
end
function M.highlight(str)
+ str = str or ""
return '' .. str .. ""
end
+function M.banner(str)
+ str = str or ""
+ return '' .. str .. "
"
+end
+
+function M.center(str)
+ str = str or ""
+ return '' .. str .. "
"
+end
+
+function M.nav(link, str)
+ link = link or "example.com"
+ str = str or link
+ return '' .. str .. ""
+end
+
return M
diff --git a/examples/canonical/Fes.toml b/examples/canonical/Fes.toml
new file mode 100644
index 0000000..24430e8
--- /dev/null
+++ b/examples/canonical/Fes.toml
@@ -0,0 +1,5 @@
+[app]
+
+name = "canonical"
+version = "0.0.1"
+authors = ["vx-clutch"]
diff --git a/examples/canonical/include/header.lua b/examples/canonical/include/header.lua
new file mode 100644
index 0000000..9c87ed0
--- /dev/null
+++ b/examples/canonical/include/header.lua
@@ -0,0 +1,16 @@
+local header = {}
+
+header.render = function(std)
+ return table.concat({
+ std.center(std.h1("Canonical")),
+ std.center(table.concat({
+ std.nav("example"),
+ std.nav("example"),
+ std.nav("example"),
+ std.nav("example"),
+ std.nav("example"),
+ }))
+ })
+end
+
+return header
diff --git a/examples/canonical/www/index.lua b/examples/canonical/www/index.lua
new file mode 100644
index 0000000..3cddd3c
--- /dev/null
+++ b/examples/canonical/www/index.lua
@@ -0,0 +1,17 @@
+local fes = require("fes")
+local std = fes.std
+
+local site = fes.fes()
+
+site.title = "Canonical"
+site.copyright = std.copyright() .. " " .. std.external("https://git.vxserver.dev/fSD", "fSD")
+
+site:banner(fes.app.header.render(std))
+
+site:note(table.concat({
+ std.h1("Canonical"),
+ std.p("This is the example for the canonical 'fes' site, by canonical is meant a format and " .. std.external("https://git.vxserver.dev/fSD/fes/src/branch/master/examples/canonical/www/index.lua", "code") .. " that resembles the typical use case of the Microframework"),
+ std.p("This page also serves as a test for the integrity of a 'fes' build, given that it uses plenty crucial features to show everything from the HTML to CSS as well as the interactivity of certain elements."),
+}))
+
+return site
diff --git a/examples/hello-world/Fes.toml b/examples/hello-world/Fes.toml
new file mode 100644
index 0000000..a2376b5
--- /dev/null
+++ b/examples/hello-world/Fes.toml
@@ -0,0 +1,5 @@
+[app]
+
+name = "hello-world"
+version = "0.0.1"
+authors = ["vx-clutch"]
diff --git a/examples/hello-world/www/index.lua b/examples/hello-world/www/index.lua
new file mode 100644
index 0000000..b8adf7e
--- /dev/null
+++ b/examples/hello-world/www/index.lua
@@ -0,0 +1,9 @@
+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:h1("Hello, World!")
+
+return site
diff --git a/examples/multi-page/Fes.toml b/examples/multi-page/Fes.toml
new file mode 100644
index 0000000..219e8bc
--- /dev/null
+++ b/examples/multi-page/Fes.toml
@@ -0,0 +1,5 @@
+[app]
+
+name = "multi-page"
+version = "0.0.1"
+authors = ["vx-clutch"]
diff --git a/examples/multi-page/www/index.lua b/examples/multi-page/www/index.lua
new file mode 100644
index 0000000..41ee99e
--- /dev/null
+++ b/examples/multi-page/www/index.lua
@@ -0,0 +1,15 @@
+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:h1("Home")
+site:note(
+ fes.std.ul({
+ fes.std.a("page1"),
+ fes.std.a("page2"),
+ })
+)
+
+return site
diff --git a/examples/multi-page/www/page1.lua b/examples/multi-page/www/page1.lua
new file mode 100644
index 0000000..7072077
--- /dev/null
+++ b/examples/multi-page/www/page1.lua
@@ -0,0 +1,15 @@
+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:h1("Page 1")
+site:note(
+ fes.std.ul({
+ fes.std.a("/", "home"),
+ fes.std.a("page2"),
+ })
+)
+
+return site
diff --git a/examples/multi-page/www/page2.lua b/examples/multi-page/www/page2.lua
new file mode 100644
index 0000000..1a97048
--- /dev/null
+++ b/examples/multi-page/www/page2.lua
@@ -0,0 +1,15 @@
+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:h1("Page 2")
+site:note(
+ fes.std.ul({
+ fes.std.a("/", "home"),
+ fes.std.a("page1"),
+ })
+)
+
+return site
diff --git a/main.go b/main.go
index 11e6dda..55db57a 100644
--- a/main.go
+++ b/main.go
@@ -1,285 +1,29 @@
package main
import (
+ "embed"
_ "embed"
"flag"
"fmt"
- "net/http"
"os"
- "os/exec"
- "os/user"
- "path/filepath"
- "regexp"
- "strings"
- "time"
- "github.com/gomarkdown/markdown"
- "github.com/gomarkdown/markdown/html"
- "github.com/gomarkdown/markdown/parser"
- "github.com/pelletier/go-toml/v2"
- lua "github.com/yuin/gopher-lua"
+ "fes/src/config"
+ "fes/src/new"
+ "fes/src/server"
)
-//go:embed core/builtin.lua
-var builtinLua string
+//go:embed core/*
+var core embed.FS
-//go:embed core/markdown.lua
-var markdownLua string
-
-//go:embed core/std.lua
-var stdLua string
-
-//go:embed core/console.lua
-var consoleLua string
-
-const version = "1.0.0"
-
-type MyConfig struct {
- Site struct {
- Name string `toml:"name"`
- Version string `toml:"version"`
- Authors []string `toml:"authors"`
- } `toml:"site"`
- Fes struct {
- Version string `toml:"version"`
- CUSTOM_CSS string `toml:"CUSTOM_CSS,omitempty"`
- } `toml:"fes"`
-}
-
-var port = flag.Int("p", 3000, "set the server port")
-
-func markdownToHTML(mdText string) string {
- extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
- p := parser.NewWithExtensions(extensions)
- doc := p.Parse([]byte(mdText))
-
- htmlFlags := html.CommonFlags | html.HrefTargetBlank
- opts := html.RendererOptions{Flags: htmlFlags}
- renderer := html.NewRenderer(opts)
-
- return string(markdown.Render(doc, renderer))
-}
-
-func loadLua(luaDir string, entry string, cfg *MyConfig) (string, error) {
- L := lua.NewState()
- defer L.Close()
-
- L.PreloadModule("core.std", func(L *lua.LState) int {
- if err := L.DoString(stdLua); err != nil {
- panic(err)
- }
- L.Push(L.Get(-1))
- return 1
- })
-
- L.PreloadModule("fes", func(L *lua.LState) int {
- mod := L.NewTable()
-
- coreModules := map[string]string{
- "builtin": builtinLua,
- "markdown": markdownLua,
- "std": stdLua,
- "console": consoleLua,
- }
-
- for modName, luaCode := range coreModules {
- if err := L.DoString(luaCode); err != nil {
- fmt.Println("error loading", modName, ":", err)
- continue
- }
- val := L.Get(-1)
- L.Pop(1)
- tbl, ok := val.(*lua.LTable)
- if !ok {
- t := L.NewTable()
- t.RawSetString("value", val)
- tbl = t
- }
- if modName == "builtin" {
- tbl.ForEach(func(key, value lua.LValue) {
- mod.RawSet(key, value)
- })
- } else {
- mod.RawSetString(modName, tbl)
- }
- }
-
- if cfg != nil {
- configTable := L.NewTable()
- siteTable := L.NewTable()
- siteTable.RawSetString("version", lua.LString(cfg.Site.Version))
- siteTable.RawSetString("name", lua.LString(cfg.Site.Name))
- authorsTable := L.NewTable()
- for i, author := range cfg.Site.Authors {
- authorsTable.RawSetInt(i+1, lua.LString(author))
- }
- siteTable.RawSetString("authors", authorsTable)
- configTable.RawSetString("site", siteTable)
- fesTable := L.NewTable()
- fesTable.RawSetString("version", lua.LString(cfg.Fes.Version))
- configTable.RawSetString("fes", fesTable)
- mod.RawSetString("config", configTable)
- }
-
- mod.RawSetString("markdown_to_html", L.NewFunction(func(L *lua.LState) int {
- mdText := L.ToString(1)
- html := markdownToHTML(mdText)
- L.Push(lua.LString(html))
- return 1
- }))
- L.Push(mod)
- return 1
- })
-
- if err := L.DoFile(entry); err != nil {
- return "", err
- }
-
- top := L.GetTop()
- if top == 0 {
- fmt.Println("warning: no return value from Lua file")
- return "", nil
- }
-
- resultVal := L.Get(-1)
- L.SetGlobal("__fes_result", resultVal)
- if err := L.DoString("return tostring(__fes_result)"); err != nil {
- L.GetGlobal("__fes_result")
- if s := L.ToString(-1); s != "" {
- return s, nil
- }
- return "", nil
- }
- if s := L.ToString(-1); s != "" {
- return s, nil
- }
- return "", nil
-}
-
-func getName() string {
- out, err := exec.Command("git", "config", "user.name").Output()
- if err == nil {
- s := strings.TrimSpace(string(out))
- if s != "" {
- return s
- }
- }
- u, err := user.Current()
- if err == nil && u.Username != "" {
- return u.Username
- }
- return ""
-}
-
-func newProject(dir string) error {
- if err := os.MkdirAll(filepath.Join(dir, "www"), 0755); err != nil {
- return err
- }
- indexLua := filepath.Join(dir, "www", "index.lua")
- if _, err := os.Stat(indexLua); os.IsNotExist(err) {
- content := `local fes = require("fes")
-local site = fes.site_builder()
-
-site:h1("Hello, World!")
-
-return site
-`
- if err := os.WriteFile(indexLua, []byte(content), 0644); err != nil {
- return err
- }
- }
- indexFes := filepath.Join(dir, "Fes.toml")
- if _, err := os.Stat(indexFes); os.IsNotExist(err) {
- content := fmt.Sprintf(`[site]
-
-name = "%s"
-version = "0.0.1"
-authors = ["%s"]
-
-[fes]
-version = "%s"
-CUSTOM_CSS =
-`, dir, getName(), version)
- if err := os.WriteFile(indexFes, []byte(content), 0644); err != nil {
- return err
- }
- }
- fmt.Println("Created new project at", dir)
- return nil
-}
-
-func fixMalformedToml(content string) string {
- re := regexp.MustCompile(`(?m)^(\s*\w+\s*=\s*)$`)
- return re.ReplaceAllStringFunc(content, func(match string) string {
- parts := strings.Split(strings.TrimSpace(match), "=")
- if len(parts) == 2 && strings.TrimSpace(parts[1]) == "" {
- key := strings.TrimSpace(parts[0])
- return key + " = \"\""
- }
- return match
- })
-}
-
-func startServer(dir string) error {
- doc, err := os.ReadFile(filepath.Join(dir, "Fes.toml"))
- if err != nil {
- return err
- }
-
- docStr := fixMalformedToml(string(doc))
-
- var cfg MyConfig
- err = toml.Unmarshal([]byte(docStr), &cfg)
- if err != nil {
- return fmt.Errorf("failed to parse Fes.toml: %w", err)
- }
-
- wwwDir := filepath.Join(dir, "www")
-
- entries, err := os.ReadDir(wwwDir)
- if err != nil {
- return fmt.Errorf("failed to read www directory: %w", err)
- }
-
- 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) {
- fmt.Printf("[%s] LOAD /%s\n", time.Now().Format(time.RFC1123), lp)
- 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)
+func init() {
+ config.Port = flag.Int("p", 3000, "set the server port")
+ config.Core = core
}
func main() {
flag.Parse()
if len(os.Args) < 3 {
fmt.Println("Usage: fes ")
- fmt.Println("Commands: new, run")
os.Exit(1)
}
@@ -288,11 +32,11 @@ func main() {
switch cmd {
case "new":
- if err := newProject(dir); err != nil {
+ if err := new.Project(dir); err != nil {
panic(err)
}
case "run":
- if err := startServer(dir); err != nil {
+ if err := server.Start(dir); err != nil {
panic(err)
}
default:
diff --git a/src/config/config.go b/src/config/config.go
new file mode 100644
index 0000000..ba878fe
--- /dev/null
+++ b/src/config/config.go
@@ -0,0 +1,14 @@
+package config
+
+import "embed"
+
+var Core embed.FS
+var Port *int
+
+type MyConfig struct {
+ App struct {
+ Name string `toml:"name"`
+ Version string `toml:"version"`
+ Authors []string `toml:"authors"`
+ } `toml:"app"`
+}
diff --git a/src/new/new.go b/src/new/new.go
new file mode 100644
index 0000000..0c534f8
--- /dev/null
+++ b/src/new/new.go
@@ -0,0 +1,57 @@
+package new
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "strings"
+)
+
+func getName() string {
+ out, err := exec.Command("git", "config", "user.name").Output()
+ if err == nil {
+ s := strings.TrimSpace(string(out))
+ if s != "" {
+ return s
+ }
+ }
+ u, err := user.Current()
+ if err == nil && u.Username != "" {
+ return u.Username
+ }
+ return ""
+}
+
+func Project(dir string) error {
+ if err := os.MkdirAll(filepath.Join(dir, "www"), 0755); err != nil {
+ return err
+ }
+ indexLua := filepath.Join(dir, "www", "index.lua")
+ if _, err := os.Stat(indexLua); os.IsNotExist(err) {
+ content := `local fes = require("fes")
+local site = fes.fes()
+
+site:h1("Hello, World!")
+
+return site
+`
+ if err := os.WriteFile(indexLua, []byte(content), 0644); err != nil {
+ return err
+ }
+ }
+ indexFes := filepath.Join(dir, "Fes.toml")
+ if _, err := os.Stat(indexFes); os.IsNotExist(err) {
+ content := fmt.Sprintf(`[app]
+
+name = "%s"
+version = "0.0.1"
+authors = ["%s"]`, dir, getName())
+ if err := os.WriteFile(indexFes, []byte(content), 0644); err != nil {
+ return err
+ }
+ }
+ fmt.Println("Created new project at", dir)
+ return nil
+}
diff --git a/src/server/server.go b/src/server/server.go
new file mode 100644
index 0000000..522aa05
--- /dev/null
+++ b/src/server/server.go
@@ -0,0 +1,247 @@
+package server
+
+import (
+ "fmt"
+ "io/fs"
+ "net/http"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/gomarkdown/markdown"
+ "github.com/gomarkdown/markdown/html"
+ "github.com/gomarkdown/markdown/parser"
+ "github.com/pelletier/go-toml/v2"
+ lua "github.com/yuin/gopher-lua"
+
+ "fes/src/config"
+)
+
+func fixMalformedToml(content string) string {
+ re := regexp.MustCompile(`(?m)^(\s*\w+\s*=\s*)$`)
+ return re.ReplaceAllStringFunc(content, func(match string) string {
+ parts := strings.Split(strings.TrimSpace(match), "=")
+ if len(parts) == 2 && strings.TrimSpace(parts[1]) == "" {
+ key := strings.TrimSpace(parts[0])
+ return key + " = \"\""
+ }
+ return match
+ })
+}
+
+func markdownToHTML(mdText string) string {
+ extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
+ p := parser.NewWithExtensions(extensions)
+ doc := p.Parse([]byte(mdText))
+ htmlFlags := html.CommonFlags | html.HrefTargetBlank
+ opts := html.RendererOptions{Flags: htmlFlags}
+ renderer := html.NewRenderer(opts)
+ return string(markdown.Render(doc, renderer))
+}
+
+func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
+ app := L.NewTable()
+ ents, err := os.ReadDir(includeDir)
+ if err != nil {
+ return app
+ }
+ for _, e := range ents {
+ if e.IsDir() {
+ continue
+ }
+ name := e.Name()
+ if !strings.HasSuffix(name, ".lua") {
+ continue
+ }
+ base := strings.TrimSuffix(name, ".lua")
+ path := filepath.Join(includeDir, name)
+ if err := L.DoFile(path); err != nil {
+ fmt.Printf("Failed to load %s: %v\n", path, err)
+ continue
+ }
+ val := L.Get(-1)
+ L.Pop(1)
+ tbl, ok := val.(*lua.LTable)
+ if !ok {
+ tbl = L.NewTable()
+ }
+ app.RawSetString(base, tbl)
+ }
+ return app
+}
+
+func loadLua(luaDir string, entry string, cfg *config.MyConfig) (string, error) {
+ L := lua.NewState()
+ defer L.Close()
+
+ rdents, err := fs.ReadDir(config.Core, "core")
+ if err == nil {
+ for _, de := range rdents {
+ if de.IsDir() {
+ continue
+ }
+ name := de.Name()
+ if !strings.HasSuffix(name, ".lua") {
+ continue
+ }
+ path := filepath.Join("core", name)
+ data, err := config.Core.ReadFile(path)
+ if err != nil {
+ continue
+ }
+ if err := L.DoString(string(data)); err != nil {
+ continue
+ }
+ }
+ }
+
+ L.PreloadModule("core.std", func(L *lua.LState) int {
+ data, err := config.Core.ReadFile("core/std.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{}
+ if ents, err := fs.ReadDir(config.Core, "core"); err == nil {
+ for _, e := range ents {
+ if e.IsDir() {
+ continue
+ }
+ n := e.Name()
+ if strings.HasSuffix(n, ".lua") {
+ coreModules = append(coreModules, strings.TrimSuffix(n, ".lua"))
+ }
+ }
+ }
+ for _, modName := range coreModules {
+ path := filepath.Join("core", modName+".lua")
+ data, err := config.Core.ReadFile(path)
+ if err != nil {
+ continue
+ }
+ if err := L.DoString(string(data)); err != nil {
+ continue
+ }
+ val := L.Get(-1)
+ L.Pop(1)
+ tbl, ok := val.(*lua.LTable)
+ if !ok || tbl == nil {
+ tbl = L.NewTable()
+ }
+ if modName == "builtin" {
+ tbl.ForEach(func(k, v lua.LValue) {
+ mod.RawSet(k, v)
+ })
+ } else {
+ mod.RawSetString(modName, tbl)
+ }
+ }
+
+ includeDir := filepath.Join(luaDir, "include")
+ appTbl := loadIncludeModules(L, includeDir)
+ mod.RawSetString("app", appTbl)
+
+ if cfg != nil {
+ siteTable := L.NewTable()
+ siteTable.RawSetString("version", lua.LString(cfg.App.Version))
+ siteTable.RawSetString("name", lua.LString(cfg.App.Name))
+ authorsTable := L.NewTable()
+ for i, author := range cfg.App.Authors {
+ authorsTable.RawSetInt(i+1, lua.LString(author))
+ }
+ siteTable.RawSetString("authors", authorsTable)
+ mod.RawSetString("site", siteTable)
+ }
+
+ mod.RawSetString("markdown_to_html", L.NewFunction(func(L *lua.LState) int {
+ mdText := L.ToString(1)
+ html := markdownToHTML(mdText)
+ L.Push(lua.LString(html))
+ return 1
+ }))
+
+ L.Push(mod)
+ return 1
+ })
+
+ if err := L.DoFile(entry); err != nil {
+ return "", err
+ }
+
+ top := L.GetTop()
+ if top == 0 {
+ return "", nil
+ }
+ resultVal := L.Get(-1)
+ L.SetGlobal("__fes_result", resultVal)
+ if err := L.DoString("return tostring(__fes_result)"); err != nil {
+ L.GetGlobal("__fes_result")
+ if s := L.ToString(-1); s != "" {
+ return s, nil
+ }
+ return "", nil
+ }
+ if s := L.ToString(-1); s != "" {
+ return s, nil
+ }
+ return "", nil
+}
+
+func Start(dir string) error {
+ doc, err := os.ReadFile(filepath.Join(dir, "Fes.toml"))
+ if err != nil {
+ return err
+ }
+ docStr := fixMalformedToml(string(doc))
+ var cfg config.MyConfig
+ err = toml.Unmarshal([]byte(docStr), &cfg)
+ if err != nil {
+ return fmt.Errorf("failed to parse Fes.toml: %w", err)
+ }
+
+ wwwDir := filepath.Join(dir, "www")
+ entries, err := os.ReadDir(wwwDir)
+ if err != nil {
+ return fmt.Errorf("failed to read www directory: %w", err)
+ }
+
+ 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) {
+ fmt.Printf("-> %s\n", lp)
+ 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", *config.Port)
+ return http.ListenAndServe(fmt.Sprintf(":%d", *config.Port), nil)
+}
diff --git a/test/Fes.toml b/test/Fes.toml
deleted file mode 100644
index 3698755..0000000
--- a/test/Fes.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[site]
-
-name = "test"
-version = "0.0.1"
-authors = ["vx-clutch"]
-
-[fes]
-version = "1.0.0"
-CUSTOM_CSS =
diff --git a/test/www/index.lua b/test/www/index.lua
deleted file mode 100644
index 4b8cfb1..0000000
--- a/test/www/index.lua
+++ /dev/null
@@ -1,11 +0,0 @@
-local fes = require("fes")
-local site = fes()
-
-site:h1("")
-site:h2("")
-site:h3("")
-site:h4("")
-site:h5("")
-site:h6("")
-
-return site