From de69397aa561047d96bd946e122764043d4eaa9b Mon Sep 17 00:00:00 2001 From: vx-clutch Date: Thu, 15 Jan 2026 12:23:13 -0500 Subject: [PATCH] start rewrite --- main.go | 12 +- modules/server/render.go | 6 + modules/server/server.go | 478 +-------------------------------------- modules/server/util.go | 28 --- 4 files changed, 12 insertions(+), 512 deletions(-) create mode 100644 modules/server/render.go diff --git a/main.go b/main.go index 30941bf..ce47324 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "embed" - "errors" "flag" "fmt" "os" @@ -103,16 +102,7 @@ func main() { runtime.ReadMemStats(&m) ui.Log("FRE memory usage when created %v Mb", m.TotalAlloc/1024/1024) - if err := server.Start(dir); err != nil { - if errors.Is(err, os.ErrNotExist) { - fmt.Fprintf(os.Stderr, "%s does not exist\n", dir) - fmt.Fprintf(os.Stderr, "Try: fes new %s\n", dir) - os.Exit(1) - } else { - fmt.Fprintln(os.Stderr, "Error:", err) - os.Exit(1) - } - } + server.Start(dir) default: fmt.Fprintf(os.Stderr, "Unknown command: %s\n", cmd) flag.Usage() diff --git a/modules/server/render.go b/modules/server/render.go new file mode 100644 index 0000000..1cf66d7 --- /dev/null +++ b/modules/server/render.go @@ -0,0 +1,6 @@ +package server + +/* returns a string of rendered html */ +func render(luapath string) (string, error) { + return "", nil +} diff --git a/modules/server/server.go b/modules/server/server.go index b253de5..3cf31e4 100644 --- a/modules/server/server.go +++ b/modules/server/server.go @@ -1,486 +1,18 @@ package server import ( - "errors" "fes/modules/config" - "fes/modules/ui" "fmt" - "html/template" - "io/fs" + "log" "net/http" - "os" - "path" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/pelletier/go-toml/v2" - lua "github.com/yuin/gopher-lua" ) -/* this is the request data we pass over the bus to the application, via the fes.bus interface */ -type reqData struct { - path string - params map[string]string -} - -/* performs relavent handling based on the directory passaed - * - * Special directories - * - www/ <= contains lua routes. - * - static/ <= static content accessable at /static/path or /static/dir/path. - * - include/ <= globally accessable lua functions, cannot directly access "fes" right now. - * - archive/ <= contains user facing files such as archives or dists. - * - */ -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() { - nextBase := joinBase(base, entry.Name()) - subEntries, err := os.ReadDir(path) - if err != nil { - return fmt.Errorf("failed to read directory %s: %w", path, err) - } - if err := handleDir(subEntries, path, routes, nextBase, isStatic); err != nil { - return err - } - continue - } - route := joinBase(base, entry.Name()) - if !isStatic && strings.HasSuffix(entry.Name(), ".lua") { - name := strings.TrimSuffix(entry.Name(), ".lua") - if name == "index" { - routes[basePath(base)] = path - routes[route] = path - continue - } - route = joinBase(base, name) - } else if !isStatic && strings.HasSuffix(entry.Name(), ".md") { - name := strings.TrimSuffix(entry.Name(), ".md") - if name == "index" { - routes[basePath(base)] = path - routes[route] = path - continue - } - route = joinBase(base, name) - } - routes[route] = path - } - return nil -} - -// TODO(vx-clutch): this should not be a function -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() || !strings.HasSuffix(e.Name(), ".lua") { - continue - } - base := strings.TrimSuffix(e.Name(), ".lua") - path := filepath.Join(includeDir, e.Name()) - if _, err := os.Stat(path); err != nil { - tbl := L.NewTable() - tbl.RawSetString("error", lua.LString(fmt.Sprintf("file not found: %s", path))) - app.RawSetString(base, tbl) - continue - } - if err := L.DoFile(path); err != nil { - tbl := L.NewTable() - tbl.RawSetString("error", lua.LString(err.Error())) - app.RawSetString(base, tbl) - continue - } - val := L.Get(-1) - L.Pop(1) - tbl, ok := val.(*lua.LTable) - if !ok || tbl == nil { - tbl = L.NewTable() - } - app.RawSetString(base, tbl) - } - return app -} - -/* renders the given lua route */ -func renderRoute(entry string, cfg *config.AppConfig, requestData reqData) ([]byte, error) { - L := lua.NewState() - defer L.Close() - - libFiles, err := fs.ReadDir(config.Lib, "lib") - if err == nil { - for _, de := range libFiles { - if de.IsDir() || !strings.HasSuffix(de.Name(), ".lua") { - continue - } - path := filepath.Join("lib", de.Name()) - fileData, err := config.Lib.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.Lib.ReadFile(path) - if err != nil { - panic(err) - } - if err := L.DoString(string(fileData)); err != nil { - panic(err) - } - L.Push(L.Get(-1)) - return 1 - }) - } - - preloadLuaModule("lib.std", "lib/std.lua") - preloadLuaModule("lib.symbol", "lib/symbol.lua") - preloadLuaModule("lib.util", "lib/util.lua") - - L.PreloadModule("fes", func(L *lua.LState) int { - mod := L.NewTable() - libModules := []string{} - if ents, err := fs.ReadDir(config.Lib, "lib"); err == nil { - for _, e := range ents { - if e.IsDir() || !strings.HasSuffix(e.Name(), ".lua") { - continue - } - libModules = append(libModules, strings.TrimSuffix(e.Name(), ".lua")) - } - } - for _, modName := range libModules { - path := filepath.Join("lib", modName+".lua") - fileData, err := config.Lib.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 == "fes" { - tbl.ForEach(func(k, v lua.LValue) { mod.RawSet(k, v) }) - } else { - mod.RawSetString(modName, tbl) - } - } - - mod.RawSetString("app", loadIncludeModules(L, filepath.Join(".", "include"))) - - if cfg != nil { - site := L.NewTable() - site.RawSetString("version", lua.LString(cfg.App.Version)) - site.RawSetString("name", lua.LString(cfg.App.Name)) - authors := L.NewTable() - for i, a := range cfg.App.Authors { - authors.RawSetInt(i+1, lua.LString(a)) - } - site.RawSetString("authors", authors) - mod.RawSetString("site", site) - } - - bus := L.NewTable() - bus.RawSetString("url", lua.LString(requestData.path)) - params := L.NewTable() - for k, v := range requestData.params { - params.RawSetString(k, lua.LString(v)) - } - 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 - }) - - if err := L.DoFile(entry); err != nil { - return []byte(""), err - } - - if L.GetTop() == 0 { - return []byte(""), nil - } - - 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 []byte(s), nil - } - return []byte(""), nil - } - - if s := L.ToString(-1); s != "" { - return []byte(s), nil - } - return []byte(""), nil -} - -/* this indexes and generate the page for viewing the archive directory */ -func generateArchiveIndex(fsPath string, urlPath string) (string, error) { - info, err := os.Stat(fsPath) - if err != nil { - return "", err - } - if !info.IsDir() { - return "", fmt.Errorf("not a directory") - } - ents, err := os.ReadDir(fsPath) - if err != nil { - return "", err - } - type entryInfo struct { - name string - isDir bool - href string - size int64 - mod time.Time - } - var list []entryInfo - for _, e := range ents { - n := e.Name() - full := filepath.Join(fsPath, n) - st, err := os.Stat(full) - if err != nil { - continue - } - isd := st.IsDir() - displayName := n - if isd { - displayName = n + "/" - } - href := path.Join(urlPath, n) - if isd && !strings.HasSuffix(href, "/") { - href = href + "/" - } - size := int64(-1) - if !isd { - size = st.Size() - } - list = append(list, entryInfo{name: displayName, isDir: isd, href: href, size: size, mod: st.ModTime()}) - } - sort.Slice(list, func(i, j int) bool { - if list[i].isDir != list[j].isDir { - return list[i].isDir - } - return strings.ToLower(list[i].name) < strings.ToLower(list[j].name) - }) - - urlPath = basePath(strings.TrimPrefix(urlPath, "/archive")) - - var b strings.Builder - - b.WriteString("\nIndex of ") - b.WriteString(template.HTMLEscapeString(urlPath)) - b.WriteString("\n\n

Index of ") - b.WriteString(template.HTMLEscapeString(urlPath)) - b.WriteString("


")
-
-	if urlPath != "/" {
-		b.WriteString(
-			`../` + "\n",
-		)
-	} else {
-		b.WriteString(
-			`../` + "\n",
-		)
-	}
-
-	nameCol := 50
-	for _, ei := range list {
-		escapedName := template.HTMLEscapeString(ei.name)
-		dateStr := ei.mod.Local().Format("02-Jan-2006 15:04")
-		var sizeStr string
-		if ei.isDir {
-			sizeStr = "-"
-		} else {
-			sizeStr = fmt.Sprintf("%d", ei.size)
-		}
-		spaces := 1
-		if len(escapedName) < nameCol {
-			spaces = nameCol - len(escapedName)
-		}
-		line := `` + escapedName + `` + strings.Repeat(" ", spaces) + dateStr + strings.Repeat(" ", 19-len(sizeStr)) + sizeStr + "\n"
-		b.WriteString(line)
-	}
-	b.WriteString("

\n") - return b.String(), nil -} - -/* generates the data for the not found page. Checks for user-defined source in this order - * 404.lua => 404.md => 404.html => default. - */ -func generateNotFoundData(cfg *config.AppConfig) []byte { - notFoundData := []byte(` - -404 Not Found - -

404 Not Found

-
fes
- - -`) - if _, err := os.Stat(filepath.Join("www", "404.lua")); err == nil { - if nf, err := renderRoute("www/404.lua", cfg, reqData{}); err == nil { - notFoundData = nf - } - } else if _, err := os.Stat("www/404.md"); err == nil { - if buf, err := os.ReadFile("www/404.html"); err == nil { - notFoundData = []byte(markdownToHTML(string(buf))) - } - } else if _, err := os.Stat("www/404.html"); err == nil { - if buf, err := os.ReadFile("www/404.html"); err == nil { - notFoundData = buf - } - } - return notFoundData -} - -/* helper to load all special directories */ -func loadDirs() map[string]string { - routes := make(map[string]string) - - if entries, err := os.ReadDir("www"); err == nil { - if err := handleDir(entries, "www", routes, "", false); err != nil { - ui.Warning("failed to handle www directory", err) - } - } - - if entries, err := os.ReadDir("static"); err == nil { - if err := handleDir(entries, "static", routes, "/static", true); err != nil { - ui.Warning("failed to handle static directory", err) - } - } - - if entries, err := os.ReadDir("archive"); err == nil { - if err := handleDir(entries, "archive", routes, "/archive", true); err != nil { - ui.Warning("failed to handle archive directory", err) - } - } - - return routes -} - -/* helper to parse the Fes.toml and generate config */ -func parseConfig() config.AppConfig { - defaultCfg := config.AppConfig{} - defaultCfg.App.Authors = []string{"unknown"} - defaultCfg.App.Name = "unknown" - defaultCfg.App.Version = "unknown" - - tomlDocument, err := os.ReadFile("Fes.toml") - if err != nil { - if errors.Is(err, os.ErrNotExist) { - ui.WARN("no config file found, using the default config. In order to specify a config file write to Fes.toml") - return defaultCfg - } else { - ui.Error("failed to read Fes.toml", err) - os.Exit(1) - } - } - docStr := fixMalformedToml(string(tomlDocument)) - var cfg config.AppConfig - if err := toml.Unmarshal([]byte(docStr), &cfg); err != nil { - ui.Warning("failed to parse Fes.toml", err) - cfg = defaultCfg - } - return cfg -} - -/* helper to read the archive files */ -func readArchive(w http.ResponseWriter, route string) error { - fsPath := "." + route - if info, err := os.Stat(fsPath); err == nil && info.IsDir() { - if page, err := generateArchiveIndex(fsPath, route); err == nil { - w.Write([]byte(page)) - return nil - } else { - return err - } - } - return nil -} - -/* start the Fes server */ -func Start(dir string) error { - if err := os.Chdir(dir); err != nil { - return ui.Error(fmt.Sprintf("failed to change directory to %s", dir), err) - } - - ui.Log("Running root=%s, port=%d.", filepath.Clean(dir), *config.Port) - - cfg := parseConfig() - notFoundData := generateNotFoundData(&cfg) - routes := loadDirs() +var routes map[string]string +func Start(dir string) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - route, ok := routes[r.URL.Path] - - var err error = nil - - /* defer won't update paramaters unless we do this. */ - defer func() { - ui.Path(route, err) - }() - - if !ok { - err = config.ErrRouteMiss - route = r.URL.Path - - if strings.HasPrefix(route, "/archive") { - err = readArchive(w, route) - } else { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte(notFoundData)) - } - return - } - - params := make(map[string]string) - for k, v := range r.URL.Query() { - if len(v) > 0 { - params[k] = v[0] - } - } - - var data []byte - if strings.HasSuffix(route, ".lua") { - data, err = renderRoute(route, &cfg, reqData{path: r.URL.Path, params: params}) - } else if strings.HasSuffix(route, ".md") { - data, err = os.ReadFile(route) - data = []byte(markdownToHTML(string(data))) - data = []byte("\n" + string(data)) - } else { - data, err = os.ReadFile(route) - } - - if err != nil { - http.Error(w, fmt.Sprintf("Error loading page: %v", err), http.StatusInternalServerError) - } - - w.Write(data) + w.Write([]byte("

Sup bitch

")) }) - ui.Log("Server initialized") - ui.Log("Ready to accept connections tcp") - return http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", *config.Port), nil) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *config.Port), nil)) } diff --git a/modules/server/util.go b/modules/server/util.go index cb4a30a..8e54629 100644 --- a/modules/server/util.go +++ b/modules/server/util.go @@ -4,36 +4,8 @@ import ( "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" - "regexp" - "strings" ) -func joinBase(base, name string) string { - if base == "" { - return "/" + name - } - return base + "/" + name -} - -func basePath(base string) string { - if base == "" || 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 { - 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)