From d480edb65af5d6e9d8906940cdd4093761c1b451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Sun, 9 Jan 2022 00:20:24 +0100 Subject: template: Restructuring and adding tests. --- internal/feed/template/funcs.go | 72 +++++++++++++++++++++++++ internal/feed/template/funcs_test.go | 57 ++++++++++++++++++++ internal/feed/template/template.go | 96 ++++++++------------------------- internal/feed/template/template_test.go | 7 +++ 4 files changed, 159 insertions(+), 73 deletions(-) create mode 100644 internal/feed/template/funcs.go create mode 100644 internal/feed/template/funcs_test.go create mode 100644 internal/feed/template/template_test.go (limited to 'internal') diff --git a/internal/feed/template/funcs.go b/internal/feed/template/funcs.go new file mode 100644 index 0000000..300b9ca --- /dev/null +++ b/internal/feed/template/funcs.go @@ -0,0 +1,72 @@ +package template + +import ( + "fmt" + html "html/template" + "strconv" + "strings" + + "github.com/Necoro/feed2imap-go/pkg/log" +) + +// dict creates a map out of the passed in key/value pairs. +func dict(v ...interface{}) map[string]interface{} { + dict := make(map[string]interface{}) + lenv := len(v) + for i := 0; i < lenv; i += 2 { + key := v[i].(string) + if i+1 >= lenv { + dict[key] = "" + continue + } + dict[key] = v[i+1] + } + return dict +} + +// join takes a separator and a list of strings and puts the former in between each pair of the latter. +func join(sep string, parts []string) string { + return strings.Join(parts, sep) +} + +// lastUrlPart returns the last part of a URL string +func lastUrlPart(url string) string { + split := strings.Split(url, "/") + return split[len(split)-1] +} + +// byteCount receives an integer as a string, that is interpreted as a size in bytes. +// This size is then equipped with the corresponding unit: +// +func byteCount(str string) string { + var b uint64 + if str != "" { + var err error + if b, err = strconv.ParseUint(str, 10, 64); err != nil { + log.Printf("Cannot convert '%s' to byte count: %s", str, err) + } + } + + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := uint64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp]) +} + +func _html(s string) html.HTML { + return html.HTML(s) +} + +var funcMap = map[string]interface{}{ + "dict": dict, + "join": join, + "lastUrlPart": lastUrlPart, + "byteCount": byteCount, + "html": _html, +} diff --git a/internal/feed/template/funcs_test.go b/internal/feed/template/funcs_test.go new file mode 100644 index 0000000..c75d27d --- /dev/null +++ b/internal/feed/template/funcs_test.go @@ -0,0 +1,57 @@ +package template + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestByteCount(t *testing.T) { + tests := map[string]struct { + inp string + out string + }{ + "Empty": {"", "0 B"}, + "Byte": {"123", "123 B"}, + "KByte": {"2048", "2.0 KB"}, + "KByte slight": {"2049", "2.0 KB"}, + "KByte round": {"2560", "2.5 KB"}, + "MByte": {"2097152", "2.0 MB"}, + } + + for name, tt := range tests { + t.Run(name, func(tst *testing.T) { + out := byteCount(tt.inp) + + if diff := cmp.Diff(tt.out, out); diff != "" { + tst.Error(diff) + } + }) + } +} + +func TestDict(t *testing.T) { + type i []interface{} + type o map[string]interface{} + + tests := map[string]struct { + inp i + out o + }{ + "Empty": {i{}, o{}}, + "One": {i{"1"}, o{"1": ""}}, + "Two": {i{"1", 1}, o{"1": 1}}, + "Three": {i{"1", "2", "3"}, o{"1": "2", "3": ""}}, + "Four": {i{"1", 2, "3", '4'}, o{"1": 2, "3": '4'}}, + } + + for name, tt := range tests { + t.Run(name, func(tst *testing.T) { + out := dict(tt.inp...) + + if diff := cmp.Diff(tt.out, o(out)); diff != "" { + tst.Error(diff) + } + }) + } +} diff --git a/internal/feed/template/template.go b/internal/feed/template/template.go index 9804190..8b3fb73 100644 --- a/internal/feed/template/template.go +++ b/internal/feed/template/template.go @@ -2,96 +2,46 @@ package template import ( _ "embed" - "fmt" html "html/template" "io" - "strconv" - "strings" text "text/template" - - "github.com/Necoro/feed2imap-go/pkg/log" ) -type Template interface { +type template interface { Execute(wr io.Writer, data interface{}) error } +type Template struct { + template + useHtml bool + dflt string +} + //go:embed html.tpl -var htmlTpl string +var defaultHtmlTpl string //go:embed text.tpl -var textTpl string - -var Html = fromString("Feed", htmlTpl, true) -var Text = fromString("Feed", textTpl, false) - -func must(t Template, err error) Template { - if err != nil { - panic(err) - } - return t -} - -func dict(v ...interface{}) map[string]interface{} { - dict := make(map[string]interface{}) - lenv := len(v) - for i := 0; i < lenv; i += 2 { - key := v[i].(string) - if i+1 >= lenv { - dict[key] = "" - continue - } - dict[key] = v[i+1] - } - return dict -} +var defaultTextTpl string -func join(sep string, parts []string) string { - return strings.Join(parts, sep) +var Html = Template{ + useHtml: true, + dflt: defaultHtmlTpl, } -func lastUrlPart(url string) string { - split := strings.Split(url, "/") - return split[len(split)-1] +var Text = Template{ + useHtml: false, + dflt: defaultTextTpl, } -func byteCount(str string) string { - var b uint64 - if str != "" { - var err error - if b, err = strconv.ParseUint(str, 10, 64); err != nil { - log.Printf("Cannot convert '%s' to byte count: %s", str, err) - } - } - - const unit = 1024 - if b < unit { - return fmt.Sprintf("%d B", b) - } - div, exp := uint64(unit), 0 - for n := b / unit; n >= unit; n /= unit { - div *= unit - exp++ +func (tpl *Template) loadDefault() { + if tpl.useHtml { + tpl.template = html.Must(html.New("Html").Funcs(funcMap).Parse(tpl.dflt)) + } else { + tpl.template = text.Must(text.New("Text").Funcs(funcMap).Parse(tpl.dflt)) } - return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp]) -} - -func _html(s string) html.HTML { - return html.HTML(s) -} - -var funcMap = html.FuncMap{ - "dict": dict, - "join": join, - "lastUrlPart": lastUrlPart, - "byteCount": byteCount, - "html": _html, } -func fromString(name, templateStr string, useHtml bool) Template { - if useHtml { - return must(html.New(name).Funcs(funcMap).Parse(templateStr)) - } else { - return must(text.New(name).Funcs(text.FuncMap(funcMap)).Parse(templateStr)) - } +func init() { + Html.loadDefault() + Text.loadDefault() } diff --git a/internal/feed/template/template_test.go b/internal/feed/template/template_test.go new file mode 100644 index 0000000..c3fbb7a --- /dev/null +++ b/internal/feed/template/template_test.go @@ -0,0 +1,7 @@ +package template + +import "testing" + +func TestTemplateDefaults(t *testing.T) { + // Dummy test to ensure init() works, i.e. the default templates are loaded +} -- cgit v1.2.3