mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
templates: Add humanize
function (#4767)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
This commit is contained in:
parent
499ad6d182
commit
6891f7f421
3 changed files with 112 additions and 0 deletions
|
@ -238,6 +238,29 @@ func init() {
|
||||||
// {{stripHTML "Shows <b>only</b> text content"}}
|
// {{stripHTML "Shows <b>only</b> text content"}}
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
|
// ##### `humanize`
|
||||||
|
//
|
||||||
|
// Transforms size and time inputs to a human readable format.
|
||||||
|
// This uses the [go-humanize](https://github.com/dustin/go-humanize) library.
|
||||||
|
//
|
||||||
|
// The first argument must be a format type, and the last argument
|
||||||
|
// is the input, or the input can be piped in. The supported format
|
||||||
|
// types are:
|
||||||
|
// - **size** which turns an integer amount of bytes into a string like `2.3 MB`
|
||||||
|
// - **time** which turns a time string into a relative time string like `2 weeks ago`
|
||||||
|
//
|
||||||
|
// For the `time` format, the layout for parsing the input can be configured
|
||||||
|
// by appending a colon `:` followed by the desired time layout. You can
|
||||||
|
// find the documentation on time layouts [in Go's docs](https://pkg.go.dev/time#pkg-constants).
|
||||||
|
// The default time layout is `RFC1123Z`, i.e. `Mon, 02 Jan 2006 15:04:05 -0700`.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// {{humanize "size" "2048000"}}
|
||||||
|
// {{placeholder "http.response.header.Content-Length" | humanize "size"}}
|
||||||
|
// {{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}}
|
||||||
|
// {{humanize "time:2006-Jan-02" "2022-May-05"}}
|
||||||
|
// ```
|
||||||
|
|
||||||
type Templates struct {
|
type Templates struct {
|
||||||
// The root path from which to load files. Required if template functions
|
// The root path from which to load files. Required if template functions
|
||||||
// accessing the file system are used (such as include). Default is
|
// accessing the file system are used (such as include). Default is
|
||||||
|
|
|
@ -26,11 +26,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/sprig/v3"
|
"github.com/Masterminds/sprig/v3"
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
"github.com/alecthomas/chroma/formatters/html"
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
highlighting "github.com/yuin/goldmark-highlighting"
|
highlighting "github.com/yuin/goldmark-highlighting"
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
|
@ -81,6 +83,7 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
|
||||||
"placeholder": c.funcPlaceholder,
|
"placeholder": c.funcPlaceholder,
|
||||||
"fileExists": c.funcFileExists,
|
"fileExists": c.funcFileExists,
|
||||||
"httpError": c.funcHTTPError,
|
"httpError": c.funcHTTPError,
|
||||||
|
"humanize": c.funcHumanize,
|
||||||
})
|
})
|
||||||
return c.tpl
|
return c.tpl
|
||||||
}
|
}
|
||||||
|
@ -398,6 +401,43 @@ func (c TemplateContext) funcHTTPError(statusCode int) (bool, error) {
|
||||||
return false, caddyhttp.Error(statusCode, nil)
|
return false, caddyhttp.Error(statusCode, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// funcHumanize transforms size and time inputs to a human readable format.
|
||||||
|
//
|
||||||
|
// Size inputs are expected to be integers, and are formatted as a
|
||||||
|
// byte size, such as "83 MB".
|
||||||
|
//
|
||||||
|
// Time inputs are parsed using the given layout (default layout is RFC1123Z)
|
||||||
|
// and are formatted as a relative time, such as "2 weeks ago".
|
||||||
|
// See https://pkg.go.dev/time#pkg-constants for time layout docs.
|
||||||
|
func (c TemplateContext) funcHumanize(formatType, data string) (string, error) {
|
||||||
|
// The format type can optionally be followed
|
||||||
|
// by a colon to provide arguments for the format
|
||||||
|
parts := strings.Split(formatType, ":")
|
||||||
|
|
||||||
|
switch parts[0] {
|
||||||
|
case "size":
|
||||||
|
dataint, dataerr := strconv.ParseUint(data, 10, 64)
|
||||||
|
if dataerr != nil {
|
||||||
|
return "", fmt.Errorf("humanize: size cannot be parsed: %s", dataerr.Error())
|
||||||
|
}
|
||||||
|
return humanize.Bytes(dataint), nil
|
||||||
|
|
||||||
|
case "time":
|
||||||
|
timelayout := time.RFC1123Z
|
||||||
|
if len(parts) > 1 {
|
||||||
|
timelayout = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
dataint, dataerr := time.Parse(timelayout, data)
|
||||||
|
if dataerr != nil {
|
||||||
|
return "", fmt.Errorf("humanize: time cannot be parsed: %s", dataerr.Error())
|
||||||
|
}
|
||||||
|
return humanize.Time(dataint), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("no know function was given")
|
||||||
|
}
|
||||||
|
|
||||||
// WrappedHeader wraps niladic functions so that they
|
// WrappedHeader wraps niladic functions so that they
|
||||||
// can be used in templates. (Template functions must
|
// can be used in templates. (Template functions must
|
||||||
// return a value.)
|
// return a value.)
|
||||||
|
|
|
@ -606,6 +606,55 @@ title = "Welcome"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHumanize(t *testing.T) {
|
||||||
|
tplContext := getContextOrFail(t)
|
||||||
|
for i, test := range []struct {
|
||||||
|
format string
|
||||||
|
inputData string
|
||||||
|
expect string
|
||||||
|
errorCase bool
|
||||||
|
verifyErr func(actual_string, substring string) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
format: "size",
|
||||||
|
inputData: "2048000",
|
||||||
|
expect: "2.0 MB",
|
||||||
|
errorCase: false,
|
||||||
|
verifyErr: strings.Contains,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "time",
|
||||||
|
inputData: "Fri, 05 May 2022 15:04:05 +0200",
|
||||||
|
expect: "ago",
|
||||||
|
errorCase: false,
|
||||||
|
verifyErr: strings.HasSuffix,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "time:2006-Jan-02",
|
||||||
|
inputData: "2022-May-05",
|
||||||
|
expect: "ago",
|
||||||
|
errorCase: false,
|
||||||
|
verifyErr: strings.HasSuffix,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "time",
|
||||||
|
inputData: "Fri, 05 May 2022 15:04:05 GMT+0200",
|
||||||
|
expect: "error:",
|
||||||
|
errorCase: true,
|
||||||
|
verifyErr: strings.HasPrefix,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if actual, err := tplContext.funcHumanize(test.format, test.inputData); !test.verifyErr(actual, test.expect) {
|
||||||
|
if !test.errorCase {
|
||||||
|
t.Errorf("Test %d: Expected '%s' but got '%s'", i, test.expect, actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d: error: %s", i, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getContextOrFail(t *testing.T) TemplateContext {
|
func getContextOrFail(t *testing.T) TemplateContext {
|
||||||
tplContext, err := initTestContext()
|
tplContext, err := initTestContext()
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
|
Loading…
Reference in a new issue