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  }