Skip to content

Commit

Permalink
Added support for default (and user defined) functions
Browse files Browse the repository at this point in the history
  • Loading branch information
xeoncross committed Nov 3, 2018
1 parent 14c4216 commit 8b0f478
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 65 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ We solve this problem by adding a simple [template comment](https://golang.org/p

This comment is removed by `html/template` in the final output, but tells `got` to load this child template inside `mobilelayout.html`.

## Subfolders

Simple sites can easily fit everything under the `pages`, `includes`, and `layouts` namespaces. However, for better organization of your HTML snippets you can also use subfolders. For example, you might have custom sidebar modules you wish to isolate to their own files. Including items in subfolders (and beyond) is as easy as regular files. Imagine you had the following file:

/templates/includes/sidebar/active_users.html

You can access this in your templates using the string `includes/sidebar/active_users`.

## Benchmarks

Expand All @@ -82,11 +89,6 @@ This library adds almost no overhead to `html/template` for rendering templates.

This library is as fast as `html/template` because the organizational sorting and inheritance calculations are performed on the initial load.

## Roadmap

- Template Functions
- Allow registering functions to provide global template variables: (nonces, session data, etc...)


## Template Functions (Recommended)

Expand Down
50 changes: 50 additions & 0 deletions files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package got

import (
"io/ioutil"
"os"
"path/filepath"
"strings"
)

// locate templates in possibly nested subfolders
func findTemplatesRecursively(path string, extension string) (paths []string, err error) {
err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err == nil {
if strings.Contains(path, extension) {
paths = append(paths, path)
}
}
return err
})
return
}

// Handles reading templates files in the given directory + ending path
func loadTemplateFiles(dir, path, extension string) (templates map[string][]byte, err error) {
var files []string
files, err = findTemplatesRecursively(filepath.Join(dir, path), extension)
if err != nil {
return
}

templates = make(map[string][]byte)

for _, path = range files {
var b []byte
b, err = ioutil.ReadFile(path)
if err != nil {
return
}

// Convert "templates/layouts/base.html" to "layouts/base"
// For subfolders the extra folder name is included:
// "templates/includes/sidebar/ad1.html" to "includes/sidebar/ad1"
name := strings.TrimPrefix(filepath.Clean(path), filepath.Clean(dir)+"/")
name = strings.TrimSuffix(name, filepath.Ext(name))

templates[name] = b
}

return
}
90 changes: 90 additions & 0 deletions functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package got

import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"html/template"
"strings"
"time"
)

// Functions I've found to be required in most every web-site template engine
// Many borrowed from https://github.com/Masterminds/sprig

// DefaultFunctions for templates
var DefaultFunctions = template.FuncMap{
"title": strings.Title,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"trim": strings.TrimSpace,
// Display singluar or plural based on count
"plural": func(one, many string, count int) string {
if count == 1 {
return one
}
return many
},
// Current Date (Local server time)
"date": func() string {
return time.Now().Format("2006-01-02")
},
// Current Unix timestamp
"unixtimestamp": func() string {
return fmt.Sprintf("%d", time.Now().Unix())
},
// json encodes an item into a JSON string
"json": func(v interface{}) string {
output, _ := json.Marshal(v)
return string(output)
},
// Allow unsafe injection into HTML
"noescape": func(a ...interface{}) template.HTML {
return template.HTML(fmt.Sprint(a...))
},
// Allow unsafe URL injections into HTML
"noescapeurl": func(u string) template.URL {
return template.URL(u)
},
// Modern Hash
"sha256": func(input string) string {
hash := sha256.Sum256([]byte(input))
return hex.EncodeToString(hash[:])
},
// Legacy
"sha1": func(input string) string {
hash := sha1.Sum([]byte(input))
return hex.EncodeToString(hash[:])
},
// Gravatar
"md5": func(input string) string {
hash := md5.Sum([]byte(input))
return hex.EncodeToString(hash[:])
},
// Popular encodings
"base64encode": func(v string) string {
return base64.StdEncoding.EncodeToString([]byte(v))
},
"base64decode": func(v string) string {
data, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return err.Error()
}
return string(data)
},
"base32encode": func(v string) string {
return base32.StdEncoding.EncodeToString([]byte(v))
},
"base32decode": func(v string) string {
data, err := base32.StdEncoding.DecodeString(v)
if err != nil {
return err.Error()
}
return string(data)
},
}
73 changes: 15 additions & 58 deletions templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"path/filepath"
"regexp"
"strings"
)

