gosora/common/templates/templates.go
Azareal 76cfcb509b Moved the phrase logic to the phrases package.
Removed some initialisation boilerplate from the test code.
De-duplicated some of the common portions of the benchmarks.
Added miscinit to reduce the amount of initialisation boilerplate in the tests and to better catch initialisation errors.
Added a profile benchmark.
2018-11-01 16:43:56 +10:00

1065 lines
33 KiB
Go

package tmpl
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"reflect"
"strconv"
"strings"
"text/template/parse"
)
// TODO: Turn this file into a library
var textOverlapList = make(map[string]int)
// TODO: Stop hard-coding this here
var langPkg = "github.com/Azareal/Gosora/common/phrases"
type VarItem struct {
Name string
Destination string
Type string
}
type VarItemReflect struct {
Name string
Destination string
Value reflect.Value
}
type CTemplateConfig struct {
Minify bool
Debug bool
SuperDebug bool
SkipHandles bool
SkipTmplPtrMap bool
SkipInitBlock bool
PackageName string
}
// nolint
type CTemplateSet struct {
templateList map[string]*parse.Tree
fileDir string
funcMap map[string]interface{}
importMap map[string]string
TemplateFragmentCount map[string]int
Fragments map[string]int
fragmentCursor map[string]int
FragOut string
varList map[string]VarItem
localVars map[string]map[string]VarItemReflect
hasDispInt bool
localDispStructIndex int
langIndexToName []string
stats map[string]int
previousNode parse.NodeType
currentNode parse.NodeType
nextNode parse.NodeType
//tempVars map[string]string
config CTemplateConfig
baseImportMap map[string]string
buildTags string
expectsInt interface{}
}
func NewCTemplateSet() *CTemplateSet {
return &CTemplateSet{
config: CTemplateConfig{
PackageName: "main",
},
baseImportMap: map[string]string{},
funcMap: map[string]interface{}{
"and": "&&",
"not": "!",
"or": "||",
"eq": true,
"ge": true,
"gt": true,
"le": true,
"lt": true,
"ne": true,
"add": true,
"subtract": true,
"multiply": true,
"divide": true,
"dock": true,
"lang": true,
"level": true,
"scope": true,
"dyntmpl": true,
},
}
}
func (c *CTemplateSet) SetConfig(config CTemplateConfig) {
if config.PackageName == "" {
config.PackageName = "main"
}
c.config = config
}
func (c *CTemplateSet) GetConfig() CTemplateConfig {
return c.config
}
func (c *CTemplateSet) SetBaseImportMap(importMap map[string]string) {
c.baseImportMap = importMap
}
func (c *CTemplateSet) SetBuildTags(tags string) {
c.buildTags = tags
}
func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
if c.config.Debug {
fmt.Println("Compiling template '" + name + "'")
}
c.importMap = map[string]string{}
for index, item := range c.baseImportMap {
c.importMap[index] = item
}
if len(imports) > 0 {
for _, importItem := range imports {
c.importMap[importItem] = importItem
}
}
c.fileDir = fileDir
c.varList = varList
c.hasDispInt = false
c.localDispStructIndex = 0
c.stats = make(map[string]int)
c.expectsInt = expectsInt
holdreflect := reflect.ValueOf(expectsInt)
res, err := ioutil.ReadFile(fileDir + "overrides/" + name)
if err != nil {
c.detail("override path: ", fileDir+"overrides/"+name)
c.detail("override err: ", err)
res, err = ioutil.ReadFile(fileDir + name)
if err != nil {
return "", err
}
}
content := string(res)
if c.config.Minify {
content = minify(content)
}
tree := parse.New(name, c.funcMap)
var treeSet = make(map[string]*parse.Tree)
tree, err = tree.Parse(content, "{{", "}}", treeSet, c.funcMap)
if err != nil {
return "", err
}
c.detail(name)
fname := strings.TrimSuffix(name, filepath.Ext(name))
c.templateList = map[string]*parse.Tree{fname: tree}
varholder := "tmpl_" + fname + "_vars"
c.detail(c.templateList)
c.localVars = make(map[string]map[string]VarItemReflect)
c.localVars[fname] = make(map[string]VarItemReflect)
c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect}
if c.Fragments == nil {
c.Fragments = make(map[string]int)
}
c.fragmentCursor = map[string]int{fname: 0}
c.langIndexToName = nil
// TODO: Is this the first template loaded in? We really should have some sort of constructor for CTemplateSet
if c.TemplateFragmentCount == nil {
c.TemplateFragmentCount = make(map[string]int)
}
out += c.rootIterate(c.templateList[fname], varholder, holdreflect, fname)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
if len(c.langIndexToName) > 0 {
c.importMap[langPkg] = langPkg
}
var importList string
for _, item := range c.importMap {
importList += "import \"" + item + "\"\n"
}
var varString string
for _, varItem := range c.varList {
varString += "var " + varItem.Name + " " + varItem.Type + " = " + varItem.Destination + "\n"
}
var fout string
if c.buildTags != "" {
fout += "// +build " + c.buildTags + "\n\n"
}
fout += "// Code generated by Gosora. More below:\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n"
fout += "package " + c.config.PackageName + "\n" + importList + "\n"
if !c.config.SkipInitBlock {
if len(c.langIndexToName) > 0 {
fout += "var " + fname + "_tmpl_phrase_id int\n\n"
}
fout += "// nolint\nfunc init() {\n"
if !c.config.SkipHandles {
fout += "\tcommon.Template_" + fname + "_handle = Template_" + fname + "\n"
fout += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n\tcommon.TmplPtrMap[\"" + fname + "\"] = &common.Template_" + fname + "_handle\n"
}
if !c.config.SkipTmplPtrMap {
fout += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = Template_" + fname + "\n"
}
if len(c.langIndexToName) > 0 {
fout += "\t" + fname + "_tmpl_phrase_id = phrases.RegisterTmplPhraseNames([]string{\n"
for _, name := range c.langIndexToName {
fout += "\t\t" + `"` + name + `"` + ",\n"
}
fout += "\t})\n"
}
fout += "}\n\n"
}
fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars " + expects + ", w io.Writer) error {\n"
if len(c.langIndexToName) > 0 {
fout += "var plist = phrases.GetTmplPhrasesBytes(" + fname + "_tmpl_phrase_id)\n"
}
fout += varString + out + "return nil\n}\n"
fout = strings.Replace(fout, `))
w.Write([]byte(`, " + ", -1)
fout = strings.Replace(fout, "` + `", "", -1)
//spstr := "`([:space:]*)`"
//whitespaceWrites := regexp.MustCompile(`(?s)w.Write\(\[\]byte\(`+spstr+`\)\)`)
//fout = whitespaceWrites.ReplaceAllString(fout,"")
if c.config.Debug {
for index, count := range c.stats {
fmt.Println(index+": ", strconv.Itoa(count))
}
fmt.Println(" ")
}
c.detail("Output!")
c.detail(fout)
return fout, nil
}
func (c *CTemplateSet) rootIterate(tree *parse.Tree, varholder string, holdreflect reflect.Value, fname string) (out string) {
c.detail(tree.Root)
treeLength := len(tree.Root.Nodes)
for index, node := range tree.Root.Nodes {
c.detail("Node:", node.String())
c.previousNode = c.currentNode
c.currentNode = node.Type()
if treeLength != (index + 1) {
c.nextNode = tree.Root.Nodes[index+1].Type()
}
out += c.compileSwitch(varholder, holdreflect, fname, node)
}
return out
}
func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value, templateName string, node parse.Node) (out string) {
c.detail("in compileSwitch")
switch node := node.(type) {
case *parse.ActionNode:
c.detail("Action Node")
if node.Pipe == nil {
break
}
for _, cmd := range node.Pipe.Cmds {
out += c.compileSubswitch(varholder, holdreflect, templateName, cmd)
}
case *parse.IfNode:
c.detail("If Node:")
c.detail("node.Pipe", node.Pipe)
var expr string
for _, cmd := range node.Pipe.Cmds {
c.detail("If Node Bit:", cmd)
c.detail("Bit Type:", reflect.ValueOf(cmd).Type().Name())
expr += c.compileVarswitch(varholder, holdreflect, templateName, cmd)
c.detail("Expression Step:", c.compileVarswitch(varholder, holdreflect, templateName, cmd))
}
c.detail("Expression:", expr)
c.previousNode = c.currentNode
c.currentNode = parse.NodeList
c.nextNode = -1
out = "if " + expr + " {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.List) + "}"
if node.ElseList == nil {
c.detail("Selected Branch 1")
return out + "\n"
}
c.detail("Selected Branch 2")
return out + " else {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.ElseList) + "}\n"
case *parse.ListNode:
c.detail("List Node")
for _, subnode := range node.Nodes {
out += c.compileSwitch(varholder, holdreflect, templateName, subnode)
}
case *parse.RangeNode:
return c.compileRangeNode(varholder, holdreflect, templateName, node)
case *parse.TemplateNode:
return c.compileSubtemplate(varholder, holdreflect, node)
case *parse.TextNode:
c.previousNode = c.currentNode
c.currentNode = node.Type()
c.nextNode = 0
tmpText := bytes.TrimSpace(node.Text)
if len(tmpText) == 0 {
return ""
}
fragmentName := templateName + "_" + strconv.Itoa(c.fragmentCursor[templateName])
fragmentPrefix := templateName + "_frags[" + strconv.Itoa(c.fragmentCursor[templateName]) + "]"
_, ok := c.Fragments[fragmentName]
if !ok {
c.Fragments[fragmentName] = len(node.Text)
c.FragOut += fragmentPrefix + " = []byte(`" + string(node.Text) + "`)\n"
}
c.fragmentCursor[templateName] = c.fragmentCursor[templateName] + 1
return "w.Write(" + fragmentPrefix + ")\n"
default:
return c.unknownNode(node)
}
return out
}
func (c *CTemplateSet) compileRangeNode(varholder string, holdreflect reflect.Value, templateName string, node *parse.RangeNode) (out string) {
c.detail("Range Node!")
c.detail(node.Pipe)
var outVal reflect.Value
for _, cmd := range node.Pipe.Cmds {
c.detail("Range Bit:", cmd)
out, outVal = c.compileReflectSwitch(varholder, holdreflect, templateName, cmd)
}
c.detail("Returned:", out)
c.detail("Range Kind Switch!")
switch outVal.Kind() {
case reflect.Map:
var item reflect.Value
for _, key := range outVal.MapKeys() {
item = outVal.MapIndex(key)
}
c.detail("Range item:", item)
if !item.IsValid() {
panic("item" + "^\n" + "Invalid map. Maybe, it doesn't have any entries for the template engine to analyse?")
}
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n}"
if node.ElseList != nil {
out += " else {\n" + c.compileSwitch("item", item, templateName, node.ElseList) + "}\n"
}
case reflect.Slice:
if outVal.Len() == 0 {
panic("The sample data needs at-least one or more elements for the slices. We're looking into removing this requirement at some point!")
}
item := outVal.Index(0)
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n}"
if node.ElseList != nil {
out += " else {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.ElseList) + "}"
}
case reflect.Invalid:
return ""
}
return out + "\n"
}
func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string) {
c.detail("in compileSubswitch")
firstWord := node.Args[0]
switch n := firstWord.(type) {
case *parse.FieldNode:
c.detail("Field Node:", n.Ident)
/* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Variable declarations are coming soon! */
cur := holdreflect
var varbit string
if cur.Kind() == reflect.Interface {
cur = cur.Elem()
varbit += ".(" + cur.Type().Name() + ")"
}
// ! Might not work so well for non-struct pointers
skipPointers := func(cur reflect.Value, id string) reflect.Value {
if cur.Kind() == reflect.Ptr {
c.detail("Looping over pointer")
for cur.Kind() == reflect.Ptr {
cur = cur.Elem()
}
c.detail("Data Kind:", cur.Kind().String())
c.detail("Field Bit:", id)
}
return cur
}
var assLines string
var multiline = false
for _, id := range n.Ident {
c.detail("Data Kind:", cur.Kind().String())
c.detail("Field Bit:", id)
cur = skipPointers(cur, id)
if !cur.IsValid() {
c.error("Debug Data:")
c.error("Holdreflect:", holdreflect)
c.error("Holdreflect.Kind():", holdreflect.Kind())
if !c.config.SuperDebug {
c.error("cur.Kind():", cur.Kind().String())
}
c.error("")
if !multiline {
panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
panic(varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
c.detail("in-loop varbit: " + varbit)
if cur.Kind() == reflect.Map {
cur = cur.MapIndex(reflect.ValueOf(id))
varbit += "[\"" + id + "\"]"
cur = skipPointers(cur, id)
if cur.Kind() == reflect.Struct || cur.Kind() == reflect.Interface {
// TODO: Move the newVarByte declaration to the top level or to the if level, if a dispInt is only used in a particular if statement
var dispStr, newVarByte string
if cur.Kind() == reflect.Interface {
dispStr = "Int"
if !c.hasDispInt {
newVarByte = ":"
c.hasDispInt = true
}
}
// TODO: De-dupe identical struct types rather than allocating a variable for each one
if cur.Kind() == reflect.Struct {
dispStr = "Struct" + strconv.Itoa(c.localDispStructIndex)
newVarByte = ":"
c.localDispStructIndex++
}
varholder = "disp" + dispStr
varbit = varholder + " " + newVarByte + "= " + varholder + varbit + "\n"
multiline = true
} else {
continue
}
}
if cur.Kind() != reflect.Interface {
cur = cur.FieldByName(id)
varbit += "." + id
}
// TODO: Handle deeply nested pointers mixed with interfaces mixed with pointers better
if cur.Kind() == reflect.Interface {
cur = cur.Elem()
varbit += ".("
// TODO: Surely, there's a better way of doing this?
if cur.Type().PkgPath() != "main" && cur.Type().PkgPath() != "" {
c.importMap["html/template"] = "html/template"
varbit += strings.TrimPrefix(cur.Type().PkgPath(), "html/") + "."
}
varbit += cur.Type().Name() + ")"
}
c.detail("End Cycle: ", varbit)
}
if multiline {
assSplit := strings.Split(varbit, "\n")
varbit = assSplit[len(assSplit)-1]
assSplit = assSplit[:len(assSplit)-1]
assLines = strings.Join(assSplit, "\n") + "\n"
}
varbit = varholder + varbit
out = c.compileVarsub(varbit, cur, assLines)
for _, varItem := range c.varList {
if strings.HasPrefix(out, varItem.Destination) {
out = strings.Replace(out, varItem.Destination, varItem.Name, 1)
}
}
return out
case *parse.DotNode:
c.detail("Dot Node:", node.String())
return c.compileVarsub(varholder, holdreflect, "")
case *parse.NilNode:
panic("Nil is not a command x.x")
case *parse.VariableNode:
c.detail("Variable Node:", n.String())
c.detail(n.Ident)
varname, reflectVal := c.compileIfVarsub(n.String(), varholder, templateName, holdreflect)
return c.compileVarsub(varname, reflectVal, "")
case *parse.StringNode:
return n.Quoted
case *parse.IdentifierNode:
c.detail("Identifier Node:", node)
c.detail("Identifier Node Args:", node.Args)
out, outval, lit := c.compileIdentSwitch(varholder, holdreflect, templateName, node)
if lit {
return out
}
return c.compileVarsub(out, outval, "")
default:
return c.unknownNode(node)
}
}
func (c *CTemplateSet) compileVarswitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string) {
c.detail("in compileVarswitch")
firstWord := node.Args[0]
switch n := firstWord.(type) {
case *parse.FieldNode:
if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident)
for _, id := range n.Ident {
fmt.Println("Field Bit:", id)
}
}
/* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */
return c.compileBoolsub(n.String(), varholder, templateName, holdreflect)
case *parse.ChainNode:
c.detail("Chain Node:", n.Node)
c.detail("Node Args:", node.Args)
case *parse.IdentifierNode:
c.detail("Identifier Node:", node)
c.detail("Node Args:", node.Args)
return c.compileIdentSwitchN(varholder, holdreflect, templateName, node)
case *parse.DotNode:
return varholder
case *parse.VariableNode:
c.detail("Variable Node:", n.String())
c.detail("Node Identifier:", n.Ident)
out, _ = c.compileIfVarsub(n.String(), varholder, templateName, holdreflect)
case *parse.NilNode:
panic("Nil is not a command x.x")
case *parse.PipeNode:
c.detail("Pipe Node!")
c.detail(n)
c.detail("Node Args:", node.Args)
out += c.compileIdentSwitchN(varholder, holdreflect, templateName, node)
c.detail("Out:", out)
default:
return c.unknownNode(firstWord)
}
return out
}
func (c *CTemplateSet) unknownNode(node parse.Node) (out string) {
fmt.Println("Unknown Kind:", reflect.ValueOf(node).Elem().Kind())
fmt.Println("Unknown Type:", reflect.ValueOf(node).Elem().Type().Name())
panic("I don't know what node this is! Grr...")
}
func (c *CTemplateSet) compileIdentSwitchN(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string) {
c.detail("in compileIdentSwitchN")
out, _, _ = c.compileIdentSwitch(varholder, holdreflect, templateName, node)
return out
}
func (c *CTemplateSet) dumpSymbol(pos int, node *parse.CommandNode, symbol string) {
c.detail("symbol: ", symbol)
c.detail("node.Args[pos + 1]", node.Args[pos+1])
c.detail("node.Args[pos + 2]", node.Args[pos+2])
}
func (c *CTemplateSet) compareFunc(varholder string, holdreflect reflect.Value, templateName string, pos int, node *parse.CommandNode, compare string) (out string) {
c.dumpSymbol(pos, node, compare)
return c.compileIfVarsubN(node.Args[pos+1].String(), varholder, templateName, holdreflect) + " " + compare + " " + c.compileIfVarsubN(node.Args[pos+2].String(), varholder, templateName, holdreflect)
}
func (c *CTemplateSet) simpleMath(varholder string, holdreflect reflect.Value, templateName string, pos int, node *parse.CommandNode, symbol string) (out string, val reflect.Value) {
leftParam, val2 := c.compileIfVarsub(node.Args[pos+1].String(), varholder, templateName, holdreflect)
rightParam, val3 := c.compileIfVarsub(node.Args[pos+2].String(), varholder, templateName, holdreflect)
if val2.IsValid() {
val = val2
} else if val3.IsValid() {
val = val3
} else {
// TODO: What does this do?
numSample := 1
val = reflect.ValueOf(numSample)
}
c.dumpSymbol(pos, node, symbol)
return leftParam + " " + symbol + " " + rightParam, val
}
func (c *CTemplateSet) compareJoin(varholder string, holdreflect reflect.Value, templateName string, pos int, node *parse.CommandNode, symbol string) (pos2 int, out string) {
c.detailf("Building %s function", symbol)
if pos == 0 {
fmt.Println("pos:", pos)
panic(symbol + " is missing a left operand")
}
if len(node.Args) <= pos {
fmt.Println("post pos:", pos)
fmt.Println("len(node.Args):", len(node.Args))
panic(symbol + " is missing a right operand")
}
left := c.compileBoolsub(node.Args[pos-1].String(), varholder, templateName, holdreflect)
_, funcExists := c.funcMap[node.Args[pos+1].String()]
var right string
if !funcExists {
right = c.compileBoolsub(node.Args[pos+1].String(), varholder, templateName, holdreflect)
}
out = left + " " + symbol + " " + right
c.detail("Left operand:", node.Args[pos-1])
c.detail("Right operand:", node.Args[pos+1])
if !funcExists {
pos++
}
c.detail("pos:", pos)
c.detail("len(node.Args):", len(node.Args))
return pos, out
}
func (c *CTemplateSet) compileIdentSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, val reflect.Value, literal bool) {
c.detail("in compileIdentSwitch")
ArgLoop:
for pos := 0; pos < len(node.Args); pos++ {
id := node.Args[pos]
c.detail("pos:", pos)
c.detail("ID:", id)
switch id.String() {
case "not":
out += "!"
case "or", "and":
var rout string
pos, rout = c.compareJoin(varholder, holdreflect, templateName, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this
out += rout
case "le": // TODO: Can we condense these comparison cases down into one?
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<=")
break ArgLoop
case "lt":
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<")
break ArgLoop
case "gt":
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">")
break ArgLoop
case "ge":
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">=")
break ArgLoop
case "eq":
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "==")
break ArgLoop
case "ne":
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "!=")
break ArgLoop
case "add":
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "+")
out += rout
val = rval
break ArgLoop
case "subtract":
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "-")
out += rout
val = rval
break ArgLoop
case "divide":
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "/")
out += rout
val = rval
break ArgLoop
case "multiply":
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "*")
out += rout
val = rval
break ArgLoop
case "dock":
var leftParam, rightParam string
// TODO: Implement string literals properly
leftOperand := node.Args[pos+1].String()
rightOperand := node.Args[pos+2].String()
if len(leftOperand) == 0 || len(rightOperand) == 0 {
panic("The left or right operand for function dock cannot be left blank")
}
if leftOperand[0] == '"' {
leftParam = leftOperand
} else {
leftParam, _ = c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
}
if rightOperand[0] == '"' {
panic("The right operand for function dock cannot be a string")
}
rightParam, val3 := c.compileIfVarsub(rightOperand, varholder, templateName, holdreflect)
if !val3.IsValid() {
panic("val3 is invalid")
}
val = val3
// TODO: Refactor this
out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n"
literal = true
break ArgLoop
case "lang":
var leftParam string
// TODO: Implement string literals properly
leftOperand := node.Args[pos+1].String()
if len(leftOperand) == 0 {
panic("The left operand for the language string cannot be left blank")
}
if leftOperand[0] == '"' {
// ! Slightly crude but it does the job
leftParam = strings.Replace(leftOperand, "\"", "", -1)
} else {
panic("Phrase names cannot be dynamic")
}
c.langIndexToName = append(c.langIndexToName, leftParam)
out = "w.Write(plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n"
literal = true
break ArgLoop
case "level":
var leftParam string
// TODO: Implement level literals
leftOperand := node.Args[pos+1].String()
if len(leftOperand) == 0 {
panic("The leftoperand for function level cannot be left blank")
}
leftParam, _ = c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
// TODO: Refactor this
out = "w.Write([]byte(phrases.GetLevelPhrase(" + leftParam + ")))\n"
literal = true
c.importMap[langPkg] = langPkg
break ArgLoop
case "scope":
literal = true
break ArgLoop
case "dyntmpl":
var nameParam, pageParam, headParam string
// TODO: Implement string literals properly
// TODO: Should we check to see if pos+3 is within the bounds of the slice?
nameOperand := node.Args[pos+1].String()
pageOperand := node.Args[pos+2].String()
headOperand := node.Args[pos+3].String()
if len(nameOperand) == 0 || len(pageOperand) == 0 || len(headOperand) == 0 {
panic("None of the three operands for function dyntmpl can be left blank")
}
if nameOperand[0] == '"' {
nameParam = nameOperand
} else {
nameParam, _ = c.compileIfVarsub(nameOperand, varholder, templateName, holdreflect)
}
if pageOperand[0] == '"' {
panic("The page operand for function dyntmpl cannot be a string")
}
if headOperand[0] == '"' {
panic("The head operand for function dyntmpl cannot be a string")
}
pageParam, val3 := c.compileIfVarsub(pageOperand, varholder, templateName, holdreflect)
if !val3.IsValid() {
panic("val3 is invalid")
}
headParam, val4 := c.compileIfVarsub(headOperand, varholder, templateName, holdreflect)
if !val4.IsValid() {
panic("val4 is invalid")
}
val = val4
// TODO: Refactor this
// TODO: Call the template function directly rather than going through RunThemeTemplate to eliminate a round of indirection?
out = "{\nerr := common.RunThemeTemplate(" + headParam + ".Theme.Name," + nameParam + "," + pageParam + ",w)\n"
out += "if err != nil {\nreturn err\n}\n}\n"
literal = true
break ArgLoop
default:
c.detail("Variable!")
if len(node.Args) > (pos + 1) {
nextNode := node.Args[pos+1].String()
if nextNode == "or" || nextNode == "and" {
continue
}
}
out += c.compileIfVarsubN(id.String(), varholder, templateName, holdreflect)
}
}
return out, val, literal
}
func (c *CTemplateSet) compileReflectSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, outVal reflect.Value) {
c.detail("in compileReflectSwitch")
firstWord := node.Args[0]
switch n := firstWord.(type) {
case *parse.FieldNode:
if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident)
for _, id := range n.Ident {
fmt.Println("Field Bit:", id)
}
}
/* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */
return c.compileIfVarsub(n.String(), varholder, templateName, holdreflect)
case *parse.ChainNode:
c.detail("Chain Node:", n.Node)
c.detail("node.Args:", node.Args)
case *parse.DotNode:
return varholder, holdreflect
case *parse.NilNode:
panic("Nil is not a command x.x")
default:
//panic("I don't know what node this is")
}
return "", outVal
}
func (c *CTemplateSet) compileIfVarsubN(varname string, varholder string, templateName string, cur reflect.Value) (out string) {
c.detail("in compileIfVarsubN")
out, _ = c.compileIfVarsub(varname, varholder, templateName, cur)
return out
}
func (c *CTemplateSet) compileIfVarsub(varname string, varholder string, templateName string, cur reflect.Value) (out string, val reflect.Value) {
c.detail("in compileIfVarsub")
if varname[0] != '.' && varname[0] != '$' {
return varname, cur
}
bits := strings.Split(varname, ".")
if varname[0] == '$' {
var res VarItemReflect
if varname[1] == '.' {
res = c.localVars[templateName]["."]
} else {
res = c.localVars[templateName][strings.TrimPrefix(bits[0], "$")]
}
out += res.Destination
cur = res.Value
if cur.Kind() == reflect.Interface {
cur = cur.Elem()
}
} else {
out += varholder
if cur.Kind() == reflect.Interface {
cur = cur.Elem()
out += ".(" + cur.Type().Name() + ")"
}
}
bits[0] = strings.TrimPrefix(bits[0], "$")
c.detail("Cur Kind:", cur.Kind())
c.detail("Cur Type:", cur.Type().Name())
for _, bit := range bits {
c.detail("Variable Field:", bit)
if bit == "" {
continue
}
// TODO: Fix this up so that it works for regular pointers and not just struct pointers. Ditto for the other cur.Kind() == reflect.Ptr we have in this file
if cur.Kind() == reflect.Ptr {
c.detail("Looping over pointer")
for cur.Kind() == reflect.Ptr {
cur = cur.Elem()
}
c.detail("Data Kind:", cur.Kind().String())
c.detail("Field Bit:", bit)
}
cur = cur.FieldByName(bit)
out += "." + bit
if cur.Kind() == reflect.Interface {
cur = cur.Elem()
out += ".(" + cur.Type().Name() + ")"
}
if !cur.IsValid() {
fmt.Println("cur: ", cur)
panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
c.detail("Data Kind:", cur.Kind())
c.detail("Data Type:", cur.Type().Name())
}
c.detail("Out Value:", out)
c.detail("Out Kind:", cur.Kind())
c.detail("Out Type:", cur.Type().Name())
for _, varItem := range c.varList {
if strings.HasPrefix(out, varItem.Destination) {
out = strings.Replace(out, varItem.Destination, varItem.Name, 1)
}
}
c.detail("Out Value:", out)
c.detail("Out Kind:", cur.Kind())
c.detail("Out Type:", cur.Type().Name())
_, ok := c.stats[out]
if ok {
c.stats[out]++
} else {
c.stats[out] = 1
}
return out, cur
}
func (c *CTemplateSet) compileBoolsub(varname string, varholder string, templateName string, val reflect.Value) string {
c.detail("in compileBoolsub")
out, val := c.compileIfVarsub(varname, varholder, templateName, val)
// TODO: What if it's a pointer or an interface? I *think* we've got pointers handled somewhere, but not interfaces which we don't know the types of at compile time
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
out += " > 0"
case reflect.Bool: // Do nothing
case reflect.String:
out += " != \"\""
case reflect.Slice, reflect.Map:
out = "len(" + out + ") != 0"
default:
fmt.Println("Variable Name:", varname)
fmt.Println("Variable Holder:", varholder)
fmt.Println("Variable Kind:", val.Kind())
panic("I don't know what this variable's type is o.o\n")
}
return out
}
func (c *CTemplateSet) compileVarsub(varname string, val reflect.Value, assLines string) (out string) {
c.detail("in compileVarsub")
// Is this a literal string?
if len(varname) != 0 && varname[0] == '"' {
return assLines + "w.Write([]byte(" + varname + "))\n"
}
for _, varItem := range c.varList {
if strings.HasPrefix(varname, varItem.Destination) {
varname = strings.Replace(varname, varItem.Destination, varItem.Name, 1)
}
}
_, ok := c.stats[varname]
if ok {
c.stats[varname]++
} else {
c.stats[varname] = 1
}
if val.Kind() == reflect.Interface {
val = val.Elem()
}
c.detail("varname: ", varname)
c.detail("assLines: ", assLines)
switch val.Kind() {
case reflect.Int:
c.importMap["strconv"] = "strconv"
out = "w.Write([]byte(strconv.Itoa(" + varname + ")))\n"
case reflect.Bool:
out = "if " + varname + " {\nw.Write([]byte(\"true\"))} else {\nw.Write([]byte(\"false\"))\n}\n"
case reflect.String:
if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") {
varname = "string(" + varname + ")"
}
out = "w.Write([]byte(" + varname + "))\n"
case reflect.Int64:
c.importMap["strconv"] = "strconv"
out = "w.Write([]byte(strconv.FormatInt(" + varname + ", 10)))\n"
default:
if !val.IsValid() {
panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
fmt.Println("Unknown Variable Name:", varname)
fmt.Println("Unknown Kind:", val.Kind())
fmt.Println("Unknown Type:", val.Type().Name())
panic("-- I don't know what this variable's type is o.o\n")
}
c.detail("out: ", out)
return assLines + out
}
func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflect.Value, node *parse.TemplateNode) (out string) {
c.detail("in compileSubtemplate")
c.detail("Template Node: ", node.Name)
fname := strings.TrimSuffix(node.Name, filepath.Ext(node.Name))
varholder := "tmpl_" + fname + "_vars"
var holdreflect reflect.Value
if node.Pipe != nil {
for _, cmd := range node.Pipe.Cmds {
firstWord := cmd.Args[0]
switch firstWord.(type) {
case *parse.DotNode:
varholder = pvarholder
holdreflect = pholdreflect
case *parse.NilNode:
panic("Nil is not a command x.x")
default:
c.detail("Unknown Node: ", firstWord)
panic("")
}
}
}
// TODO: Cascade errors back up the tree to the caller?
res, err := ioutil.ReadFile(c.fileDir + "overrides/" + node.Name)
if err != nil {
c.detail("override path: ", c.fileDir+"overrides/"+node.Name)
c.detail("override err: ", err)
res, err = ioutil.ReadFile(c.fileDir + node.Name)
if err != nil {
log.Fatal(err)
}
}
content := string(res)
if c.config.Minify {
content = minify(content)
}
tree := parse.New(node.Name, c.funcMap)
var treeSet = make(map[string]*parse.Tree)
tree, err = tree.Parse(content, "{{", "}}", treeSet, c.funcMap)
if err != nil {
log.Fatal(err)
}
c.templateList[fname] = tree
subtree := c.templateList[fname]
c.detail("subtree.Root", subtree.Root)
c.localVars[fname] = make(map[string]VarItemReflect)
c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect}
c.fragmentCursor[fname] = 0
out += c.rootIterate(subtree, varholder, holdreflect, fname)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
return out
}
// TODO: Should we rethink the way the log methods work or their names?
func (c *CTemplateSet) detail(args ...interface{}) {
if c.config.SuperDebug {
fmt.Println(args...)
}
}
func (c *CTemplateSet) detailf(left string, args ...interface{}) {
if c.config.SuperDebug {
fmt.Printf(left, args...)
}
}
func (c *CTemplateSet) error(args ...interface{}) {
if c.config.Debug {
fmt.Println(args...)
}
}