@marcel91200 / caddy Mirror · read-only / commits / 6891f7f4
Fork 0

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() {