Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 18:00 UTC
templates: Add `humanize` function (#4767)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Aleks committed
May 25, 2022 at 01:47 UTC
6891f7f421eac71dac8f8687255ede5189e7eb3a
3 files changed
+112
modules/caddyhttp/templates/templates.go
+23
@@ -238,6 +238,29 @@ func init() {
238
// {{stripHTML "Shows <b>only</b> text content"}}
239
// ```
240
//
241
+// ##### `humanize`
242
+//
243
+// Transforms size and time inputs to a human readable format.
244
+// This uses the [go-humanize](https://github.com/dustin/go-humanize) library.
245
+//
246
+// The first argument must be a format type, and the last argument
247
+// is the input, or the input can be piped in. The supported format
248
+// types are:
249
+// - **size** which turns an integer amount of bytes into a string like `2.3 MB`
250
+// - **time** which turns a time string into a relative time string like `2 weeks ago`
251
+//
252
+// For the `time` format, the layout for parsing the input can be configured
253
+// by appending a colon `:` followed by the desired time layout. You can
254
+// find the documentation on time layouts [in Go's docs](https://pkg.go.dev/time#pkg-constants).
255
+// The default time layout is `RFC1123Z`, i.e. `Mon, 02 Jan 2006 15:04:05 -0700`.
256
+//
257
+// ```
258
+// {{humanize "size" "2048000"}}
259
+// {{placeholder "http.response.header.Content-Length" | humanize "size"}}
260
+// {{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}}
261
+// {{humanize "time:2006-Jan-02" "2022-May-05"}}
262
+// ```
263
+
264
type Templates struct {
265
// The root path from which to load files. Required if template functions
266
// accessing the file system are used (such as include). Default is
modules/caddyhttp/templates/tplcontext.go
+40
@@ -26,11 +26,13 @@ import (
26
"strings"
27
"sync"
28
"text/template"
29
+ "time"
30
31
"github.com/Masterminds/sprig/v3"
32
"github.com/alecthomas/chroma/formatters/html"
33
"github.com/caddyserver/caddy/v2"
34
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
35
+ "github.com/dustin/go-humanize"
36
"github.com/yuin/goldmark"
37
highlighting "github.com/yuin/goldmark-highlighting"
38
"github.com/yuin/goldmark/extension"
@@ -81,6 +83,7 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
83
"placeholder": c.funcPlaceholder,
84
"fileExists": c.funcFileExists,
85
"httpError": c.funcHTTPError,
86
+ "humanize": c.funcHumanize,
87
})
88
return c.tpl
89
}
@@ -398,6 +401,43 @@ func (c TemplateContext) funcHTTPError(statusCode int) (bool, error) {
401
return false, caddyhttp.Error(statusCode, nil)
402
}
403
404
+// funcHumanize transforms size and time inputs to a human readable format.
405
+//
406
+// Size inputs are expected to be integers, and are formatted as a
407
+// byte size, such as "83 MB".
408
+//
409
+// Time inputs are parsed using the given layout (default layout is RFC1123Z)
410
+// and are formatted as a relative time, such as "2 weeks ago".
411
+// See https://pkg.go.dev/time#pkg-constants for time layout docs.
412
+func (c TemplateContext) funcHumanize(formatType, data string) (string, error) {
413
+ // The format type can optionally be followed
414
+ // by a colon to provide arguments for the format
415
+ parts := strings.Split(formatType, ":")
416
+
417
+ switch parts[0] {
418
+ case "size":
419
+ dataint, dataerr := strconv.ParseUint(data, 10, 64)
420
+ if dataerr != nil {
421
+ return "", fmt.Errorf("humanize: size cannot be parsed: %s", dataerr.Error())
422
+ }
423
+ return humanize.Bytes(dataint), nil
424
+
425
+ case "time":
426
+ timelayout := time.RFC1123Z
427
+ if len(parts) > 1 {
428
+ timelayout = parts[1]
429
+ }
430
+
431
+ dataint, dataerr := time.Parse(timelayout, data)
432
+ if dataerr != nil {
433
+ return "", fmt.Errorf("humanize: time cannot be parsed: %s", dataerr.Error())
434
+ }
435
+ return humanize.Time(dataint), nil
436
+ }
437
+
438
+ return "", fmt.Errorf("no know function was given")
439
+}
440
+
441
// WrappedHeader wraps niladic functions so that they
442
// can be used in templates. (Template functions must
443
// return a value.)
modules/caddyhttp/templates/tplcontext_test.go
+49
@@ -606,6 +606,55 @@ title = "Welcome"
606
607
}
608
609
+func TestHumanize(t *testing.T) {
610
+ tplContext := getContextOrFail(t)
611
+ for i, test := range []struct {
612
+ format string
613
+ inputData string
614
+ expect string
615
+ errorCase bool
616
+ verifyErr func(actual_string, substring string) bool
617
+ }{
618
+ {
619
+ format: "size",
620
+ inputData: "2048000",
621
+ expect: "2.0 MB",
622
+ errorCase: false,
623
+ verifyErr: strings.Contains,
624
+ },
625
+ {
626
+ format: "time",
627
+ inputData: "Fri, 05 May 2022 15:04:05 +0200",
628
+ expect: "ago",
629
+ errorCase: false,
630
+ verifyErr: strings.HasSuffix,
631
+ },
632
+ {
633
+ format: "time:2006-Jan-02",
634
+ inputData: "2022-May-05",
635
+ expect: "ago",
636
+ errorCase: false,
637
+ verifyErr: strings.HasSuffix,
638
+ },
639
+ {
640
+ format: "time",
641
+ inputData: "Fri, 05 May 2022 15:04:05 GMT+0200",
642
+ expect: "error:",
643
+ errorCase: true,
644
+ verifyErr: strings.HasPrefix,
645
+ },
646
+ } {
647
+ if actual, err := tplContext.funcHumanize(test.format, test.inputData); !test.verifyErr(actual, test.expect) {
648
+ if !test.errorCase {
649
+ t.Errorf("Test %d: Expected '%s' but got '%s'", i, test.expect, actual)
650
+ if err != nil {
651
+ t.Errorf("Test %d: error: %s", i, err.Error())
652
+ }
653
+ }
654
+ }
655
+ }
656
+}
657
+
658
func getContextOrFail(t *testing.T) TemplateContext {
659
tplContext, err := initTestContext()
660
t.Cleanup(func() {