"errors"
"html/template"
"maps"
+ "regexp"
"strings"
"git.earlybird.gay/today-engine/include"
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<templateattr>[^<>]+?)%%=""`)
+
+func removeEmptyAttrValues(raw string) string {
+ return emptyAttrRegex.ReplaceAllString(raw, "$templateattr")
+}
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)
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)
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, " ")
done = true
}
token := data[:endToken]
-
// Track when we're in a template using open/close brackets
if token == tokenOpenTemplate {
inTemplate = true
// 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.
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
}