From 02382fa88ee87ed81dbd63fabc52b832b3f75b15 Mon Sep 17 00:00:00 2001 From: early Date: Fri, 20 Dec 2024 17:52:27 -0700 Subject: [PATCH] Update database location choice, hunt down a bunch of slot-related bugs --- README.md | 6 +++ app/app.go | 11 +++++ cmd/test/parts-nesting/index.html | 18 +++++++ cmd/test/parts-nesting/main.go | 49 +++++++++++++++++++ cmd/test/parts-nesting/my-text.go | 26 ++++++++++ cmd/test/parts-nesting/my-text.html | 3 ++ database/database.go | 18 +++++-- database/database_test.go | 14 ++---- .../v0.10/migrate.sql | 0 .../{ => named-versions-init}/v0.2/init.sql | 0 .../v0.2/migrate.sql | 0 .../{ => named-versions-init}/v0/init.sql | 0 database/test/named-versions-init/note.txt | 3 ++ include/localized.go | 2 +- web/htmltree/attrs.go | 4 +- web/internal/compile/compile.go | 5 ++ web/internal/compile/compute.go | 10 ---- web/internal/compile/template.go | 46 ++++++++++------- web/page/page.go | 29 ++++++----- web/part/part.go | 11 +++-- 20 files changed, 194 insertions(+), 61 deletions(-) create mode 100644 cmd/test/parts-nesting/index.html create mode 100644 cmd/test/parts-nesting/main.go create mode 100644 cmd/test/parts-nesting/my-text.go create mode 100644 cmd/test/parts-nesting/my-text.html rename database/test/named-versions-init/{ => named-versions-init}/v0.10/migrate.sql (100%) rename database/test/named-versions-init/{ => named-versions-init}/v0.2/init.sql (100%) rename database/test/named-versions-init/{ => named-versions-init}/v0.2/migrate.sql (100%) rename database/test/named-versions-init/{ => named-versions-init}/v0/init.sql (100%) create mode 100644 database/test/named-versions-init/note.txt diff --git a/README.md b/README.md index 05b634d..94b82b6 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,9 @@ Today is a web framework for making server-side rendered websites in Go. ## Installation and Requirements `go get git.earlybird.gay/today@latest` + +## Known Issues/Limitations + +| Issue | Documented | Fix Planned | +| ------------------------------------------------------------------------------------------------- | ----------- | ----------- | +| Conditionally filling a template slot still removes the default slot if condition isn't fulfilled | Dec 20 2024 | Yes | diff --git a/app/app.go b/app/app.go index a73d41c..6869830 100644 --- a/app/app.go +++ b/app/app.go @@ -13,6 +13,7 @@ import ( "path" "path/filepath" "runtime" + "runtime/debug" "slices" "strings" "sync" @@ -278,6 +279,7 @@ func (app *App) Handle(expr string, handler http.Handler) { for _, middleware := range app.preHandler { handler = middleware(handler) } + app.Logger.Debug("handling today page", "path", translatedExpr, "page", todayPage.Name(), "language", lang) app.ServeMux.Handle(translatedExpr, handler) } } else { @@ -285,6 +287,7 @@ func (app *App) Handle(expr string, handler http.Handler) { for _, middleware := range app.preHandler { handler = middleware(handler) } + app.Logger.Debug("handling", "path", expr) app.ServeMux.Handle(expr, handler) } } @@ -325,6 +328,7 @@ func (app *App) initOnce() { return } app.setDefaults() + app.Logger.Debug("handling static files") app.registerStatic() app.ready.Store(true) @@ -362,6 +366,13 @@ func (app *App) ListenAndServe() error { func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { app.initOnce() + defer func() { + if x := recover(); x != nil { + fmt.Println(x) + fmt.Println(string(debug.Stack())) + w.WriteHeader(http.StatusInternalServerError) + } + }() app.Logger.Debug("serving request", "host", r.Host, "path", r.URL.Path) app.handler.ServeHTTP(w, r) } diff --git a/cmd/test/parts-nesting/index.html b/cmd/test/parts-nesting/index.html new file mode 100644 index 0000000..c9f838e --- /dev/null +++ b/cmd/test/parts-nesting/index.html @@ -0,0 +1,18 @@ + + + Today Test Page + + +
+ +

{{ .header.Value }}

+

{{ . }}

