mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
tpl/lang: Add NumFmt function
NumFmt formats a number with a given precision using the requested decimal, grouping, and negative characters. Fixes #1444
This commit is contained in:
parent
e92ce83d5e
commit
93b3b13867
4 changed files with 177 additions and 0 deletions
|
@ -460,6 +460,24 @@ e.g.
|
||||||
|
|
||||||
* `{{ int "123" }}` → 123
|
* `{{ int "123" }}` → 123
|
||||||
|
|
||||||
|
### lang.NumFmt
|
||||||
|
|
||||||
|
`NumFmt` formats a number with the given precision using the *decimal*,
|
||||||
|
*grouping*, and *negative* options. The `options` parameter is a
|
||||||
|
string consisting of `<negative> <decimal> <grouping>`. The default
|
||||||
|
`options` value is `- . ,`.
|
||||||
|
|
||||||
|
Note that numbers are rounded up at 5 or greater.
|
||||||
|
So, with precision set to 0, 1.5 becomes `2`, and 1.4 becomes `1`.
|
||||||
|
|
||||||
|
```
|
||||||
|
{{ lang.NumFmt 2 12345.6789 }} → 12,345.68
|
||||||
|
{{ lang.NumFmt 2 12345.6789 "- , ." }} → 12.345,68
|
||||||
|
{{ lang.NumFmt 0 -12345.6789 "- . ," }} → -12,346
|
||||||
|
{{ lang.NumFmt 6 -12345.6789 "- ." }} → -12345.678900
|
||||||
|
{{ -98765.4321 | lang.NumFmt 2 }} → -98,765.43
|
||||||
|
```
|
||||||
|
|
||||||
## Strings
|
## Strings
|
||||||
|
|
||||||
### printf
|
### printf
|
||||||
|
|
|
@ -34,6 +34,16 @@ func init() {
|
||||||
[][2]string{},
|
[][2]string{},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ns.AddMethodMapping(ctx.NumFmt,
|
||||||
|
nil,
|
||||||
|
[][2]string{
|
||||||
|
{`{{ lang.NumFmt 2 12345.6789 }}`, `12,345.68`},
|
||||||
|
{`{{ lang.NumFmt 2 12345.6789 "- , ." }}`, `12.345,68`},
|
||||||
|
{`{{ lang.NumFmt 6 -12345.6789 "- ." }}`, `-12345.678900`},
|
||||||
|
{`{{ lang.NumFmt 0 -12345.6789 "- . ," }}`, `-12,346`},
|
||||||
|
{`{{ -98765.4321 | lang.NumFmt 2 }}`, `-98,765.43`},
|
||||||
|
},
|
||||||
|
)
|
||||||
return ns
|
return ns
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,11 @@
|
||||||
package lang
|
package lang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"github.com/spf13/hugo/deps"
|
"github.com/spf13/hugo/deps"
|
||||||
)
|
)
|
||||||
|
@ -39,3 +44,93 @@ func (ns *Namespace) Translate(id interface{}, args ...interface{}) (string, err
|
||||||
|
|
||||||
return ns.deps.Translate(sid, args...), nil
|
return ns.deps.Translate(sid, args...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumFmt formats a number with the given precision using the
|
||||||
|
// negative, decimal, and grouping options. The `options`
|
||||||
|
// parameter is a string consisting of `<negative> <decimal> <grouping>`. The
|
||||||
|
// default `options` value is `- . ,`.
|
||||||
|
//
|
||||||
|
// Note that numbers are rounded up at 5 or greater.
|
||||||
|
// So, with precision set to 0, 1.5 becomes `2`, and 1.4 becomes `1`.
|
||||||
|
func (ns *Namespace) NumFmt(precision, number interface{}, options ...interface{}) (string, error) {
|
||||||
|
prec, err := cast.ToIntE(precision)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := cast.ToFloat64E(number)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var neg, dec, grp string
|
||||||
|
|
||||||
|
if len(options) == 0 {
|
||||||
|
// TODO(moorereason): move to site config
|
||||||
|
neg, dec, grp = "-", ".", ","
|
||||||
|
} else {
|
||||||
|
s, err := cast.ToStringE(options[0])
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := strings.Fields(s)
|
||||||
|
switch len(rs) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
neg = rs[0]
|
||||||
|
case 2:
|
||||||
|
neg, dec = rs[0], rs[1]
|
||||||
|
case 3:
|
||||||
|
neg, dec, grp = rs[0], rs[1], rs[2]
|
||||||
|
default:
|
||||||
|
return "", errors.New("too many fields in options parameter to NumFmt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic from MIT Licensed github.com/go-playground/locales/
|
||||||
|
// Original Copyright (c) 2016 Go Playground
|
||||||
|
|
||||||
|
s := strconv.FormatFloat(math.Abs(n), 'f', prec, 64)
|
||||||
|
L := len(s) + 2 + len(s[:len(s)-1-prec])/3
|
||||||
|
|
||||||
|
var count int
|
||||||
|
inWhole := prec == 0
|
||||||
|
b := make([]byte, 0, L)
|
||||||
|
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
if s[i] == '.' {
|
||||||
|
for j := len(dec) - 1; j >= 0; j-- {
|
||||||
|
b = append(b, dec[j])
|
||||||
|
}
|
||||||
|
inWhole = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inWhole {
|
||||||
|
if count == 3 {
|
||||||
|
for j := len(grp) - 1; j >= 0; j-- {
|
||||||
|
b = append(b, grp[j])
|
||||||
|
}
|
||||||
|
count = 1
|
||||||
|
} else {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b = append(b, s[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 0 {
|
||||||
|
for j := len(neg) - 1; j >= 0; j-- {
|
||||||
|
b = append(b, neg[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse
|
||||||
|
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
b[i], b[j] = b[j], b[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
54
tpl/lang/lang_test.go
Normal file
54
tpl/lang/lang_test.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package lang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/hugo/deps"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNumFormat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ns := New(&deps.Deps{})
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
prec int
|
||||||
|
n float64
|
||||||
|
runes string
|
||||||
|
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{2, -12345.6789, "", "-12,345.68"},
|
||||||
|
{2, -12345.6789, "- . ,", "-12,345.68"},
|
||||||
|
{2, -12345.1234, "- . ,", "-12,345.12"},
|
||||||
|
|
||||||
|
{2, 12345.6789, "- . ,", "12,345.68"},
|
||||||
|
{0, 12345.6789, "- . ,", "12,346"},
|
||||||
|
{11, -12345.6789, "- . ,", "-12,345.67890000000"},
|
||||||
|
|
||||||
|
{3, -12345.6789, "- ,", "-12345,679"},
|
||||||
|
{6, -12345.6789, "- , .", "-12.345,678900"},
|
||||||
|
|
||||||
|
// Arabic, ar_AE
|
||||||
|
{6, -12345.6789, "- ٫ ٬", "-12٬345٫678900"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
errMsg := fmt.Sprintf("[%d] %v", i, c)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(c.runes) == 0 {
|
||||||
|
s, err = ns.NumFmt(c.prec, c.n)
|
||||||
|
} else {
|
||||||
|
s, err = ns.NumFmt(c.prec, c.n, c.runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err, errMsg)
|
||||||
|
assert.Equal(t, c.want, s, errMsg)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue