-
-
Save logrusorgru/abd846adb521a6fb39c7405f32fec0cf to your computer and use it in GitHub Desktop.
// | |
// Copyright (c) 2018 Konstanin Ivanov <[email protected]>. | |
// All rights reserved. This program is free software. It comes without | |
// any warranty, to the extent permitted by applicable law. You can | |
// redistribute it and/or modify it under the terms of the Do What | |
// The Fuck You Want To Public License, Version 2, as published by | |
// Sam Hocevar. See below for more details. | |
// | |
// | |
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
// Version 2, December 2004 | |
// | |
// Copyright (C) 2004 Sam Hocevar <[email protected]> | |
// | |
// Everyone is permitted to copy and distribute verbatim or modified | |
// copies of this license document, and changing it is allowed as long | |
// as the name is changed. | |
// | |
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
// | |
// 0. You just DO WHAT THE FUCK YOU WANT TO. | |
// | |
// ************************************************************************** // | |
// // | |
// This gist shows a convenient way to load and use HTML templates in Golang // | |
// web-applications. The way includes: // | |
// // | |
// - recursive loading, unlike (html/template).ParseGlob does it // | |
// - short (Rails-like) template name without shared prefix (dir) // | |
// and without file extension // | |
// // | |
// For example, there is a tree of templates // | |
// views/ // | |
// static/ // | |
// home.html // | |
// about.html // | |
// privacypolicy.html // | |
// help.html // | |
// user/ // | |
// new.html // | |
// edit.html // | |
// show.html // | |
// form.html // | |
// layout/ // | |
// head.html // | |
// foot.html // | |
// // | |
// Thus, the home.html can include head.html and foor.html following way // | |
// // | |
// {{ template "layout/head" . }} // | |
// // | |
// <h1> Home page </h1> // | |
// <!-- other content of the home.html // | |
// // | |
// {{ template "layout/foot" . }} // | |
// // | |
// This is acceptable for user/new and user/edit which can include user/form // | |
// along with the layout/head and layout/foot. // | |
// // | |
// ************************************************************************** // | |
// A Tmpl implements keeper, loader and reloader for HTML templates | |
type Tmpl struct { | |
*template.Template // root template | |
} | |
// NewTmpl creates new Tmpl. | |
func NewTmpl() (tmpl *Tmpl) { | |
tmpl = new(Tmpl) | |
tmpl.Template = template.New("") // unnamed root template | |
return | |
} | |
// SetFuncs sets template functions to underlying templates | |
func (t *Tmpl) SetFuncs(funcMap template.FuncMap) { | |
t.Template = t.Template.Funcs(funcMap) | |
} | |
// Load templates. The dir argument is a directory to load templates from. | |
// The ext argument is extension of tempaltes. | |
func (t *Tmpl) Load(dir, ext string) (err error) { | |
// get absolute path | |
if dir, err = filepath.Abs(dir); err != nil { | |
return fmt.Errorf("getting absolute path: %w", err) | |
} | |
var root = t.Template | |
var walkFunc = func(path string, info os.FileInfo, err error) (_ error) { | |
// handle walking error if any | |
if err != nil { | |
return err | |
} | |
// skip all except regular files | |
// TODO (kostyarin): follow symlinks (?) | |
if !info.Mode().IsRegular() { | |
return | |
} | |
// filter by extension | |
if filepath.Ext(path) != ext { | |
return | |
} | |
// get relative path | |
var rel string | |
if rel, err = filepath.Rel(dir, path); err != nil { | |
return err | |
} | |
// name of a template is its relative path | |
// without extension | |
rel = strings.TrimSuffix(rel, ext) | |
rel = strings.Join(strings.Split(rel, string(os.PathSeparator)), "/") | |
// load or reload | |
var ( | |
nt = root.New(rel) | |
b []byte | |
) | |
if b, err = ioutil.ReadFile(path); err != nil { | |
return err | |
} | |
_, err = nt.Parse(string(b)) | |
return err | |
} | |
if err = filepath.Walk(dir, walkFunc); err != nil { | |
return | |
} | |
t.Template = root // set or replace (does it needed?) | |
return | |
} | |
// Render is equal to ExecuteTemplate. | |
// | |
// DEPRECATED: use Go native ExeuteTempalte instead. | |
func (t *Tmpl) Render(w io.Writer, name string, data interface{}) error { | |
return t.ExecuteTemplate(w, name, data) | |
} |
Can I ask you how to use it?
For a directory structure, for example,
templates/
one/
one-a.html
one-b.html
two/
two-a.html
two-b.html
Load templates
var templs, err = NeTempl("./templates/", ".html", false)
if err != nil {
// handle error
}
Use the templates
// the w is a io.Writer, can be a http.ResponseWriter for example
err = templs.Render(w, "one/one-a", map[string]interface{
"key": "value, for example"
})
if err != nil {
// rendering or writing error
}
Everything is described in the load.go header.
Thank you for your quick answer.
I'm using the precious code as you indicated but I have this error:
panic: NewTmpl: template: tests/templates/test.tmpl:1: function "title" not defined
I think the error is correct because if one of the templates contains the following code:
func {{title .EntityName}} ...
it doesn't know how to interpret "title
" and I can't even use:
templs.Funcs(funcMap)
after the NewTempl()
because there is already the call to Load
in it that uses nt.Parse(string(b))
.
Do you think a change is needed?
Should we also pass funcs
in NewTempl()
?
panic: NewTmpl: template: tests/templates/test.tmpl:1: function "title" not defined
Do you think a change is needed?
Should we also pass funcs in NewTempl()?
Hm. May be. Pass funcs in the NewTempl or move the Load out of the NewTempl and load outside, when functions are set. Also, I would change Funcs
to SetFuncs
for a better naming. And I would get rid out of the development stuff (the .devel
and all related).
@frederikhors , I've updated the load.go
. And I've not tested it.
var templs = NeTempl()
// may be not 100% correct here
templs.SetFuncs(template.FuncMap{
"title": strings.Title,
})
var err = templs.Load("./templates/", ".html")
if err != nil {
// handle error
}
Use the templates
// the w is a io.Writer, can be a http.ResponseWriter for example
err = templs.ExecuteTemplatte(w, "one/one-a", map[string]interface{
"key": "value, for example"
})
if err != nil {
// rendering or writing error
}
You forgot t.dir
on line https://gist.github.com/logrusorgru/abd846adb521a6fb39c7405f32fec0cf#file-load-go-L136.
Why did you remove the develop part? Do you find it no longer useful?
Plus I'm having a problem because I'm on Windows I think.
All the templates it finds have the key like: templates\customDir\subdir\file
.
And even if I use path.Join()
everywhere it won't find them unless I point to them using \
instead of /
(example "one\\one-a
" instead of "one/one-a
").
This is very strange. Do you understand why?
This is very strange. Do you understand why?
It uses relative filesystem path as a template name. You can add this line (below), to convert Windows-like paths to UNIX-like for a template name. The line is
rel = strings.TrimSuffix(rel, ext)
rel = strings.Join(strings.Split(rel, os.PathSeparator), "/") // additional line
This way, all template names will be UNIX-like (e.g. /
-separated). I've added this to the load.go
.
Why did you remove the develop part? Do you find it no longer useful?
Yes, I think it useless.
Maybe we should use: string(os.PathSeparator)
.
Yep
Can I ask you how to use it?