]> git.earlybird.gay Git - today/commitdiff
revise compute pattern but again lol
authorearly <me@earlybird.gay>
Wed, 28 Aug 2024 04:47:34 +0000 (22:47 -0600)
committerearly <me@earlybird.gay>
Wed, 28 Aug 2024 04:47:34 +0000 (22:47 -0600)
cmd/standard-test/index.html
cmd/standard-test/main.go
cmd/standard-test/test_thing.html [new file with mode: 0644]
internal/compile/compile.go
internal/compile/compute.go
internal/compile/template.go
page/serve.go
render/data.go

index a5e890c73b191b2f98eeb944ab3f5ed34aa1b930..bb6af6cdb35a34cf77afed5d34bbceaa8a4ff8f5 100644 (file)
@@ -1,14 +1,19 @@
 <!DOCTYPE html>
 <html>
-<header>
+
+<head>
     <title>Today Engine Examples</title>
     <link rel="stylesheet" href="/style.css">
-</header>
+</head>
 
 <body>
     <main>
         <stdp-contact-form :action="/contact" id="cf-get"></stdp-contact-form>
         <stdp-contact-form :action="/contact" :method="POST" id="cf-post"></stdp-contact-form>
+
+        {{- range $i, $v := SliceOfLen 5 }}
+        <test-thing :value="."></test-thing>
+        {{ end -}}
     </main>
 </body>
 
index f90d82eab18505f7145a84a55bf7552e04c3f657..43a1495a81e9a65583a8f3f97667b719b6fe4c76 100644 (file)
@@ -1,19 +1,28 @@
 package main
 
 import (
-       "html/template"
+       "context"
        "net/http"
        "syscall"
+       "text/template"
 
+       tapp "git.earlybird.gay/today-app/app"
        "git.earlybird.gay/today-engine/page"
+       "git.earlybird.gay/today-engine/part"
+       "git.earlybird.gay/today-engine/render"
        stdpart "git.earlybird.gay/today-engine/standard/part"
+)
 
