Use static assets for HTML templates

- implement custom http.Filesystem boardvoting.AssetFS
- replace "footer" and "header" with "footer.html" and "header.html"
- change renderTemplate to use Assets
- use boardvoting.GetAssetFS() with http.Fileserver
debian
Jan Dittberner 6 years ago
parent 4dd5e09820
commit 94dcb5bd75

@ -10,7 +10,7 @@ import (
"encoding/pem"
"flag"
"fmt"
"git.cacert.org/cacert-boardvoting/boardvoting"
"github.com/Masterminds/sprig"
"github.com/gorilla/sessions"
_ "github.com/mattn/go-sqlite3"
@ -35,21 +35,31 @@ var log *logging.Logger
const sessionCookieName = "votesession"
func getTemplateFilenames(templates []string) (result []string) {
result = make([]string, len(templates))
for i := range templates {
result[i] = fmt.Sprintf("templates/%s", templates[i])
}
return result
}
func renderTemplate(w http.ResponseWriter, templates []string, context interface{}) {
funcMaps := sprig.FuncMap()
funcMaps["nl2br"] = func(text string) template.HTML {
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
}
t := template.Must(template.New(templates[0]).Funcs(funcMaps).ParseFiles(getTemplateFilenames(templates)...))
if err := t.Execute(w, context); err != nil {
var baseTemplate *template.Template
for count, t := range templates {
if assetBytes, err := boardvoting.Asset(fmt.Sprintf("templates/%s", t)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
if count == 0 {
if baseTemplate, err = template.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} else {
if _, err := baseTemplate.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
}
if err := baseTemplate.Execute(w, context); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
@ -733,7 +743,7 @@ func readConfig() {
}
func setupDbConfig(ctx context.Context) {
database, err := sql.Open("sqlite3", config.DatabaseFile)
database, err := sql.Open("sqlite3", config.DatabaseFile)
if err != nil {
log.Panicf("Opening database failed: %v", err)
}
@ -777,7 +787,7 @@ func setupHandlers() {
http.Handle("/newmotion/", motionsHandler{})
http.Handle("/proxy/", &decisionVoteHandler{})
http.Handle("/vote/", &decisionVoteHandler{})
http.Handle("/static/", http.FileServer(http.Dir(".")))
http.Handle("/static/", http.FileServer(boardvoting.GetAssetFS()))
http.Handle("/", http.RedirectHandler("/motions/", http.StatusMovedPermanently))
}

@ -1,3 +1,143 @@
package boardvoting
//go:generate go-bindata -pkg $GOPACKAGE -o assets.go ./migrations/... ./static/... ./templates/...
import (
"bytes"
"errors"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
var defaultFileTimestamp = time.Now()
type AssetFS struct {
Asset func(path string) ([]byte, error)
AssetDir func(path string) ([]string, error)
AssetInfo func(path string) (os.FileInfo, error)
}
type FakeFile struct {
Path string
Dir bool
Len int64
Timestamp time.Time
}
func (f *FakeFile) Name() string {
_, name := filepath.Split(f.Path)
return name
}
func (f *FakeFile) Size() int64 { return f.Len }
func (f *FakeFile) Mode() os.FileMode {
mode := os.FileMode(0644)
if f.Dir {
return mode | os.ModeDir
}
return mode
}
func (f *FakeFile) ModTime() time.Time { return f.Timestamp }
func (f *FakeFile) IsDir() bool { return f.Dir }
func (f *FakeFile) Sys() interface{} { return nil }
type AssetFile struct {
*bytes.Reader
io.Closer
FakeFile
}
func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile {
if timestamp.IsZero() {
timestamp = defaultFileTimestamp
}
return &AssetFile{
bytes.NewReader(content),
ioutil.NopCloser(nil),
FakeFile{name, false, int64(len(content)), timestamp},
}
}
func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, errors.New("not a directory")
}
func (f *AssetFile) Size() int64 {
return f.FakeFile.Size()
}
func (f *AssetFile) Stat() (os.FileInfo, error) {
return f, nil
}
type AssetDirectory struct {
AssetFile
ChildrenRead int
Children []os.FileInfo
}
func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) {
if count <= 0 {
return f.Children, nil
}
if f.ChildrenRead+count > len(f.Children) {
count = len(f.Children) - f.ChildrenRead
}
rv := f.Children[f.ChildrenRead : f.ChildrenRead+count]
f.ChildrenRead += count
return rv, nil
}
func (f *AssetDirectory) Stat() (os.FileInfo, error) {
return f, nil
}
func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory {
fileInfos := make([]os.FileInfo, 0, len(children))
for _, child := range children {
_, err := fs.AssetDir(filepath.Join(name, child))
fileInfos = append(fileInfos, &FakeFile{child, err == nil, 0, time.Time{}})
}
return &AssetDirectory{
AssetFile{
bytes.NewReader(nil),
ioutil.NopCloser(nil),
FakeFile{name, true, 0, time.Time{}},
},
0,
fileInfos}
}
func (f *AssetFS) Open(name string) (http.File, error) {
if len(name) > 0 && name[0] == '/' {
name = name[1:]
}
if b, err := f.Asset(name); err == nil {
timestamp := defaultFileTimestamp
if f.AssetInfo != nil {
if info, err := f.AssetInfo(name); err == nil {
timestamp = info.ModTime()
}
}
return NewAssetFile(name, b, timestamp), nil
}
if children, err := f.AssetDir(name); err == nil {
return NewAssetDirectory(name, children, f), nil
} else {
if strings.Contains(err.Error(), "not found") {
return nil, os.ErrNotExist
}
return nil, err
}
}
func GetAssetFS() *AssetFS {
return &AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo}
}

@ -1,4 +1,4 @@
{{ template "header" . }}
{{ template "header.html" . }}
<div class="column">
<div class="ui basic segment">
<div class="ui floated right secondary menu">
@ -70,4 +70,4 @@
</form>
</div>
</div>
{{ template "footer" . }}
{{ template "footer.html" . }}

@ -1,7 +1,7 @@
{{ template "header" . }}
{{ template "header.html" . }}
<div class="column">
<div class="ui negative message">
<div class="header">You are not authorized to act here!</div>
<div class="header.html">You are not authorized to act here!</div>
<p>If you think this is in error, please contact the administrator.</p>
<p>If you don't know who that is, it is definitely not an error ;)</p>
{{ if .Emails }}
@ -14,4 +14,4 @@
{{ end }}
</div>
</div>
{{ template "footer" . }}
{{ template "footer.html" . }}

@ -1,4 +1,4 @@
{{ template "header" . }}
{{ template "header.html" . }}
<div class="column">
<div class="ui basic segment">
<div class="ui floated right secondary menu">
@ -24,4 +24,4 @@
{{ end }}
</div>
</form>
{{ template "footer" . }}
{{ template "footer.html" . }}

@ -1,4 +1,4 @@
{{ template "header" . }}
{{ template "header.html" . }}
<div class="column">
<div class="ui floated right secondary menu">
<a href="/motions/" class="item" title="Show all votes">Back to
@ -69,4 +69,4 @@
</form>
</div>
</div>
{{ template "footer" . }}
{{ template "footer.html" . }}

@ -1,4 +1,4 @@
{{ define "footer" }}
{{ define "footer.html" }}
</div>
<script type="text/javascript">
$(document).ready(function() {

@ -1,4 +1,4 @@
{{ define "header" -}}
{{ define "header.html" -}}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>

@ -1,4 +1,4 @@
{{ template "header" . }}
{{ template "header.html" . }}
{{ $voter := .Voter }}
<div class="column">
<div class="ui basic segment">
@ -16,4 +16,4 @@
</div>
</div>
{{ end}}
{{ template "footer" . }}
{{ template "footer.html" . }}

@ -1,7 +1,7 @@
{{ define "motion_fragment" }}
<span class="ui {{ template "status_class" .Status }} ribbon label">{{ .Status|toString|title }}</span>
<span class="header">{{ .Modified|date "2006-01-02 15:04:05 UTC" }}</span>
<h3 class="header"><a href="/motions/{{ .Tag }}">{{ .Tag }}: {{ .Title }}</a></h3>
<span class="header.html">{{ .Modified|date "2006-01-02 15:04:05 UTC" }}</span>
<h3 class="header.html"><a href="/motions/{{ .Tag }}">{{ .Tag }}: {{ .Title }}</a></h3>
<p>{{ wrap 76 .Content | nl2br }}</p>
<table class="ui small definition table">
<tbody>

@ -1,4 +1,4 @@
{{ template "header" . }}
{{ template "header.html" . }}
{{ $voter := .Voter }}
<div class="column">
<div class="ui basic segment">
@ -49,4 +49,4 @@
<p>There are no motions in the system yet.</p>
{{ end }}
{{ end }}
{{ template "footer" . }}
{{ template "footer.html" . }}

@ -1,4 +1,4 @@
{{ template "header" . }}
{{ template "header.html" . }}
{{ $form := .Form }}
<div class="column">
<div class="ui basic segment">
@ -51,4 +51,4 @@
</form>
</div>
</div>
{{ template "footer" . }}
{{ template "footer.html" . }}

@ -1,4 +1,4 @@
{{ template "header" . }}
{{ template "header.html" . }}
<div class="column">
<div class="ui basic segment">
<div class="ui floated right secondary menu">
@ -18,4 +18,4 @@
<button class="ui primary left labeled icon button" type="submit"><i class="trash icon"></i> Withdraw</button>
</div>
</form>
{{ template "footer" . }}
{{ template "footer.html" . }}

Loading…
Cancel
Save