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...) } }