From df211b85cd2540225cf05ad576b7c2214a248114 Mon Sep 17 00:00:00 2001 From: early Date: Sun, 25 Aug 2024 00:49:57 -0600 Subject: [PATCH] make conditional params work --- internal/compile/compile.go | 27 +++++++++++-- internal/compile/template.go | 76 ++++++++++++++++++++++++++++++++++-- render/data.go | 12 ++++-- 3 files changed, 105 insertions(+), 10 deletions(-) diff --git a/internal/compile/compile.go b/internal/compile/compile.go index 7b12752..16554a0 100644 --- a/internal/compile/compile.go +++ b/internal/compile/compile.go @@ -5,6 +5,7 @@ import ( "errors" "html/template" "maps" + "regexp" "strings" "git.earlybird.gay/today-engine/include" @@ -116,15 +117,35 @@ func Compile(root TemplateSource, transform ...func(root *html.Node)) (Result, e tf(document) } - buf := new(strings.Builder) - err = html.Render(buf, document) + raw, err := renderDocument(document) if err != nil { return result, err } result = Result{ - TemplateRaw: html.UnescapeString(buf.String()), + TemplateRaw: raw, TemplateFuncs: templateFuncs, TemplateDataLoader: computeRoot, } return result, err } + +func renderDocument(document *html.Node) (string, error) { + // Basic render + buf := new(strings.Builder) + err := html.Render(buf, document) + if err != nil { + return "", err + } + raw := html.UnescapeString(buf.String()) + + // Clean boolean attributes + raw = removeEmptyAttrValues(raw) + + return raw, nil +} + +var emptyAttrRegex = regexp.MustCompile(`%%(?P[^<>]+?)%%=""`) + +func removeEmptyAttrValues(raw string) string { + return emptyAttrRegex.ReplaceAllString(raw, "$templateattr") +} diff --git a/internal/compile/template.go b/internal/compile/template.go index a53bdc6..bbac337 100644 --- a/internal/compile/template.go +++ b/internal/compile/template.go @@ -19,6 +19,7 @@ const ( var funcRegexp = regexp.MustCompile(`^[a-zA-Z]\w*$`) var fieldRegexp = regexp.MustCompile(`^(?:.[a-zA-Z]\w*)+$`) +var pipelineTokens = []string{"with", "if"} func isFunc(token string) bool { return funcRegexp.MatchString(token) @@ -28,8 +29,22 @@ func isField(token string) bool { return fieldRegexp.MatchString(token) } +func isPipeline(token string) bool { + for _, ptok := range pipelineTokens { + if strings.Contains(token, ptok) { + return true + } + } + return false +} + +func isEndPipeline(token string) bool { + return strings.Contains(token, "end") +} + +// replaceTemplateFuncs replaces template functions in a node with their +// namespaced versions. func replaceTemplateFuncs(source TemplateSource, n *html.Node) { - var done, inTemplate bool var datas []string var sets []func(string) @@ -40,14 +55,69 @@ func replaceTemplateFuncs(source TemplateSource, n *html.Node) { n.Data = s }) case html.ElementNode: + depth := 0 + inTemplate := false + var key, space string for _, attr := range n.Attr { + key += space + attr.Key + // Track if in a template in the attributes list + if !inTemplate && strings.HasPrefix(attr.Key, tokenOpenTemplate) { + inTemplate = true + if depth == 0 { + key = "%%" + key + } + } + // If in a template, and the current key indicates entering a + // template pipeline, increase depth + if inTemplate && isPipeline(attr.Key) { + depth++ + } + if inTemplate && isEndPipeline(attr.Key) { + depth-- + } + // Track exiting a template + if inTemplate && strings.HasSuffix(attr.Key, tokenCloseTemplate) { + inTemplate = false + if depth == 0 { + key = key + "%%" + } + } + // Accumulate keys if they are in a template, or outside of a template + // but inside a pipeline + if inTemplate || depth > 0 { + space = " " + // Accumulate attribute values outside of templates into the key + // IF the attribute is rendered as a result of a pipeline + if depth > 0 && attr.Val != "" { + key += `="` + attr.Val + `"` + space = "" + continue + } + continue + } + setKey := key + // set 1; process key and transform setKey + datas = append(datas, setKey) + sets = append(sets, func(s string) { + setKey = s + }) + + // set 2; set attr setKey="s" datas = append(datas, attr.Val) sets = append(sets, func(s string) { - htmltree.SetAttr(n, attr.Key, s) + htmltree.SetAttr(n, setKey, s) }) + + // Reset key and spacing + key = "" + space = "" } + + // Reset attributes + n.Attr = make([]html.Attribute, 0, len(n.Attr)) } for i, data := range datas { + var done, inTemplate bool var output []string for { endToken := strings.Index(data, " ") @@ -56,7 +126,6 @@ func replaceTemplateFuncs(source TemplateSource, n *html.Node) { done = true } token := data[:endToken] - // Track when we're in a template using open/close brackets if token == tokenOpenTemplate { inTemplate = true @@ -178,6 +247,7 @@ func insertTemplateSource(subSource TemplateSource, context *html.Node) (*comput // Remove :data attributes from the root of context, and prepare them // to be used as data for the computeNode. asData := removeDataAttrs(context) + asData["id"] = insertedId // ===== SUBSOURCE PROCESSING ===== // Parse the subSource in the context of context. // Require that subSource be contained in a template. diff --git a/render/data.go b/render/data.go index 5029a84..d76dc2f 100644 --- a/render/data.go +++ b/render/data.go @@ -20,15 +20,19 @@ func (d Data) Get(key string) any { return d[key] } +func (d Data) Request() *http.Request { + return d.Get("request").(*http.Request) +} + +func (d Data) ID() string { + return d.Get("id").(string) +} + // Set sets key to value. -// Any key may only be set *once*. Trying to set it again will panic. func (d Data) Set(key string, value any) { if !keyRegexp.MatchString(key) { panic("key " + key + " is not valid") } - if _, ok := d[key]; ok { - panic("key " + key + " already set") - } d[key] = value } -- 2.39.5