dati

A Go library/binary to parse & execute data against template langauges.
git clone git://src.gearsix.net/dati
Log | Files | Refs | Atom | README | LICENSE

commit 550d80f2d8fcca03e6a736127b585a4b5f9cae81
parent ef066916f4421681b9e95d54788f71e9f8590d93
Author: gearsix <gearsix@tuta.io>
Date:   Sat,  2 Oct 2021 15:02:07 +0100

renamed LoadTemplateFile -> LoadTemplateFilepath

Diffstat:
MCHANGELOG | 15+++++++++++++++
ATODO | 1+
Mtemplate.go | 22+++++++++++-----------
Atemplate.go.orig | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtemplate_test.go | 22+++++++++++-----------
5 files changed, 334 insertions(+), 22 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,5 +1,20 @@ # CHANGELOG +## v0.3.0 + +- added LoadTemplate, loads templates from io.Reader params, template language and name must be specified. +This function is the template.go counterpart to data.go#LoadData +- added LoadTemplateStrings loads templates from string params (calls LoadTemplate) + +## v0.2.2 + +- added .Source to suti.Template, contains the filepath of the source file for the template +- More tidyup + +## v0.2.1 + +- Tidyup work + ## v0.2.0 - massive refactored to data API: diff --git a/TODO b/TODO @@ -0,0 +1 @@ +- Make `loadTemplateFileX` public diff --git a/template.go b/template.go @@ -187,31 +187,31 @@ func loadTemplateFileMst(root string, partials ...string) (*mst.Template, error) return mst.ParseFilePartials(root, mstfp) } -// LoadTemplateFile loads a Template from file `root`. All files in `partials` +// LoadTemplateFilepath loads a Template from file `root`. All files in `partials` // that have the same template type (identified by file extension) are also // parsed and associated with the parsed root template. -func LoadTemplateFile(root string, partials ...string) (t Template, e error) { - if len(root) == 0 { - e = fmt.Errorf("no root template specified") +func LoadTemplateFilepath(rootPath string, partialPaths ...string) (t Template, e error) { + if len(rootPath) == 0 { + e = fmt.Errorf("no rootPath template specified") } - if stat, err := os.Stat(root); err != nil { + if stat, err := os.Stat(rootPath); err != nil { e = err } else if stat.IsDir() { - e = fmt.Errorf("root path must be a file, not a directory: %s", root) + e = fmt.Errorf("rootPath path must be a file, not a directory: %s", rootPath) } if e != nil { return } - t = Template{Source: root} - ttype := getTemplateType(root) + t = Template{Source: rootPath} + ttype := getTemplateType(rootPath) if ttype == "tmpl" || ttype == "gotmpl" { - t.Template, e = loadTemplateFileTmpl(root, partials...) + t.Template, e = loadTemplateFileTmpl(rootPath, partialPaths...) } else if ttype == "hmpl" || ttype == "gohmpl" { - t.Template, e = loadTemplateFileHmpl(root, partials...) + t.Template, e = loadTemplateFileHmpl(rootPath, partialPaths...) } else if ttype == "mst" || ttype == "mustache" { - t.Template, e = loadTemplateFileMst(root, partials...) + t.Template, e = loadTemplateFileMst(rootPath, partialPaths...) } else { e = fmt.Errorf("'%s' is not a supported template language", ttype) } diff --git a/template.go.orig b/template.go.orig @@ -0,0 +1,296 @@ +package suti + +/* +Copyright (C) 2021 gearsix <gearsix@tuta.io> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +import ( + "bytes" + "fmt" + mst "github.com/cbroglie/mustache" + hmpl "html/template" + "io/fs" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + tmpl "text/template" +) + +// SupportedTemplateLangs provides a list of supported languages for template files (lower-case) +var SupportedTemplateLangs = []string{"tmpl", "hmpl", "mst"} + +// IsSupportedTemplateLang provides the index of `SupportedTemplateLangs` that `lang` is at. +// If `lang` is not in `SupportedTemplateLangs`, `-1` will be returned. +// File extensions can be passed in `lang`, the prefixed `.` will be trimmed. +func IsSupportedTemplateLang(lang string) int { + lang = strings.ToLower(lang) + if len(lang) > 0 && lang[0] == '.' { + lang = lang[1:] + } + for i, l := range SupportedTemplateLangs { + if lang == l { + return i + } + } + return -1 +} + +func getTemplateType(path string) string { + return strings.TrimPrefix(filepath.Ext(path), ".") +} + +// Template is a generic interface to any template parsed from LoadTemplateFile +type Template struct { + Source string + Template interface{} +} + +// Execute executes `t` against `d`. Reflection is used to determine +// the template type and call it's execution fuction. +func (t *Template) Execute(d interface{}) (result bytes.Buffer, err error) { + var funcName string + var params []reflect.Value + switch (reflect.TypeOf(t.Template).String()) { + case "*template.Template": // golang templates + funcName = "Execute" + params = []reflect.Value{reflect.ValueOf(&result), reflect.ValueOf(d)} + case "*mustache.Template": + funcName = "FRender" + params = []reflect.Value{reflect.ValueOf(&result), reflect.ValueOf(d)} + default: + err = fmt.Errorf("unable to infer template type '%s'", reflect.TypeOf(t.Template).String()) + } + + if err == nil { + rval := reflect.ValueOf(t.Template).MethodByName(funcName).Call(params) + if !rval[0].IsNil() { // err != nil + err = rval[0].Interface().(error) + } + } + + return +} + +// TODO +func LoadTemplateFileTmpl(root string, partials ...string) (*tmpl.Template, error) { + var stat os.FileInfo + t, e := tmpl.ParseFiles(root) + + for i := 0; i < len(partials) && e == nil; i++ { + p := partials[i] + ptype := getTemplateType(p) + + stat, e = os.Stat(p) + if e == nil { + if ptype == "tmpl" || ptype == "gotmpl" { + t, e = t.ParseFiles(p) + } else if strings.Contains(p, "*") { + t, e = t.ParseGlob(p) + } else if stat.IsDir() { + e = filepath.Walk(p, func(path string, info fs.FileInfo, err error) error { + if err == nil && !info.IsDir() { + ptype = getTemplateType(path) + if ptype == "tmpl" || ptype == "gotmpl" { + t, err = t.ParseFiles(path) + } + } + return err + }) + } else { + return nil, fmt.Errorf("non-matching filetype (%s)", p) + } + } + } + + return t, e +} + +// TODO +func LoadTemplateFileHmpl(root string, partials ...string) (*hmpl.Template, error) { + var stat os.FileInfo + t, e := hmpl.ParseFiles(root) + + for i := 0; i < len(partials) && e == nil; i++ { + p := partials[i] + ptype := getTemplateType(p) + + stat, e = os.Stat(p) + if e == nil { + if ptype == "hmpl" || ptype == "gohmpl" { + t, e = t.ParseFiles(p) + } else if strings.Contains(p, "*") { + t, e = t.ParseGlob(p) + } else if stat.IsDir() { + e = filepath.Walk(p, func(path string, info fs.FileInfo, err error) error { + if err == nil && !info.IsDir() { + ptype = getTemplateType(path) + if ptype == "hmpl" || ptype == "gohmpl" { + t, err = t.ParseFiles(path) + } + } + return err + }) + } else { + return nil, fmt.Errorf("non-matching filetype (%s)", p) + } + } + } + + return t, e +} + +// TODO +func LoadTemplateFileMst(root string, partials ...string) (*mst.Template, error) { + var err error + for p, partial := range partials { + if err != nil { + break + } + + if stat, e := os.Stat(partial); e != nil { + partials = append(partials[:p], partials[p+1:]...) + err = e + } else if stat.IsDir() == false { + partials[p] = filepath.Dir(partial) + } else if strings.Contains(partial, "*") { + if paths, e := filepath.Glob(partial); e != nil { + partials = append(partials[:p], partials[p+1:]...) + err = e + } else { + partials = append(partials[:p], partials[p+1:]...) + partials = append(partials, paths...) + } + } + } + + if err != nil { + return nil, err + } + + mstfp := &mst.FileProvider{ + Paths: partials, + Extensions: []string{".mst", ".mustache"}, + } + return mst.ParseFilePartials(root, mstfp) +} + +// LoadTemplateFile loads a Template from file `root`. All files in `partials` +// that have the same template type (identified by file extension) are also +// parsed and associated with the parsed root template. +func LoadTemplateFile(root string, partials ...string) (t Template, e error) { + if len(root) == 0 { + e = fmt.Errorf("no root template specified") + } + if stat, err := os.Stat(root); err != nil { + e = err + } else if stat.IsDir() { + e = fmt.Errorf("root path must be a file, not a directory: %s", root) + } + +<<<<<<< HEAD + if e == nil { + t = Template{Source: root} + ttype := getTemplateType(root) + if ttype == "tmpl" || ttype == "gotmpl" { + t.Template, e = LoadTemplateFileTmpl(root, partials...) + } else if ttype == "hmpl" || ttype == "gohmpl" { + t.Template, e = LoadTemplateFileHmpl(root, partials...) + } else if ttype == "mst" || ttype == "mustache" { + t.Template, e = LoadTemplateFileMst(root, partials...) + } else { + e = fmt.Errorf("'%s' is not a supported template language", ttype) +======= + if e != nil { + return + } + + t = Template{Source: root} + ttype := getTemplateType(root) + if ttype == "tmpl" || ttype == "gotmpl" { + t.Template, e = loadTemplateFileTmpl(root, partials...) + } else if ttype == "hmpl" || ttype == "gohmpl" { + t.Template, e = loadTemplateFileHmpl(root, partials...) + } else if ttype == "mst" || ttype == "mustache" { + t.Template, e = loadTemplateFileMst(root, partials...) + } else { + e = fmt.Errorf("'%s' is not a supported template language", ttype) + } + + return +} + +// LoadTemplateString loads a Template from string `root` of type `ttype`, named `name`. +// `ttype` must be an element in `SupportedTemplateLangs`. +// `name` is optional, if empty the template name will be "template". +// `root` should be a string of template, with syntax matching that of `ttype`. +// `partials` should be a string of template, with syntax matching that of `ttype`. +func LoadTemplateString(ttype string, name string, root string, partials ...string) (t Template, e error) { + if len(root) == 0 { + e = fmt.Errorf("no root template") + } + if IsSupportedTemplateLang(ttype) == -1 { + e = fmt.Errorf("invalid type '%s'", ttype) + } + if e != nil { + return + } + + if len(name) == 0 { + name = "template" + } + + switch(ttype) { + case "tmpl": + var template *tmpl.Template + if template, e = tmpl.New(name).Parse(root); e != nil { + break + } + for i, p := range partials { + if _, e = template.New(name + "-partial" + strconv.Itoa(i)).Parse(p); e != nil { + break + } +>>>>>>> LoadTemplateString + } + t.Template = template + case "hmpl": + var template *hmpl.Template + if template, e = hmpl.New(name).Parse(root); e != nil { + break + } + for i, p := range partials { + if _, e = template.New(name + "-partial" + strconv.Itoa(i)).Parse(p); e != nil { + break + } + } + t.Template = template + case "mst": + var template *mst.Template + mstpp := new(mst.StaticProvider) + mstpp.Partials = make(map[string]string) + for p, partial := range partials { + mstpp.Partials[name+"-partial"+strconv.Itoa(p)] = partial + } + template, e = mst.ParseStringPartials(root, mstpp) + t.Template = template + default: + e = fmt.Errorf("'%s' is not a supported template language", ttype) + } + + return +} + diff --git a/template_test.go b/template_test.go @@ -141,7 +141,7 @@ func validateTemplateFile(t *testing.T, template Template, root string, partials } } -func TestLoadTemplateFile(t *testing.T) { +func TestLoadTemplateFilepath(t *testing.T) { var gr, gp, br, bp []string tdir := t.TempDir() i := 0 @@ -176,19 +176,19 @@ func TestLoadTemplateFile(t *testing.T) { writeTestFile(t, bp[i], mstPartialBad) for g, root := range gr { // good root, good partials - if template, e := LoadTemplateFile(root, gp[g]); e != nil { + if template, e := LoadTemplateFilepath(root, gp[g]); e != nil { t.Fatal(e) } else { validateTemplateFile(t, template, root, gp[g]) } } for _, root := range br { // bad root, good partials - if _, e := LoadTemplateFile(root, gp...); e == nil { + if _, e := LoadTemplateFilepath(root, gp...); e == nil { t.Fatalf("no error for bad template with good partials\n") } } for _, root := range br { // bad root, bad partials - if _, e := LoadTemplateFile(root, bp...); e == nil { + if _, e := LoadTemplateFilepath(root, bp...); e == nil { t.Fatalf("no error for bad template with bad partials\n") } } @@ -236,7 +236,7 @@ func TestLoadTemplateString(t *testing.T) { } } -// func TestLoadTemplateString(t *testing.T) {} // This is tested by TestLoadTemplateFile and TestLoadTemplateString +// func TestLoadTemplateString(t *testing.T) {} // This is tested by TestLoadTemplateFilepath and TestLoadTemplateString func validateExecute(t *testing.T, results string, expect string, e error) { if e != nil { @@ -281,22 +281,22 @@ func TestExecute(t *testing.T) { d = append(d, data) gd["data"] = d - if tmpl1, e = LoadTemplateFile(tmplr, tmplp); e != nil { + if tmpl1, e = LoadTemplateFilepath(tmplr, tmplp); e != nil { t.Skip("setup failure:", e) } - if tmpl2, e = LoadTemplateFile(tmplr, tdir); e != nil { + if tmpl2, e = LoadTemplateFilepath(tmplr, tdir); e != nil { t.Skip("setup failure:", e) } - if hmpl1, e = LoadTemplateFile(hmplr, hmplp); e != nil { + if hmpl1, e = LoadTemplateFilepath(hmplr, hmplp); e != nil { t.Skip("setup failure:", e) } - if hmpl2, e = LoadTemplateFile(tmplr, tdir); e != nil { + if hmpl2, e = LoadTemplateFilepath(tmplr, tdir); e != nil { t.Skip("setup failure:", e) } - if mst1, e = LoadTemplateFile(mstr, mstp); e != nil { + if mst1, e = LoadTemplateFilepath(mstr, mstp); e != nil { t.Skip("setup failure:", e) } - if mst2, e = LoadTemplateFile(tmplr, tdir); e != nil { + if mst2, e = LoadTemplateFilepath(tmplr, tdir); e != nil { t.Skip("setup failure:", e) }