commit 1de4ae1a244f34ccd781113f5893378f64c84993
parent f5d928ebb90e9225240521d79c06caa55e9dbbb8
Author: vx-clutch <[email protected]>
Date: Sun, 7 Dec 2025 09:24:48 -0500
alpha p7
Diffstat:
11 files changed, 213 insertions(+), 189 deletions(-)
diff --git a/URGENT b/URGENT
@@ -0,0 +1 @@
+IT DOSN'T LOAD FES
diff --git a/core/builtin.lua b/core/builtin.lua
@@ -13,6 +13,10 @@ function M.fes(header, footer)
end
end
+ if site_config.favicon then
+ site_config.favicon = '<link rel="icon" type="image/x-icon" href="' .. site_config.favicon .. '">'
+ end
+
local self = {
version = site_config.version,
title = site_config.title,
diff --git a/examples/archive/Fes.toml b/examples/archive/Fes.toml
@@ -0,0 +1,5 @@
+[app]
+
+name = "archive"
+version = "0.0.1"
+authors = ["vx-clutch"]
+\ No newline at end of file
diff --git a/examples/archive/archive/2025/seal.png b/examples/archive/archive/2025/seal.png
Binary files differ.
diff --git a/examples/archive/www/index.lua b/examples/archive/www/index.lua
@@ -0,0 +1,8 @@
+local fes = require("fes")
+local site = fes.fes()
+
+site.title = "archive"
+
+site:h1("Hello, World!")
+
+return site
diff --git a/examples/error/Fes.toml b/examples/error/Fes.toml
@@ -1,5 +1 @@
-[app]
-
-name = "error"
-version = "0.0.1"
-authors = ["vx-clutch"]
-\ No newline at end of file
+dhasjkdhaskjdhaskhdajkshjk
diff --git a/examples/static/Fes.toml b/examples/static/Fes.toml
@@ -0,0 +1,5 @@
+[app]
+
+name = "static"
+version = "0.0.1"
+authors = ["vx-clutch"]
+\ No newline at end of file
diff --git a/examples/static/static/foo b/examples/static/static/foo
@@ -0,0 +1 @@
+this is some neat data
diff --git a/examples/static/www/index.lua b/examples/static/www/index.lua
@@ -0,0 +1,8 @@
+local fes = require("fes")
+local site = fes.fes()
+
+site.title = "static"
+
+site:h1("Hello, World!")
+
+return site
diff --git a/src/new/new.go b/src/new/new.go
@@ -1,5 +1,4 @@
package new
-
import (
"fmt"
"os"
@@ -10,8 +9,7 @@ import (
)
func getName() string {
- out, err := exec.Command("git", "config", "user.name").Output()
- if err == nil {
+ out, err := exec.Command("git", "config", "user.name").Output(); if err == nil {
s := strings.TrimSpace(string(out))
if s != "" {
return s
@@ -24,36 +22,71 @@ func getName() string {
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 := 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
+// }
+// }
+// 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
+// }
+
+func write(path string, format string, args ...interface{}) error {
+ dir := filepath.Dir(path)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ panic(err)
+ }
+ f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ _, err = fmt.Fprintf(f, format, args...)
+ return err
+}
+
func Project(dir string) error {
- if err := os.MkdirAll(filepath.Join(dir, "www"), 0755); err != nil {
+ if err := os.Mkdir(dir, 0755); err != nil {
return err
}
- indexLua := filepath.Join(dir, "www", "index.lua")
- if _, err := os.Stat(indexLua); os.IsNotExist(err) {
- content := fmt.Sprintf(`local fes = require("fes")
+ if err := os.Chdir(dir); err != nil {
+ return err
+ }
+ write("www/index.lua", `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
- }
- }
- indexFes := filepath.Join(dir, "Fes.toml")
- if _, err := os.Stat(indexFes); os.IsNotExist(err) {
- content := fmt.Sprintf(`[app]
+return site`)
+ write("Fes.toml", `[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
@@ -2,78 +2,70 @@ package server
import (
"fmt"
- "io/fs"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
+ "github.com/fatih/color"
"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"
- "github.com/fatih/color"
"fes/src/config"
)
type reqData struct {
- path string
+ path string
params map[string]string
}
-func handleDir(entries []os.DirEntry, dir string, routes map[string]string, base string) error {
+func handleDir(entries []os.DirEntry, dir string, routes map[string]string, base string, isStatic bool) error {
for _, entry := range entries {
+ path := filepath.Join(dir, entry.Name())
if entry.IsDir() {
- sub := filepath.Join(dir, entry.Name())
- subs, err := os.ReadDir(sub)
+ nextBase := joinBase(base, entry.Name())
+ subEntries, err := os.ReadDir(path)
if err != nil {
- return fmt.Errorf("failed to read %s: %w", sub, err)
- }
- var next string
- if base == "" {
- next = "/" + entry.Name()
- } else {
- next = base + "/" + entry.Name()
+ return fmt.Errorf("failed to read directory %s: %w", path, err)
}
- if err := handleDir(subs, sub, routes, next); err != nil {
+ if err := handleDir(subEntries, path, routes, nextBase, isStatic); err != nil {
return err
}
continue
}
- if strings.HasSuffix(entry.Name(), ".lua") {
+ route := joinBase(base, entry.Name())
+ if !isStatic && strings.HasSuffix(entry.Name(), ".lua") {
name := strings.TrimSuffix(entry.Name(), ".lua")
- path := filepath.Join(dir, entry.Name())
if name == "index" {
- if base == "" {
- routes["/"] = path
- routes["/index"] = path
- } else {
- routes[base] = path
- routes[base+"/index"] = path
- }
- } else {
- if base == "" {
- routes["/"+name] = path
- } else {
- routes[base+"/"+name] = path
- }
- }
- } else {
- name := entry.Name()
- path := filepath.Join(dir, entry.Name())
- if base == "" {
- routes["/"+name] = path
- } else {
- routes[base+"/"+name] = path
+ routes[basePath(base)] = path
+ routes[route] = path
+ continue
}
+ route = joinBase(base, name)
}
+ routes[route] = path
}
return nil
}
+func joinBase(base, name string) string {
+ if base == "" {
+ return "/" + name
+ }
+ return base + "/" + name
+}
+
+func basePath(base string) string {
+ if base == "" {
+ return "/"
+ }
+ return base
+}
+
func fixMalformedToml(content string) string {
re := regexp.MustCompile(`(?m)^(\s*\w+\s*=\s*)$`)
return re.ReplaceAllStringFunc(content, func(match string) string {
@@ -103,17 +95,15 @@ func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
return app
}
for _, e := range ents {
- if e.IsDir() {
- continue
- }
- name := e.Name()
- if !strings.HasSuffix(name, ".lua") {
+ if e.IsDir() || !strings.HasSuffix(e.Name(), ".lua") {
continue
}
- base := strings.TrimSuffix(name, ".lua")
- path := filepath.Join(includeDir, name)
+ base := strings.TrimSuffix(e.Name(), ".lua")
+ path := filepath.Join(includeDir, e.Name())
if err := L.DoFile(path); err != nil {
- fmt.Printf("Failed to load %s: %v\n", path, err)
+ tbl := L.NewTable()
+ tbl.RawSetString("error", lua.LString(err.Error()))
+ app.RawSetString(base, tbl)
continue
}
val := L.Get(-1)
@@ -127,35 +117,33 @@ func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
return app
}
-func loadLua(luaDir string, entry string, cfg *config.MyConfig, requestData reqData) (string, error) {
+func loadLua(luaDir, entry string, cfg *config.MyConfig, requestData reqData) (string, error) {
L := lua.NewState()
defer L.Close()
- coreFiles, err := fs.ReadDir(config.Core, "core")
- if err == nil {
- for _, de := range coreFiles {
- if de.IsDir() || !strings.HasSuffix(de.Name(), ".lua") {
- continue
- }
- path := filepath.Join("core", de.Name())
- fileData, err := config.Core.ReadFile(path)
- if err != nil {
- continue
- }
- L.DoString(string(fileData))
- }
- }
-
preloadLuaModule := func(name, path string) {
L.PreloadModule(name, func(L *lua.LState) int {
- fileData, err := config.Core.ReadFile(path)
+ data, err := config.Core.ReadFile(path)
if err != nil {
- panic(err)
+ tbl := L.NewTable()
+ tbl.RawSetString("error", lua.LString(err.Error()))
+ L.Push(tbl)
+ return 1
+ }
+ if _, err := L.LoadString(string(data)); err != nil {
+ tbl := L.NewTable()
+ tbl.RawSetString("error", lua.LString(err.Error()))
+ L.Push(tbl)
+ return 1
}
- if err := L.DoString(string(fileData)); err != nil {
- panic(err)
+ if err := L.CallByParam(lua.P{Fn: L.Get(-1), NRet: 1, Protect: true}); err != nil {
+ tbl := L.NewTable()
+ tbl.RawSetString("error", lua.LString(err.Error()))
+ L.Push(tbl)
+ return 1
}
- L.Push(L.Get(-1))
+ ret := L.Get(-1)
+ L.Push(ret)
return 1
})
}
@@ -166,42 +154,8 @@ func loadLua(luaDir string, entry string, cfg *config.MyConfig, requestData reqD
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() || !strings.HasSuffix(e.Name(), ".lua") {
- continue
- }
- coreModules = append(coreModules, strings.TrimSuffix(e.Name(), ".lua"))
- }
- }
-
- for _, modName := range coreModules {
- path := filepath.Join("core", modName+".lua")
- fileData, err := config.Core.ReadFile(path)
- if err != nil {
- continue
- }
- if err := L.DoString(string(fileData)); 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")
mod.RawSetString("app", loadIncludeModules(L, includeDir))
-
if cfg != nil {
site := L.NewTable()
site.RawSetString("version", lua.LString(cfg.App.Version))
@@ -213,7 +167,6 @@ func loadLua(luaDir string, entry string, cfg *config.MyConfig, requestData reqD
site.RawSetString("authors", authors)
mod.RawSetString("site", site)
}
-
bus := L.NewTable()
bus.RawSetString("url", lua.LString(requestData.path))
params := L.NewTable()
@@ -222,12 +175,10 @@ func loadLua(luaDir string, entry string, cfg *config.MyConfig, requestData reqD
}
bus.RawSetString("params", params)
mod.RawSetString("bus", bus)
-
mod.RawSetString("markdown_to_html", L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(markdownToHTML(L.ToString(1))))
return 1
}))
-
L.Push(mod)
return 1
})
@@ -242,35 +193,31 @@ func loadLua(luaDir string, entry string, cfg *config.MyConfig, requestData reqD
L.SetGlobal("__fes_result", L.Get(-1))
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
+ return "", err
}
- if s := L.ToString(-1); s != "" {
- return s, nil
- }
- return "", nil
+ return L.ToString(-1), nil
}
func Start(dir string) error {
- os.Chdir(dir)
+ if err := os.Chdir(dir); err != nil {
+ return fmt.Errorf("failed to change directory to %s: %w", dir, err)
+ }
dir = "."
tomlDocument, err := os.ReadFile("Fes.toml")
if err != nil {
- return err
+ return fmt.Errorf("failed to read Fes.toml: %w", err)
}
docStr := fixMalformedToml(string(tomlDocument))
var cfg config.MyConfig
- err = toml.Unmarshal([]byte(docStr), &cfg)
- if err != nil {
- return fmt.Errorf("failed to parse Fes.toml: %w", err)
+ if err := toml.Unmarshal([]byte(docStr), &cfg); err != nil {
+ fmt.Printf("Warning: failed to parse Fes.toml: %v\n", err)
+ cfg.App.Authors = []string{"unknown"}
+ cfg.App.Name = "unknown"
+ cfg.App.Version = "unknown"
}
-
notFoundData := `
<html>
<head><title>404 Not Found</title></head>
@@ -281,64 +228,79 @@ func Start(dir string) error {
</html>
`
if _, err := os.Stat(filepath.Join("www", "404.lua")); err == nil {
- notFoundData, err = loadLua(dir, "www/404.lua", &cfg, reqData{})
- if err != nil {
- panic(err)
+ if nf, err := loadLua(dir, "www/404.lua", &cfg, reqData{}); err == nil {
+ notFoundData = nf
}
} else if _, err := os.Stat("www/404.html"); err == nil {
- buf, err := os.ReadFile("www/404.html")
- if err != nil {
- panic(err)
+ if buf, err := os.ReadFile("www/404.html"); err == nil {
+ notFoundData = string(buf)
}
- notFoundData = string(buf)
}
routes := make(map[string]string)
+ if entries, err := os.ReadDir("www"); err == nil {
+ if err := handleDir(entries, "www", routes, "", false); err != nil {
+ fmt.Printf("Warning: failed to handle www directory: %v\n", err)
+ }
+ }
- entries, err := os.ReadDir("www")
- if err != nil {
- return fmt.Errorf("failed to read www directory: %w", err)
+ if entries, err := os.ReadDir("static"); err == nil {
+ if err := handleDir(entries, "static", routes, "/static", true); err != nil {
+ fmt.Printf("Warning: failed to handle static directory: %v\n", err)
+ }
}
- handleDir(entries, "www", routes, "")
- entries, err = os.ReadDir("static")
- if err == nil {
- handleDir(entries, "static", routes, "")
+ if entries, err := os.ReadDir("archive"); err == nil {
+ if err := handleDir(entries, "archive", routes, "/archive", true); err != nil {
+ fmt.Printf("Warning: failed to handle archive directory: %v\n", err)
+ }
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- path := r.URL.Path
- lp, ok := routes[path]
- if !ok {
- w.WriteHeader(http.StatusNotFound)
- w.Write([]byte(notFoundData))
- fmt.Printf("> %s ", path)
- color.Yellow("not found")
- return
- }
+ path := r.URL.Path
+ lp, ok := routes[path]
+ if !ok {
+ w.WriteHeader(http.StatusNotFound)
+ w.Write([]byte(notFoundData))
+ fmt.Printf("> %s ", path)
+ color.Yellow("not found")
+ return
+ }
- params := make(map[string]string)
- for key, val := range r.URL.Query() {
- if len(val) > 0 {
- params[key] = val[0]
+ params := make(map[string]string)
+ for k, val := range r.URL.Query() {
+ if len(val) > 0 {
+ params[k] = val[0]
+ }
}
- }
- req := reqData{
- path: r.URL.Path,
- params: params,
- }
+ req := reqData{
+ path: path,
+ params: params,
+ }
- fmt.Printf("> %s ", filepath.Base(lp))
- data, err := loadLua(dir, lp, &cfg, req)
- if err != nil {
- http.Error(w, fmt.Sprintf("Error loading page: %v", err), http.StatusInternalServerError)
- color.Red("bad")
- return
- }
- color.Green("ok")
- w.Write([]byte(data))
-})
+ fmt.Printf("> %s ", filepath.Base(lp))
+
+ var data []byte
+ var err error
+
+ if strings.HasSuffix(lp, ".lua") {
+ var b string
+ b, err = loadLua(dir, lp, &cfg, req)
+ data = []byte(b)
+ } else {
+ data, err = os.ReadFile(lp)
+ }
+
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error loading page: %v", err), http.StatusInternalServerError)
+ color.Red("bad")
+ return
+ }
+
+ color.Green("ok")
+ w.Write(data)
+ })
fmt.Printf("Server is running on http://localhost:%d\n", *config.Port)
return http.ListenAndServe(fmt.Sprintf(":%d", *config.Port), nil)