+
+ {{ range $i, $text := .texts }} + + {{ if lt $i 3 }}

{{ .Value }}

{{ end }} +

This is a my-text element with value {{ .Value }}!

+
+ {{ end }} +
+ \ No newline at end of file diff --git a/cmd/test/parts-nesting/main.go b/cmd/test/parts-nesting/main.go new file mode 100644 index 0000000..2ca3c12 --- /dev/null +++ b/cmd/test/parts-nesting/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "fmt" + "log/slog" + "os" + "text/template" + + tapp "git.earlybird.gay/today/app" + "git.earlybird.gay/today/web/page" + "git.earlybird.gay/today/web/render" +) + +var Index = page.New("Index", "index.html", + page.OnLoad(func(ctx context.Context, data render.Data) error { + data.Set("header", MyTextType{Value: "Hello, World!"}) + texts := make([]MyTextType, 0) + for i := range 5 { + texts = append(texts, MyTextType{Value: fmt.Sprintf("%d", i)}) + } + data.Set("texts", texts) + return nil + }), + page.Includes(MyText), + page.Funcs(template.FuncMap{ + "log": fmt.Println, + }), + page.Pretty(" "), +) + +func init() { + fmt.Println(Index.Raw(Index.DefaultLanguage())) + if Index.Error() != nil { + panic(Index.Error()) + } +} + +func main() { + app := tapp.New() + app.Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + })) + app.ShutdownOnSignal(os.Interrupt) + + app.Handle("/{$}", Index) + + app.ListenAndServe() +} diff --git a/cmd/test/parts-nesting/my-text.go b/cmd/test/parts-nesting/my-text.go new file mode 100644 index 0000000..c30604f --- /dev/null +++ b/cmd/test/parts-nesting/my-text.go @@ -0,0 +1,26 @@ +package main + +import ( + "context" + "fmt" + + "git.earlybird.gay/today/web/part" + "git.earlybird.gay/today/web/render" +) + +type MyTextType struct { + Value string +} + +var MyText = part.New("my-text", "my-text.html", + part.OnLoad(func(ctx context.Context, data render.Data) error { + fmt.Println(data.Get("value")) + return nil + }), +) + +func init() { + if MyText.Error() != nil { + panic(MyText.Error()) + } +} diff --git a/cmd/test/parts-nesting/my-text.html b/cmd/test/parts-nesting/my-text.html new file mode 100644 index 0000000..86f8d16 --- /dev/null +++ b/cmd/test/parts-nesting/my-text.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/database/database.go b/database/database.go index f197acc..25b9352 100644 --- a/database/database.go +++ b/database/database.go @@ -3,7 +3,6 @@ package database import ( "database/sql" "errors" - "fmt" "io" "io/fs" "os" @@ -62,11 +61,23 @@ func Subdirectory(dir string) Config { } } -func New(name string, confs ...Config) *Database { +// New creates a new Database. +// If rootDir is given and isn't absolute, it's a relative reference to +// the current file. If it's given and absolute, it's used as the root directory +// for the database and version. If it's not given, the root directory +// is the directory containing the current file. +func New(name, rootDir string, confs ...Config) *Database { db := &Database{ name: name, } - db.rootDir, _ = include.Dir("git.earlybird.gay/today/database") + if rootDir == "" { + db.rootDir, _ = include.Dir("git.earlybird.gay/today/database") + } else if !filepath.IsAbs(rootDir) { + db.rootDir, _ = include.Dir("git.earlybird.gay/today/database") + db.rootDir = filepath.Join(db.rootDir, rootDir) + } else { + db.rootDir = rootDir + } for _, conf := range confs { db.err = errors.Join(db.err, conf(db)) @@ -126,7 +137,6 @@ func New(name string, confs ...Config) *Database { if !entry.IsDir() { continue } - fmt.Println("entry", entry.Name()) if submatches := versionRegexp.FindAllStringSubmatch(entry.Name(), -1); len(submatches) == 1 { dbFolder := submatches[0] dbf, dbfName := dbFolder[0], dbFolder[1] diff --git a/database/database_test.go b/database/database_test.go index d4c3145..cde8388 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -6,8 +6,7 @@ import ( ) func TestRawInit(t *testing.T) { - db := New("raw-init", - Subdirectory("test/raw-init"), + db := New("raw-init", "test/raw-init", Init(` CREATE TABLE messages ( user TEXT, @@ -23,8 +22,7 @@ INSERT INTO messages (user, msg, likes) VALUES ('test-user', 'hello, world', 5); } func TestFileInit(t *testing.T) { - db := New("file-init", - Subdirectory("test/file-init"), + db := New("file-init", "test/file-init", Init(`init.sql`), ) if db.Error() != nil { @@ -34,9 +32,7 @@ func TestFileInit(t *testing.T) { } func TestVersionsInit(t *testing.T) { - db := New("versions-init", - Subdirectory("test/versions-init"), - ) + db := New("versions-init", "test/versions-init") if db.Error() != nil { t.Fatal(db.Error()) } @@ -44,9 +40,7 @@ func TestVersionsInit(t *testing.T) { } func TestNamedVersionsInit(t *testing.T) { - db := New("named-versions-init", - Subdirectory("test"), - ) + db := New("named-versions-init", "test/named-versions-init") if db.Error() != nil { t.Fatal(db.Error()) } diff --git a/database/test/named-versions-init/v0.10/migrate.sql b/database/test/named-versions-init/named-versions-init/v0.10/migrate.sql similarity index 100% rename from database/test/named-versions-init/v0.10/migrate.sql rename to database/test/named-versions-init/named-versions-init/v0.10/migrate.sql diff --git a/database/test/named-versions-init/v0.2/init.sql b/database/test/named-versions-init/named-versions-init/v0.2/init.sql similarity index 100% rename from database/test/named-versions-init/v0.2/init.sql rename to database/test/named-versions-init/named-versions-init/v0.2/init.sql diff --git a/database/test/named-versions-init/v0.2/migrate.sql b/database/test/named-versions-init/named-versions-init/v0.2/migrate.sql similarity index 100% rename from database/test/named-versions-init/v0.2/migrate.sql rename to database/test/named-versions-init/named-versions-init/v0.2/migrate.sql diff --git a/database/test/named-versions-init/v0/init.sql b/database/test/named-versions-init/named-versions-init/v0/init.sql similarity index 100% rename from database/test/named-versions-init/v0/init.sql rename to database/test/named-versions-init/named-versions-init/v0/init.sql diff --git a/database/test/named-versions-init/note.txt b/database/test/named-versions-init/note.txt new file mode 100644 index 0000000..4ae8a9f --- /dev/null +++ b/database/test/named-versions-init/note.txt @@ -0,0 +1,3 @@ +named-versions-init is nested so that the test can +reference the first named-versions-init folder, AND +the database can be named named-versions-init. diff --git a/include/localized.go b/include/localized.go index 87abc20..a29a8f2 100644 --- a/include/localized.go +++ b/include/localized.go @@ -72,7 +72,7 @@ findLanguages: } return map[language.Tag]Opener{ DefaultLanguage: file, - }, nil, err + }, nil, nil } } dir = dir[:strings.LastIndexByte(dir, filepath.Separator)] diff --git a/web/htmltree/attrs.go b/web/htmltree/attrs.go index 3f337bd..6e25fe6 100644 --- a/web/htmltree/attrs.go +++ b/web/htmltree/attrs.go @@ -1,7 +1,9 @@ // Copyright (C) 2024 early (LGPL) package htmltree -import "git.earlybird.gay/today/web/internal/html" +import ( + "git.earlybird.gay/today/web/internal/html" +) func GetAttr(n *html.Node, name string) string { for _, attr := range n.Attr { diff --git a/web/internal/compile/compile.go b/web/internal/compile/compile.go index 8ae35b4..0081d2b 100644 --- a/web/internal/compile/compile.go +++ b/web/internal/compile/compile.go @@ -111,6 +111,11 @@ func Compile(root TemplateSource, transform ...func(root *html.Node)) ([]Result, for fname, f := range templateSource.TemplateFuncs() { templateFuncs[snakeCase(n.Data)+"_"+fname] = f } + // Add $today_parent before template sources + n.Parent.InsertBefore(&html.Node{ + Type: html.TextNode, + Data: "{{ $today_parent := . }}", + }, n) // Parse HTML fragment and replace the content of the node with it computeSubSource, err := insertTemplateSource(lang, templateSource, n) if err != nil { diff --git a/web/internal/compile/compute.go b/web/internal/compile/compute.go index 883b888..ddc3256 100644 --- a/web/internal/compile/compute.go +++ b/web/internal/compile/compute.go @@ -15,16 +15,10 @@ type computeNode struct { htmlId string compute render.OnLoadFunc asDataFromParent map[string]string - raiseFromParent []string children []*computeNode } func (root *computeNode) Compute(r *http.Request) (render.Data, func(any) error, error) { - impose := func(cData, pData render.Data, raise []string) { - for _, key := range raise { - cData.Set(key, pData.Get(key)) - } - } var dot any setDot := func(val any) error { dot = val @@ -65,10 +59,6 @@ func (root *computeNode) Compute(r *http.Request) (render.Data, func(any) error, } childData.Set(dataKey, val) } - // Raise parent variables to the "parent" var - childParentData := make(render.Data) - impose(childParentData, data, child.raiseFromParent) - childData.Set("parent", childParentData) f(child, childData) return childData, child.compute(ctx, childData) }) diff --git a/web/internal/compile/template.go b/web/internal/compile/template.go index 733cafe..6b6f4c2 100644 --- a/web/internal/compile/template.go +++ b/web/internal/compile/template.go @@ -19,7 +19,7 @@ const ( ) var funcRegexp = regexp.MustCompile(`^\(?([a-zA-Z]\w*)\)?$`) -var fieldRegexp = regexp.MustCompile(`^(?:\.[a-zA-Z]\w*)+$`) +var fieldRegexp = regexp.MustCompile(`^(?:\.[a-zA-Z]\w*)+$|^\.$`) var scopesDownRegexp = regexp.MustCompile(`[\{\s]end[\}\s]`) var setsDotRegexp = regexp.MustCompile(`^\{\{-?\s*(?:with|range).*?\}\}$`) var templateRegexp = regexp.MustCompile(`\{\{.*\}\}`) @@ -224,11 +224,8 @@ func replaceTemplateFuncs(source TemplateSource, n *html.Node) { if funcName == "" { goto done } - // fmt.Println("func", token) - // fmt.Println(source.TemplateFuncs()) // Skip anything that the target source doesn't have as a templatefunc if _, ok := source.TemplateFuncs()[funcName]; ok { - // fmt.Println("found", token) namespaced := snakeCase(source.Name()) + "_" + funcName token = strings.Replace(token, funcName, namespaced, -1) } @@ -246,14 +243,13 @@ func replaceTemplateFuncs(source TemplateSource, n *html.Node) { } // Sets template fields (.field) in any template strings in this node to -// .parent.field. +// $today_parent.field. // Returns a list of raised fields. -func replaceTemplateFields(n *html.Node) []string { - var done, inTemplate, inAttrs bool +func replaceTemplateFields(n *html.Node) { + var inAttrs bool var datas []string var isDataAttr []bool var sets []func(string) - var raised []string switch n.Type { case html.TextNode: @@ -272,6 +268,7 @@ func replaceTemplateFields(n *html.Node) []string { } } for i, data := range datas { + var done, inTemplate bool var output []string for { endToken := strings.Index(data, " ") @@ -292,10 +289,14 @@ func replaceTemplateFields(n *html.Node) []string { // with a namespaced version if inTemplate && isField(token) || inAttrs && isDataAttr[i] { - raised = append(raised, token[1:]) - token = ".parent" + token + // special case; . is parentable but shouldn't be appended + fmt.Println("replacing token", token) + if token == "." { + token = "$today_parent" + } else { + token = "$today_parent" + token + } } - output = append(output, token) if done { break @@ -304,8 +305,6 @@ func replaceTemplateFields(n *html.Node) []string { } sets[i](strings.Join(output, " ")) } - - return raised } // Removes data attributes :name. @@ -330,18 +329,28 @@ func removeDataAttrs(n *html.Node) map[string]string { func insertTemplateSource(lang language.Tag, subSource TemplateSource, context *html.Node) (*computeNode, error) { computedName := snakeCase(subSource.Name()) // ===== CONTEXT PREPROCESSING ===== - // Raise any fields pre-existing in the context to .parent.field. - raiseFields := make([]string, 0) + // Raise any fields pre-existing in the context to $today_parent.field. raiseDepth := 0 + raiseStack := []string{} htmltree.Walk(context.FirstChild, func(n *html.Node) (bool, error) { if raiseDepth == 0 { - raiseFields = append(raiseFields, replaceTemplateFields(n)...) + fmt.Println("replace", n.Data) + replaceTemplateFields(n) + fmt.Println("result", n.Data) } if n.Type == html.TextNode { + // Scope up/down only when setting dot + // Make a better way to do this if scopesUp(n.Data) { - raiseDepth++ + if setsDot(n.Data) { + raiseDepth++ + } + raiseStack = append(raiseStack, n.Data) } else if scopesDown(n.Data) { - raiseDepth-- + if setsDot(raiseStack[len(raiseStack)-1]) { + raiseDepth-- + } + raiseStack = raiseStack[:len(raiseStack)-1] } } return false, nil @@ -508,7 +517,6 @@ func insertTemplateSource(lang language.Tag, subSource TemplateSource, context * htmlId: htmlId, compute: subSource.OnLoad(), asDataFromParent: asData, - raiseFromParent: raiseFields, } // Insert scope up/down template pipeline diff --git a/web/page/page.go b/web/page/page.go index 608c714..3aadeb0 100644 --- a/web/page/page.go +++ b/web/page/page.go @@ -31,7 +31,7 @@ type Page struct { templateFuncs template.FuncMap - raw string + raws map[language.Tag]string chooseRenderable func(r *http.Request) *renderable err error @@ -90,9 +90,14 @@ func New(name string, source string, optional ...Config) *Page { p.err = err return p } - p.languages = localeMeta.Languages - p.translations = localeMeta.Translations - p.defaultLanguage = localeMeta.DefaultLanguage + if localeMeta != nil { + p.languages = localeMeta.Languages + p.translations = localeMeta.Translations + p.defaultLanguage = localeMeta.DefaultLanguage + } else { + p.languages = []language.Tag{language.English} + p.defaultLanguage = language.English + } p.sources = sources p.onLoad = func(ctx context.Context, d render.Data) error { return nil @@ -114,8 +119,10 @@ func New(name string, source string, optional ...Config) *Page { return p } + p.raws = make(map[language.Tag]string) renderables := make(map[language.Tag]*renderable) for _, result := range results { + p.raws[result.Language] = result.TemplateRaw // templatize tmpl, err := template.New(p.name). Funcs(result.TemplateFuncs). @@ -129,20 +136,15 @@ func New(name string, source string, optional ...Config) *Page { templateLoad: result.TemplateDataLoader, } } - // result := results[0] - // p.raw = result.TemplateRaw - // p.templateLoad = result.TemplateDataLoader - - // selector := language.NewMatcher(localeMeta.Languages) p.chooseRenderable = func(r *http.Request) *renderable { lang, ok := r.Context().Value(localization.Locale).(language.Tag) if !ok { - return renderables[localeMeta.DefaultLanguage] + return renderables[p.defaultLanguage] } if chosen, ok := renderables[lang]; ok { return chosen } else { - return renderables[localeMeta.DefaultLanguage] + return renderables[p.defaultLanguage] } } return p @@ -183,6 +185,7 @@ func Static(source string) *Page { renderables := make(map[language.Tag]*renderable) for _, result := range results { + p.raws[result.Language] = result.TemplateRaw // templatize tmpl, err := template.New(p.name). Funcs(result.TemplateFuncs). @@ -286,8 +289,8 @@ func (p *Page) Includes() []compile.Source { return p.includes } -func (p *Page) Raw() string { - return p.raw +func (p *Page) Raw(lang language.Tag) string { + return p.raws[lang] } func (p *Page) Error() error { diff --git a/web/part/part.go b/web/part/part.go index 06f4105..0e00bb1 100644 --- a/web/part/part.go +++ b/web/part/part.go @@ -85,9 +85,14 @@ func New(name string, source string, optional ...Config) *Part { p.err = err return p } - p.languages = localeMeta.Languages - p.translations = localeMeta.Translations - p.defaultLanguage = localeMeta.DefaultLanguage + if localeMeta != nil { + p.languages = localeMeta.Languages + p.translations = localeMeta.Translations + p.defaultLanguage = localeMeta.DefaultLanguage + } else { + p.languages = []language.Tag{language.English} + p.defaultLanguage = language.English + } p.sources = sources if p.err != nil { return p -- 2.39.5