Files
jfa-go/template.go
Harvey Tindall 0b43ad4ed5 template: passed var and conditional names don't include braces
pass []string{"username"}, rather than []string{"{username}"}. Tests
have been updated.
2025-08-23 14:59:04 +01:00

148 lines
3.8 KiB
Go

package main
import (
"fmt"
"slices"
)
func truthy(val interface{}) bool {
switch v := val.(type) {
case string:
return v != ""
case bool:
return v
case int:
return v != 0
}
return false
}
// Templater for custom emails.
// Slices "variables", "conditionals", and map "values" should NOT wrap names in { and }.
// Variables should be written as {varName}.
// If statements should be written as {if (!)varName}...{endif}.
// Strings are true if != "", ints are true if != 0.
// Errors returned are likely warnings only.
func templateEmail(content string, variables []string, conditionals []string, values map[string]interface{}) (string, error) {
// minimum length for templatable content (albeit just "{}" -> "")
if len(content) < 2 {
return content, nil
}
ifStart, ifEnd := -1, -1
ifTrue := false
invalidIf := false
previousEnd := -2
blockRawStart := -1
blockContentStart, blockContentEnd := -1, -1
varStart, varEnd := -1, -1
varName := ""
out := ""
var err error = nil
oob := func(i int) bool { return i < 0 || i >= len(content) }
for i, c := range content {
if c == '{' {
blockContentStart = i + 1
blockRawStart = i
if content[i+1] == '{' {
err = fmt.Errorf(`double braces ("{{") at position %d, use single brace only`, i)
blockContentStart++
}
for !oob(blockContentStart) && content[blockContentStart] == ' ' {
blockContentStart++
}
if oob(blockContentStart) {
continue
}
if !oob(blockContentStart+3) && content[blockContentStart:blockContentStart+3] == "if " {
varStart = blockContentStart + 3
for content[varStart] == ' ' {
varStart++
}
}
if ifStart == -1 && (oob(i-1) || content[i-1] != '{') {
out += content[previousEnd+2 : i]
}
if invalidIf || oob(blockContentStart+5) || content[blockContentStart:blockContentStart+5] != "endif" {
continue
}
ifEnd = i - 1
if ifTrue {
toAppend, subErr := templateEmail(content[ifStart:ifEnd+1], variables, conditionals, values)
out += toAppend
if subErr != nil {
err = subErr
}
ifTrue = false
}
} else if c == '}' {
doubleBraced := !oob(i+1) && content[i+1] == '}'
if doubleBraced {
err = fmt.Errorf(`double braces ("}}") at position %d, use single brace only`, i)
}
if !oob(i-1) && content[i-1] == '}' {
continue
}
if varStart != -1 {
ifStart = i + 1
varEnd = i - 1
for !oob(varEnd) && content[varEnd] == ' ' {
varEnd--
}
varName = content[varStart : varEnd+1]
positive := true
if varName[0] == '!' {
positive = false
varName = varName[1:]
}
validVar := slices.Contains(conditionals, varName)
if validVar {
ifTrue = positive == truthy(values[varName])
} else {
invalidIf = true
ifStart, ifEnd = -1, -1
}
varStart, varEnd = -1, -1
}
blockContentEnd = i - 1
for content[blockContentEnd] == ' ' {
blockContentEnd--
}
previousEnd = i - 1
// Skip the extra brace
if doubleBraced {
previousEnd++
}
if !oob(blockContentEnd-4) && !oob(blockContentEnd+1) && content[blockContentEnd-4:blockContentEnd+1] == "endif" && !invalidIf {
continue
}
varName = content[blockContentStart : blockContentEnd+1]
blockContentStart, blockContentEnd = -1, -1
blockRawStart = -1
if ifStart != -1 {
continue
}
validVar := slices.Contains(variables, varName)
if !validVar {
out += "{" + varName + "}"
continue
}
out += fmt.Sprint(values[varName])
}
}
if blockContentStart != -1 && blockContentEnd == -1 {
err = fmt.Errorf(`incomplete block (single "{") near position %d`, blockContentStart)
// Include the brace, maybe the user wants it.
previousEnd = blockRawStart - 2
}
if previousEnd+1 != len(content)-1 {
out += content[previousEnd+2:]
}
if out == "" {
return content, err
}
return out, err
}