From 006cf39fa123f99950831056ac3cb02e43580c30 Mon Sep 17 00:00:00 2001 From: brent saner Date: Tue, 23 Dec 2025 17:26:50 -0500 Subject: [PATCH] v1.15.0 ADDED: * tplx, for one-shotting/shortcutting templating --- go.sum | 1 + tplx/consts.go | 6 ++ tplx/doc.go | 4 + tplx/errs.go | 9 ++ tplx/funcs.go | 235 +++++++++++++++++++++++++++++++++++++++++++++ tplx/funcs_test.go | 103 ++++++++++++++++++++ tplx/types.go | 19 ++++ 7 files changed, 377 insertions(+) create mode 100644 tplx/consts.go create mode 100644 tplx/doc.go create mode 100644 tplx/errs.go create mode 100644 tplx/funcs.go create mode 100644 tplx/funcs_test.go create mode 100644 tplx/types.go diff --git a/go.sum b/go.sum index 3f65379..3c97bd1 100644 --- a/go.sum +++ b/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= r00t2.io/sysutils v1.15.0 h1:FSnREfbXDhBQEO7LMpnRQeKlPshozxk9XHw3YgWRgRg= r00t2.io/sysutils v1.15.0/go.mod h1:28qB0074EIRQ8Sy/ybaA5jC3qA32iW2aYLkMCRhyAFM= +r00t2.io/sysutils v1.15.1/go.mod h1:T0iOnaZaSG5NE1hbXTqojRZc0ia/u8TB73lV7zhMz58= diff --git a/tplx/consts.go b/tplx/consts.go new file mode 100644 index 0000000..0d5ac73 --- /dev/null +++ b/tplx/consts.go @@ -0,0 +1,6 @@ +package tplx + +const ( + TplTypeText tplType = iota + TplTypeHtml +) diff --git a/tplx/doc.go b/tplx/doc.go new file mode 100644 index 0000000..1cb05ab --- /dev/null +++ b/tplx/doc.go @@ -0,0 +1,4 @@ +/* +Package tplx provides some "shortcuts" to [text/template] and [html/template] rendering. +*/ +package tplx diff --git a/tplx/errs.go b/tplx/errs.go new file mode 100644 index 0000000..e5c3dd0 --- /dev/null +++ b/tplx/errs.go @@ -0,0 +1,9 @@ +package tplx + +import ( + `errors` +) + +var ( + ErrInvalidTplType = errors.New("unknown/invalid template type") +) diff --git a/tplx/funcs.go b/tplx/funcs.go new file mode 100644 index 0000000..c6fcc84 --- /dev/null +++ b/tplx/funcs.go @@ -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 = ` + + + + Hello, {{ .Name }}! + + +

Hello, {{ .Name }}. Good to see you.

+ + + ` + + 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(, 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 +} diff --git a/tplx/funcs_test.go b/tplx/funcs_test.go new file mode 100644 index 0000000..5a89385 --- /dev/null +++ b/tplx/funcs_test.go @@ -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 = "\n\n\t\n\t\t\n\t\tHello, Bob!\n\t\n\t\n\t\t

Hello, Bob. Good to see you.

\n\t\n\n" + tTplStr string = "Greetings, {{ .Name }}!\n" + hTplStr string = ` + + + + Hello, {{ .Name }}! + + +

Hello, {{ .Name }}. Good to see you.

+ + +` +) + +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) + } +} diff --git a/tplx/types.go b/tplx/types.go new file mode 100644 index 0000000..ee490c4 --- /dev/null +++ b/tplx/types.go @@ -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) + } +)