Initial commit

This commit is contained in:
2018-12-06 15:18:05 +01:00
commit 2cb1bf1881
23 changed files with 827 additions and 0 deletions

40
template/html/data.go Normal file
View File

@ -0,0 +1,40 @@
package html
import (
"net/http"
"forge.cadoles.com/wpetit/goweb/service"
"forge.cadoles.com/wpetit/goweb/service/session"
"forge.cadoles.com/wpetit/goweb/service/template"
"github.com/pkg/errors"
)
// WithFlashes extends the template's data with session's flash messages
func WithFlashes(w http.ResponseWriter, r *http.Request, container *service.Container) template.DataExtFunc {
return func(data template.Data) (template.Data, error) {
sessionService, err := session.From(container)
if err != nil {
return nil, errors.Wrap(err, "error while retrieving session service")
}
sess, err := sessionService.Get(w, r)
if err != nil {
return nil, errors.Wrap(err, "error while retrieving session")
}
flashes := sess.Flashes(
session.FlashError, session.FlashWarn,
session.FlashSuccess, session.FlashInfo,
)
data["Flashes"] = flashes
if err := sess.Save(w, r); err != nil {
return nil, errors.Wrap(err, "error while saving session")
}
return data, nil
}
}

71
template/html/helper.go Normal file
View File

@ -0,0 +1,71 @@
package html
import (
"encoding/json"
"errors"
"reflect"
"unicode/utf8"
)
func dump(args ...interface{}) (string, error) {
out := ""
for _, a := range args {
str, err := json.MarshalIndent(a, "", " ")
if err != nil {
return "", err
}
out += string(str) + "\n"
}
return out, nil
}
func increment(value int) int {
return value + 1
}
func ellipsis(str string, max int) string {
if utf8.RuneCountInString(str) > max {
return string([]rune(str)[0:(max-3)]) + "..."
}
return str
}
func customMap(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid custoMmap call")
}
customMap := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("map keys must be strings")
}
customMap[key] = values[i+1]
}
return customMap, nil
}
func defaultVal(src interface{}, defaultValue interface{}) interface{} {
switch typ := src.(type) {
case string:
if typ == "" {
return defaultValue
}
default:
if src == nil {
return defaultValue
}
}
return src
}
func has(v interface{}, name string) bool {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if rv.Kind() != reflect.Struct {
return false
}
return rv.FieldByName(name).IsValid()
}

58
template/html/option.go Normal file
View File

@ -0,0 +1,58 @@
package html
import "html/template"
// Options are configuration options for the template service
type Options struct {
Helpers template.FuncMap
PoolSize int
}
// OptionFunc configures options for the template service
type OptionFunc func(*Options)
// WithDefaultHelpers configures the template service
// to expose the default helpers
func WithDefaultHelpers() OptionFunc {
return func(opts *Options) {
helpers := template.FuncMap{
"dump": dump,
"ellipsis": ellipsis,
"map": customMap,
"default": defaultVal,
"inc": increment,
"has": has,
}
for name, fn := range helpers {
WithHelper(name, fn)(opts)
}
}
}
// WithHelper configures the template service
// to expose the default helpers
func WithHelper(name string, fn interface{}) OptionFunc {
return func(opts *Options) {
opts.Helpers[name] = fn
}
}
// WithPoolSize configures the template service
// to use the given pool size
func WithPoolSize(size int) OptionFunc {
return func(opts *Options) {
opts.PoolSize = size
}
}
func defaultOptions() *Options {
options := &Options{}
funcs := []OptionFunc{
WithPoolSize(64),
WithDefaultHelpers(),
}
for _, f := range funcs {
f(options)
}
return options
}

89
template/html/service.go Normal file
View File

@ -0,0 +1,89 @@
package html
import (
"fmt"
"html/template"
"net/http"
"path/filepath"
"github.com/oxtoacart/bpool"
)
// TemplateService is a template/html based templating service
type TemplateService struct {
templates map[string]*template.Template
pool *bpool.BufferPool
helpers template.FuncMap
}
// LoadTemplates loads the templates used by the service
func (t *TemplateService) LoadTemplates(templatesDir string) error {
layouts, err := filepath.Glob(filepath.Join(templatesDir, "layouts", "*.tmpl"))
if err != nil {
return err
}
blocks, err := filepath.Glob(filepath.Join(templatesDir, "blocks", "*.tmpl"))
if err != nil {
return err
}
// Generate our templates map from our layouts/ and blocks/ directories
for _, layout := range layouts {
var err error
files := append(blocks, layout)
tmpl := template.New("")
tmpl.Funcs(t.helpers)
tmpl, err = tmpl.ParseFiles(files...)
if err != nil {
return err
}
t.templates[filepath.Base(layout)] = tmpl
}
return nil
}
// RenderPage renders a template to the given http.ResponseWriter
func (t *TemplateService) RenderPage(w http.ResponseWriter, templateName string, data interface{}) error {
// Ensure the template exists in the map.
tmpl, ok := t.templates[templateName]
if !ok {
return fmt.Errorf("the template '%s' does not exist", templateName)
}
// Create a buffer to temporarily write to and check if any errors were encountered.
buf := t.pool.Get()
defer t.pool.Put(buf)
if err := tmpl.ExecuteTemplate(buf, templateName, data); err != nil {
return err
}
// Set the header and write the buffer to the http.ResponseWriter
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, err := buf.WriteTo(w)
return err
}
// NewTemplateService returns a new Service
func NewTemplateService(funcs ...OptionFunc) *TemplateService {
options := defaultOptions()
for _, f := range funcs {
f(options)
}
return &TemplateService{
templates: make(map[string]*template.Template),
pool: bpool.NewBufferPool(options.PoolSize),
helpers: options.Helpers,
}
}