summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorCameron Moore <[email protected]>2016-12-28 22:09:31 -0600
committerBjørn Erik Pedersen <[email protected]>2017-05-18 09:49:20 +0300
commit93b3b1386714999d716e03b131f77234248f1724 (patch)
tree999a80a37b2ed0564ed72bbae89f4c240e6f39ce
parente92ce83d5e8837190511f5a73323e49eeb8466cd (diff)
downloadhugo-93b3b1386714999d716e03b131f77234248f1724.tar.gz
hugo-93b3b1386714999d716e03b131f77234248f1724.zip
tpl/lang: Add NumFmt function
NumFmt formats a number with a given precision using the requested decimal, grouping, and negative characters. Fixes #1444
-rw-r--r--docs/content/templates/functions.md18
-rw-r--r--tpl/lang/init.go10
-rw-r--r--tpl/lang/lang.go95
-rw-r--r--tpl/lang/lang_test.go54
4 files changed, 177 insertions, 0 deletions
diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md
index c1fd8ebad..c514a4ec8 100644
--- a/docs/content/templates/functions.md
+++ b/docs/content/templates/functions.md
@@ -460,6 +460,24 @@ e.g.
* `{{ 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
### printf
diff --git a/tpl/lang/init.go b/tpl/lang/init.go
index 6cf8e790d..ea67afab0 100644
--- a/tpl/lang/init.go
+++ b/tpl/lang/init.go
@@ -34,6 +34,16 @@ func init() {
[][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
}
diff --git a/tpl/lang/lang.go b/tpl/lang/lang.go
index c84728f3b..45a640d7a 100644
--- a/tpl/lang/lang.go
+++ b/tpl/lang/lang.go
@@ -14,6 +14,11 @@
package lang
import (
+ "errors"
+ "math"
+ "strconv"
+ "strings"
+
"github.com/spf13/cast"
"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
}
+
+// 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
+}
diff --git a/tpl/lang/lang_test.go b/tpl/lang/lang_test.go
new file mode 100644
index 000000000..45ed506b7
--- /dev/null
+++ b/tpl/lang/lang_test.go
@@ -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)
+ }
+}