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

595 lines
19 KiB
Go

package common
import (
"html/template"
"io"
"log"
"math"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/Azareal/Gosora/common/alerts"
"github.com/Azareal/Gosora/common/templates"
"github.com/Azareal/Gosora/common/phrases"
)
var Ctemplates []string
var Templates = template.New("")
var PrebuildTmplList []func(User, *Header) CTmpl
type CTmpl struct {
Name string
Filename string
Path string
StructName string
Data interface{}
Imports []string
}
// TODO: Refactor the template trees to not need these
// TODO: Stop duplicating these bits of code
// nolint
func interpretedTopicTemplate(pi TopicPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topic"]
if !ok {
mapping = "topic"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_topic_handle = interpretedTopicTemplate
var Template_topic_alt_handle = interpretedTopicTemplate
// nolint
var Template_topics_handle = func(pi TopicListPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topics"]
if !ok {
mapping = "topics"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_forum_handle = func(pi ForumPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forum"]
if !ok {
mapping = "forum"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_forums_handle = func(pi ForumsPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forums"]
if !ok {
mapping = "forums"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_profile_handle = func(pi ProfilePage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["profile"]
if !ok {
mapping = "profile"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_create_topic_handle = func(pi CreateTopicPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["create_topic"]
if !ok {
mapping = "create_topic"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_login_handle = func(pi Page, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["login"]
if !ok {
mapping = "login"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_register_handle = func(pi Page, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["register"]
if !ok {
mapping = "register"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_error_handle = func(pi ErrorPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["error"]
if !ok {
mapping = "error"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_ip_search_handle = func(pi IPSearchPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["ip_search"]
if !ok {
mapping = "ip_search"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_account_handle = func(pi AccountDashPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["account"]
if !ok {
mapping = "account"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
func tmplInitUsers() (User, User, User) {
avatar, microAvatar := BuildAvatar(62, "")
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", "", "", 0, 0, 0, "0.0.0.0.0", 0}
// TODO: Do a more accurate level calculation for this?
avatar, microAvatar = BuildAvatar(1, "")
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 58, 1000, 0, "127.0.0.1", 0}
avatar, microAvatar = BuildAvatar(2, "")
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 42, 900, 0, "::1", 0}
return user, user2, user3
}
func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Header) {
header := &Header{
Site: Site,
Settings: SettingBox.Load().(SettingMap),
Themes: Themes,
Theme: Themes[DefaultThemeBox.Load().(string)],
CurrentUser: user,
NoticeList: []string{"test"},
Stylesheets: []string{"panel"},
Scripts: []string{"whatever"},
Widgets: PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
}
var header2 = &Header{Site: Site}
*header2 = *header
header2.CurrentUser = user2
var header3 = &Header{Site: Site}
*header3 = *header
header3.CurrentUser = user3
return header, header2, header3
}
// ? - Add template hooks?
func CompileTemplates() error {
var config tmpl.CTemplateConfig
config.Minify = Config.MinifyTemplates
config.Debug = Dev.DebugMode
config.SuperDebug = Dev.TemplateDebug
c := tmpl.NewCTemplateSet()
c.SetConfig(config)
c.SetBaseImportMap(map[string]string{
"io": "io",
"github.com/Azareal/Gosora/common": "github.com/Azareal/Gosora/common",
})
c.SetBuildTags("!no_templategen")
// Schemas to train the template compiler on what to expect
// TODO: Add support for interface{}s
user, user2, user3 := tmplInitUsers()
header, header2, _ := tmplInitHeaders(user, user2, user3)
now := time.Now()
log.Print("Compiling the templates")
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
PollOption{0, "Nothing"},
PollOption{1, "Something"},
}, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "")
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, RelativeTime(now), now, RelativeTime(now), 0, "", "127.0.0.1", 1, 0, 1, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false}
var replyList []ReplyUser
// TODO: Do we want the UID on this to be 0?
avatar, microAvatar = BuildAvatar(0, "")
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, RelativeTime(now), 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
var varList = make(map[string]tmpl.VarItem)
var compile = func(name string, expects string, expectsInt interface{}) (tmpl string, err error) {
return c.Compile(name+".html", "templates/", expects, expectsInt, varList)
}
header.Title = "Topic Name"
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, 1, 1}
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
topicIDTmpl, err := compile("topic", "common.TopicPage", tpage)
if err != nil {
return err
}
topicIDAltTmpl, err := compile("topic_alt", "common.TopicPage", tpage)
if err != nil {
return err
}
varList = make(map[string]tmpl.VarItem)
header.Title = "User 526"
ppage := ProfilePage{header, replyList, user, 0, 0} // TODO: Use the score from user to generate the currentScore and nextScore
profileTmpl, err := compile("profile", "common.ProfilePage", ppage)
if err != nil {
return err
}
// TODO: Use a dummy forum list to avoid o(n) problems
var forumList []Forum
forums, err := Forums.GetAll()
if err != nil {
return err
}
for _, forum := range forums {
forumList = append(forumList, *forum)
}
varList = make(map[string]tmpl.VarItem)
header.Title = "Forum List"
forumsPage := ForumsPage{header, forumList}
forumsTmpl, err := compile("forums", "common.ForumsPage", forumsPage)
if err != nil {
return err
}
var topicsList []*TopicsRow
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
header2.Title = "Topic List"
topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
topicListTmpl, err := compile("topics", "common.TopicListPage", topicListPage)
if err != nil {
return err
}
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
header.Title = "General Forum"
forumPage := ForumPage{header, topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
forumTmpl, err := compile("forum", "common.ForumPage", forumPage)
if err != nil {
return err
}
header.Title = "Login Page"
loginPage := Page{header, tList, nil}
loginTmpl, err := compile("login", "common.Page", loginPage)
if err != nil {
return err
}
header.Title = "Registration Page"
registerPage := Page{header, tList, "nananana"}
registerTmpl, err := compile("register", "common.Page", registerPage)
if err != nil {
return err
}
header.Title = "Error"
errorPage := ErrorPage{header, "A problem has occurred in the system."}
errorTmpl, err := compile("error", "common.ErrorPage", errorPage)
if err != nil {
return err
}
var ipUserList = make(map[int]*User)
ipUserList[1] = &user2
header.Title = "IP Search"
ipSearchPage := IPSearchPage{header2, ipUserList, "::1"}
ipSearchTmpl, err := compile("ip_search", "common.IPSearchPage", ipSearchPage)
if err != nil {
return err
}
mfaSetup := false
prevScore := GetLevelScore(header.CurrentUser.Level)
currentScore := header.CurrentUser.Score - prevScore
nextScore := GetLevelScore(header.CurrentUser.Level+1) - prevScore
perc := int(math.Ceil((float64(nextScore) / float64(currentScore)) * 100))
accountPage := AccountDashPage{header, "dashboard", "account_own_edit", mfaSetup, currentScore, nextScore, user.Level + 1, perc * 2}
accountTmpl, err := compile("account", "common.AccountDashPage", accountPage)
if err != nil {
return err
}
var wg sync.WaitGroup
var writeTemplate = func(name string, content string) {
log.Print("Writing template '" + name + "'")
if content == "" {
log.Fatal("No content body")
}
wg.Add(1)
go func() {
err := writeFile("./template_"+name+".go", content)
if err != nil {
log.Fatal(err)
}
wg.Done()
}()
}
// Let plugins register their own templates
DebugLog("Registering the templates for the plugins")
config = c.GetConfig()
config.SkipHandles = true
c.SetConfig(config)
for _, tmplfunc := range PrebuildTmplList {
tmplItem := tmplfunc(user, header)
varList = make(map[string]tmpl.VarItem)
compiledTmpl, err := c.Compile(tmplItem.Filename, tmplItem.Path, tmplItem.StructName, tmplItem.Data, varList, tmplItem.Imports...)
if err != nil {
return err
}
writeTemplate(tmplItem.Name, compiledTmpl)
}
log.Print("Writing the templates")
writeTemplate("topic", topicIDTmpl)
writeTemplate("topic_alt", topicIDAltTmpl)
writeTemplate("profile", profileTmpl)
writeTemplate("forums", forumsTmpl)
writeTemplate("topics", topicListTmpl)
writeTemplate("forum", forumTmpl)
writeTemplate("login", loginTmpl)
writeTemplate("register", registerTmpl)
writeTemplate("ip_search", ipSearchTmpl)
writeTemplate("account", accountTmpl)
writeTemplate("error", errorTmpl)
writeTemplateList(c, &wg, "./")
return nil
}
func CompileJSTemplates() error {
log.Print("Compiling the JS templates")
var config tmpl.CTemplateConfig
config.Minify = Config.MinifyTemplates
config.Debug = Dev.DebugMode
config.SuperDebug = Dev.TemplateDebug
config.SkipHandles = true
config.SkipTmplPtrMap = true
config.SkipInitBlock = false
config.PackageName = "tmpl"
c := tmpl.NewCTemplateSet()
c.SetConfig(config)
c.SetBaseImportMap(map[string]string{
"io": "io",
"github.com/Azareal/Gosora/common/alerts": "github.com/Azareal/Gosora/common/alerts",
})
c.SetBuildTags("!no_templategen")
user, user2, user3 := tmplInitUsers()
header, _, _ := tmplInitHeaders(user, user2, user3)
now := time.Now()
var varList = make(map[string]tmpl.VarItem)
// TODO: Check what sort of path is sent exactly and use it here
alertItem := alerts.AlertItem{Avatar: "", ASID: 1, Path: "/", Message: "uh oh, something happened"}
alertTmpl, err := c.Compile("alert.html", "templates/", "alerts.AlertItem", alertItem, varList)
if err != nil {
return err
}
c.SetBaseImportMap(map[string]string{
"io": "io",
"github.com/Azareal/Gosora/common": "github.com/Azareal/Gosora/common",
})
// TODO: Fix the import loop so we don't have to use this hack anymore
c.SetBuildTags("!no_templategen,tmplgentopic")
var topicsRow = &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}
topicListItemTmpl, err := c.Compile("topics_topic.html", "templates/", "*common.TopicsRow", topicsRow, varList)
if err != nil {
return err
}
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
PollOption{0, "Nothing"},
PollOption{1, "Something"},
}, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "")
topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, RelativeTime(now), now, RelativeTime(now), 0, "", "127.0.0.1", 1, 0, 1, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false}
var replyList []ReplyUser
// TODO: Do we really want the UID here to be zero?
avatar, microAvatar = BuildAvatar(0, "")
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, RelativeTime(now), 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
varList = make(map[string]tmpl.VarItem)
header.Title = "Topic Name"
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, 1, 1}
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
topicIDTmpl, err := c.Compile("topic_posts.html", "templates/", "common.TopicPage", tpage, varList)
if err != nil {
return err
}
topicIDAltTmpl, err := c.Compile("topic_alt_posts.html", "templates/", "common.TopicPage", tpage, varList)
if err != nil {
return err
}
var dirPrefix = "./tmpl_client/"
var wg sync.WaitGroup
var writeTemplate = func(name string, content string) {
log.Print("Writing template '" + name + "'")
if content == "" {
log.Fatal("No content body")
}
wg.Add(1)
go func() {
err := writeFile(dirPrefix+"template_"+name+".go", content)
if err != nil {
log.Fatal(err)
}
wg.Done()
}()
}
writeTemplate("alert", alertTmpl)
writeTemplate("topics_topic", topicListItemTmpl)
writeTemplate("topic_posts", topicIDTmpl)
writeTemplate("topic_alt_posts", topicIDAltTmpl)
writeTemplateList(c, &wg, dirPrefix)
return nil
}
func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) {
log.Print("Writing template list")
wg.Add(1)
go func() {
out := "package " + c.GetConfig().PackageName + "\n\n"
var getterstr = "\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n"
for templateName, count := range c.TemplateFragmentCount {
out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n"
getterstr += "\tcase \"" + templateName + "\":\n"
getterstr += "\treturn " + templateName + "_frags\n"
}
getterstr += "}\nreturn nil\n}\n"
out += "\n// nolint\nfunc init() {\n" + c.FragOut + "\n" + getterstr + "}\n"
err := writeFile(prefix+"template_list.go", out)
if err != nil {
log.Fatal(err)
}
wg.Done()
}()
wg.Wait()
}
func arithToInt64(in interface{}) (out int64) {
switch in := in.(type) {
case int64:
out = in
case int32:
out = int64(in)
case int:
out = int64(in)
case uint32:
out = int64(in)
case uint16:
out = int64(in)
case uint8:
out = int64(in)
case uint:
out = int64(in)
}
return out
}
func arithDuoToInt64(left interface{}, right interface{}) (leftInt int64, rightInt int64) {
return arithToInt64(left), arithToInt64(right)
}
func InitTemplates() error {
DebugLog("Initialising the template system")
// TODO: Add support for floats
fmap := make(map[string]interface{})
fmap["add"] = func(left interface{}, right interface{}) interface{} {
leftInt, rightInt := arithDuoToInt64(left, right)
return leftInt + rightInt
}
fmap["subtract"] = func(left interface{}, right interface{}) interface{} {
leftInt, rightInt := arithDuoToInt64(left, right)
return leftInt - rightInt
}
fmap["multiply"] = func(left interface{}, right interface{}) interface{} {
leftInt, rightInt := arithDuoToInt64(left, right)
return leftInt * rightInt
}
fmap["divide"] = func(left interface{}, right interface{}) interface{} {
leftInt, rightInt := arithDuoToInt64(left, right)
if leftInt == 0 || rightInt == 0 {
return 0
}
return leftInt / rightInt
}
fmap["dock"] = func(dock interface{}, headerInt interface{}) interface{} {
return template.HTML(BuildWidget(dock.(string), headerInt.(*Header)))
}
fmap["lang"] = func(phraseNameInt interface{}) interface{} {
phraseName, ok := phraseNameInt.(string)
if !ok {
panic("phraseNameInt is not a string")
}
// TODO: Log non-existent phrases?
return template.HTML(phrases.GetTmplPhrase(phraseName))
}
fmap["level"] = func(levelInt interface{}) interface{} {
level, ok := levelInt.(int)
if !ok {
panic("levelInt is not an integer")
}
return template.HTML(phrases.GetLevelPhrase(level))
}
fmap["scope"] = func(name interface{}) interface{} {
return ""
}
fmap["dyntmpl"] = func(nameInt interface{}, pageInt interface{}, headerInt interface{}) interface{} {
header := headerInt.(*Header)
err := RunThemeTemplate(header.Theme.Name, nameInt.(string), pageInt, header.Writer)
if err != nil {
return err
}
return ""
}
// The interpreted templates...
DebugLog("Loading the template files...")
Templates.Funcs(fmap)
templateFiles, err := filepath.Glob("templates/*.html")
if err != nil {
return err
}
var templateFileMap = make(map[string]int)
for index, path := range templateFiles {
path = strings.Replace(path, "\\", "/", -1)
log.Print("templateFile: ", path)
templateFileMap[path] = index
}
overrideFiles, err := filepath.Glob("templates/overrides/*.html")
if err != nil {
return err
}
for _, path := range overrideFiles {
path = strings.Replace(path, "\\", "/", -1)
log.Print("overrideFile: ", path)
index, ok := templateFileMap["templates/"+strings.TrimPrefix(path, "templates/overrides/")]
if !ok {
log.Print("not ok: templates/" + strings.TrimPrefix(path, "templates/overrides/"))
templateFiles = append(templateFiles, path)
continue
}
templateFiles[index] = path
}
template.Must(Templates.ParseFiles(templateFiles...))
template.Must(Templates.ParseGlob("pages/*"))
return nil
}