Skip to content

Instantly share code, notes, and snippets.

@logrusorgru
Last active November 18, 2023 10:50
Show Gist options
  • Save logrusorgru/abd846adb521a6fb39c7405f32fec0cf to your computer and use it in GitHub Desktop.
Save logrusorgru/abd846adb521a6fb39c7405f32fec0cf to your computer and use it in GitHub Desktop.
Golang load HTML templates
//
// 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)
}
@logrusorgru
Copy link
Author

@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
}

@frederikhors
Copy link

frederikhors commented Dec 27, 2021

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?

@logrusorgru
Copy link
Author

logrusorgru commented Dec 27, 2021

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.

@frederikhors
Copy link

The error with this new code now is:

image

@frederikhors
Copy link

Maybe we should use: string(os.PathSeparator).

@logrusorgru
Copy link
Author

Yep

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment