v1.15.0
ADDED: * tplx, for one-shotting/shortcutting templating
This commit is contained in:
1
go.sum
1
go.sum
@@ -13,3 +13,4 @@ golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
|||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
r00t2.io/sysutils v1.15.0 h1:FSnREfbXDhBQEO7LMpnRQeKlPshozxk9XHw3YgWRgRg=
|
r00t2.io/sysutils v1.15.0 h1:FSnREfbXDhBQEO7LMpnRQeKlPshozxk9XHw3YgWRgRg=
|
||||||
r00t2.io/sysutils v1.15.0/go.mod h1:28qB0074EIRQ8Sy/ybaA5jC3qA32iW2aYLkMCRhyAFM=
|
r00t2.io/sysutils v1.15.0/go.mod h1:28qB0074EIRQ8Sy/ybaA5jC3qA32iW2aYLkMCRhyAFM=
|
||||||
|
r00t2.io/sysutils v1.15.1/go.mod h1:T0iOnaZaSG5NE1hbXTqojRZc0ia/u8TB73lV7zhMz58=
|
||||||
|
|||||||
6
tplx/consts.go
Normal file
6
tplx/consts.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package tplx
|
||||||
|
|
||||||
|
const (
|
||||||
|
TplTypeText tplType = iota
|
||||||
|
TplTypeHtml
|
||||||
|
)
|
||||||
4
tplx/doc.go
Normal file
4
tplx/doc.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package tplx provides some "shortcuts" to [text/template] and [html/template] rendering.
|
||||||
|
*/
|
||||||
|
package tplx
|
||||||
9
tplx/errs.go
Normal file
9
tplx/errs.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package tplx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidTplType = errors.New("unknown/invalid template type")
|
||||||
|
)
|
||||||
235
tplx/funcs.go
Normal file
235
tplx/funcs.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
package tplx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bytes`
|
||||||
|
htmlTpl `html/template`
|
||||||
|
txtTpl `text/template`
|
||||||
|
)
|
||||||
|
|
||||||
|
// MustTplStrToStr wraps [TplStrToStr] but will panic on a non-nil error instead of returning it.
|
||||||
|
func MustTplStrToStr(tplStr string, typ tplType, obj any) (s string) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if s, err = TplStrToStr(tplStr, typ, obj); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustTplToStr wraps [TplToStr] but will panic on error instead of returning it.
|
||||||
|
func MustTplToStr[T Template](tpl T, obj any) (s string) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if s, err = TplToStr(tpl, obj); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustTplToStrWith wraps [TplToStrWith] but will panic on error instead of returning it.
|
||||||
|
func MustTplToStrWith[T Template](tpl T, tplNm string, obj any) (s string) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if s, err = TplToStrWith(tpl, tplNm, obj); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TplStrToStr takes in a template string, a template type (see i.e. [TplTypeText], [TplTypeHtml]),
|
||||||
|
and an object and renders to a string.
|
||||||
|
|
||||||
|
This is obviously quite inflexible - there's no way to provide a [text/template.FuncMap]/[html/template.FuncMap],
|
||||||
|
for instance, but if more advanced template features aren't needed then this might just do the trick.
|
||||||
|
|
||||||
|
If you need something more flexible, see [TplToStr] instead.
|
||||||
|
*/
|
||||||
|
func TplStrToStr(tplStr string, typ tplType, obj any) (out string, err error) {
|
||||||
|
|
||||||
|
var ttpl *txtTpl.Template
|
||||||
|
var htpl *htmlTpl.Template
|
||||||
|
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
|
||||||
|
switch typ {
|
||||||
|
case TplTypeText:
|
||||||
|
if ttpl, err = txtTpl.New("").Parse(tplStr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = ttpl.Execute(buf, obj); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case TplTypeHtml:
|
||||||
|
if htpl, err = htmlTpl.New("").Parse(tplStr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = htpl.Execute(buf, obj); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = ErrInvalidTplType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = buf.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TplToStr takes in an [html/template] or [text/template] and an object and executes it.
|
||||||
|
|
||||||
|
PLEASE NOTE that it is expected that `tpl` has already had at least one template string `.Parse()`'d in.
|
||||||
|
|
||||||
|
If you haven't used generics in Golang yet, this function would be used via something like the following complete example
|
||||||
|
for both a [text/template.Template] (import-aliased as `txtT.Template`) and
|
||||||
|
an [html/template.Template] (import-aliased as `htmlT.Template`).
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
txtT "text/template"
|
||||||
|
htmlT "html/template"
|
||||||
|
|
||||||
|
`r00t2.io/goutils/tplx`
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
S struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tTpl *txtT.Template
|
||||||
|
hTpl *htmlT.Template
|
||||||
|
)
|
||||||
|
|
||||||
|
const tTplStr string = "Greetings, {{ .Name }}!\n"
|
||||||
|
const hTplStr string = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Hello, {{ .Name }}!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello, {{ .Name }}. Good to see you.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
var s string
|
||||||
|
var o *S
|
||||||
|
|
||||||
|
o = &S{
|
||||||
|
Name: "Bob",
|
||||||
|
}
|
||||||
|
|
||||||
|
// A text template.
|
||||||
|
if tTpl, err = txtT.
|
||||||
|
New("my_txt_template").
|
||||||
|
Parse(tTplStr); err != nil {
|
||||||
|
log.Panicf("Failed to parse text template string '%s': %v\n", tTplStr, err)
|
||||||
|
}
|
||||||
|
if s, err = tplx.TplToStr[*txtT.Template](tTpl, o); err != nil {
|
||||||
|
log.Panicf("Failed to render text template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println(s)
|
||||||
|
|
||||||
|
// An HTML template.
|
||||||
|
if hTpl, err = htmlT.
|
||||||
|
New("index.html").
|
||||||
|
Parse(hTplStr); err != nil {
|
||||||
|
log.Panicf("Failed to parse HTML template string '%s': %v\n", hTplStr, err)
|
||||||
|
}
|
||||||
|
if s, err = tplx.TplToStr[*htmlT.Template](hTpl, o); err != nil {
|
||||||
|
log.Panicf("Failed to render HTML template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
Additionally, because this function uses a union type [Template],
|
||||||
|
you can even leave the type indicator off.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
// ...
|
||||||
|
if s, err = tplx.TplToStr(tTpl, o); err != nil {
|
||||||
|
log.Panicf("Failed to render text template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
if s, err = tplx.TplToStr(hTpl, o); err != nil {
|
||||||
|
log.Panicf("Failed to render HTML template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
|
||||||
|
However, this is not recommended for readability purposes - including
|
||||||
|
the type indicator indicates (heh heh) to others reading your code
|
||||||
|
what type `tTpl` and `hTpl` are without needing to cross-reference
|
||||||
|
their declaration/assignment/definition.
|
||||||
|
|
||||||
|
For more information on generics in Golang, see:
|
||||||
|
|
||||||
|
* The introductory [blog post]
|
||||||
|
* The official [tutorial]
|
||||||
|
* The syntax [reference doc]
|
||||||
|
* The (community-maintained/unofficial) [Go by Example: Generics]
|
||||||
|
|
||||||
|
[blog post]: https://go.dev/blog/intro-generics
|
||||||
|
[tutorial]: https://go.dev/doc/tutorial/generics
|
||||||
|
[reference doc]: https://go.dev/ref/spec#Instantiations
|
||||||
|
[Go by Example: Generics]: https://gobyexample.com/generics
|
||||||
|
*/
|
||||||
|
func TplToStr[T Template](tpl T, obj any) (out string, err error) {
|
||||||
|
|
||||||
|
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err = tpl.Execute(buf, obj); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = buf.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TplToStrWith functions the exact same as [TplToStr] but allows you to specify the
|
||||||
|
template entry point (template name) named `nm`.
|
||||||
|
|
||||||
|
For example (see [TplToStr] for a full example):
|
||||||
|
|
||||||
|
// ...
|
||||||
|
var tplNm string = "index.html"
|
||||||
|
|
||||||
|
if s, err = tplx.TplToStrWith(tTpl, tplNm, o); err != nil {
|
||||||
|
log.Panicf("Failed to render HTML template '%s' to string: %v\n", tplNm, err)
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
|
||||||
|
would call the equivalent of:
|
||||||
|
|
||||||
|
// ...
|
||||||
|
if err = tpl.ExecuteTemplate(<internal buffer>, tplNm, o); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func TplToStrWith[T Template](tpl T, tplNm string, obj any) (out string, err error) {
|
||||||
|
|
||||||
|
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err = tpl.ExecuteTemplate(buf, tplNm, obj); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = buf.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
103
tplx/funcs_test.go
Normal file
103
tplx/funcs_test.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package tplx
|
||||||
|
|
||||||
|
import (
|
||||||
|
htmlT `html/template`
|
||||||
|
`log`
|
||||||
|
"testing"
|
||||||
|
txtT `text/template`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
txtTplNm string = "my_txt_template"
|
||||||
|
htmlTplNm string = "index.html"
|
||||||
|
tgtTxt string = "Greetings, Bob!\n"
|
||||||
|
tgtHtml string = "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>Hello, Bob!</title>\n\t</head>\n\t<body>\n\t\t<p>Hello, Bob. Good to see you.</p>\n\t</body>\n</html>\n"
|
||||||
|
tTplStr string = "Greetings, {{ .Name }}!\n"
|
||||||
|
hTplStr string = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Hello, {{ .Name }}!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello, {{ .Name }}. Good to see you.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tTpl *txtT.Template = txtT.Must(txtT.New(txtTplNm).Parse(tTplStr))
|
||||||
|
hTpl *htmlT.Template = htmlT.Must(htmlT.New(htmlTplNm).Parse(hTplStr))
|
||||||
|
o struct{ Name string } = struct{ Name string }{
|
||||||
|
Name: "Bob",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTpl(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var s string
|
||||||
|
|
||||||
|
// if s, err = TplToStr[*txtT.Template](tTpl, o); err != nil {
|
||||||
|
if s, err = TplToStr(tTpl, o); err != nil {
|
||||||
|
t.Fatalf("Failed to render text template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
t.Logf("Text template (%#v): '%s'", s, s)
|
||||||
|
if s != tgtTxt {
|
||||||
|
t.Fatalf("Mismatch on text template '%s'", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if s, err = TplToStr[*htmlT.Template](hTpl, o); err != nil {
|
||||||
|
if s, err = TplToStr(hTpl, o); err != nil {
|
||||||
|
log.Panicf("Failed to render HTML template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
t.Logf("HTML template (%#v):\n%s", s, s)
|
||||||
|
if s != tgtHtml {
|
||||||
|
t.Fatalf("Mismatch on HTML template '%s'", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTplStr(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if s, err = TplStrToStr(tTplStr, TplTypeText, o); err != nil {
|
||||||
|
t.Fatalf("Failed to render text template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
t.Logf("Text template (%#v): '%s'", s, s)
|
||||||
|
if s != tgtTxt {
|
||||||
|
t.Fatalf("Mismatch on text template '%s'", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, err = TplStrToStr(hTplStr, TplTypeHtml, o); err != nil {
|
||||||
|
log.Panicf("Failed to render HTML template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
t.Logf("HTML template (%#v):\n%s", s, s)
|
||||||
|
if s != tgtHtml {
|
||||||
|
t.Fatalf("Mismatch on HTML template '%s'", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTplWith(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if s, err = TplToStrWith(tTpl, txtTplNm, o); err != nil {
|
||||||
|
t.Fatalf("Failed to render text template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
t.Logf("Text template (%#v): '%s'", s, s)
|
||||||
|
if s != tgtTxt {
|
||||||
|
t.Fatalf("Mismatch on text template '%s'", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, err = TplToStrWith(hTpl, htmlTplNm, o); err != nil {
|
||||||
|
log.Panicf("Failed to render HTML template to string: %v\n", err)
|
||||||
|
}
|
||||||
|
t.Logf("HTML template (%#v):\n%s", s, s)
|
||||||
|
if s != tgtHtml {
|
||||||
|
t.Fatalf("Mismatch on HTML template '%s'", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
19
tplx/types.go
Normal file
19
tplx/types.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package tplx
|
||||||
|
|
||||||
|
import (
|
||||||
|
htmlTpl `html/template`
|
||||||
|
`io`
|
||||||
|
txtTpl `text/template`
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
tplType uint8
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Template interface {
|
||||||
|
*txtTpl.Template | *htmlTpl.Template
|
||||||
|
Execute(w io.Writer, obj any) (err error)
|
||||||
|
ExecuteTemplate(w io.Writer, tplNm string, obj any) (err error)
|
||||||
|
}
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user