Archived
1
0

add support for alternative template implementation

This commit is contained in:
Jeff Becker 2017-11-02 07:32:24 -04:00
parent 3c0122e8a2
commit 38a162e416
No known key found for this signature in database
GPG Key ID: F357B3B42F6F9B05
6 changed files with 607 additions and 554 deletions

View File

@ -68,7 +68,7 @@ func (self *catalogModel) Navbar() string {
}) })
param["prefix"] = self.prefix param["prefix"] = self.prefix
param["links"] = links param["links"] = links
return template.renderTemplate("navbar.mustache", param, self._i18n) return template.renderTemplate("navbar", param, self._i18n)
} }
func (self *catalogModel) MarshalJSON() (b []byte, err error) { func (self *catalogModel) MarshalJSON() (b []byte, err error) {
@ -162,7 +162,7 @@ func (self *boardModel) Navbar() string {
param["frontend"] = self.frontend param["frontend"] = self.frontend
param["prefix"] = self.prefix param["prefix"] = self.prefix
param["links"] = self.PageList() param["links"] = self.PageList()
return template.renderTemplate("navbar.mustache", param, self._i18n) return template.renderTemplate("navbar", param, self._i18n)
} }
func (self *boardModel) Board() string { func (self *boardModel) Board() string {
@ -517,7 +517,7 @@ func (self *post) SetIndex(idx int) {
func (self *post) RenderPost() string { func (self *post) RenderPost() string {
param := make(map[string]interface{}) param := make(map[string]interface{})
param["post"] = self param["post"] = self
return template.renderTemplate("post.mustache", param, self._i18n) return template.renderTemplate("post", param, self._i18n)
} }
func (self *post) RenderTruncatedPost() string { func (self *post) RenderTruncatedPost() string {
@ -623,7 +623,7 @@ func (self *thread) Navbar() string {
param["frontend"] = self.Board() param["frontend"] = self.Board()
param["links"] = self.links param["links"] = self.links
param["prefix"] = self.prefix param["prefix"] = self.prefix
return template.renderTemplate("navbar.mustache", param, self._i18n) return template.renderTemplate("navbar", param, self._i18n)
} }
func (self *thread) Board() string { func (self *thread) Board() string {
@ -649,11 +649,6 @@ func (self *thread) ImageCount() (count int) {
return return
} }
// get our default template dir
func defaultTemplateDir() string {
return filepath.Join("contrib", "templates", "default")
}
func createThreadModel(posts ...PostModel) ThreadModel { func createThreadModel(posts ...PostModel) ThreadModel {
op := posts[0] op := posts[0]
group := op.Board() group := op.Board()

View File

@ -5,534 +5,59 @@
package srnd package srnd
import ( import (
"bytes"
"encoding/json"
"fmt"
"github.com/cbroglie/mustache"
tinyhtml "github.com/whyrusleeping/tinyhtml"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"sync"
) )
type templateEngine struct { type TemplateDriver interface {
// loaded templates RenderString(template string, obj interface{}) (string, error)
templates map[string]string Render(template string, obj interface{}, w io.Writer) error
// root directory for templates Ext() string
template_dir string
// mutex for accessing templates
templates_mtx sync.RWMutex
// do we want to minimize the html generated?
Minimize bool
// database
DB Database
} }
func (self *templateEngine) templateCached(name string) (ok bool) { // get our default template dir
self.templates_mtx.Lock() func defaultTemplateDir() string {
_, ok = self.templates[name] p, _ := filepath.Abs(filepath.Join("contrib", "templates", "default"))
self.templates_mtx.Unlock()
return
}
// explicitly reload a template
func (self *templateEngine) reloadTemplate(name string) {
self.templates_mtx.Lock()
self.templates[name] = self.loadTemplate(name)
self.templates_mtx.Unlock()
}
// check if we have this template
func (self *templateEngine) hasTemplate(name string) bool {
return CheckFile(self.templateFilepath(name))
}
// explicitly reload all loaded templates
func (self *templateEngine) reloadAllTemplates() {
loadThese := []string{}
// get all the names of the templates we have loaded
self.templates_mtx.Lock()
for tname, _ := range self.templates {
loadThese = append(loadThese, tname)
}
self.templates_mtx.Unlock()
// for each template we have loaded, reload the contents from file
for _, tname := range loadThese {
self.reloadTemplate(tname)
}
}
// get cached post model from cache after updating it
func (self *templateEngine) updatePostModel(prefix, frontend, msgid, rootmsgid, group string, db Database) PostModel {
return db.GetPostModel(prefix, msgid)
/*
// get board
self.groups_mtx.Lock()
board := self.groups[group]
self.groups_mtx.Unlock()
var th ThreadModel
if msgid == rootmsgid {
// new thread
if len(board) > 0 {
page := board[0]
page.Update(db)
th = page.GetThread(rootmsgid)
}
} else {
// reply
for _, page := range board {
t := page.GetThread(rootmsgid)
if t != nil {
th = t
th.Update(db)
break
}
}
}
if th == nil {
// reload board, this will be a heavy operation
board.UpdateAll(db)
// find it
for _, page := range board {
t := page.GetThread(rootmsgid)
if t != nil {
th = t
th.Update(db)
break
}
}
for _, page := range board {
updateLinkCacheForBoard(page)
}
self.groups_mtx.Lock()
self.groups[group] = board
self.groups_mtx.Unlock()
}
if th == nil {
if rootmsgid == msgid {
return db.GetPostModel(prefix, rootmsgid)
}
log.Println("template could not find thread", rootmsgid, "in", group)
return nil
}
// found
m := th.OP()
if m.MessageID() == msgid {
return m
}
for _, p := range th.Replies() {
if p.MessageID() == msgid {
// found as reply
return p return p
} }
}
log.Println("template could not find post model for thread", rootmsgid, "in", group) func templateDriverForFile(fname string) TemplateDriver {
// not found switch strings.ToLower(filepath.Ext(fname)) {
case ".mustache":
return new(mustacheDriver)
case ".tmpl":
return new(stdTemplateDriver)
default:
return nil return nil
*/
}
// get the filepath to a template
func (self *templateEngine) templateFilepath(name string) string {
if strings.Count(name, "..") > 0 {
return ""
}
return filepath.Join(self.template_dir, name)
}
// load a template from file, return as string
func (self *templateEngine) loadTemplate(name string) (t string) {
b, err := ioutil.ReadFile(self.templateFilepath(name))
if err == nil {
t = string(b)
} else {
log.Println("error loading template", err)
t = err.Error()
}
return
}
// get a template, if it's not cached load from file and cache it
func (self *templateEngine) getTemplate(name string) (t string) {
if !self.templateCached(name) {
self.templates_mtx.Lock()
self.templates[name] = self.loadTemplate(name)
self.templates_mtx.Unlock()
}
self.templates_mtx.Lock()
t, _ = self.templates[name]
self.templates_mtx.Unlock()
return
}
// render a template, self explanitory
func (self *templateEngine) renderTemplate(name string, obj map[string]interface{}, i18n *I18N) string {
t := self.getTemplate(name)
if i18n == nil {
i18n = I18nProvider
}
obj["i18n"] = i18n
s, err := mustache.Render(t, obj)
if err == nil {
return s
} else {
return err.Error()
} }
} }
// write a template to an io.Writer func templateDriverFromDir(dir string) TemplateDriver {
func (self *templateEngine) writeTemplate(name string, obj map[string]interface{}, wr io.Writer, i18n *I18N) (err error) { files, err := ioutil.ReadDir(dir)
str := self.renderTemplate(name, obj, i18n) if err == nil && len(files) > 0 {
var r io.Reader for idx := range files {
r = bytes.NewBufferString(str) if !files[idx].IsDir() {
if self.Minimize { return templateDriverForFile(files[idx].Name())
r = tinyhtml.New(r)
}
_, err = io.Copy(wr, r)
return
}
// easy wrapper for json.NewEncoder
func (self *templateEngine) renderJSON(wr io.Writer, obj interface{}) {
err := json.NewEncoder(wr).Encode(obj)
if err != nil {
log.Println("error rendering json", err)
}
}
// get a board model given a newsgroup
// load un updated board model if we don't have it
func (self *templateEngine) obtainBoard(prefix, frontend, group string, db Database) (model GroupModel) {
// warning, we attempt to do smart reloading
// dark magic may lurk here
p := db.GetGroupPageCount(group)
pages := int(p)
perpage, _ := db.GetThreadsPerPage(group)
// reload all the pages
var newModel GroupModel
for page := 0; page < pages; page++ {
newModel = append(newModel, db.GetGroupForPage(prefix, frontend, group, page, int(perpage)))
}
model = newModel
return
}
func (self *templateEngine) genCatalog(prefix, frontend, group string, wr io.Writer, db Database, i18n *I18N) {
board := self.obtainBoard(prefix, frontend, group, db)
catalog := new(catalogModel)
catalog.prefix = prefix
catalog.frontend = frontend
catalog.board = group
catalog.I18N(i18n)
for page, bm := range board {
for _, th := range bm.Threads() {
th.Update(db)
catalog.threads = append(catalog.threads, &catalogItemModel{op: th.OP(), page: page, replycount: len(th.Replies())})
} }
} }
self.writeTemplate("catalog.mustache", map[string]interface{}{"board": catalog}, wr, i18n)
}
// generate a board page
func (self *templateEngine) genBoardPage(allowFiles, requireCaptcha bool, prefix, frontend, newsgroup string, page int, wr io.Writer, db Database, json bool, i18n *I18N) {
// get the board page model
perpage, _ := db.GetThreadsPerPage(newsgroup)
boardPage := db.GetGroupForPage(prefix, frontend, newsgroup, page, int(perpage))
boardPage.Update(db)
boardPage.I18N(i18n)
// render it
if json {
self.renderJSON(wr, boardPage)
} else {
form := renderPostForm(prefix, newsgroup, "", allowFiles, requireCaptcha, i18n)
self.writeTemplate("board.mustache", map[string]interface{}{"board": boardPage, "page": page, "form": form}, wr, i18n)
} }
} log.Println("no template found in ", dir, " ", err)
return nil
func (self *templateEngine) genUkko(prefix, frontend string, wr io.Writer, database Database, json bool, i18n *I18N) {
self.genUkkoPaginated(prefix, frontend, wr, database, 0, json, i18n)
}
func (self *templateEngine) genUkkoPaginated(prefix, frontend string, wr io.Writer, database Database, page int, json bool, i18n *I18N) {
var threads []ThreadModel
for _, article := range database.GetLastBumpedThreadsPaginated("", 10, page*10) {
root := article[0]
thread, err := database.GetThreadModel(prefix, root)
if err == nil {
thread.I18N(i18n)
threads = append(threads, thread)
}
}
obj := map[string]interface{}{"prefix": prefix, "threads": threads, "page": page}
if page > 0 {
obj["prev"] = map[string]interface{}{"no": page - 1}
}
if page < 10 {
obj["next"] = map[string]interface{}{"no": page + 1}
}
if json {
self.renderJSON(wr, obj)
} else {
// render ukko navbar
navbar := make(map[string]interface{})
navbar["name"] = "Overboard"
navbar["frontend"] = frontend
navbar["prefix"] = prefix
// inject navbar
obj["navbar"] = self.renderTemplate("navbar.mustache", navbar, i18n)
// render
self.writeTemplate("ukko.mustache", obj, wr, i18n)
}
}
func (self *templateEngine) genThread(allowFiles, requireCaptcha bool, root ArticleEntry, prefix, frontend string, wr io.Writer, db Database, json bool, i18n *I18N) {
newsgroup := root.Newsgroup()
msgid := root.MessageID()
/*
if !db.HasArticleLocal(msgid) {
log.Println("don't have", msgid, "locally, not regenerating")
return
}
*/
t, err := db.GetThreadModel(prefix, msgid)
if err == nil {
if json {
self.renderJSON(wr, t)
} else {
t.I18N(i18n)
form := renderPostForm(prefix, newsgroup, msgid, allowFiles, requireCaptcha, i18n)
self.writeTemplate("thread.mustache", map[string]interface{}{"thread": t, "board": map[string]interface{}{"Name": newsgroup, "Frontend": frontend, "AllowFiles": allowFiles}, "form": form, "prefix": prefix}, wr, i18n)
}
} else {
log.Println("templates: error getting thread for ", msgid, err.Error())
}
/*
// get the board model, don't update the board
board := self.obtainBoard(prefix, frontend, newsgroup, false, db)
// find the thread model in question
for _, pagemodel := range board {
t := pagemodel.GetThread(msgid)
if t != nil {
// update thread
t.Update(db)
// render it
if json {
self.renderJSON(wr, t)
} else {
form := renderPostForm(prefix, newsgroup, msgid, allowFiles)
self.writeTemplate("thread.mustache", map[string]interface{}{"thread": t, "board": pagemodel, "form": form}, wr)
}
return
}
}
log.Println("thread not found for message id", msgid)
return
// we didn't find it D:
// reload everything
// TODO: should we reload everything!?
b := self.obtainBoard(prefix, frontend, newsgroup, true, db)
// find the thread model in question
for _, pagemodel := range b {
t := pagemodel.GetThread(msgid)
if t != nil {
// we found it
// render thread
t.Update(db)
if json {
self.renderJSON(wr, t)
} else {
form := renderPostForm(prefix, newsgroup, msgid, allowFiles)
self.writeTemplate("thread.mustache", map[string]interface{}{"thread": t, "board": pagemodel, "form": form}, wr)
}
self.groups_mtx.Lock()
self.groups[newsgroup] = b
self.groups_mtx.Unlock()
return
}
}
// it's not there wtf
log.Println("thread not found for message id", msgid)
*/
}
// change the directory we are using for templates
func (self *templateEngine) changeTemplateDir(dirname string) {
log.Println("change template directory to", dirname)
self.template_dir = dirname
self.reloadAllTemplates()
}
func (self *templateEngine) createNotFoundHandler(prefix, frontend string) (h http.Handler) {
h = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
self.renderNotFound(w, r, prefix, frontend, nil)
})
return
}
// default renderer of 404 pages
func (self *templateEngine) renderNotFound(wr http.ResponseWriter, r *http.Request, prefix, frontend string, i18n *I18N) {
wr.WriteHeader(404)
opts := make(map[string]interface{})
opts["prefix"] = prefix
opts["frontend"] = frontend
self.writeTemplate("404.mustache", opts, wr, i18n)
} }
func newTemplateEngine(dir string) *templateEngine { func newTemplateEngine(dir string) *templateEngine {
return &templateEngine{ return &templateEngine{
templates: make(map[string]string), templates: make(map[string]string),
template_dir: dir, template_dir: dir,
driver: templateDriverFromDir(dir),
} }
} }
func (self *templateEngine) findLink(prefix, hash string) (url string) {
ents, _ := self.DB.GetCitesByPostHashLike(hash)
if len(ents) > 0 {
url = fmt.Sprintf("%st/%s/#%s", prefix, HashMessageID(ents[0].Reference()), HashMessageID(ents[0].MessageID()))
}
return
}
var template = newTemplateEngine(defaultTemplateDir()) var template = newTemplateEngine(defaultTemplateDir())
func renderPostForm(prefix, board, op_msg_id string, files, captcha bool, i18n *I18N) string {
url := prefix + "post/" + board
button := "New Thread"
if op_msg_id != "" {
button = "Reply"
if i18n != nil {
b := i18n.Translate("postbutton_reply")
if b != "" {
button = b
}
}
} else if i18n != nil {
b := i18n.Translate("postbutton_thread")
if b != "" {
button = b
}
}
return template.renderTemplate("postform.mustache", map[string]interface{}{"post_url": url, "reference": op_msg_id, "button": button, "files": files, "prefix": prefix, "DisableCaptcha": !captcha}, i18n)
}
// generate misc graphs
func (self *templateEngine) genGraphs(prefix string, wr io.Writer, db Database, i18n *I18N) {
//
// begin gen history.html
//
var all_posts postsGraph
// this may take a bit
log.Println("getting monthly post history...")
posts := db.GetMonthlyPostHistory()
if posts == nil {
// wtf?
log.Println("no monthly posts gotten wtfug yo?")
} else {
for _, entry := range posts {
all_posts = append(all_posts, postsGraphRow{
day: entry.Time(),
Num: entry.Count(),
})
}
}
sort.Sort(all_posts)
_, err := io.WriteString(wr, self.renderTemplate("graph_history.mustache", map[string]interface{}{"history": all_posts}, i18n))
if err != nil {
log.Println("error writing history graph", err)
}
//
// end gen history.html
//
}
func (self *templateEngine) genBoardList(prefix, name string, wr io.Writer, db Database, i18n *I18N) {
// the graph for the front page
var frontpage_graph boardPageRows
// for each group
groups := db.GetAllNewsgroups()
for _, group := range groups {
// posts this hour
hour := db.CountPostsInGroup(group, 3600)
// posts today
day := db.CountPostsInGroup(group, 86400)
// posts total
all := db.CountPostsInGroup(group, 0)
frontpage_graph = append(frontpage_graph, boardPageRow{
All: all,
Day: day,
Hour: hour,
Board: group,
})
}
param := map[string]interface{}{
"prefix": prefix,
"frontend": name,
}
sort.Sort(frontpage_graph)
param["graph"] = frontpage_graph
_, err := io.WriteString(wr, self.renderTemplate("boardlist.mustache", param, i18n))
if err != nil {
log.Println("error writing board list page", err)
}
}
// generate front page
func (self *templateEngine) genFrontPage(top_count int, prefix, frontend_name string, indexwr, boardswr io.Writer, db Database, i18n *I18N) {
models := db.GetLastPostedPostModels(prefix, 20)
for idx := range models {
models[idx].I18N(i18n)
}
wr := indexwr
param := make(map[string]interface{})
param["overview"] = self.renderTemplate("overview.mustache", map[string]interface{}{"overview": overviewModel(models)}, i18n)
/*
sort.Sort(posts_graph)
param["postsgraph"] = self.renderTemplate("posts_graph.mustache", map[string]interface{}{"graph": posts_graph})
if len(frontpage_graph) > top_count {
param["boardgraph"] = frontpage_graph[:top_count]
} else {
param["boardgraph"] = frontpage_graph
}
*/
param["frontend"] = frontend_name
param["totalposts"] = db.ArticleCount()
param["prefix"] = prefix
// render and inject navbar
param["navbar"] = self.renderTemplate("navbar.mustache", map[string]interface{}{"name": "Front Page", "frontend": frontend_name, "prefix": prefix}, i18n)
_, err := io.WriteString(wr, self.renderTemplate("frontpage.mustache", param, i18n))
if err != nil {
log.Println("error writing front page", err)
}
/*
wr = boardswr
*/
}
func ReloadTemplates() { func ReloadTemplates() {
log.Println("reload templates") log.Println("reload templates")
template.reloadAllTemplates() template.reloadAllTemplates()

View File

@ -0,0 +1,522 @@
//
// templates.go
// template model interfaces
//
package srnd
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"sort"
"strings"
"sync"
)
type templateEngine struct {
// loaded templates
templates map[string]string
// root directory for templates
template_dir string
// mutex for accessing templates
templates_mtx sync.RWMutex
// do we want to minimize the html generated?
Minimize bool
// database
DB Database
// template driver
driver TemplateDriver
}
func (self *templateEngine) templateCached(name string) (ok bool) {
self.templates_mtx.Lock()
_, ok = self.templates[name]
self.templates_mtx.Unlock()
return
}
// explicitly reload a template
func (self *templateEngine) reloadTemplate(name string) {
self.templates_mtx.Lock()
self.templates[name] = self.loadTemplate(name)
self.templates_mtx.Unlock()
}
// check if we have this template
func (self *templateEngine) hasTemplate(name string) bool {
return CheckFile(self.templateFilepath(name))
}
// explicitly reload all loaded templates
func (self *templateEngine) reloadAllTemplates() {
loadThese := []string{}
// get all the names of the templates we have loaded
self.templates_mtx.Lock()
for tname, _ := range self.templates {
loadThese = append(loadThese, tname)
}
self.templates_mtx.Unlock()
// for each template we have loaded, reload the contents from file
for _, tname := range loadThese {
self.reloadTemplate(tname)
}
}
// get cached post model from cache after updating it
func (self *templateEngine) updatePostModel(prefix, frontend, msgid, rootmsgid, group string, db Database) PostModel {
return db.GetPostModel(prefix, msgid)
/*
// get board
self.groups_mtx.Lock()
board := self.groups[group]
self.groups_mtx.Unlock()
var th ThreadModel
if msgid == rootmsgid {
// new thread
if len(board) > 0 {
page := board[0]
page.Update(db)
th = page.GetThread(rootmsgid)
}
} else {
// reply
for _, page := range board {
t := page.GetThread(rootmsgid)
if t != nil {
th = t
th.Update(db)
break
}
}
}
if th == nil {
// reload board, this will be a heavy operation
board.UpdateAll(db)
// find it
for _, page := range board {
t := page.GetThread(rootmsgid)
if t != nil {
th = t
th.Update(db)
break
}
}
for _, page := range board {
updateLinkCacheForBoard(page)
}
self.groups_mtx.Lock()
self.groups[group] = board
self.groups_mtx.Unlock()
}
if th == nil {
if rootmsgid == msgid {
return db.GetPostModel(prefix, rootmsgid)
}
log.Println("template could not find thread", rootmsgid, "in", group)
return nil
}
// found
m := th.OP()
if m.MessageID() == msgid {
return m
}
for _, p := range th.Replies() {
if p.MessageID() == msgid {
// found as reply
return p
}
}
log.Println("template could not find post model for thread", rootmsgid, "in", group)
// not found
return nil
*/
}
// get the filepath to a template
func (self *templateEngine) templateFilepath(name string) string {
if strings.Count(name, "..") > 0 {
return ""
}
return filepath.Join(self.template_dir, name+self.driver.Ext())
}
// load a template from file, return as string
func (self *templateEngine) loadTemplate(name string) (t string) {
b, err := ioutil.ReadFile(self.templateFilepath(name))
if err == nil {
t = string(b)
} else {
log.Println("error loading template", err)
t = err.Error()
}
return
}
// get a template, if it's not cached load from file and cache it
func (self *templateEngine) getTemplate(name string) (t string) {
if !self.templateCached(name) {
self.templates_mtx.Lock()
self.templates[name] = self.loadTemplate(name)
self.templates_mtx.Unlock()
}
self.templates_mtx.Lock()
t, _ = self.templates[name]
self.templates_mtx.Unlock()
return
}
// render a template, self explanitory
func (self *templateEngine) renderTemplate(name string, obj map[string]interface{}, i18n *I18N) string {
t := self.getTemplate(name)
if i18n == nil {
i18n = I18nProvider
}
obj["i18n"] = i18n
s, err := self.driver.RenderString(t, obj)
if err == nil {
return s
} else {
return err.Error()
}
}
// write a template to an io.Writer
func (self *templateEngine) writeTemplate(name string, obj map[string]interface{}, wr io.Writer, i18n *I18N) (err error) {
t := self.getTemplate(name)
if i18n == nil {
i18n = I18nProvider
}
obj["i18n"] = i18n
return self.driver.Render(t, obj, wr)
}
// easy wrapper for json.NewEncoder
func (self *templateEngine) renderJSON(wr io.Writer, obj interface{}) {
err := json.NewEncoder(wr).Encode(obj)
if err != nil {
log.Println("error rendering json", err)
}
}
// get a board model given a newsgroup
// load un updated board model if we don't have it
func (self *templateEngine) obtainBoard(prefix, frontend, group string, db Database) (model GroupModel) {
// warning, we attempt to do smart reloading
// dark magic may lurk here
p := db.GetGroupPageCount(group)
pages := int(p)
perpage, _ := db.GetThreadsPerPage(group)
// reload all the pages
var newModel GroupModel
for page := 0; page < pages; page++ {
newModel = append(newModel, db.GetGroupForPage(prefix, frontend, group, page, int(perpage)))
}
model = newModel
return
}
func (self *templateEngine) genCatalog(prefix, frontend, group string, wr io.Writer, db Database, i18n *I18N) {
board := self.obtainBoard(prefix, frontend, group, db)
catalog := new(catalogModel)
catalog.prefix = prefix
catalog.frontend = frontend
catalog.board = group
catalog.I18N(i18n)
for page, bm := range board {
for _, th := range bm.Threads() {
th.Update(db)
catalog.threads = append(catalog.threads, &catalogItemModel{op: th.OP(), page: page, replycount: len(th.Replies())})
}
}
self.writeTemplate("catalog", map[string]interface{}{"board": catalog}, wr, i18n)
}
// generate a board page
func (self *templateEngine) genBoardPage(allowFiles, requireCaptcha bool, prefix, frontend, newsgroup string, page int, wr io.Writer, db Database, json bool, i18n *I18N) {
// get the board page model
perpage, _ := db.GetThreadsPerPage(newsgroup)
boardPage := db.GetGroupForPage(prefix, frontend, newsgroup, page, int(perpage))
boardPage.Update(db)
boardPage.I18N(i18n)
// render it
if json {
self.renderJSON(wr, boardPage)
} else {
form := renderPostForm(prefix, newsgroup, "", allowFiles, requireCaptcha, i18n)
self.writeTemplate("board", map[string]interface{}{"board": boardPage, "page": page, "form": form}, wr, i18n)
}
}
func (self *templateEngine) genUkko(prefix, frontend string, wr io.Writer, database Database, json bool, i18n *I18N) {
self.genUkkoPaginated(prefix, frontend, wr, database, 0, json, i18n)
}
func (self *templateEngine) genUkkoPaginated(prefix, frontend string, wr io.Writer, database Database, page int, json bool, i18n *I18N) {
var threads []ThreadModel
for _, article := range database.GetLastBumpedThreadsPaginated("", 10, page*10) {
root := article[0]
thread, err := database.GetThreadModel(prefix, root)
if err == nil {
thread.I18N(i18n)
threads = append(threads, thread)
}
}
obj := map[string]interface{}{"prefix": prefix, "threads": threads, "page": page}
if page > 0 {
obj["prev"] = map[string]interface{}{"no": page - 1}
}
if page < 10 {
obj["next"] = map[string]interface{}{"no": page + 1}
}
if json {
self.renderJSON(wr, obj)
} else {
// render ukko navbar
navbar := make(map[string]interface{})
navbar["name"] = "Overboard"
navbar["frontend"] = frontend
navbar["prefix"] = prefix
// inject navbar
obj["navbar"] = self.renderTemplate("navbar", navbar, i18n)
// render
self.writeTemplate("ukko", obj, wr, i18n)
}
}
func (self *templateEngine) genThread(allowFiles, requireCaptcha bool, root ArticleEntry, prefix, frontend string, wr io.Writer, db Database, json bool, i18n *I18N) {
newsgroup := root.Newsgroup()
msgid := root.MessageID()
/*
if !db.HasArticleLocal(msgid) {
log.Println("don't have", msgid, "locally, not regenerating")
return
}
*/
t, err := db.GetThreadModel(prefix, msgid)
if err == nil {
if json {
self.renderJSON(wr, t)
} else {
t.I18N(i18n)
form := renderPostForm(prefix, newsgroup, msgid, allowFiles, requireCaptcha, i18n)
self.writeTemplate("thread", map[string]interface{}{"thread": t, "board": map[string]interface{}{"Name": newsgroup, "Frontend": frontend, "AllowFiles": allowFiles}, "form": form, "prefix": prefix}, wr, i18n)
}
} else {
log.Println("templates: error getting thread for ", msgid, err.Error())
}
/*
// get the board model, don't update the board
board := self.obtainBoard(prefix, frontend, newsgroup, false, db)
// find the thread model in question
for _, pagemodel := range board {
t := pagemodel.GetThread(msgid)
if t != nil {
// update thread
t.Update(db)
// render it
if json {
self.renderJSON(wr, t)
} else {
form := renderPostForm(prefix, newsgroup, msgid, allowFiles)
self.writeTemplate("thread.mustache", map[string]interface{}{"thread": t, "board": pagemodel, "form": form}, wr)
}
return
}
}
log.Println("thread not found for message id", msgid)
return
// we didn't find it D:
// reload everything
// TODO: should we reload everything!?
b := self.obtainBoard(prefix, frontend, newsgroup, true, db)
// find the thread model in question
for _, pagemodel := range b {
t := pagemodel.GetThread(msgid)
if t != nil {
// we found it
// render thread
t.Update(db)
if json {
self.renderJSON(wr, t)
} else {
form := renderPostForm(prefix, newsgroup, msgid, allowFiles)
self.writeTemplate("thread.mustache", map[string]interface{}{"thread": t, "board": pagemodel, "form": form}, wr)
}
self.groups_mtx.Lock()
self.groups[newsgroup] = b
self.groups_mtx.Unlock()
return
}
}
// it's not there wtf
log.Println("thread not found for message id", msgid)
*/
}
// change the directory we are using for templates
func (self *templateEngine) changeTemplateDir(dirname string) {
log.Println("change template directory to", dirname)
self.template_dir = dirname
self.reloadAllTemplates()
}
func (self *templateEngine) createNotFoundHandler(prefix, frontend string) (h http.Handler) {
h = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
self.renderNotFound(w, r, prefix, frontend, nil)
})
return
}
// default renderer of 404 pages
func (self *templateEngine) renderNotFound(wr http.ResponseWriter, r *http.Request, prefix, frontend string, i18n *I18N) {
wr.WriteHeader(404)
opts := make(map[string]interface{})
opts["prefix"] = prefix
opts["frontend"] = frontend
self.writeTemplate("404", opts, wr, i18n)
}
func (self *templateEngine) findLink(prefix, hash string) (url string) {
ents, _ := self.DB.GetCitesByPostHashLike(hash)
if len(ents) > 0 {
url = fmt.Sprintf("%st/%s/#%s", prefix, HashMessageID(ents[0].Reference()), HashMessageID(ents[0].MessageID()))
}
return
}
func renderPostForm(prefix, board, op_msg_id string, files, captcha bool, i18n *I18N) string {
url := prefix + "post/" + board
button := "New Thread"
if op_msg_id != "" {
button = "Reply"
if i18n != nil {
b := i18n.Translate("postbutton_reply")
if b != "" {
button = b
}
}
} else if i18n != nil {
b := i18n.Translate("postbutton_thread")
if b != "" {
button = b
}
}
return template.renderTemplate("postform", map[string]interface{}{"post_url": url, "reference": op_msg_id, "button": button, "files": files, "prefix": prefix, "DisableCaptcha": !captcha}, i18n)
}
// generate misc graphs
func (self *templateEngine) genGraphs(prefix string, wr io.Writer, db Database, i18n *I18N) {
//
// begin gen history.html
//
var all_posts postsGraph
// this may take a bit
log.Println("getting monthly post history...")
posts := db.GetMonthlyPostHistory()
if posts == nil {
// wtf?
log.Println("no monthly posts gotten wtfug yo?")
} else {
for _, entry := range posts {
all_posts = append(all_posts, postsGraphRow{
day: entry.Time(),
Num: entry.Count(),
})
}
}
sort.Sort(all_posts)
_, err := io.WriteString(wr, self.renderTemplate("graph_history", map[string]interface{}{"history": all_posts}, i18n))
if err != nil {
log.Println("error writing history graph", err)
}
//
// end gen history.html
//
}
func (self *templateEngine) genBoardList(prefix, name string, wr io.Writer, db Database, i18n *I18N) {
// the graph for the front page
var frontpage_graph boardPageRows
// for each group
groups := db.GetAllNewsgroups()
for _, group := range groups {
// posts this hour
hour := db.CountPostsInGroup(group, 3600)
// posts today
day := db.CountPostsInGroup(group, 86400)
// posts total
all := db.CountPostsInGroup(group, 0)
frontpage_graph = append(frontpage_graph, boardPageRow{
All: all,
Day: day,
Hour: hour,
Board: group,
})
}
param := map[string]interface{}{
"prefix": prefix,
"frontend": name,
}
sort.Sort(frontpage_graph)
param["graph"] = frontpage_graph
_, err := io.WriteString(wr, self.renderTemplate("boardlist", param, i18n))
if err != nil {
log.Println("error writing board list page", err)
}
}
// generate front page
func (self *templateEngine) genFrontPage(top_count int, prefix, frontend_name string, indexwr, boardswr io.Writer, db Database, i18n *I18N) {
models := db.GetLastPostedPostModels(prefix, 20)
for idx := range models {
models[idx].I18N(i18n)
}
wr := indexwr
param := make(map[string]interface{})
param["overview"] = self.renderTemplate("overview", map[string]interface{}{"overview": overviewModel(models)}, i18n)
/*
sort.Sort(posts_graph)
param["postsgraph"] = self.renderTemplate("posts_graph.mustache", map[string]interface{}{"graph": posts_graph})
if len(frontpage_graph) > top_count {
param["boardgraph"] = frontpage_graph[:top_count]
} else {
param["boardgraph"] = frontpage_graph
}
*/
param["frontend"] = frontend_name
param["totalposts"] = db.ArticleCount()
param["prefix"] = prefix
// render and inject navbar
param["navbar"] = self.renderTemplate("navbar", map[string]interface{}{"name": "Front Page", "frontend": frontend_name, "prefix": prefix}, i18n)
_, err := io.WriteString(wr, self.renderTemplate("frontpage", param, i18n))
if err != nil {
log.Println("error writing front page", err)
}
/*
wr = boardswr
*/
}

View File

@ -0,0 +1,29 @@
//
// templates.go
// template model interfaces
//
package srnd
import (
"github.com/cbroglie/mustache"
"io"
)
type mustacheDriver struct {
}
func (d *mustacheDriver) RenderString(templ string, obj interface{}) (string, error) {
return mustache.Render(templ, obj)
}
func (d *mustacheDriver) Render(templ string, obj interface{}, w io.Writer) error {
s, err := d.RenderString(templ, obj)
if err == nil {
_, err = io.WriteString(w, s)
}
return err
}
func (d *mustacheDriver) Ext() string {
return ".mustache"
}

View File

@ -0,0 +1,27 @@
package srnd
import (
"bytes"
stdtemplate "html/template"
"io"
)
type stdTemplateDriver struct {
}
func (d *stdTemplateDriver) Render(templ string, obj interface{}, w io.Writer) error {
return stdtemplate.Must(stdtemplate.New("").Parse(templ)).Execute(w, obj)
}
func (d *stdTemplateDriver) RenderString(templ string, obj interface{}) (string, error) {
buff := new(bytes.Buffer)
err := d.Render(templ, obj, buff)
if err == nil {
return buff.String(), nil
}
return "", err
}
func (d *stdTemplateDriver) Ext() string {
return ".tmpl"
}

View File

@ -1,45 +0,0 @@
package srnd
import (
"log"
"os"
"testing"
)
func makeBenchmarkDB() Database {
return NewDatabase("postgres", "srnd", "/var/run/postgresql", "", "", "")
}
func BenchmarkRenderBoardPage(b *testing.B) {
db := makeBenchmarkDB()
db.CreateTables()
defer db.Close()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
wr, err := os.Create("boardpage.html")
if err == nil {
template.genBoardPage(true, true, "prefix", "test", "overchan.random", 0, wr, db, false, nil)
} else {
log.Println("did not write", "boardpage.html", err)
}
wr.Close()
}
})
}
func BenchmarkRenderThread(b *testing.B) {
db := makeBenchmarkDB()
db.CreateTables()
defer db.Close()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
wr, err := os.Create("thread.html")
if err == nil {
template.genThread(true, true, ArticleEntry{"<c49be1451427261@nntp.nsfl.tk>", "overchan.random"}, "prefix", "frontend", wr, db, false, nil)
} else {
log.Println("did not write", "thread.html", err)
}
wr.Close()
}
})
}