-       tapp "git.earlybird.gay/today-app/app"
+var Thing = part.New("test-thing", "test_thing.html",
+       part.OnLoad(func(ctx context.Context, data render.Data) error {
+               // fmt.Println(data.Get("value"))
+               return nil
+       }),
 )
 
 var Page = page.New("index", "index.html",
        page.Includes(
-               stdpart.ContactForm([]string{"Feedback"}),
+               stdpart.ContactForm([]string{"Feedback"}), Thing,
        ),
        page.Funcs(template.FuncMap{
                "SliceOfLen": func(i int) []int {
diff --git a/cmd/standard-test/test_thing.html b/cmd/standard-test/test_thing.html
new file mode 100644 (file)
index 0000000..5988d75
--- /dev/null
@@ -0,0 +1,3 @@
+<template>
+  <p>{{ .value }}</p>
+</template>
\ No newline at end of file
index 6b846821782025682493fb954b5e982aa9c2e90e..8d3279bb2a634903e15264bf819794c26c6980e4 100644 (file)
@@ -121,6 +121,25 @@ func Compile(root TemplateSource, transform ...func(root *html.Node)) (Result, e
                return result, err
        }
 
+       // POSTPROCESSING
+
+       // Assign .SetDot to $setDot
+       document.InsertBefore(&html.Node{
+               Type: html.TextNode,
+               Data: `{{ $setDot := .SetDot }}{{ with .Data }}`,
+       }, document.FirstChild)
+       // Add end to end
+       document.InsertBefore(&html.Node{
+               Type: html.TextNode,
+               Data: "{{ end -}}",
+       }, nil)
+
+       // Split up templates in text nodes
+       splitTemplateNodes(document)
+
+       // Traverse the document and add $setDot on entry and exit from pipelines
+       addSetDots(document)
+
        for _, tf := range transform {
                tf(document)
        }
index 79e6bcb940c740e3ff340b558a4274a3f7d3125d..5c5f69a9eb886bb61dc7e2134a2ccbe9df8c80fa 100644 (file)
@@ -19,13 +19,19 @@ type computeNode struct {
        children         []*computeNode
 }
 
-func (root *computeNode) Compute(r *http.Request) (render.Data, error) {
+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
+               return nil
+       }
        ctx := r.Context()
+
        var f func(n *computeNode, data render.Data) error
        f = func(n *computeNode, data render.Data) error {
                // Set the htmlId of the component if it exists as "id"
@@ -42,31 +48,30 @@ func (root *computeNode) Compute(r *http.Request) (render.Data, error) {
                        if computeFuncs.IsSet(computeFuncName) {
                                continue
                        }
-                       childData := render.NewData(r)
-                       // Stuff here is available to the OnLoad AND renderer:
-                       // Assign all data from parent
-                       for dataKey, rawOrIdentifier := range child.asDataFromParent {
-                               // Assume raw...
-                               var val any = rawOrIdentifier
-                               // ...but treat as an identifier if starts with .
-                               if rawOrIdentifier[0] == '.' {
-                                       val = getVarFromData(data, rawOrIdentifier)
-                                       if val == nil {
-                                               return fmt.Errorf("key %s not found in passed data", rawOrIdentifier)
+                       // Defer rendering of the child to when it's needed
+                       computeFuncs.Set(computeFuncName, func() (render.Data, error) {
+                               childData := render.NewData(r)
+                               // Stuff here is available to the OnLoad AND renderer:
+                               // Assign all data from parent
+                               for dataKey, rawOrIdentifier := range child.asDataFromParent {
+                                       // Assume raw...
+                                       var val any = rawOrIdentifier
+                                       // ...but treat as an identifier if starts with .
+                                       if rawOrIdentifier[0] == '.' {
+                                               val = getVarFromData(dot, rawOrIdentifier)
+                                               if val == nil {
+                                                       return nil, fmt.Errorf("key %s not found in passed data", rawOrIdentifier)
+                                               }
                                        }
+                                       childData.Set(dataKey, val)
                                }
-                               childData.Set(dataKey, val)
-                       }
-                       // Raise parent variables to the "parent" var
-                       childParentData := make(render.Data)
-                       impose(childParentData, data, child.raiseFromParent)
-                       childData.Set("parent", childParentData)
-                       if computeFuncName != "" {
-                               computeFuncs.Set(computeFuncName, func() (render.Data, error) {
-                                       return childData, child.compute(ctx, childData)
-                               })
-                       }
-                       f(child, childData)
+                               // 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)
+                       })
                        // Stuff here is ONLY available to the renderer:
                }
                data.Set("compute", computeFuncs)
@@ -74,18 +79,24 @@ func (root *computeNode) Compute(r *http.Request) (render.Data, error) {
        }
 
        data := render.NewData(r)
-       return data, f(root, data)
+       return data, setDot, f(root, data)
 }
 
 // Get a variable from data.
 // key must be of the form .x.y.z, and will try to get that value from data.
-func getVarFromData(data render.Data, key string) any {
+func getVarFromData(data any, key string) any {
+       // special case; if the key is ".", just return data.
+       if key == "." {
+               return data
+       }
        var val any = data
        keyStack := strings.Split(key, ".")[1:]
-
 findKey:
        for val != nil && len(keyStack) > 0 {
                key = keyStack[0]
+               if key == "" {
+                       return data
+               }
                keyStack = keyStack[1:]
                refV := reflect.ValueOf(val)
                refT := refV.Type()
index 4f67c2d49aa96e9a9502900389a4c7c81afa491d..b9ecacf78480ba8004130c3f51259898f723a16c 100644 (file)
@@ -19,8 +19,8 @@ const (
 
 var funcRegexp = regexp.MustCompile(`^[a-zA-Z]\w*$`)
 var fieldRegexp = regexp.MustCompile(`^(?:\.[a-zA-Z]\w*)+$`)
+var setsDotRegexp = regexp.MustCompile(`^\{\{-?\s*(?:with|range).*?\}\}$`)
 var templateRegexp = regexp.MustCompile(`\{\{.*\}\}`)
-var pipelineTokens = []string{"with", "if"}
 
 func isFunc(token string) bool {
        return funcRegexp.MatchString(token)
@@ -34,19 +34,20 @@ func containsTemplate(token string) bool {
        return templateRegexp.MatchString(token)
 }
 
-func isPipeline(token string) bool {
-       for _, ptok := range pipelineTokens {
-               if strings.Contains(token, ptok) {
-                       return true
-               }
-       }
-       return false
+func scopesUp(token string) bool {
+       return strings.Contains(token, "with") ||
+               strings.Contains(token, "if") ||
+               strings.Contains(token, "range")
 }
 
-func isEndPipeline(token string) bool {
+func scopesDown(token string) bool {
        return strings.Contains(token, "end")
 }
 
+func setsDot(token string) bool {
+       return setsDotRegexp.MatchString(token)
+}
+
 func getAttr(n *html.Node, key string) string {
        idx := slices.IndexFunc(n.Attr, func(attr html.Attribute) bool {
                return attr.Key == key
@@ -57,6 +58,65 @@ func getAttr(n *html.Node, key string) string {
        return n.Attr[idx].Val
 }
 
+func splitTemplateNodes(n *html.Node) {
+       htmltree.Walk(n, func(n *html.Node) (bool, error) {
+               if n.Parent == nil {
+                       return false, nil
+               } else if n.Type != html.TextNode {
+                       return false, nil
+               } else if !containsTemplate(n.Data) {
+                       return false, nil
+               }
+
+               // n is a text node with at least one template
+               // split it into text nodes where every {{ template }} is its own node
+               splitNodes := make([]string, 0)
+
+               var t, i, open, close, depth int
+               limit := 0
+               for limit < 10 {
+                       limit++
+                       // Find next open and close token
+                       // Increment to the closest one, if at least one exists
+                       open = strings.Index(n.Data[i:], tokenOpenTemplate)
+                       close = strings.Index(n.Data[i:], tokenCloseTemplate)
+
+                       if open != -1 && open < close {
+                               if depth == 0 {
+                                       // If we're just starting a pipeline, set the "template"
+                                       // cursor to the template open position
+                                       t += open
+                               }
+                               depth++
+                               i += open + 2
+                       } else if close != -1 {
+                               depth--
+                               if depth == 0 {
+                                       // If we're closing a pipeline, append from the "template"
+                                       // cursor to the closing position, + 2 for the length
+                                       // of the closeToken
+                                       splitNodes = append(splitNodes, n.Data[t:i+close+2])
+                                       t = i + close + 2
+                               }
+                               i += close + 2
+                       } else {
+                               break
+                       }
+               }
+
+               // Append all splitNodes as nodes where n was
+               head := n.NextSibling
+               for _, data := range splitNodes {
+                       n.Parent.InsertBefore(&html.Node{
+                               Type: html.TextNode,
+                               Data: data,
+                       }, head)
+               }
+               n.Parent.RemoveChild(n)
+               return false, nil
+       })
+}
+
 // replaceTemplateFuncs replaces template functions in a node with their
 // namespaced versions.
 func replaceTemplateFuncs(source TemplateSource, n *html.Node) {
@@ -84,10 +144,10 @@ func replaceTemplateFuncs(source TemplateSource, n *html.Node) {
                        }
                        // If in a template, and the current key indicates entering a
                        // template pipeline, increase depth
-                       if inTemplate && isPipeline(attr.Key) {
+                       if inTemplate && scopesUp(attr.Key) {
                                depth++
                        }
-                       if inTemplate && isEndPipeline(attr.Key) {
+                       if inTemplate && scopesDown(attr.Key) {
                                depth--
                        }
                        // Track exiting a template
@@ -353,7 +413,7 @@ func insertTemplateSource(subSource TemplateSource, context *html.Node) (*comput
        // Generate a computeNode for this part.
        htmlId := getAttr(context, "id")
        scopeName := computedName + "_" + snakeCase(htmlId)
-       if containsTemplate(htmlId) {
+       if htmlId == "" || containsTemplate(htmlId) {
                htmlId = ""
                scopeName = computedName
        }
@@ -380,7 +440,7 @@ func insertTemplateSource(subSource TemplateSource, context *html.Node) (*comput
 }
 
 func scopeNodes(scopeName string) (up, down *html.Node) {
-       upVal := fmt.Sprintf(`{{- $data := call $compute.%s }}{{ with $data }}`, scopeName)
+       upVal := fmt.Sprintf(`{{ with call $compute.%s }}`, scopeName)
        up = &html.Node{
                Type: html.TextNode,
                Data: upVal,
@@ -392,3 +452,40 @@ func scopeNodes(scopeName string) (up, down *html.Node) {
        }
        return up, down
 }
+
+func addSetDots(document *html.Node) {
+       endAcc := make([]bool, 0)
+       htmltree.Walk(document, func(n *html.Node) (bool, error) {
+               if n.Type != html.TextNode {
+                       return false, nil
+               } else if !containsTemplate(n.Data) {
+                       return false, nil
+               }
+
+               if scopesUp(n.Data) {
+                       if setsDot(n.Data) {
+                               n.Parent.InsertBefore(&html.Node{
+                                       Type: html.TextNode,
+                                       Data: `{{ call $setDot . }}`,
+                               }, n.NextSibling)
+                               endAcc = append(endAcc, true)
+                       } else {
+                               endAcc = append(endAcc, false)
+                       }
+               } else if scopesDown(n.Data) || strings.Contains(n.Data, "else") {
+                       shouldSet := endAcc[len(endAcc)-1]
+                       endAcc = endAcc[:len(endAcc)-1]
+                       if shouldSet {
+                               n.Parent.InsertBefore(&html.Node{
+                                       Type: html.TextNode,
+                                       Data: "{{ call $setDot . }}",
+                               }, n.NextSibling)
+                       }
+                       if strings.Contains(n.Data, "else") {
+                               endAcc = append(endAcc, false)
+                       }
+               }
+
+               return false, nil
+       })
+}
index 2f91ad5a3373e92f71939d911bd57716a648c1c2..ad05884f38bc10a3eea58048d58903adf48d67de 100644 (file)
@@ -3,14 +3,27 @@ package page
 
 import (
        "net/http"
+
+       "git.earlybird.gay/today-engine/render"
 )
 
+type RootData struct {
+       SetDot func(value any) error
+
+       Data render.Data
+}
+
 func (p *Page) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-       data, err := p.templateLoad.Compute(r)
+       data, setDot, err := p.templateLoad.Compute(r)
        if err != nil {
                panic(err)
        }
-       err = p.template.Execute(w, data)
+       root := RootData{
+               SetDot: setDot,
+
+               Data: data,
+       }
+       err = p.template.Execute(w, root)
        if err != nil {
                panic(err)
        }
index 4e38e9e8fb1b9be4fd325bae8f911d310e408a70..3f9fe9feb2269d3962413bcb7e7c04e678b01a81 100644 (file)
@@ -47,7 +47,7 @@ func (d Data) Set(key string, value any) {
        d[key] = value
 }
 
-type OnLoadFunc func(context.Context, Data) error
+type OnLoadFunc func(ctx context.Context, data Data) error
 type Loader interface {
-       Compute(r *http.Request) (Data, error)
+       Compute(r *http.Request) (Data, func(any) error, error)
 }