Skip to content

Commit

Permalink
tpl/lang: Add NumFmt function
Browse files Browse the repository at this point in the history
NumFmt formats a number with a given precision using the requested
decimal, grouping, and negative characters.

Fixes gohugoio#1444
  • Loading branch information
moorereason committed May 18, 2017
1 parent e682fcc commit 9dee1da
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 0 deletions.
18 changes: 18 additions & 0 deletions docs/content/templates/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions tpl/lang/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

}
Expand Down
95 changes: 95 additions & 0 deletions tpl/lang/lang.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
package lang

import (
"errors"
"math"
"strconv"
"strings"

"github.com/spf13/cast"
"github.com/spf13/hugo/deps"
)
Expand All @@ -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
}
54 changes: 54 additions & 0 deletions tpl/lang/lang_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 9dee1da

Please sign in to comment.