// Children define the base template using comments: { /* use basetemplate */ }
Expand Down Expand Up @@ -47,19 +45,6 @@ func (t *NotFoundError) Error() string {
// // Template for displaying errors
// var ErrorTemplate = template.Must(template.New("error").Parse(errorTemplateHTML))

// FindTemplates in path recursively
// func FindTemplates(path string, extension string) (paths []string, err error) {
// err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
// if err == nil {
// if strings.Contains(path, extension) {
// paths = append(paths, path)
// }
// }
// return err
// })
// return
// }

// Templates Collection
type Templates struct {
Extension string
Expand All @@ -68,18 +53,6 @@ type Templates struct {
Functions template.FuncMap
}

// DefaultFunctions for templates
var DefaultFunctions = template.FuncMap{
// Allow unsafe injection into HTML
"noescape": func(a ...interface{}) template.HTML {
return template.HTML(fmt.Sprint(a...))
},
"title": strings.Title,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"trim": strings.TrimSpace,
}

// New Templates collection
func New(templatesDir, extension string) (*Templates, error) {
t := &Templates{
Expand All @@ -89,56 +62,41 @@ func New(templatesDir, extension string) (*Templates, error) {
Functions: DefaultFunctions,
}

return t, t.Load()
return t, t.load()
}

func LoadTemplateFiles(dir, path string) (templates map[string][]byte, err error) {
var files []string
files, err = filepath.Glob(filepath.Join(dir, path))
if err != nil {
return
}

templates = make(map[string][]byte)

for _, path = range files {
// fmt.Printf("Loading: %s\n", path)
var b []byte
b, err = ioutil.ReadFile(path)
if err != nil {
return
}

// Convert "templates/layouts/base.html" to "layouts/base"
name := strings.TrimPrefix(filepath.Clean(path), filepath.Clean(dir)+"/")
name = strings.TrimSuffix(name, filepath.Ext(name))

// fmt.Printf("%q = %q\n", name, b)
templates[name] = b
// Funcs function map for templates
func (t *Templates) Funcs(functions template.FuncMap) *Templates {
for _, tmpl := range t.Templates {
tmpl.Funcs(functions)
}

return
return t
}

func (t *Templates) Load() (err error) {
// Handles loading required templates
func (t *Templates) load() (err error) {

// Child pages to render
var pages map[string][]byte
pages, err = LoadTemplateFiles(t.Dir, "pages/*"+t.Extension)
// pages, err = loadTemplateFiles(t.Dir, "pages/*"+t.Extension)
pages, err = loadTemplateFiles(t.Dir, "pages/", t.Extension)
if err != nil {
return
}

// Shared templates across multiple pages (sidebars, scripts, footers, etc...)
var includes map[string][]byte
includes, err = LoadTemplateFiles(t.Dir, "includes/*"+t.Extension)
// includes, err = loadTemplateFiles(t.Dir, "includes/*"+t.Extension)
includes, err = loadTemplateFiles(t.Dir, "includes", t.Extension)
if err != nil {
return
}

// Layouts used by pages
var layouts map[string][]byte
layouts, err = LoadTemplateFiles(t.Dir, "layouts/*"+t.Extension)
// layouts, err = loadTemplateFiles(t.Dir, "layouts/*"+t.Extension)
layouts, err = loadTemplateFiles(t.Dir, "layouts", t.Extension)
if err != nil {
return
}
Expand All @@ -149,7 +107,7 @@ func (t *Templates) Load() (err error) {
matches := parentRegex.FindSubmatch(b)
basename := filepath.Base(name)

tmpl, err = template.New(basename).Parse(string(b))
tmpl, err = template.New(basename).Funcs(t.Functions).Parse(string(b))

// Uses a layout
if len(matches) == 2 {
Expand All @@ -165,7 +123,6 @@ func (t *Templates) Load() (err error) {

if len(includes) > 0 {
for name, src := range includes {
// fmt.Printf("\tAdding:%s = %s\n", name, string(src))
_, err = tmpl.New(name).Parse(string(src))
if err != nil {
return
Expand Down
5 changes: 3 additions & 2 deletions templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ type templateFile struct {

var testingTemplateFiles = []templateFile{
// We have two pages each using a different parent layout
{"pages/home.html", `{{define "content"}}home {{.Name}}{{end}} {{/* use one */}}`},
// One of the pages is using a subfolder
{"pages/home/home.html", `{{define "content"}}home {{.Name}}{{end}} {{/* use one */}}`},
{"pages/about.html", `{{define "content"}}about {{.Name}}{{end}}{{/* use two */}}`},
// We have two different layouts (using two different styles)
{"layouts/one.html", `Layout 1: {{.Name}} {{block "content" .}}{{end}} {{block "includes/sidebar" .}}{{end}}`},
Expand Down Expand Up @@ -254,7 +255,7 @@ func BenchmarkNativeTemplates(b *testing.B) {
t := template.New("")

var by []byte
for _, name := range []string{"pages/home", "layouts/one", "includes/sidebar"} {
for _, name := range []string{"pages/home/home", "layouts/one", "includes/sidebar"} {
by, err = ioutil.ReadFile(filepath.Join(dir, name+".html"))
if err != nil {
b.Error(err)
Expand Down

0 comments on commit 8b0f478

Please sign in to comment.