github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/templates/templates.go (about) 1 package tmpl 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "path/filepath" 10 "reflect" 11 "runtime/debug" 12 "strconv" 13 "strings" 14 "text/template/parse" 15 "time" 16 "unicode" 17 ) 18 19 // TODO: Turn this file into a library 20 var textOverlapList = make(map[string]int) 21 22 // TODO: Stop hard-coding this here 23 var langPkg = "github.com/Azareal/Gosora/common/phrases" 24 25 type VarItem struct { 26 Name string 27 Destination string 28 Type string 29 } 30 31 type VarItemReflect struct { 32 Name string 33 Destination string 34 Value reflect.Value 35 } 36 37 type CTemplateConfig struct { 38 Minify bool 39 Debug bool 40 SuperDebug bool 41 SkipHandles bool 42 SkipTmplPtrMap bool 43 SkipInitBlock bool 44 PackageName string 45 DockToID map[string]int 46 } 47 48 // nolint 49 type CTemplateSet struct { 50 templateList map[string]*parse.Tree 51 fileDir string 52 logDir string 53 funcMap map[string]interface{} 54 importMap map[string]string 55 //templateFragmentCount map[string]int 56 fragOnce map[string]bool 57 fragmentCursor map[string]int 58 FragOut []OutFrag 59 fragBuf []Fragment 60 varList map[string]VarItem 61 localVars map[string]map[string]VarItemReflect 62 hasDispInt bool 63 localDispStructIndex int 64 langIndexToName []string 65 guestOnly bool 66 memberOnly bool 67 stats map[string]int 68 //tempVars map[string]string 69 config CTemplateConfig 70 baseImportMap map[string]string 71 buildTags string 72 73 overridenTrack map[string]map[string]bool 74 overridenRoots map[string]map[string]bool 75 themeName string 76 perThemeTmpls map[string]bool 77 78 logger *log.Logger 79 loggerf *os.File 80 lang string 81 82 fsb strings.Builder 83 } 84 85 func NewCTemplateSet(in string, logDir ...string) *CTemplateSet { 86 var llogDir string 87 if len(logDir) > 0 { 88 llogDir = logDir[0] 89 } 90 f, err := os.OpenFile(llogDir+"tmpls-"+in+"-"+strconv.FormatInt(time.Now().Unix(), 10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) 91 if err != nil { 92 panic(err) 93 } 94 return &CTemplateSet{ 95 config: CTemplateConfig{ 96 PackageName: "main", 97 }, 98 logDir: llogDir, 99 baseImportMap: map[string]string{}, 100 overridenRoots: map[string]map[string]bool{}, 101 funcMap: map[string]interface{}{ 102 "and": "&&", 103 "not": "!", 104 "or": "||", 105 "eq": "==", 106 "ge": ">=", 107 "gt": ">", 108 "le": "<=", 109 "lt": "<", 110 "ne": "!=", 111 "add": "+", 112 "subtract": "-", 113 "multiply": "*", 114 "divide": "/", 115 "dock": true, 116 "hasWidgets": true, 117 "elapsed": true, 118 "lang": true, 119 "langf": true, 120 "level": true, 121 "bunit": true, 122 "abstime": true, 123 "reltime": true, 124 "scope": true, 125 "dyntmpl": true, 126 "ptmpl": true, 127 "js": true, 128 "index": true, 129 "flush": true, 130 "res": true, 131 }, 132 logger: log.New(f, "", log.LstdFlags), 133 loggerf: f, 134 lang: in, 135 } 136 } 137 138 func (c *CTemplateSet) SetConfig(config CTemplateConfig) { 139 if config.PackageName == "" { 140 config.PackageName = "main" 141 } 142 c.config = config 143 } 144 145 func (c *CTemplateSet) GetConfig() CTemplateConfig { 146 return c.config 147 } 148 149 func (c *CTemplateSet) SetBaseImportMap(importMap map[string]string) { 150 c.baseImportMap = importMap 151 } 152 153 func (c *CTemplateSet) SetBuildTags(tags string) { 154 c.buildTags = tags 155 } 156 157 func (c *CTemplateSet) SetOverrideTrack(overriden map[string]map[string]bool) { 158 c.overridenTrack = overriden 159 } 160 161 func (c *CTemplateSet) GetOverridenRoots() map[string]map[string]bool { 162 return c.overridenRoots 163 } 164 165 func (c *CTemplateSet) SetThemeName(name string) { 166 c.themeName = name 167 } 168 169 func (c *CTemplateSet) SetPerThemeTmpls(perThemeTmpls map[string]bool) { 170 c.perThemeTmpls = perThemeTmpls 171 } 172 173 func (c *CTemplateSet) ResetLogs(in string) { 174 f, err := os.OpenFile(c.logDir+"tmpls-"+in+"-"+strconv.FormatInt(time.Now().Unix(), 10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) 175 if err != nil { 176 panic(err) 177 } 178 c.logger = log.New(f, "", log.LstdFlags) 179 c.loggerf = f 180 } 181 182 type SkipBlock struct { 183 Frags map[int]int 184 LastCount int 185 ClosestFragSkip int 186 } 187 type Skipper struct { 188 Count int 189 Index int 190 } 191 192 type OutFrag struct { 193 TmplName string 194 Index int 195 Body string 196 } 197 198 func (c *CTemplateSet) buildImportList() (importList string) { 199 if len(c.importMap) == 0 { 200 return "" 201 } 202 var ilsb strings.Builder 203 ilsb.Grow(10 + (len(c.importMap) * 3)) 204 ilsb.WriteString("import (") 205 for _, item := range c.importMap { 206 ispl := strings.Split(item, " ") 207 if len(ispl) > 1 { 208 //importList += ispl[0] + " \"" + ispl[1] + "\"\n" 209 ilsb.WriteString(ispl[0]) 210 ilsb.WriteString(" \"") 211 ilsb.WriteString(ispl[1]) 212 ilsb.WriteString("\"\n") 213 } else { 214 //importList += "\"" + item + "\"\n" 215 ilsb.WriteString("\"") 216 ilsb.WriteString(item) 217 ilsb.WriteString("\"\n") 218 } 219 } 220 //importList += ")\n" 221 ilsb.WriteString(")\n") 222 return ilsb.String() 223 } 224 225 func (c *CTemplateSet) CompileByLoggedin(name, fileDir, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (stub, gout, mout string, e error) { 226 c.importMap = map[string]string{} 227 for index, item := range c.baseImportMap { 228 c.importMap[index] = item 229 } 230 for _, importItem := range imports { 231 c.importMap[importItem] = importItem 232 } 233 c.importMap["errors"] = "errors" 234 importList := c.buildImportList() 235 236 fname := strings.TrimSuffix(name, filepath.Ext(name)) 237 if c.themeName != "" { 238 _, ok := c.perThemeTmpls[fname] 239 if !ok { 240 return "", "", "", nil 241 } 242 fname += "_" + c.themeName 243 } 244 c.importMap["github.com/Azareal/Gosora/common"] = "c github.com/Azareal/Gosora/common" 245 246 c.fsb.Reset() 247 stub = `package ` + c.config.PackageName + "\n" + importList + "\n" 248 249 if !c.config.SkipInitBlock { 250 //stub += "// nolint\nfunc init() {\n" 251 c.fsb.WriteString("// nolint\nfunc init() {\n") 252 if !c.config.SkipHandles && c.themeName == "" { 253 //stub += "\tc.Tmpl_" + fname + "_handle = Tmpl_" + fname + "\n" 254 c.fsb.WriteString("\tc.Tmpl_") 255 c.fsb.WriteString(fname) 256 c.fsb.WriteString("_handle = Tmpl_") 257 c.fsb.WriteString(fname) 258 //stub += "\tc.Ctemplates = append(c.Ctemplates,\"" + fname + "\")\n" 259 c.fsb.WriteString("\n\tc.Ctemplates = append(c.Ctemplates,\"") 260 c.fsb.WriteString(fname) 261 c.fsb.WriteString("\")\n") 262 } 263 if !c.config.SkipTmplPtrMap { 264 //stub += "tmpl := Tmpl_" + fname + "\n" 265 c.fsb.WriteString("tmpl := Tmpl_") 266 c.fsb.WriteString(fname) 267 //stub += "\tc.TmplPtrMap[\"" + fname + "\"] = &tmpl\n" 268 c.fsb.WriteString("\n\tc.TmplPtrMap[\"") 269 c.fsb.WriteString(fname) 270 c.fsb.WriteString("\"] = &tmpl\n") 271 //stub += "\tc.TmplPtrMap[\"o_" + fname + "\"] = tmpl\n" 272 c.fsb.WriteString("\tc.TmplPtrMap[\"o_") 273 c.fsb.WriteString(fname) 274 c.fsb.WriteString("\"] = tmpl\n") 275 } 276 //stub += "}\n\n" 277 c.fsb.WriteString("}\n\n") 278 } 279 stub += c.fsb.String() 280 281 // TODO: Try to remove this redundant interface cast 282 stub += ` 283 // nolint 284 func Tmpl_` + fname + `(tmpl_i interface{}, w io.Writer) error { 285 tmpl_vars, ok := tmpl_i.(` + expects + `) 286 if !ok { 287 return errors.New("invalid page struct value") 288 } 289 if tmpl_vars.CurrentUser.Loggedin { 290 return Tmpl_` + fname + `_member(tmpl_i, w) 291 } 292 return Tmpl_` + fname + `_guest(tmpl_i, w) 293 }` 294 295 c.fileDir = fileDir 296 content, e := c.loadTemplate(c.fileDir, name) 297 if e != nil { 298 c.detail("bailing out:", e) 299 return "", "", "", e 300 } 301 302 c.guestOnly = true 303 gout, e = c.compile(name, content, expects, expectsInt, varList, imports...) 304 if e != nil { 305 return "", "", "", e 306 } 307 c.guestOnly = false 308 309 c.memberOnly = true 310 mout, e = c.compile(name, content, expects, expectsInt, varList, imports...) 311 c.memberOnly = false 312 313 return stub, gout, mout, e 314 } 315 316 func (c *CTemplateSet) Compile(name, fileDir, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, e error) { 317 if c.config.Debug { 318 c.logger.Println("Compiling template '" + name + "'") 319 } 320 c.fileDir = fileDir 321 content, e := c.loadTemplate(c.fileDir, name) 322 if e != nil { 323 c.detail("bailing out:", e) 324 return "", e 325 } 326 327 return c.compile(name, content, expects, expectsInt, varList, imports...) 328 } 329 330 func (c *CTemplateSet) compile(name, content, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) { 331 defer func() { 332 if r := recover(); r != nil { 333 fmt.Println(r) 334 debug.PrintStack() 335 if err := c.loggerf.Sync(); err != nil { 336 fmt.Println(err) 337 } 338 log.Fatal("") 339 return 340 } 341 }() 342 //c.dumpCall("compile", name, content, expects, expectsInt, varList, imports) 343 //c.detailf("c: %+v\n", c) 344 c.importMap = map[string]string{} 345 for index, item := range c.baseImportMap { 346 c.importMap[index] = item 347 } 348 c.importMap["errors"] = "errors" 349 for _, importItem := range imports { 350 c.importMap[importItem] = importItem 351 } 352 353 c.varList = varList 354 c.hasDispInt = false 355 c.localDispStructIndex = 0 356 c.stats = make(map[string]int) 357 358 //tree := parse.New(name, c.funcMap) 359 //treeSet := make(map[string]*parse.Tree) 360 treeSet, err := parse.Parse(name, content, "{{", "}}", c.funcMap) 361 if err != nil { 362 return "", err 363 } 364 c.detail(name) 365 c.detailf("treeSet: %+v\n", treeSet) 366 367 fname := strings.TrimSuffix(name, filepath.Ext(name)) 368 if c.themeName != "" { 369 _, ok := c.perThemeTmpls[fname] 370 if !ok { 371 c.detail("fname not in c.perThemeTmpls") 372 c.detail("c.perThemeTmpls", c.perThemeTmpls) 373 return "", nil 374 } 375 fname += "_" + c.themeName 376 } 377 if c.guestOnly { 378 fname += "_guest" 379 } else if c.memberOnly { 380 fname += "_member" 381 } 382 383 c.detail("root overridenTrack loop") 384 c.detail("fname:", fname) 385 for themeName, track := range c.overridenTrack { 386 c.detail("themeName:", themeName) 387 c.detailf("track: %+v\n", track) 388 croot, ok := c.overridenRoots[themeName] 389 if !ok { 390 croot = make(map[string]bool) 391 c.overridenRoots[themeName] = croot 392 } 393 c.detailf("croot: %+v\n", croot) 394 for tmplName, _ := range track { 395 cname := tmplName 396 if c.guestOnly { 397 cname += "_guest" 398 } else if c.memberOnly { 399 cname += "_member" 400 } 401 c.detail("cname:", cname) 402 if fname == cname { 403 c.detail("match") 404 croot[strings.TrimSuffix(strings.TrimSuffix(fname, "_guest"), "_member")] = true 405 } else { 406 c.detail("no match") 407 } 408 } 409 } 410 c.detailf("c.overridenRoots: %+v\n", c.overridenRoots) 411 412 var outBuf []OutBufferFrame 413 rootHold := "tmpl_" + fname + "_vars" 414 //rootHold := "tmpl_vars" 415 con := CContext{ 416 RootHolder: rootHold, 417 VarHolder: rootHold, 418 HoldReflect: reflect.ValueOf(expectsInt), 419 RootTemplateName: fname, 420 TemplateName: fname, 421 OutBuf: &outBuf, 422 } 423 424 c.templateList = map[string]*parse.Tree{} 425 for nname, tree := range treeSet { 426 if name == nname { 427 c.templateList[fname] = tree 428 } else { 429 if strings.HasPrefix(nname, ".html") { 430 nname = strings.TrimSuffix(nname, ".html") 431 } 432 c.templateList[nname] = tree 433 } 434 } 435 c.detailf("c.templateList: %+v\n", c.templateList) 436 437 c.localVars = make(map[string]map[string]VarItemReflect) 438 c.localVars[fname] = make(map[string]VarItemReflect) 439 c.localVars[fname]["."] = VarItemReflect{".", con.VarHolder, con.HoldReflect} 440 if c.fragOnce == nil { 441 c.fragOnce = make(map[string]bool) 442 } 443 c.fragmentCursor = map[string]int{fname: 0} 444 c.fragBuf = nil 445 c.langIndexToName = nil 446 447 // TODO: Is this the first template loaded in? We really should have some sort of constructor for CTemplateSet 448 //if c.templateFragmentCount == nil { 449 // c.templateFragmentCount = make(map[string]int) 450 //} 451 //c.detailf("c: %+v\n", c) 452 453 c.detailf("name: %+v\n", name) 454 c.detailf("fname: %+v\n", fname) 455 startIndex := con.StartTemplate("") 456 ttree := c.templateList[fname] 457 if ttree == nil { 458 panic("ttree is nil") 459 } 460 c.rootIterate(ttree, con) 461 con.EndTemplate("") 462 c.afterTemplate(con, startIndex) 463 //c.templateFragmentCount[fname] = c.fragmentCursor[fname] + 1 464 465 _, ok := c.fragOnce[fname] 466 if !ok { 467 c.fragOnce[fname] = true 468 } 469 if len(c.langIndexToName) > 0 { 470 c.importMap[langPkg] = langPkg 471 } 472 // TODO: Simplify this logic by doing some reordering? 473 if c.lang == "normal" { 474 c.importMap["net/http"] = "net/http" 475 } 476 importList := c.buildImportList() 477 478 c.fsb.Reset() 479 //var fout string 480 if c.buildTags != "" { 481 //fout += "// +build " + c.buildTags + "\n\n" 482 c.fsb.WriteString("// +build ") 483 c.fsb.WriteString(c.buildTags) 484 c.fsb.WriteString("\n\n") 485 } 486 //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" 487 c.fsb.WriteString("// 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") 488 //fout += "package " + c.config.PackageName + "\n" + importList + "\n" 489 c.fsb.WriteString("package ") 490 c.fsb.WriteString(c.config.PackageName) 491 c.fsb.WriteString("\n") 492 c.fsb.WriteString(importList) 493 c.fsb.WriteString("\n") 494 495 if c.lang == "js" { 496 //var l string 497 if len(c.langIndexToName) > 0 { 498 /*var lsb strings.Builder 499 lsb.Grow(len(c.langIndexToName) * (1 + 2)) 500 for i, name := range c.langIndexToName { 501 //l += `"` + name + `"` + ",\n" 502 if i == 0 { 503 //l += `"` + name + `"` 504 lsb.WriteRune('"') 505 } else { 506 //l += `,"` + name + `"` 507 lsb.WriteString(`,"`) 508 } 509 lsb.WriteString(name) 510 lsb.WriteRune('"') 511 }*/ 512 //fout += "if(tmplInits===undefined) var tmplInits={}\n" 513 c.fsb.WriteString("if(tmplInits===undefined) var tmplInits={}\n") 514 //fout += "tmplInits[\"tmpl_" + fname + "\"]=[" + lsb.String() + "]" 515 c.fsb.WriteString("tmplInits[\"tmpl_") 516 c.fsb.WriteString(fname) 517 c.fsb.WriteString("\"]=[") 518 519 c.fsb.Grow(len(c.langIndexToName) * (1 + 2)) 520 for i, name := range c.langIndexToName { 521 //l += `"` + name + `"` + ",\n" 522 if i == 0 { 523 //l += `"` + name + `"` 524 c.fsb.WriteRune('"') 525 } else { 526 //l += `,"` + name + `"` 527 c.fsb.WriteString(`,"`) 528 } 529 c.fsb.WriteString(name) 530 c.fsb.WriteRune('"') 531 } 532 533 c.fsb.WriteString("]") 534 } else { 535 //fout += "if(tmplInits===undefined) var tmplInits={}\n" 536 c.fsb.WriteString("if(tmplInits===undefined) var tmplInits={}\n") 537 //fout += "tmplInits[\"tmpl_" + fname + "\"]=[]" 538 c.fsb.WriteString("tmplInits[\"tmpl_") 539 c.fsb.WriteString(fname) 540 c.fsb.WriteString("\"]=[]") 541 } 542 /*if len(l) > 0 { 543 l = "\n" + l 544 }*/ 545 } else if !c.config.SkipInitBlock { 546 if len(c.langIndexToName) > 0 { 547 //fout += "var " + fname + "_tmpl_phrase_id int\n\n" 548 c.fsb.WriteString("var ") 549 c.fsb.WriteString(fname) 550 c.fsb.WriteString("_tmpl_phrase_id int\n\n") 551 c.fsb.WriteString("var ") 552 c.fsb.WriteString(fname) 553 if len(c.langIndexToName) > 1 { 554 //fout += "var " + fname + "_phrase_arr [" + strconv.Itoa(len(c.langIndexToName)) + "][]byte\n\n" 555 c.fsb.WriteString("_phrase_arr [") 556 c.fsb.WriteString(strconv.Itoa(len(c.langIndexToName))) 557 c.fsb.WriteString("][]byte\n\n") 558 } else { 559 //fout += "var " + fname + "_phrase []byte\n\n" 560 c.fsb.WriteString("_phrase []byte\n\n") 561 } 562 } 563 //fout += "// nolint\nfunc init() {\n" 564 c.fsb.WriteString("// nolint\nfunc init() {\n") 565 566 if !c.config.SkipHandles && c.themeName == "" { 567 //fout += "\tc.Tmpl_" + fname + "_handle = Tmpl_" + fname 568 c.fsb.WriteString("\tc.Tmpl_") 569 c.fsb.WriteString(fname) 570 c.fsb.WriteString("_handle = Tmpl_") 571 c.fsb.WriteString(fname) 572 //fout += "\n\tc.Ctemplates = append(c.Ctemplates,\"" + fname + "\")\n" 573 c.fsb.WriteString("\n\tc.Ctemplates = append(c.Ctemplates,\"") 574 c.fsb.WriteString(fname) 575 c.fsb.WriteString("\")\n") 576 } 577 578 if !c.config.SkipTmplPtrMap { 579 //fout += "tmpl := Tmpl_" + fname + "\n" 580 c.fsb.WriteString("tmpl := Tmpl_") 581 c.fsb.WriteString(fname) 582 //fout += "\tc.TmplPtrMap[\"" + fname + "\"] = &tmpl\n" 583 c.fsb.WriteString("\n\tc.TmplPtrMap[\"") 584 c.fsb.WriteString(fname) 585 c.fsb.WriteString("\"] = &tmpl\n") 586 //fout += "\tc.TmplPtrMap[\"o_" + fname + "\"] = tmpl\n" 587 c.fsb.WriteString("\tc.TmplPtrMap[\"o_") 588 c.fsb.WriteString(fname) 589 c.fsb.WriteString("\"] = tmpl\n") 590 } 591 if len(c.langIndexToName) > 0 { 592 //fout += "\t" + fname + "_tmpl_phrase_id = phrases.RegisterTmplPhraseNames([]string{\n" 593 c.fsb.WriteString("\t") 594 c.fsb.WriteString(fname) 595 c.fsb.WriteString("_tmpl_phrase_id = phrases.RegisterTmplPhraseNames([]string{\n") 596 for _, name := range c.langIndexToName { 597 //fout += "\t\t" + `"` + name + `"` + ",\n" 598 c.fsb.WriteString("\t\t\"") 599 c.fsb.WriteString(name) 600 c.fsb.WriteString("\",\n") 601 } 602 //fout += "\t})\n" 603 c.fsb.WriteString("\t})\n") 604 605 if len(c.langIndexToName) > 1 { 606 /*fout += ` phrases.AddTmplIndexCallback(func(phraseSet [][]byte) { 607 copy(` + fname + `_phrase_arr[:], phraseSet) 608 }) 609 `*/ 610 c.fsb.WriteString(` phrases.AddTmplIndexCallback(func(phraseSet [][]byte) { 611 copy(`) 612 c.fsb.WriteString(fname) 613 c.fsb.WriteString(`_phrase_arr[:], phraseSet) 614 }) 615 `) 616 } else { 617 /*fout += ` phrases.AddTmplIndexCallback(func(phraseSet [][]byte) { 618 ` + fname + `_phrase = phraseSet[0] 619 }) 620 `*/ 621 c.fsb.WriteString(` phrases.AddTmplIndexCallback(func(phraseSet [][]byte) { 622 `) 623 c.fsb.WriteString(fname) 624 c.fsb.WriteString(`_phrase = phraseSet[0] 625 }) 626 `) 627 } 628 } 629 //fout += "}\n\n" 630 c.fsb.WriteString("}\n\n") 631 } 632 633 c.fsb.WriteString("// nolint\nfunc Tmpl_") 634 c.fsb.WriteString(fname) 635 if c.lang == "normal" { 636 /*fout += "// nolint\nfunc Tmpl_" + fname + "(tmpl_i interface{}, w io.Writer) error {\n" 637 fout += `tmpl_` + fname + `_vars, ok := tmpl_i.(` + expects + `) 638 if !ok { 639 return errors.New("invalid page struct value") 640 } 641 `*/ 642 c.fsb.WriteString("(tmpl_i interface{}, w io.Writer) error {\n") 643 644 c.fsb.WriteString(`tmpl_`) 645 c.fsb.WriteString(fname) 646 c.fsb.WriteString(`_vars, ok := tmpl_i.(`) 647 c.fsb.WriteString(expects) 648 c.fsb.WriteString(`) 649 if !ok { 650 return errors.New("invalid page struct value") 651 } 652 var iw http.ResponseWriter 653 if gzw, ok := w.(c.GzipResponseWriter); ok { 654 iw = gzw.ResponseWriter 655 w = gzw.Writer 656 } 657 _ = iw 658 var tmp []byte 659 _ = tmp 660 `) 661 } else { 662 //fout += "// nolint\nfunc Tmpl_" + fname + "(tmpl_" + fname + "_vars interface{}, w io.Writer) error {\n" 663 c.fsb.WriteString("(tmpl_") 664 c.fsb.WriteString(fname) 665 c.fsb.WriteString("_vars interface{}, w io.Writer) error {\n") 666 //fout += "// nolint\nfunc Tmpl_" + fname + "(tmpl_vars interface{}, w io.Writer) error {\n" 667 } 668 669 //var fsb strings.Builder 670 if len(c.langIndexToName) > 0 { 671 //fout += "//var plist = phrases.GetTmplPhrasesBytes(" + fname + "_tmpl_phrase_id)\n" 672 c.fsb.WriteString("//var plist = phrases.GetTmplPhrasesBytes(") 673 c.fsb.WriteString(fname) 674 c.fsb.WriteString("_tmpl_phrase_id)\n") 675 676 //fout += "if len(plist) > 0 {\n_ = plist[len(plist)-1]\n}\n" 677 //fout += "var plist = " + fname + "_phrase_arr\n" 678 } 679 680 //var varString string 681 //var vssb strings.Builder 682 c.fsb.Grow(10 + 3) 683 for _, varItem := range c.varList { 684 //varString += "var " + varItem.Name + " " + varItem.Type + " = " + varItem.Destination + "\n" 685 c.fsb.WriteString("var ") 686 c.fsb.WriteString(varItem.Name) 687 c.fsb.WriteRune(' ') 688 c.fsb.WriteString(varItem.Type) 689 c.fsb.WriteString(" = ") 690 c.fsb.WriteString(varItem.Destination) 691 c.fsb.WriteString("\n") 692 } 693 694 //c.fsb.WriteString(varString) 695 //fout += varString 696 skipped := make(map[string]*SkipBlock) // map[templateName]*SkipBlock{map[atIndexAndAfter]skipThisMuch,lastCount} 697 698 writeTextFrame := func(tmplName string, index int) { 699 out := "w.Write(" + tmplName + "_frags[" + strconv.Itoa(index) + "]" + ")\n" 700 c.detail("writing ", out) 701 //fout += out 702 c.fsb.WriteString(out) 703 } 704 705 for fid := 0; len(outBuf) > fid; fid++ { 706 fr := outBuf[fid] 707 c.detail(fr.Type + " frame") 708 switch { 709 case fr.Type == "text": 710 c.detail(fr) 711 oid := fid 712 c.detail("oid:", oid) 713 skipBlock, ok := skipped[fr.TemplateName] 714 if !ok { 715 skipBlock = &SkipBlock{make(map[int]int), 0, 0} 716 skipped[fr.TemplateName] = skipBlock 717 } 718 skip := skipBlock.LastCount 719 c.detailf("skipblock %+v\n", skipBlock) 720 //var count int 721 for len(outBuf) > fid+1 && outBuf[fid+1].Type == "text" && outBuf[fid+1].TemplateName == fr.TemplateName { 722 c.detail("pre fid:", fid) 723 //count++ 724 next := outBuf[fid+1] 725 c.detail("next frame:", next) 726 c.detail("frame frag:", c.fragBuf[fr.Extra2.(int)]) 727 c.detail("next frag:", c.fragBuf[next.Extra2.(int)]) 728 c.fragBuf[fr.Extra2.(int)].Body += c.fragBuf[next.Extra2.(int)].Body 729 c.fragBuf[next.Extra2.(int)].Seen = true 730 fid++ 731 skipBlock.LastCount++ 732 skipBlock.Frags[fr.Extra.(int)] = skipBlock.LastCount 733 c.detail("post fid:", fid) 734 } 735 writeTextFrame(fr.TemplateName, fr.Extra.(int)-skip) 736 case fr.Type == "varsub" || fr.Type == "cvarsub": 737 //fout += "w.Write(" + fr.Body + ")\n" 738 c.fsb.WriteString("w.Write(") 739 c.fsb.WriteString(fr.Body) 740 c.fsb.WriteString(")\n") 741 case fr.Type == "lang": 742 //fout += "w.Write(plist[" + strconv.Itoa(fr.Extra.(int)) + "])\n" 743 c.fsb.WriteString("w.Write(") 744 c.fsb.WriteString(fname) 745 if len(c.langIndexToName) == 1 { 746 //fout += "w.Write(" + fname + "_phrase)\n" 747 c.fsb.WriteString("_phrase)\n") 748 } else { 749 //fout += "w.Write(" + fname + "_phrase_arr[" + strconv.Itoa(fr.Extra.(int)) + "])\n" 750 c.fsb.WriteString("_phrase_arr[") 751 c.fsb.WriteString(strconv.Itoa(fr.Extra.(int))) 752 c.fsb.WriteString("])\n") 753 } 754 //case fr.Type == "identifier": 755 default: 756 //fout += fr.Body 757 c.fsb.WriteString(fr.Body) 758 } 759 } 760 //fout += "return nil\n}\n" 761 c.fsb.WriteString("return nil\n}\n") 762 //fout += c.fsb.String() 763 764 writeFrag := func(tmplName string, index int, body string) { 765 //c.detail("writing ", fragmentPrefix) 766 c.FragOut = append(c.FragOut, OutFrag{tmplName, index, body}) 767 } 768 769 for _, frag := range c.fragBuf { 770 c.detail("frag:", frag) 771 if frag.Seen { 772 c.detail("invisible") 773 continue 774 } 775 // TODO: What if the same template is invoked in multiple spots in a template? 776 skipBlock := skipped[frag.TemplateName] 777 skip := skipBlock.Frags[skipBlock.ClosestFragSkip] 778 _, ok := skipBlock.Frags[frag.Index] 779 if ok { 780 skipBlock.ClosestFragSkip = frag.Index 781 } 782 c.detailf("skipblock %+v\n", skipBlock) 783 c.detail("skipping ", skip) 784 index := frag.Index - skip 785 if index < 0 { 786 index = 0 787 } 788 writeFrag(frag.TemplateName, index, frag.Body) 789 } 790 791 fout := strings.Replace(c.fsb.String(), `)) 792 w.Write([]byte(`, " + ", -1) 793 fout = strings.Replace(fout, "` + `", "", -1) 794 795 if c.config.Debug { 796 for index, count := range c.stats { 797 c.logger.Println(index+": ", strconv.Itoa(count)) 798 } 799 c.logger.Println(" ") 800 } 801 c.detail("Output!") 802 c.detail(fout) 803 return fout, nil 804 } 805 806 func (c *CTemplateSet) rootIterate(tree *parse.Tree, con CContext) { 807 c.dumpCall("rootIterate", tree, con) 808 if tree.Root == nil { 809 c.detailf("tree: %+v\n", tree) 810 panic("tree root node is empty") 811 } 812 c.detail(tree.Root) 813 for _, node := range tree.Root.Nodes { 814 c.detail("Node:", node.String()) 815 c.compileSwitch(con, node) 816 } 817 c.retCall("rootIterate") 818 } 819 820 func inSlice(haystack []string, expr string) bool { 821 for _, needle := range haystack { 822 if needle == expr { 823 return true 824 } 825 } 826 return false 827 } 828 829 func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) { 830 c.dumpCall("compileSwitch", con, node) 831 defer c.retCall("compileSwitch") 832 switch node := node.(type) { 833 case *parse.ActionNode: 834 c.detail("Action Node") 835 if node.Pipe == nil { 836 break 837 } 838 for _, cmd := range node.Pipe.Cmds { 839 c.compileSubSwitch(con, cmd) 840 } 841 case *parse.IfNode: 842 c.detail("If Node:") 843 c.detail("node.Pipe", node.Pipe) 844 var expr string 845 for _, cmd := range node.Pipe.Cmds { 846 c.detail("If Node Bit:", cmd) 847 c.detail("Bit Type:", reflect.ValueOf(cmd).Type().Name()) 848 exprStep := c.compileExprSwitch(con, cmd) 849 expr += exprStep 850 c.detail("Expression Step:", exprStep) 851 } 852 853 c.detail("Expression:", expr) 854 // Simple member / guest optimisation for now 855 // TODO: Expand upon this 856 userExprs, negUserExprs := buildUserExprs(con.RootHolder) 857 if c.guestOnly { 858 c.detail("optimising away member branch") 859 if inSlice(userExprs, expr) { 860 c.detail("positive conditional:", expr) 861 if node.ElseList != nil { 862 c.compileSwitch(con, node.ElseList) 863 } 864 return 865 } else if inSlice(negUserExprs, expr) { 866 c.detail("negative conditional:", expr) 867 c.compileSwitch(con, node.List) 868 return 869 } 870 } else if c.memberOnly { 871 c.detail("optimising away guest branch") 872 if (con.RootHolder + ".CurrentUser.Loggedin") == expr { 873 c.detail("positive conditional:", expr) 874 c.compileSwitch(con, node.List) 875 return 876 } else if ("!" + con.RootHolder + ".CurrentUser.Loggedin") == expr { 877 c.detail("negative conditional:", expr) 878 if node.ElseList != nil { 879 c.compileSwitch(con, node.ElseList) 880 } 881 return 882 } 883 } 884 885 // simple constant folding 886 if expr == "true" { 887 c.compileSwitch(con, node.List) 888 return 889 } else if expr == "false" { 890 c.compileSwitch(con, node.ElseList) 891 return 892 } 893 894 var startIf int 895 var nilIf = strings.HasPrefix(expr, con.RootHolder) && strings.HasSuffix(expr, "!=nil") 896 if nilIf { 897 startIf = con.StartIfPtr("if " + expr + " {\n") 898 } else { 899 startIf = con.StartIf("if " + expr + " {\n") 900 } 901 c.compileSwitch(con, node.List) 902 if node.ElseList == nil { 903 c.detail("Selected Branch 1") 904 con.EndIf(startIf, "}\n") 905 if nilIf { 906 c.afterTemplate(con, startIf) 907 } 908 } else { 909 c.detail("Selected Branch 2") 910 con.EndIf(startIf, "}") 911 if nilIf { 912 c.afterTemplate(con, startIf) 913 } 914 con.Push("startelse", " else {\n") 915 c.compileSwitch(con, node.ElseList) 916 con.Push("endelse", "}\n") 917 } 918 case *parse.ListNode: 919 c.detailf("List Node: %+v\n", node) 920 for _, subnode := range node.Nodes { 921 c.compileSwitch(con, subnode) 922 } 923 case *parse.RangeNode: 924 c.compileRangeNode(con, node) 925 case *parse.TemplateNode: 926 c.compileSubTemplate(con, node) 927 case *parse.TextNode: 928 c.addText(con, node.Text) 929 default: 930 c.unknownNode(node) 931 } 932 } 933 934 func (c *CTemplateSet) addText(con CContext, text []byte) { 935 c.dumpCall("addText", con, text) 936 tmpText := bytes.TrimSpace(text) 937 if len(tmpText) == 0 { 938 return 939 } 940 nodeText := string(text) 941 c.detail("con.TemplateName:", con.TemplateName) 942 fragIndex := c.fragmentCursor[con.TemplateName] 943 _, ok := c.fragOnce[con.TemplateName] 944 c.fragBuf = append(c.fragBuf, Fragment{nodeText, con.TemplateName, fragIndex, ok}) 945 con.PushText(strconv.Itoa(fragIndex), fragIndex, len(c.fragBuf)-1) 946 c.fragmentCursor[con.TemplateName] = fragIndex + 1 947 } 948 949 func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) { 950 c.dumpCall("compileRangeNode", con, node) 951 defer c.retCall("compileRangeNode") 952 c.detail("node.Pipe:", node.Pipe) 953 var expr string 954 var outVal reflect.Value 955 for _, cmd := range node.Pipe.Cmds { 956 c.detail("Range Bit:", cmd) 957 // ! This bit is slightly suspect, hm. 958 expr, outVal = c.compileReflectSwitch(con, cmd) 959 } 960 c.detail("Expr:", expr) 961 c.detail("Range Kind Switch!") 962 963 startIf := func(item reflect.Value, useCopy bool) { 964 sIndex := con.StartIf("if len(" + expr + ")!=0 {\n") 965 startIndex := con.StartLoop("for _, item := range " + expr + " {\n") 966 ccon := con 967 var depth string 968 if ccon.VarHolder == "item" { 969 depth = strings.TrimPrefix(ccon.VarHolder, "item") 970 if depth != "" { 971 idepth, err := strconv.Atoi(depth) 972 if err != nil { 973 panic(err) 974 } 975 depth = strconv.Itoa(idepth + 1) 976 } 977 } 978 ccon.VarHolder = "item" + depth 979 ccon.HoldReflect = item 980 c.compileSwitch(ccon, node.List) 981 if con.LastBufIndex() == startIndex { 982 con.DiscardAndAfter(startIndex - 1) 983 return 984 } 985 con.EndLoop("}\n") 986 c.afterTemplate(con, startIndex) 987 if node.ElseList != nil { 988 con.EndIf(sIndex, "}") 989 con.Push("startelse", " else {\n") 990 if !useCopy { 991 ccon = con 992 } 993 c.compileSwitch(ccon, node.ElseList) 994 con.Push("endelse", "}\n") 995 } else { 996 con.EndIf(sIndex, "}\n") 997 } 998 } 999 1000 switch outVal.Kind() { 1001 case reflect.Map: 1002 var item reflect.Value 1003 for _, key := range outVal.MapKeys() { 1004 item = outVal.MapIndex(key) 1005 } 1006 c.detail("Range item:", item) 1007 if !item.IsValid() { 1008 c.critical("expr:", expr) 1009 c.critical("con.VarHolder", con.VarHolder) 1010 panic("item" + "^\n" + "Invalid map. Maybe, it doesn't have any entries for the template engine to analyse?") 1011 } 1012 startIf(item, true) 1013 case reflect.Slice: 1014 if outVal.Len() == 0 { 1015 c.critical("expr:", expr) 1016 c.critical("con.VarHolder", con.VarHolder) 1017 panic("The sample data needs at-least one or more elements for the slices. We're looking into removing this requirement at some point!") 1018 } 1019 startIf(outVal.Index(0), false) 1020 case reflect.Invalid: 1021 return 1022 } 1023 } 1024 1025 // ! Temporary, we probably want something that is good with non-struct pointers too 1026 // For compileSubSwitch and compileSubTemplate 1027 func (c *CTemplateSet) skipStructPointers(cur reflect.Value, id string) reflect.Value { 1028 if cur.Kind() == reflect.Ptr { 1029 c.detail("Looping over pointer") 1030 for cur.Kind() == reflect.Ptr { 1031 cur = cur.Elem() 1032 } 1033 c.detail("Data Kind:", cur.Kind().String()) 1034 c.detail("Field Bit:", id) 1035 } 1036 return cur 1037 } 1038 1039 // For compileSubSwitch and compileSubTemplate 1040 func (c *CTemplateSet) checkIfValid(cur reflect.Value, varHolder string, holdReflect reflect.Value, varBit string, multiline bool) { 1041 if !cur.IsValid() { 1042 c.critical("Debug Data:") 1043 c.critical("Holdreflect:", holdReflect) 1044 c.critical("Holdreflect.Kind():", holdReflect.Kind()) 1045 if !c.config.SuperDebug { 1046 c.critical("cur.Kind():", cur.Kind().String()) 1047 } 1048 c.critical("") 1049 if !multiline { 1050 panic(varHolder + varBit + "^\n" + "Invalid value. Maybe, it doesn't exist?") 1051 } 1052 panic(varBit + "^\n" + "Invalid value. Maybe, it doesn't exist?") 1053 } 1054 } 1055 1056 func (c *CTemplateSet) compileSubSwitch(con CContext, node *parse.CommandNode) { 1057 c.dumpCall("compileSubSwitch", con, node) 1058 switch n := node.Args[0].(type) { 1059 case *parse.FieldNode: 1060 c.detail("Field Node:", n.Ident) 1061 /* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Variable declarations are coming soon! */ 1062 cur := con.HoldReflect 1063 1064 var varBit string 1065 if cur.Kind() == reflect.Interface { 1066 cur = cur.Elem() 1067 varBit += ".(" + cur.Type().Name() + ")" 1068 } 1069 1070 var assLines string 1071 multiline := false 1072 for _, id := range n.Ident { 1073 c.detail("Data Kind:", cur.Kind().String()) 1074 c.detail("Field Bit:", id) 1075 cur = c.skipStructPointers(cur, id) 1076 c.checkIfValid(cur, con.VarHolder, con.HoldReflect, varBit, multiline) 1077 1078 c.detail("in-loop varBit:" + varBit) 1079 if cur.Kind() == reflect.Map { 1080 cur = cur.MapIndex(reflect.ValueOf(id)) 1081 varBit += "[\"" + id + "\"]" 1082 cur = c.skipStructPointers(cur, id) 1083 1084 if cur.Kind() == reflect.Struct || cur.Kind() == reflect.Interface { 1085 // 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 1086 var dispStr, newVarByte string 1087 if cur.Kind() == reflect.Interface { 1088 dispStr = "Int" 1089 if !c.hasDispInt { 1090 newVarByte = ":" 1091 c.hasDispInt = true 1092 } 1093 } 1094 // TODO: De-dupe identical struct types rather than allocating a variable for each one 1095 if cur.Kind() == reflect.Struct { 1096 dispStr = "Struct" + strconv.Itoa(c.localDispStructIndex) 1097 newVarByte = ":" 1098 c.localDispStructIndex++ 1099 } 1100 con.VarHolder = "disp" + dispStr 1101 varBit = con.VarHolder + " " + newVarByte + "= " + con.VarHolder + varBit + "\n" 1102 multiline = true 1103 } else { 1104 continue 1105 } 1106 } 1107 if cur.Kind() != reflect.Interface { 1108 cur = cur.FieldByName(id) 1109 varBit += "." + id 1110 } 1111 1112 // TODO: Handle deeply nested pointers mixed with interfaces mixed with pointers better 1113 if cur.Kind() == reflect.Interface { 1114 cur = cur.Elem() 1115 varBit += ".(" 1116 // TODO: Surely, there's a better way of doing this? 1117 if cur.Type().PkgPath() != "main" && cur.Type().PkgPath() != "" { 1118 c.importMap["html/template"] = "html/template" 1119 varBit += strings.TrimPrefix(cur.Type().PkgPath(), "html/") + "." 1120 } 1121 varBit += cur.Type().Name() + ")" 1122 } 1123 c.detail("End Cycle:", varBit) 1124 } 1125 1126 if multiline { 1127 assSplit := strings.Split(varBit, "\n") 1128 varBit = assSplit[len(assSplit)-1] 1129 assSplit = assSplit[:len(assSplit)-1] 1130 assLines = strings.Join(assSplit, "\n") + "\n" 1131 } 1132 c.compileVarSub(con, con.VarHolder+varBit, cur, assLines, func(in string) string { 1133 for _, varItem := range c.varList { 1134 if strings.HasPrefix(in, varItem.Destination) { 1135 in = strings.Replace(in, varItem.Destination, varItem.Name, 1) 1136 } 1137 } 1138 return in 1139 }) 1140 case *parse.DotNode: 1141 c.detail("Dot Node:", node.String()) 1142 c.compileVarSub(con, con.VarHolder, con.HoldReflect, "", nil) 1143 case *parse.NilNode: 1144 panic("Nil is not a command x.x") 1145 case *parse.VariableNode: 1146 c.detail("Variable Node:", n.String()) 1147 c.detail(n.Ident) 1148 varname, reflectVal := c.compileIfVarSub(con, n.String()) 1149 c.compileVarSub(con, varname, reflectVal, "", nil) 1150 case *parse.StringNode: 1151 con.Push("stringnode", n.Quoted) 1152 case *parse.IdentifierNode: 1153 c.detail("Identifier Node:", node) 1154 c.detail("Identifier Node Args:", node.Args) 1155 out, outval, lit, noident := c.compileIdentSwitch(con, node) 1156 if noident { 1157 return 1158 } else if lit { 1159 con.Push("identifier", out) 1160 return 1161 } 1162 c.compileVarSub(con, out, outval, "", nil) 1163 default: 1164 c.unknownNode(node) 1165 } 1166 } 1167 1168 func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode) (out string) { 1169 c.dumpCall("compileExprSwitch", con, node) 1170 firstWord := node.Args[0] 1171 switch n := firstWord.(type) { 1172 case *parse.FieldNode: 1173 if c.config.SuperDebug { 1174 c.logger.Println("Field Node:", n.Ident) 1175 for _, id := range n.Ident { 1176 c.logger.Println("Field Bit:", id) 1177 } 1178 } 1179 /* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */ 1180 out = c.compileBoolSub(con, n.String()) 1181 case *parse.ChainNode: 1182 c.detail("Chain Node:", n.Node) 1183 c.detail("Node Args:", node.Args) 1184 case *parse.IdentifierNode: 1185 c.detail("Identifier Node:", node) 1186 c.detail("Node Args:", node.Args) 1187 out = c.compileIdentSwitchN(con, node) 1188 case *parse.DotNode: 1189 out = con.VarHolder 1190 case *parse.VariableNode: 1191 c.detail("Variable Node:", n.String()) 1192 c.detail("Node Identifier:", n.Ident) 1193 out, _ = c.compileIfVarSub(con, n.String()) 1194 case *parse.NilNode: 1195 panic("Nil is not a command x.x") 1196 case *parse.PipeNode: 1197 c.detail("Pipe Node:", n) 1198 c.detail("Node Args:", node.Args) 1199 out += c.compileIdentSwitchN(con, node) 1200 default: 1201 c.unknownNode(firstWord) 1202 } 1203 c.retCall("compileExprSwitch", out) 1204 return out 1205 } 1206 1207 func (c *CTemplateSet) unknownNode(n parse.Node) { 1208 el := reflect.ValueOf(n).Elem() 1209 c.logger.Println("Unknown Kind:", el.Kind()) 1210 c.logger.Println("Unknown Type:", el.Type().Name()) 1211 panic("I don't know what node this is! Grr...") 1212 } 1213 1214 func (c *CTemplateSet) compileIdentSwitchN(con CContext, n *parse.CommandNode) (out string) { 1215 c.detail("in compileIdentSwitchN") 1216 out, _, _, _ = c.compileIdentSwitch(con, n) 1217 return out 1218 } 1219 1220 func (c *CTemplateSet) dumpSymbol(pos int, n *parse.CommandNode, symbol string) { 1221 c.detail("symbol:", symbol) 1222 c.detail("n.Args[pos+1]", n.Args[pos+1]) 1223 c.detail("n.Args[pos+2]", n.Args[pos+2]) 1224 } 1225 1226 func (c *CTemplateSet) compareFunc(con CContext, pos int, n *parse.CommandNode, compare string) (out string) { 1227 c.dumpSymbol(pos, n, compare) 1228 return c.compileIfVarSubN(con, n.Args[pos+1].String()) + " " + compare + " " + c.compileIfVarSubN(con, n.Args[pos+2].String()) 1229 } 1230 1231 func (c *CTemplateSet) simpleMath(con CContext, pos int, n *parse.CommandNode, symbol string) (out string, val reflect.Value) { 1232 leftParam, val2 := c.compileIfVarSub(con, n.Args[pos+1].String()) 1233 rightParam, val3 := c.compileIfVarSub(con, n.Args[pos+2].String()) 1234 if val2.IsValid() { 1235 val = val2 1236 } else if val3.IsValid() { 1237 val = val3 1238 } else { 1239 // TODO: What does this do? 1240 numSample := 1 1241 val = reflect.ValueOf(numSample) 1242 } 1243 c.dumpSymbol(pos, n, symbol) 1244 return leftParam + " " + symbol + " " + rightParam, val 1245 } 1246 1247 func (c *CTemplateSet) compareJoin(con CContext, pos int, node *parse.CommandNode, symbol string) (pos2 int, out string) { 1248 c.detailf("Building %s function", symbol) 1249 if pos == 0 { 1250 c.logger.Println("pos:", pos) 1251 panic(symbol + " is missing a left operand") 1252 } 1253 if len(node.Args) <= pos { 1254 c.logger.Println("post pos:", pos) 1255 c.logger.Println("len(node.Args):", len(node.Args)) 1256 panic(symbol + " is missing a right operand") 1257 } 1258 1259 left := c.compileBoolSub(con, node.Args[pos-1].String()) 1260 _, funcExists := c.funcMap[node.Args[pos+1].String()] 1261 1262 var right string 1263 if !funcExists { 1264 right = c.compileBoolSub(con, node.Args[pos+1].String()) 1265 } 1266 out = left + " " + symbol + " " + right 1267 1268 c.detail("Left op:", node.Args[pos-1]) 1269 c.detail("Right op:", node.Args[pos+1]) 1270 if !funcExists { 1271 pos++ 1272 } 1273 c.detail("pos:", pos) 1274 c.detail("len(node.Args):", len(node.Args)) 1275 1276 return pos, out 1277 } 1278 1279 func (c *CTemplateSet) compileIdentSwitch(con CContext, node *parse.CommandNode) (out string, val reflect.Value, literal, notIdent bool) { 1280 c.dumpCall("compileIdentSwitch", con, node) 1281 litString := func(inner string, bytes bool) { 1282 if !bytes { 1283 inner = "StringToBytes(" + inner + "/*,tmp*/)" 1284 } 1285 out = "w.Write(" + inner + ")\n" 1286 literal = true 1287 } 1288 ArgLoop: 1289 for pos := 0; pos < len(node.Args); pos++ { 1290 id := node.Args[pos] 1291 c.detail("pos:", pos) 1292 c.detail("id:", id) 1293 switch id.String() { 1294 case "not": 1295 out += "!" 1296 case "or", "and": 1297 var rout string 1298 pos, rout = c.compareJoin(con, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this 1299 out += rout 1300 case "le", "lt", "gt", "ge": 1301 out += c.compareFunc(con, pos, node, c.funcMap[id.String()].(string)) 1302 break ArgLoop 1303 case "eq", "ne": 1304 o := c.compareFunc(con, pos, node, c.funcMap[id.String()].(string)) 1305 if out == "!" { 1306 o = "(" + o + ")" 1307 } 1308 out += o 1309 break ArgLoop 1310 case "add", "subtract", "divide", "multiply": 1311 rout, rval := c.simpleMath(con, pos, node, c.funcMap[id.String()].(string)) 1312 out += rout 1313 val = rval 1314 break ArgLoop 1315 case "elapsed": 1316 leftOp := node.Args[pos+1].String() 1317 leftParam, _ := c.compileIfVarSub(con, leftOp) 1318 // TODO: Refactor this 1319 // TODO: Validate that this is actually a time.Time 1320 //litString("time.Since("+leftParam+").String()", false) 1321 c.importMap["time"] = "time" 1322 c.importMap["github.com/Azareal/Gosora/uutils"] = "github.com/Azareal/Gosora/uutils" 1323 litString("time.Duration(uutils.Nanotime() - "+leftParam+").String()", false) 1324 break ArgLoop 1325 case "dock": 1326 // TODO: Implement string literals properly 1327 leftOp := node.Args[pos+1].String() 1328 rightOp := node.Args[pos+2].String() 1329 if len(leftOp) == 0 || len(rightOp) == 0 { 1330 panic("The left or right operand for function dock cannot be left blank") 1331 } 1332 leftParam := leftOp 1333 if leftOp[0] != '"' { 1334 leftParam, _ = c.compileIfVarSub(con, leftParam) 1335 } 1336 if rightOp[0] == '"' { 1337 panic("The right operand for function dock cannot be a string") 1338 } 1339 rightParam, val3 := c.compileIfVarSub(con, rightOp) 1340 if !val3.IsValid() { 1341 panic("val3 is invalid") 1342 } 1343 val = val3 1344 1345 // TODO: Refactor this 1346 if leftParam[0] == '"' { 1347 leftParam = strings.TrimSuffix(strings.TrimPrefix(leftParam, "\""), "\"") 1348 id, ok := c.config.DockToID[leftParam] 1349 if ok { 1350 out = "c.BuildWidget3(" + strconv.Itoa(id) + "," + rightParam + ")\n" 1351 literal = true 1352 break ArgLoop 1353 } 1354 } 1355 litString("c.BuildWidget("+leftParam+","+rightParam+")", false) 1356 break ArgLoop 1357 case "hasWidgets": 1358 // TODO: Implement string literals properly 1359 leftOp := node.Args[pos+1].String() 1360 rightOp := node.Args[pos+2].String() 1361 if len(leftOp) == 0 || len(rightOp) == 0 { 1362 panic("The left or right operand for function dock cannot be left blank") 1363 } 1364 leftParam := leftOp 1365 if leftOp[0] != '"' { 1366 leftParam, _ = c.compileIfVarSub(con, leftParam) 1367 } 1368 if rightOp[0] == '"' { 1369 panic("The right operand for function dock cannot be a string") 1370 } 1371 rightParam, val3 := c.compileIfVarSub(con, rightOp) 1372 if !val3.IsValid() { 1373 panic("val3 is invalid") 1374 } 1375 val = val3 1376 1377 // TODO: Refactor this 1378 if leftParam[0] == '"' { 1379 leftParam = strings.TrimSuffix(strings.TrimPrefix(leftParam, "\""), "\"") 1380 id, ok := c.config.DockToID[leftParam] 1381 if ok { 1382 out = "c.HasWidgets2(" + strconv.Itoa(id) + "," + rightParam + ")" 1383 literal = true 1384 break ArgLoop 1385 } 1386 } 1387 out = "c.HasWidgets(" + leftParam + "," + rightParam + ")" 1388 literal = true 1389 break ArgLoop 1390 case "js": 1391 if c.lang == "js" { 1392 out = "true" 1393 } else { 1394 out = "false" 1395 } 1396 literal = true 1397 break ArgLoop 1398 case "lang": 1399 // TODO: Implement string literals properly 1400 leftOp := node.Args[pos+1].String() 1401 if len(leftOp) == 0 { 1402 panic("The left operand for the language string cannot be left blank") 1403 } 1404 if leftOp[0] == '"' { 1405 // ! Slightly crude but it does the job 1406 leftParam := strings.Replace(leftOp, "\"", "", -1) 1407 c.langIndexToName = append(c.langIndexToName, leftParam) 1408 notIdent = true 1409 con.PushPhrase(len(c.langIndexToName) - 1) 1410 } else { 1411 leftParam := leftOp 1412 if leftOp[0] != '"' { 1413 leftParam, _ = c.compileIfVarSub(con, leftParam) 1414 } 1415 // TODO: Add an optimisation if it's a string literal passsed in from a parent template rather than a true dynamic 1416 litString("phrases.GetTmplPhrasef("+leftParam+")", false) 1417 c.importMap[langPkg] = langPkg 1418 } 1419 break ArgLoop 1420 case "langf": 1421 // TODO: Implement string literals properly 1422 leftOp := node.Args[pos+1].String() 1423 if len(leftOp) == 0 { 1424 panic("The left operand for the language string cannot be left blank") 1425 } 1426 if leftOp[0] != '"' { 1427 panic("Phrase names cannot be dynamic") 1428 } 1429 1430 var olist []string 1431 for i := pos + 2; i < len(node.Args); i++ { 1432 op := node.Args[i].String() 1433 if op != "" { 1434 if /*op[0] == '.' || */ op[0] == '$' { 1435 panic("langf args cannot be dynamic") 1436 } 1437 if op[0] != '.' && op[0] != '"' && !unicode.IsDigit(rune(op[0])) { 1438 break 1439 } 1440 olist = append(olist, op) 1441 } 1442 } 1443 if len(olist) == 0 { 1444 panic("You must provide parameters for langf") 1445 } 1446 1447 ob := "," 1448 for _, op := range olist { 1449 if op[0] == '.' { 1450 param, val3 := c.compileIfVarSub(con, op) 1451 if !val3.IsValid() { 1452 panic("val3 is invalid") 1453 } 1454 ob += param + "," 1455 continue 1456 } 1457 allNum := true 1458 for _, o := range op { 1459 if !unicode.IsDigit(o) { 1460 allNum = false 1461 } 1462 } 1463 if allNum { 1464 ob += strings.Replace(op, "\"", "\\\"", -1) + "," 1465 } else { 1466 ob += ob + "," 1467 } 1468 } 1469 if ob != "" { 1470 ob = ob[:len(ob)-1] 1471 } 1472 1473 // TODO: Implement string literals properly 1474 // ! Slightly crude but it does the job 1475 litString("phrases.GetTmplPhrasef("+leftOp+ob+")", false) 1476 c.importMap[langPkg] = langPkg 1477 break ArgLoop 1478 case "level": 1479 // TODO: Implement level literals 1480 leftOp := node.Args[pos+1].String() 1481 if len(leftOp) == 0 { 1482 panic("The leftoperand for function level cannot be left blank") 1483 } 1484 leftParam, _ := c.compileIfVarSub(con, leftOp) 1485 // TODO: Refactor this 1486 litString("phrases.GetLevelPhrase("+leftParam+")", false) 1487 c.importMap[langPkg] = langPkg 1488 break ArgLoop 1489 case "bunit": 1490 // TODO: Implement bunit literals 1491 leftOp := node.Args[pos+1].String() 1492 if len(leftOp) == 0 { 1493 panic("The leftoperand for function buint cannot be left blank") 1494 } 1495 leftParam, _ := c.compileIfVarSub(con, leftOp) 1496 out = "{\nbyteFloat, unit := c.ConvertByteUnit(float64(" + leftParam + "))\n" 1497 out += "w.Write(StringToBytes(fmt.Sprintf(\"%.1f\", byteFloat)/*,tmp*/))\nw.Write(StringToBytes(unit/*,tmp*/))\n}\n" 1498 literal = true 1499 c.importMap["fmt"] = "fmt" 1500 break ArgLoop 1501 case "abstime": 1502 // TODO: Implement level literals 1503 leftOp := node.Args[pos+1].String() 1504 if len(leftOp) == 0 { 1505 panic("The leftoperand for function abstime cannot be left blank") 1506 } 1507 leftParam, _ := c.compileIfVarSub(con, leftOp) 1508 // TODO: Refactor this 1509 litString(leftParam+".Format(\"2006-01-02 15:04:05\")", false) 1510 break ArgLoop 1511 case "reltime": 1512 // TODO: Implement level literals 1513 leftOp := node.Args[pos+1].String() 1514 if len(leftOp) == 0 { 1515 panic("The leftoperand for function reltime cannot be left blank") 1516 } 1517 leftParam, _ := c.compileIfVarSub(con, leftOp) 1518 // TODO: Refactor this 1519 litString("c.RelativeTime("+leftParam+")", false) 1520 break ArgLoop 1521 case "scope": 1522 literal = true 1523 break ArgLoop 1524 // TODO: Optimise ptmpl 1525 case "dyntmpl", "ptmpl": 1526 var pageParam, headParam string 1527 // TODO: Implement string literals properly 1528 // TODO: Should we check to see if pos+3 is within the bounds of the slice? 1529 nameOp := node.Args[pos+1].String() 1530 pageOp := node.Args[pos+2].String() 1531 headOp := node.Args[pos+3].String() 1532 if len(nameOp) == 0 || len(pageOp) == 0 || len(headOp) == 0 { 1533 panic("None of the three operands for function dyntmpl can be left blank") 1534 } 1535 nameParam := nameOp 1536 if nameOp[0] != '"' { 1537 nameParam, _ = c.compileIfVarSub(con, nameParam) 1538 } 1539 if pageOp[0] == '"' { 1540 panic("The page operand for function dyntmpl cannot be a string") 1541 } 1542 if headOp[0] == '"' { 1543 panic("The head operand for function dyntmpl cannot be a string") 1544 } 1545 1546 pageParam, val3 := c.compileIfVarSub(con, pageOp) 1547 if !val3.IsValid() { 1548 panic("val3 is invalid") 1549 } 1550 headParam, val4 := c.compileIfVarSub(con, headOp) 1551 if !val4.IsValid() { 1552 panic("val4 is invalid") 1553 } 1554 val = val4 1555 1556 // TODO: Refactor this 1557 // TODO: Call the template function directly rather than going through RunThemeTemplate to eliminate a round of indirection? 1558 out = "{\ne := " + headParam + ".Theme.RunTmpl(" + nameParam + "," + pageParam + ",w)\n" 1559 out += "if e != nil {\nreturn e\n}\n}\n" 1560 literal = true 1561 break ArgLoop 1562 case "flush": 1563 literal = true 1564 break ArgLoop 1565 /*if c.lang == "js" { 1566 continue 1567 } 1568 out = "if fl, ok := iw.(http.Flusher); ok {\nfl.Flush()\n}\n" 1569 literal = true 1570 c.importMap["net/http"] = "net/http" 1571 break ArgLoop*/ 1572 // TODO: Test this 1573 case "res": 1574 leftOp := node.Args[pos+1].String() 1575 if len(leftOp) == 0 { 1576 panic("The leftoperand for function res cannot be left blank") 1577 } 1578 leftParam, _ := c.compileIfVarSub(con, leftOp) 1579 literal = true 1580 if leftParam[0] == '"' { 1581 if leftParam[1] == '/' && leftParam[2] == '/' { 1582 litString(leftParam, false) 1583 break ArgLoop 1584 } 1585 out = "{n := " + leftParam + "\nif f, ok := c.StaticFiles.GetShort(n); ok {\nw.Write(StringToBytes(f.OName))\n} else {\nw.Write(StringToBytes(n))\n}}\n" 1586 break ArgLoop 1587 } 1588 out = "{n := " + leftParam + "\nif n[0] == '/' && n[1] == '/' {\n} else {\nif f, ok := c.StaticFiles.GetShort(n); ok {\nn = f.OName\n}\nw.Write(StringToBytes(n))\n}\n" 1589 break ArgLoop 1590 default: 1591 c.detail("Variable!") 1592 if len(node.Args) > (pos + 1) { 1593 nextNode := node.Args[pos+1].String() 1594 if nextNode == "or" || nextNode == "and" { 1595 continue 1596 } 1597 } 1598 out += c.compileIfVarSubN(con, id.String()) 1599 } 1600 } 1601 c.retCall("compileIdentSwitch", out, val, literal) 1602 return out, val, literal, notIdent 1603 } 1604 1605 func (c *CTemplateSet) compileReflectSwitch(con CContext, node *parse.CommandNode) (out string, outVal reflect.Value) { 1606 c.dumpCall("compileReflectSwitch", con, node) 1607 firstWord := node.Args[0] 1608 switch n := firstWord.(type) { 1609 case *parse.FieldNode: 1610 if c.config.SuperDebug { 1611 c.logger.Println("Field Node:", n.Ident) 1612 for _, id := range n.Ident { 1613 c.logger.Println("Field Bit:", id) 1614 } 1615 } 1616 /* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Coming Soon. */ 1617 return c.compileIfVarSub(con, n.String()) 1618 case *parse.ChainNode: 1619 c.detail("Chain Node:", n.Node) 1620 c.detail("node.Args:", node.Args) 1621 case *parse.DotNode: 1622 return con.VarHolder, con.HoldReflect 1623 case *parse.NilNode: 1624 panic("Nil is not a command x.x") 1625 default: 1626 //panic("I don't know what node this is") 1627 } 1628 return out, outVal 1629 } 1630 1631 func (c *CTemplateSet) compileIfVarSubN(con CContext, varname string) (out string) { 1632 c.dumpCall("compileIfVarSubN", con, varname) 1633 out, _ = c.compileIfVarSub(con, varname) 1634 return out 1635 } 1636 1637 func (c *CTemplateSet) compileIfVarSub(con CContext, varname string) (out string, val reflect.Value) { 1638 c.dumpCall("compileIfVarSub", con, varname) 1639 cur := con.HoldReflect 1640 if varname[0] != '.' && varname[0] != '$' { 1641 return varname, cur 1642 } 1643 1644 stepInterface := func() { 1645 nobreak := (cur.Type().Name() == "nobreak") 1646 c.detailf("cur.Type().Name(): %+v\n", cur.Type().Name()) 1647 if cur.Kind() == reflect.Interface && !nobreak { 1648 cur = cur.Elem() 1649 out += ".(" + cur.Type().Name() + ")" 1650 } 1651 } 1652 1653 bits := strings.Split(varname, ".") 1654 if varname[0] == '$' { 1655 var res VarItemReflect 1656 if varname[1] == '.' { 1657 res = c.localVars[con.TemplateName]["."] 1658 } else { 1659 res = c.localVars[con.TemplateName][strings.TrimPrefix(bits[0], "$")] 1660 } 1661 out += res.Destination 1662 cur = res.Value 1663 1664 if cur.Kind() == reflect.Interface { 1665 cur = cur.Elem() 1666 } 1667 } else { 1668 out += con.VarHolder 1669 stepInterface() 1670 } 1671 bits[0] = strings.TrimPrefix(bits[0], "$") 1672 1673 dumpKind := func(pre string) { 1674 c.detail(pre+" Kind:", cur.Kind()) 1675 c.detail(pre+" Type:", cur.Type().Name()) 1676 } 1677 dumpKind("Cur") 1678 for _, bit := range bits { 1679 c.detail("Variable Field:", bit) 1680 if bit == "" { 1681 continue 1682 } 1683 1684 // 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 1685 if cur.Kind() == reflect.Ptr { 1686 c.detail("Looping over pointer") 1687 for cur.Kind() == reflect.Ptr { 1688 cur = cur.Elem() 1689 } 1690 c.detail("Data Kind:", cur.Kind().String()) 1691 c.detail("Field Bit:", bit) 1692 } 1693 1694 cur = cur.FieldByName(bit) 1695 out += "." + bit 1696 if !cur.IsValid() { 1697 c.logger.Println("cur: ", cur) 1698 panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?") 1699 } 1700 stepInterface() 1701 if !cur.IsValid() { 1702 c.logger.Println("cur: ", cur) 1703 panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?") 1704 } 1705 dumpKind("Data") 1706 } 1707 1708 c.detail("Out Value:", out) 1709 dumpKind("Out") 1710 for _, varItem := range c.varList { 1711 if strings.HasPrefix(out, varItem.Destination) { 1712 out = strings.Replace(out, varItem.Destination, varItem.Name, 1) 1713 } 1714 } 1715 1716 _, ok := c.stats[out] 1717 if ok { 1718 c.stats[out]++ 1719 } else { 1720 c.stats[out] = 1 1721 } 1722 1723 c.retCall("compileIfVarSub", out, cur) 1724 return out, cur 1725 } 1726 1727 func (c *CTemplateSet) compileBoolSub(con CContext, varname string) string { 1728 c.dumpCall("compileBoolSub", con, varname) 1729 out, val := c.compileIfVarSub(con, varname) 1730 // 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 1731 switch val.Kind() { 1732 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: 1733 out += ">0" 1734 case reflect.Bool: // Do nothing 1735 case reflect.String: 1736 out += "!=\"\"" 1737 case reflect.Slice, reflect.Map: 1738 out = "len(" + out + ")!=0" 1739 // TODO: Follow the pointer and evaluate it? 1740 case reflect.Ptr: 1741 out += "!=nil" 1742 default: 1743 c.logger.Println("Variable Name:", varname) 1744 c.logger.Println("Variable Holder:", con.VarHolder) 1745 c.logger.Println("Variable Kind:", con.HoldReflect.Kind()) 1746 panic("I don't know what this variable's type is o.o\n") 1747 } 1748 c.retCall("compileBoolSub", out) 1749 return out 1750 } 1751 1752 // For debugging the template generator 1753 func (c *CTemplateSet) debugParam(param interface{}, depth int) (pstr string) { 1754 switch p := param.(type) { 1755 case CContext: 1756 return "con," 1757 case reflect.Value: 1758 if p.Kind() == reflect.Ptr || p.Kind() == reflect.Interface { 1759 for p.Kind() == reflect.Ptr || p.Kind() == reflect.Interface { 1760 if p.Kind() == reflect.Ptr { 1761 pstr += "*" 1762 } else { 1763 pstr += "£" 1764 } 1765 p = p.Elem() 1766 } 1767 } 1768 kind := p.Kind().String() 1769 if kind != "struct" { 1770 pstr += kind 1771 } else { 1772 pstr += p.Type().Name() 1773 } 1774 return pstr + "," 1775 case string: 1776 return "\"" + p + "\"," 1777 case int: 1778 return strconv.Itoa(p) + "," 1779 case bool: 1780 if p { 1781 return "true," 1782 } 1783 return "false," 1784 case func(string) string: 1785 if p == nil { 1786 return "nil," 1787 } 1788 return "func(string) string)," 1789 default: 1790 return "?," 1791 } 1792 } 1793 func (c *CTemplateSet) dumpCall(name string, params ...interface{}) { 1794 var pstr string 1795 for _, param := range params { 1796 pstr += c.debugParam(param, 0) 1797 } 1798 if len(pstr) > 0 { 1799 pstr = pstr[:len(pstr)-1] 1800 } 1801 c.detail("called " + name + "(" + pstr + ")") 1802 } 1803 func (c *CTemplateSet) retCall(name string, params ...interface{}) { 1804 var pstr string 1805 for _, param := range params { 1806 pstr += c.debugParam(param, 0) 1807 } 1808 if len(pstr) > 0 { 1809 pstr = pstr[:len(pstr)-1] 1810 } 1811 c.detail("returned from " + name + " => (" + pstr + ")") 1812 } 1813 1814 func buildUserExprs(holder string) ([]string, []string) { 1815 userExprs := []string{ 1816 holder + ".CurrentUser.Loggedin", 1817 holder + ".CurrentUser.IsSuperMod", 1818 holder + ".CurrentUser.IsAdmin", 1819 } 1820 negUserExprs := []string{ 1821 "!" + holder + ".CurrentUser.Loggedin", 1822 "!" + holder + ".CurrentUser.IsSuperMod", 1823 "!" + holder + ".CurrentUser.IsAdmin", 1824 } 1825 return userExprs, negUserExprs 1826 } 1827 1828 func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.Value, assLines string, onEnd func(string) string) { 1829 c.dumpCall("compileVarSub", con, varname, val, assLines, onEnd) 1830 defer c.retCall("compileVarSub") 1831 if onEnd == nil { 1832 onEnd = func(in string) string { 1833 return in 1834 } 1835 } 1836 1837 // Is this a literal string? 1838 if len(varname) != 0 && varname[0] == '"' { 1839 con.Push("lvarsub", onEnd(assLines+"w.Write(StringToBytes("+varname+"/*,tmp*/))\n")) 1840 return 1841 } 1842 for _, varItem := range c.varList { 1843 if strings.HasPrefix(varname, varItem.Destination) { 1844 varname = strings.Replace(varname, varItem.Destination, varItem.Name, 1) 1845 } 1846 } 1847 1848 _, ok := c.stats[varname] 1849 if ok { 1850 c.stats[varname]++ 1851 } else { 1852 c.stats[varname] = 1 1853 } 1854 if val.Kind() == reflect.Interface { 1855 val = val.Elem() 1856 } 1857 if val.Kind() == reflect.Ptr { 1858 for val.Kind() == reflect.Ptr { 1859 val = val.Elem() 1860 varname = "*" + varname 1861 } 1862 } 1863 1864 c.detail("varname:", varname) 1865 c.detail("assLines:", assLines) 1866 var base string 1867 switch val.Kind() { 1868 case reflect.Int: 1869 c.importMap["strconv"] = "strconv" 1870 base = "StringToBytes(strconv.Itoa(" + varname + ")/*,tmp*/)" 1871 case reflect.Bool: 1872 // TODO: Take c.memberOnly into account 1873 // TODO: Make this a template fragment so more optimisations can be applied to this 1874 // TODO: De-duplicate this logic 1875 userExprs, negUserExprs := buildUserExprs(con.RootHolder) 1876 if c.guestOnly { 1877 c.detail("optimising away member branch") 1878 if inSlice(userExprs, varname) { 1879 c.detail("positive condition:", varname) 1880 c.addText(con, []byte("false")) 1881 return 1882 } else if inSlice(negUserExprs, varname) { 1883 c.detail("negative condition:", varname) 1884 c.addText(con, []byte("true")) 1885 return 1886 } 1887 } else if c.memberOnly { 1888 c.detail("optimising away guest branch") 1889 if (con.RootHolder + ".CurrentUser.Loggedin") == varname { 1890 c.detail("positive condition:", varname) 1891 c.addText(con, []byte("true")) 1892 return 1893 } else if ("!" + con.RootHolder + ".CurrentUser.Loggedin") == varname { 1894 c.detail("negative condition:", varname) 1895 c.addText(con, []byte("false")) 1896 return 1897 } 1898 } 1899 startIf := con.StartIf("if " + varname + " {\n") 1900 c.addText(con, []byte("true")) 1901 con.EndIf(startIf, "} ") 1902 con.Push("startelse", "else {\n") 1903 c.addText(con, []byte("false")) 1904 con.Push("endelse", "}\n") 1905 return 1906 case reflect.Slice: 1907 if val.Len() == 0 { 1908 c.critical("varname:", varname) 1909 panic("The sample data needs at-least one or more elements for the slices. We're looking into removing this requirement at some point!") 1910 } 1911 item := val.Index(0) 1912 if item.Type().Name() != "uint8" { // uint8 == byte, complicated because it's a type alias 1913 panic("unable to format " + item.Type().Name() + " as text") 1914 } 1915 base = varname 1916 case reflect.String: 1917 if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") { 1918 varname = "string(" + varname + ")" 1919 } 1920 base = "StringToBytes(" + varname + "/*,tmp*/)" 1921 // We don't to waste time on this conversion / w.Write call when guests don't have sessions 1922 // TODO: Implement this properly 1923 if c.guestOnly && base == "StringToBytes("+con.RootHolder+".CurrentUser.Session/*,tmp*/))" { 1924 return 1925 } 1926 case reflect.Int8, reflect.Int16, reflect.Int32: 1927 c.importMap["strconv"] = "strconv" 1928 base = "StringToBytes(strconv.FormatInt(int64(" + varname + "), 10)/*,tmp*/)" 1929 case reflect.Int64: 1930 c.importMap["strconv"] = "strconv" 1931 base = "StringToBytes(strconv.FormatInt(" + varname + ", 10)/*,tmp*/)" 1932 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 1933 c.importMap["strconv"] = "strconv" 1934 base = "StringToBytes(strconv.FormatUint(uint64(" + varname + "), 10)/*,tmp*/)" 1935 case reflect.Uint64: 1936 c.importMap["strconv"] = "strconv" 1937 base = "StringToBytes(strconv.FormatUint(" + varname + ", 10)/*,tmp*/)" 1938 case reflect.Struct: 1939 // TODO: Avoid clashing with other packages which have structs named Time 1940 if val.Type().Name() == "Time" { 1941 base = "StringToBytes(" + varname + ".String()/*,tmp*/)" 1942 } else { 1943 if !val.IsValid() { 1944 panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?") 1945 } 1946 c.logger.Println("Unknown Struct Name:", varname) 1947 c.logger.Println("Unknown Struct:", val.Type().Name()) 1948 panic("-- I don't know what this variable's type is o.o\n") 1949 } 1950 default: 1951 if !val.IsValid() { 1952 panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?") 1953 } 1954 c.logger.Println("Unknown Variable Name:", varname) 1955 c.logger.Println("Unknown Kind:", val.Kind()) 1956 c.logger.Println("Unknown Type:", val.Type().Name()) 1957 panic("-- I don't know what this variable's type is o.o\n") 1958 } 1959 c.detail("base:", base) 1960 if assLines == "" { 1961 con.Push("varsub", base) 1962 } else { 1963 con.Push("lvarsub", onEnd(assLines+base)) 1964 } 1965 } 1966 1967 func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNode) { 1968 c.dumpCall("compileSubTemplate", pcon, node) 1969 defer c.retCall("compileSubTemplate") 1970 c.detail("Template Node: ", node.Name) 1971 1972 fname := strings.TrimSuffix(node.Name, filepath.Ext(node.Name)) 1973 if c.themeName != "" { 1974 _, ok := c.perThemeTmpls[fname] 1975 if !ok { 1976 c.detail("fname not in c.perThemeTmpls") 1977 c.detail("c.perThemeTmpls", c.perThemeTmpls) 1978 } 1979 fname += "_" + c.themeName 1980 } 1981 if c.guestOnly { 1982 fname += "_guest" 1983 } else if c.memberOnly { 1984 fname += "_member" 1985 } 1986 1987 _, ok := c.templateList[fname] 1988 if !ok { 1989 // TODO: Cascade errors back up the tree to the caller? 1990 content, err := c.loadTemplate(c.fileDir, node.Name) 1991 if err != nil { 1992 c.logger.Fatal(err) 1993 } 1994 1995 //tree := parse.New(node.Name, c.funcMap) 1996 //treeSet := make(map[string]*parse.Tree) 1997 treeSet, err := parse.Parse(node.Name, content, "{{", "}}", c.funcMap) 1998 if err != nil { 1999 c.logger.Fatal(err) 2000 } 2001 c.detailf("treeSet: %+v\n", treeSet) 2002 2003 for nname, tree := range treeSet { 2004 if node.Name == nname { 2005 c.templateList[fname] = tree 2006 } else { 2007 if !strings.HasPrefix(nname, ".html") { 2008 c.templateList[nname] = tree 2009 } else { 2010 c.templateList[strings.TrimSuffix(nname, ".html")] = tree 2011 } 2012 } 2013 } 2014 c.detailf("c.templateList: %+v\n", c.templateList) 2015 } 2016 2017 con := pcon 2018 con.VarHolder = "tmpl_" + fname + "_vars" 2019 con.TemplateName = fname 2020 if node.Pipe != nil { 2021 for _, cmd := range node.Pipe.Cmds { 2022 switch p := cmd.Args[0].(type) { 2023 case *parse.FieldNode: 2024 // TODO: Incomplete but it should cover the basics 2025 cur := pcon.HoldReflect 2026 var varBit string 2027 if cur.Kind() == reflect.Interface { 2028 cur = cur.Elem() 2029 varBit += ".(" + cur.Type().Name() + ")" 2030 } 2031 2032 for _, id := range p.Ident { 2033 c.detail("Data Kind:", cur.Kind().String()) 2034 c.detail("Field Bit:", id) 2035 cur = c.skipStructPointers(cur, id) 2036 c.checkIfValid(cur, pcon.VarHolder, pcon.HoldReflect, varBit, false) 2037 2038 if cur.Kind() != reflect.Interface { 2039 cur = cur.FieldByName(id) 2040 varBit += "." + id 2041 } 2042 2043 // TODO: Handle deeply nested pointers mixed with interfaces mixed with pointers better 2044 if cur.Kind() == reflect.Interface { 2045 cur = cur.Elem() 2046 varBit += ".(" 2047 // TODO: Surely, there's a better way of doing this? 2048 if cur.Type().PkgPath() != "main" && cur.Type().PkgPath() != "" { 2049 c.importMap["html/template"] = "html/template" 2050 varBit += strings.TrimPrefix(cur.Type().PkgPath(), "html/") + "." 2051 } 2052 varBit += cur.Type().Name() + ")" 2053 } 2054 } 2055 con.VarHolder = pcon.VarHolder + varBit 2056 con.HoldReflect = cur 2057 case *parse.StringNode: 2058 //con.VarHolder = pcon.VarHolder 2059 //con.HoldReflect = pcon.HoldReflect 2060 con.VarHolder = p.Quoted 2061 con.HoldReflect = reflect.ValueOf(p.Quoted) 2062 case *parse.DotNode: 2063 con.VarHolder = pcon.VarHolder 2064 con.HoldReflect = pcon.HoldReflect 2065 case *parse.NilNode: 2066 panic("Nil is not a command x.x") 2067 default: 2068 c.critical("Unknown Param Type:", p) 2069 pvar := reflect.ValueOf(p) 2070 c.critical("param kind:", pvar.Kind().String()) 2071 c.critical("param type:", pvar.Type().Name()) 2072 if pvar.Kind() == reflect.Ptr { 2073 c.critical("Looping over pointer") 2074 for pvar.Kind() == reflect.Ptr { 2075 pvar = pvar.Elem() 2076 } 2077 c.critical("concrete kind:", pvar.Kind().String()) 2078 c.critical("concrete type:", pvar.Type().Name()) 2079 } 2080 panic("") 2081 } 2082 } 2083 } 2084 2085 //c.templateList[fname] = tree 2086 subtree := c.templateList[fname] 2087 c.detail("subtree.Root", subtree.Root) 2088 c.localVars[fname] = make(map[string]VarItemReflect) 2089 c.localVars[fname]["."] = VarItemReflect{".", con.VarHolder, con.HoldReflect} 2090 c.fragmentCursor[fname] = 0 2091 2092 var startBit, endBit string 2093 if con.LoopDepth != 0 { 2094 startBit = "{\n" 2095 endBit = "}\n" 2096 } 2097 con.StartTemplate(startBit) 2098 c.rootIterate(subtree, con) 2099 con.EndTemplate(endBit) 2100 //c.templateFragmentCount[fname] = c.fragmentCursor[fname] + 1 2101 if _, ok := c.fragOnce[fname]; !ok { 2102 c.fragOnce[fname] = true 2103 } 2104 2105 // map[string]map[string]bool 2106 c.detail("overridenTrack loop") 2107 c.detail("fname:", fname) 2108 for themeName, track := range c.overridenTrack { 2109 c.detail("themeName:", themeName) 2110 c.detailf("track: %+v\n", track) 2111 croot, ok := c.overridenRoots[themeName] 2112 if !ok { 2113 croot = make(map[string]bool) 2114 c.overridenRoots[themeName] = croot 2115 } 2116 c.detailf("croot: %+v\n", croot) 2117 for tmplName, _ := range track { 2118 cname := tmplName 2119 if c.guestOnly { 2120 cname += "_guest" 2121 } else if c.memberOnly { 2122 cname += "_member" 2123 } 2124 c.detail("cname:", cname) 2125 if fname == cname { 2126 c.detail("match") 2127 croot[strings.TrimSuffix(strings.TrimSuffix(con.RootTemplateName, "_guest"), "_member")] = true 2128 } else { 2129 c.detail("no match") 2130 } 2131 } 2132 } 2133 c.detailf("c.overridenRoots: %+v\n", c.overridenRoots) 2134 } 2135 2136 func (c *CTemplateSet) loadTemplate(fileDir, name string) (content string, err error) { 2137 c.dumpCall("loadTemplate", fileDir, name) 2138 c.detail("c.themeName:", c.themeName) 2139 if c.themeName != "" { 2140 t := "./themes/" + c.themeName + "/overrides/" + name 2141 c.detail("per-theme override:", true) 2142 res, err := ioutil.ReadFile(t) 2143 if err == nil { 2144 content = string(res) 2145 if c.config.Minify { 2146 content = Minify(content) 2147 } 2148 return content, nil 2149 } 2150 c.detail("override err:", err) 2151 } 2152 2153 res, err := ioutil.ReadFile(c.fileDir + "overrides/" + name) 2154 if err != nil { 2155 c.detail("override path:", c.fileDir+"overrides/"+name) 2156 c.detail("override err:", err) 2157 res, err = ioutil.ReadFile(c.fileDir + name) 2158 if err != nil { 2159 return "", err 2160 } 2161 } 2162 content = string(res) 2163 if c.config.Minify { 2164 content = Minify(content) 2165 } 2166 return content, nil 2167 } 2168 2169 func (c *CTemplateSet) afterTemplate(con CContext, startIndex int /*, svmap map[string]int*/) { 2170 c.dumpCall("afterTemplate", con, startIndex) 2171 defer c.retCall("afterTemplate") 2172 2173 loopDepth := 0 2174 ifNilDepth := 0 2175 var outBuf = *con.OutBuf 2176 varcounts := make(map[string]int) 2177 loopStart := startIndex 2178 otype := outBuf[startIndex].Type 2179 if otype == "startloop" && (len(outBuf) > startIndex+1) { 2180 loopStart++ 2181 } 2182 if otype == "startif" && (len(outBuf) > startIndex+1) { 2183 loopStart++ 2184 } 2185 2186 // Exclude varsubs within loops for now 2187 OLoop: 2188 for i := loopStart; i < len(outBuf); i++ { 2189 item := outBuf[i] 2190 c.detail("item:", item) 2191 switch item.Type { 2192 case "startloop": 2193 loopDepth++ 2194 c.detail("loopDepth:", loopDepth) 2195 case "endloop": 2196 loopDepth-- 2197 c.detail("loopDepth:", loopDepth) 2198 if loopDepth == -1 { 2199 break OLoop 2200 } 2201 case "startif": 2202 if item.Extra.(bool) == true { 2203 ifNilDepth++ 2204 } 2205 case "endif": 2206 item2 := outBuf[item.Extra.(int)] 2207 if item2.Extra.(bool) == true { 2208 ifNilDepth-- 2209 } 2210 if ifNilDepth == -1 { 2211 break OLoop 2212 } 2213 case "varsub": 2214 if loopDepth == 0 && ifNilDepth == 0 { 2215 count := varcounts[item.Body] 2216 varcounts[item.Body] = count + 1 2217 c.detail("count " + strconv.Itoa(count) + " for " + item.Body) 2218 c.detail("loopDepth:", loopDepth) 2219 } 2220 } 2221 } 2222 2223 var varstr string 2224 var i int 2225 varmap := make(map[string]int) 2226 /*for svkey, sventry := range svmap { 2227 varmap[svkey] = sventry 2228 }*/ 2229 for name, count := range varcounts { 2230 if count > 1 { 2231 varstr += "var c_v_" + strconv.Itoa(i) + "=" + name + "\n" 2232 varmap[name] = i 2233 i++ 2234 } 2235 } 2236 2237 // Exclude varsubs within loops for now 2238 loopDepth = 0 2239 ifNilDepth = 0 2240 OOLoop: 2241 for i := loopStart; i < len(outBuf); i++ { 2242 item := outBuf[i] 2243 switch item.Type { 2244 case "startloop": 2245 loopDepth++ 2246 case "endloop": 2247 loopDepth-- 2248 if loopDepth == -1 { 2249 break OOLoop 2250 } //con.Push("startif", "if "+varname+" {\n") 2251 case "startif": 2252 if item.Extra.(bool) == true { 2253 ifNilDepth++ 2254 } 2255 case "endif": 2256 item2 := outBuf[item.Extra.(int)] 2257 if item2.Extra.(bool) == true { 2258 ifNilDepth-- 2259 } 2260 if ifNilDepth == -1 { 2261 break OOLoop 2262 } 2263 case "varsub": 2264 if loopDepth == 0 && ifNilDepth == 0 { 2265 index, ok := varmap[item.Body] 2266 if ok { 2267 item.Body = "c_v_" + strconv.Itoa(index) 2268 item.Type = "cvarsub" 2269 outBuf[i] = item 2270 } 2271 } 2272 } 2273 } 2274 2275 con.AttachVars(varstr, startIndex) 2276 } 2277 2278 const ( 2279 ATTmpl = iota 2280 ATLoop 2281 ATIfPtr 2282 ) 2283 2284 func (c *CTemplateSet) afterTemplateV2(con CContext, startIndex int /*, typ int*/, svmap map[string]int) { 2285 c.dumpCall("afterTemplateV2", con, startIndex) 2286 defer c.retCall("afterTemplateV2") 2287 2288 loopDepth, ifNilDepth := 0, 0 2289 var outBuf = *con.OutBuf 2290 varcounts := make(map[string]int) 2291 loopStart := startIndex 2292 otype := outBuf[startIndex].Type 2293 if otype == "startloop" && (len(outBuf) > startIndex+1) { 2294 loopStart++ 2295 } 2296 if otype == "startif" && (len(outBuf) > startIndex+1) { 2297 loopStart++ 2298 } 2299 2300 // Exclude varsubs within loops for now 2301 OLoop: 2302 for i := loopStart; i < len(outBuf); i++ { 2303 item := outBuf[i] 2304 c.detail("item:", item) 2305 switch item.Type { 2306 case "startloop": 2307 loopDepth++ 2308 c.detail("loopDepth:", loopDepth) 2309 case "endloop": 2310 loopDepth-- 2311 c.detail("loopDepth:", loopDepth) 2312 if loopDepth == -1 { 2313 break OLoop 2314 } 2315 case "startif": 2316 if item.Extra.(bool) == true { 2317 ifNilDepth++ 2318 } 2319 case "endif": 2320 item2 := outBuf[item.Extra.(int)] 2321 if item2.Extra.(bool) == true { 2322 ifNilDepth-- 2323 } 2324 if ifNilDepth == -1 { 2325 break OLoop 2326 } 2327 case "varsub": 2328 if loopDepth == 0 && ifNilDepth == 0 { 2329 count := varcounts[item.Body] 2330 varcounts[item.Body] = count + 1 2331 c.detail("count " + strconv.Itoa(count) + " for " + item.Body) 2332 c.detail("loopDepth:", loopDepth) 2333 } 2334 } 2335 } 2336 2337 var varstr string 2338 var i int 2339 varmap := make(map[string]int) 2340 /*for svkey, sventry := range svmap { 2341 varmap[svkey] = sventry 2342 }*/ 2343 for name, count := range varcounts { 2344 if count > 1 { 2345 varstr += "var c_v_" + strconv.Itoa(i) + "=" + name + "\n" 2346 varmap[name] = i 2347 i++ 2348 } 2349 } 2350 2351 // Exclude varsubs within loops for now 2352 loopDepth, ifNilDepth = 0, 0 2353 OOLoop: 2354 for i := loopStart; i < len(outBuf); i++ { 2355 item := outBuf[i] 2356 switch item.Type { 2357 case "startloop": 2358 loopDepth++ 2359 case "endloop": 2360 loopDepth-- 2361 if loopDepth == -1 { 2362 break OOLoop 2363 } //con.Push("startif", "if "+varname+" {\n") 2364 case "startif": 2365 if item.Extra.(bool) == true { 2366 ifNilDepth++ 2367 } 2368 case "endif": 2369 item2 := outBuf[item.Extra.(int)] 2370 if item2.Extra.(bool) == true { 2371 ifNilDepth-- 2372 } 2373 if ifNilDepth == -1 { 2374 break OOLoop 2375 } 2376 case "varsub": 2377 if loopDepth == 0 && ifNilDepth == 0 { 2378 index, ok := varmap[item.Body] 2379 if ok { 2380 item.Body = "c_v_" + strconv.Itoa(index) 2381 item.Type = "cvarsub" 2382 outBuf[i] = item 2383 } 2384 } 2385 } 2386 } 2387 2388 con.AttachVars(varstr, startIndex) 2389 } 2390 2391 // TODO: Should we rethink the way the log methods work or their names? 2392 2393 func (c *CTemplateSet) detail(args ...interface{}) { 2394 if c.config.SuperDebug { 2395 c.logger.Println(args...) 2396 } 2397 } 2398 2399 func (c *CTemplateSet) detailf(left string, args ...interface{}) { 2400 if c.config.SuperDebug { 2401 c.logger.Printf(left, args...) 2402 } 2403 } 2404 2405 func (c *CTemplateSet) error(args ...interface{}) { 2406 if c.config.Debug { 2407 c.logger.Println(args...) 2408 } 2409 } 2410 2411 func (c *CTemplateSet) critical(args ...interface{}) { 2412 c.logger.Println(args...) 2413 }