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 4316ce2c0d58463e589cb0d73e378934be6d660a
parent 558cb2cc2e31e860daae15f1d5b82803d1630e3e
Author: gearsix <gearsix@tuta.io>
Date:   Sat,  2 Oct 2021 00:37:40 +0100

Merge branch 'LoadTemplateString'

Diffstat:
Mtemplate.go | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mtemplate_test.go | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 167 insertions(+), 35 deletions(-)

diff --git a/template.go b/template.go @@ -26,6 +26,7 @@ import ( "os" "path/filepath" "reflect" + "strconv" "strings" tmpl "text/template" ) @@ -85,8 +86,7 @@ func (t *Template) Execute(d interface{}) (result bytes.Buffer, err error) { return } -// TODO -func LoadTemplateFileTmpl(root string, partials ...string) (*tmpl.Template, error) { +func loadTemplateFileTmpl(root string, partials ...string) (*tmpl.Template, error) { var stat os.FileInfo t, e := tmpl.ParseFiles(root) @@ -111,7 +111,7 @@ func LoadTemplateFileTmpl(root string, partials ...string) (*tmpl.Template, erro return err }) } else { - return nil, fmt.Errorf("non-matching filetype") + return nil, fmt.Errorf("non-matching filetype (%s)", p) } } } @@ -119,8 +119,7 @@ func LoadTemplateFileTmpl(root string, partials ...string) (*tmpl.Template, erro return t, e } -// TODO -func LoadTemplateFileHmpl(root string, partials ...string) (*hmpl.Template, error) { +func loadTemplateFileHmpl(root string, partials ...string) (*hmpl.Template, error) { var stat os.FileInfo t, e := hmpl.ParseFiles(root) @@ -145,7 +144,7 @@ func LoadTemplateFileHmpl(root string, partials ...string) (*hmpl.Template, erro return err }) } else { - return nil, fmt.Errorf("non-matching filetype") + return nil, fmt.Errorf("non-matching filetype (%s)", p) } } } @@ -153,8 +152,7 @@ func LoadTemplateFileHmpl(root string, partials ...string) (*hmpl.Template, erro return t, e } -// TODO -func LoadTemplateFileMst(root string, partials ...string) (*mst.Template, error) { +func loadTemplateFileMst(root string, partials ...string) (*mst.Template, error) { var err error for p, partial := range partials { if err != nil { @@ -195,25 +193,85 @@ 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) } - 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 + } } + 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 @@ -25,6 +25,24 @@ import ( "testing" ) +const tmplRootGood = "{{.eg}} {{ template \"tmplPartialGood.tmpl\" . }}" +const tmplPartialGood = "{{range .data}}{{.eg}}{{end}}" +const tmplResult = "0 00" +const tmplRootBad = "{{ example }}} {{{ template \"tmplPartialBad.tmpl\" . }}" +const tmplPartialBad = "{{{ .example }}" + +const hmplRootGood = "<!DOCTYPE html><html><p>{{.eg}} {{ template \"hmplPartialGood.hmpl\" . }}</p></html>" +const hmplPartialGood = "<b>{{range .data}}{{.eg}}{{end}}</b>" +const hmplResult = "<!DOCTYPE html><html><p>0 <b>00</b></p></html>" +const hmplRootBad = "{{ example }} {{{ template \"hmplPartialBad.hmpl\" . }}" +const hmplPartialBad = "<b>{{{ .example2 }}</b>" + +const mstRootGood = "{{eg}} {{> mstPartialGood}}" +const mstPartialGood = "{{#data}}{{eg}}{{/data}}" +const mstResult = "0 00" +const mstRootBad = "{{> badPartial.mst}}{{#doesnt-exist}}{{/exit}}" +const mstPartialBad = "p{{$}{{ > noexist}}" + func TestIsSupportedTemplateLang(t *testing.T) { exts := []string{ ".tmpl", "tmpl", "TMPL", ".TMPL", @@ -55,23 +73,37 @@ func TestIsSupportedTemplateLang(t *testing.T) { } } -const tmplRootGood = "{{.eg}} {{ template \"tmplPartialGood.tmpl\" . }}" -const tmplPartialGood = "{{range .data}}{{.eg}}{{end}}" -const tmplResult = "0 00" -const tmplRootBad = "{{ example }}} {{{ template \"tmplPartialBad.tmpl\" . }}" -const tmplPartialBad = "{{{ .example }}" +func validateTemplate(t *testing.T, template Template, templateType string, rootName string, partialNames ...string) { + types := map[string]string{ + "tmpl": "*template.Template", + "gotmpl": "*template.Template", + "hmpl": "*template.Template", + "gohmpl": "*template.Template", + "mst": "*mustache.Template", + "mustache": "*mustache.Template", + } -const hmplRootGood = "<!DOCTYPE html><html><p>{{.eg}} {{ template \"hmplPartialGood.hmpl\" . }}</p></html>" -const hmplPartialGood = "<b>{{range .data}}{{.eg}}{{end}}</b>" -const hmplResult = "<!DOCTYPE html><html><p>0 <b>00</b></p></html>" -const hmplRootBad = "{{ example }} {{{ template \"hmplPartialBad.hmpl\" . }}" -const hmplPartialBad = "<b>{{{ .example2 }}</b>" + rt := reflect.TypeOf(template.Template).String() + if rt != types[templateType] { + t.Fatalf("invalid template type '%s' loaded, should be '%s'", rt, types[templateType]) + } -const mstRootGood = "{{eg}} {{> mstPartialGood}}" -const mstPartialGood = "{{#data}}{{eg}}{{/data}}" -const mstResult = "0 00" -const mstRootBad = "{{> badPartial.mst}}{{#doesnt-exist}}{{/exit}}" -const mstPartialBad = "p{{$}{{ > noexist}}" + if types[templateType] == "*template.Template" { + var rv []reflect.Value + for _, p := range partialNames { + rv := reflect.ValueOf(template.Template).MethodByName("Lookup").Call([]reflect.Value{reflect.ValueOf(p)}) + if rv[0].IsNil() { + t.Fatalf("missing defined template '%s'", p) + rv = reflect.ValueOf(template.Template).MethodByName("DefinedTemplates").Call([]reflect.Value{}) + t.Log(rv) + } + } + rv = reflect.ValueOf(template.Template).MethodByName("Name").Call([]reflect.Value{}) + if rv[0].String() != rootName { + t.Fatalf("invalid template name: %s does not match %s", rv[0].String(), rootName) + } + } +} func validateTemplateFile(t *testing.T, template Template, root string, partials ...string) { types := map[string]string{ @@ -162,6 +194,48 @@ func TestLoadTemplateFile(t *testing.T) { } } +func TestLoadTemplateString(t *testing.T) { + var gr, gp, br, bp []string + + gr = append(gr, tmplRootGood) + gp = append(gp, tmplPartialGood) + br = append(br, tmplRootBad) + bp = append(bp, tmplPartialBad) + + gr = append(gr, hmplRootGood) + gp = append(gp, hmplPartialGood) + br = append(br, hmplRootBad) + bp = append(bp, hmplPartialBad) + + gr = append(gr, mstRootGood) + gp = append(gp, mstPartialGood) + br = append(br, mstRootBad) + bp = append(bp, mstPartialBad) + + name := "test" + var ttype string + for g, root := range gr { // good root, good partials + ttype = SupportedTemplateLangs[g] + if template, e := LoadTemplateString(ttype, name, root, gp[g]); e != nil { + t.Fatalf("'%s' template failed to load: %s", ttype, e) + } else { + validateTemplate(t, template, ttype, name, name + "-partial0") + } + } + for b, root := range br { // bad root, good partials + ttype = SupportedTemplateLangs[b] + if _, e := LoadTemplateString(ttype, name, root, gp...); e == nil { + t.Fatalf("no error for bad template with good partials\n") + } + } + for b, root := range br { // bad root, bad partials + ttype = SupportedTemplateLangs[b] + if _, e := LoadTemplateString(ttype, name, root, bp...); e == nil { + t.Fatalf("no error for bad template with bad partials\n") + } + } +} + func validateExecute(t *testing.T, results string, expect string, e error) { if e != nil { t.Fatal(e)