Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local i18n #85

Merged
merged 21 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import (
"github.com/urfave/cli/v2"
)

func humanf(arg interface{}) string {
func humanf(arg float64) string {
return color.Wrap(color.BrightWhite, humanize.Hf(arg))
}

func writeAggrOutput(writer multiterm.MultilineTerm, aggr *aggregation.MatchNumerical, extra bool, quantiles []float64) int {
writer.WriteForLinef(0, "Samples: %v", color.Wrap(color.BrightWhite, humanize.Hi(aggr.Count())))
writer.WriteForLinef(0, "Samples: %v", color.Wrap(color.BrightWhite, humanize.Hui(aggr.Count())))
writer.WriteForLinef(1, "Mean: %v", humanf(aggr.Mean()))
writer.WriteForLinef(2, "StdDev: %v", humanf(aggr.StdDev()))
writer.WriteForLinef(3, "Min: %v", humanf(aggr.Min()))
Expand Down
2 changes: 1 addition & 1 deletion cmd/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func expressionFunction(c *cli.Context) error {
duration, iterations := exprofiler.Benchmark(compiled, &expCtx)
perf := (duration / time.Duration(iterations)).String()
fmt.Printf("Benchmark: %s ", color.Wrap(color.BrightWhite, perf))
fmt.Print(color.Wrapf(color.BrightBlack, "(%s iterations in %s)", humanize.Hi(iterations), duration.String()))
fmt.Print(color.Wrapf(color.BrightBlack, "(%s iterations in %s)", humanize.Hi32(iterations), duration.String()))
fmt.Print("\n")
}

Expand Down
8 changes: 4 additions & 4 deletions cmd/helpers/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (

func FWriteMatchSummary(w io.Writer, matched, total uint64) {
fmt.Fprintf(w, "Matched: %s / %s",
color.Wrapi(color.BrightGreen, humanize.Hi(matched)),
color.Wrapi(color.BrightWhite, humanize.Hi(total)))
color.Wrapi(color.BrightGreen, humanize.Hui(matched)),
color.Wrapi(color.BrightWhite, humanize.Hui(total)))
}

func FWriteExtractorSummary(extractor *extractor.Extractor, errors uint64, additionalParts ...string) string {
Expand All @@ -24,10 +24,10 @@ func FWriteExtractorSummary(extractor *extractor.Extractor, errors uint64, addit
w.WriteString(p)
}
if extractor.IgnoredLines() > 0 {
fmt.Fprintf(&w, " (Ignored: %s)", color.Wrapi(color.Red, humanize.Hi(extractor.IgnoredLines())))
fmt.Fprintf(&w, " (Ignored: %s)", color.Wrapi(color.Red, humanize.Hui(extractor.IgnoredLines())))
}
if errors > 0 {
fmt.Fprintf(&w, " %s", color.Wrapf(color.Red, "(Errors: %v)", humanize.Hi(errors)))
fmt.Fprintf(&w, " %s", color.Wrapf(color.Red, "(Errors: %v)", humanize.Hui(errors)))
}
return w.String()
}
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ require (
github.com/tidwall/gjson v1.14.1
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/text v0.3.7
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
honnef.co/go/tools v0.3.2
)
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjq
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f h1:OKYpQQVE3DKSc3r3zHVzq46vq5YH7x8xpR3/k9ixmUg=
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
2 changes: 1 addition & 1 deletion pkg/expressions/stdlib/funcsStrings.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func kfHumanizeInt(args []KeyBuilderStage) KeyBuilderStage {
if err != nil {
return ErrorType
}
return humanize.Hi(val)
return humanize.Hi32(val)
})
}

Expand Down
35 changes: 16 additions & 19 deletions pkg/humanize/humanize.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,43 @@
package humanize

import (
"fmt"
"strconv"

"golang.org/x/text/language"
"golang.org/x/text/message"
)

var printer = message.NewPrinter(language.English)

// Enabled determines whether to use language message printer, or fmt
var Enabled = true
var Decimals = 4

// H humanizes the output
func H(format string, args ...interface{}) string {
func Hi(arg int64) string {
if !Enabled {
return fmt.Sprintf(format, args...)
return strconv.FormatInt(arg, 10)
}
return printer.Sprintf(format, args...)
return humanizeInt(arg)
}

func Hi(arg interface{}) string {
func Hui(arg uint64) string {
if !Enabled {
return fmt.Sprintf("%d", arg)
return strconv.FormatUint(arg, 10)
}
return printer.Sprintf("%d", arg)
return humanizeInt(arg)
}

func Hf(arg interface{}) string {
func Hi32(arg int) string {
if !Enabled {
return fmt.Sprintf("%f", arg)
return strconv.Itoa(arg)
}
return printer.Sprintf("%.[2]*[1]f", arg, Decimals)
return humanizeInt(arg)
}

func Hf(arg float64) string {
return Hfd(arg, Decimals)
}

func Hfd(arg interface{}, decimals int) string {
func Hfd(arg float64, decimals int) string {
if !Enabled {
return fmt.Sprintf("%f", arg)
return strconv.FormatFloat(arg, 'f', decimals, 64)
}
return printer.Sprintf("%.[2]*[1]f", arg, decimals)
return humanizeFloat(arg, decimals)
}

var byteSizes = [...]string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"}
Expand Down
19 changes: 12 additions & 7 deletions pkg/humanize/humanize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ import (
"github.com/stretchr/testify/assert"
)

func TestH(t *testing.T) {
assert.Equal(t, "Hello 1,000", H("Hello %d", 1000))
}

func TestHDisabled(t *testing.T) {
Enabled = false
assert.Equal(t, "Hello 1000", H("Hello %d", 1000))
assert.Equal(t, "1000", Hi(1000))
assert.Equal(t, "1000.000000", Hf(1000.0))
assert.Equal(t, "1000.000000", Hfd(1000.0, 5))
assert.Equal(t, "1000", Hui(1000))
assert.Equal(t, "1000", Hi32(1000))
assert.Equal(t, "1000.0000", Hf(1000.0))
assert.Equal(t, "1000.00000", Hfd(1000.0, 5))
assert.Equal(t, "12341234", ByteSize(12341234))
Enabled = true
}
Expand All @@ -24,6 +21,14 @@ func TestHi(t *testing.T) {
assert.Equal(t, "1,500", Hi(1500))
}

func TestHui(t *testing.T) {
assert.Equal(t, "1,500", Hui(1500))
}

func TestHi32(t *testing.T) {
assert.Equal(t, "1,500", Hi32(1500))
}

func TestHf(t *testing.T) {
assert.Equal(t, "1,234,567.8912", Hf(1234567.89121111))
}
Expand Down
122 changes: 122 additions & 0 deletions pkg/humanize/numeric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package humanize

import (
"bytes"
"math"
"strconv"
)

/*
Previously, rare used the `message` i18n go library to add commas to numbers, but
as it turns out that was a bit overkill (Benchmarking shows easily 10x slower, and added 600 KB to the
binary). In an effort to pull out and streamline simpler parts of the overall process,
the two below functions are implementations of the simplistic english-only localization
of numbers
*/

const (
baseSeparator = ','
decimalSeparator = '.'
)

type IntType interface {
int64 | int32 | uint64 | uint32 | int | uint
}

func humanizeInt[T IntType](v T) string {
var buf [32]byte // stack alloc

if v >= 0 && v < 100 { // faster for small numbers
return strconv.FormatInt(int64(v), 10)
}

negative := v < 0
if negative {
v = -v
}

ci := 0
idx := len(buf) - 1
for v > 0 {
if ci == 3 {
buf[idx] = baseSeparator
ci = 0
idx--
}

buf[idx] = byte('0' + (v % 10))
idx--
ci++
v /= 10
}

if negative {
buf[idx] = '-'
idx--
}

return string(buf[idx+1:])
}

func isDigit(s byte) bool {
return s >= '0' && s <= '9'
}

func humanizeFloat(v float64, decimals int) string {
// Special cases
if math.IsNaN(v) {
return "NaN"
}
if math.IsInf(v, 0) {
return "Inf"
}

// Float to string is complicated, but can leverage FormatFload and insert commas
var buf [64]byte // Operations on the stack
s := strconv.AppendFloat(buf[:0], v, 'f', decimals, 64)

if v > -1000.0 && v < 1000.0 {
// performance escape hatch when no commas
return string(s)
}

negative := s[0] == '-'
if !isDigit(s[0]) { // assume it's a sign/prefix
s = s[1:]
}

decIdx := bytes.IndexByte(s, '.')
if decIdx < 0 { // no decimal
decIdx = len(s)
}

// Return stack buf
var retbuf [64]byte
ret := retbuf[:0]

if negative {
ret = append(ret, '-')
}

// write base
c3 := 3 - (decIdx % 3)

for i := 0; i < decIdx; i++ {
if c3 == 3 {
if i > 0 {
ret = append(ret, baseSeparator)
}
c3 = 0
}
ret = append(ret, s[i])
c3++
}

// write decimal
if decIdx < len(s) {
ret = append(ret, decimalSeparator)
ret = append(ret, s[decIdx+1:]...)
}

return string(ret)
}
67 changes: 67 additions & 0 deletions pkg/humanize/numeric_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package humanize

import (
"math"
"strconv"
"testing"

"github.com/stretchr/testify/assert"
)

func TestFormatInt(t *testing.T) {
assert.Equal(t, "0", humanizeInt(0))
assert.Equal(t, "1", humanizeInt(1))
assert.Equal(t, "-1", humanizeInt(-1))
assert.Equal(t, "10", humanizeInt(10))
assert.Equal(t, "100", humanizeInt(100))
assert.Equal(t, "1,000", humanizeInt(1000))
assert.Equal(t, "10,000", humanizeInt(10000))

assert.Equal(t, "-100", humanizeInt(-100))
assert.Equal(t, "-1,000", humanizeInt(-1000))
assert.Equal(t, "-123,123", humanizeInt(-123123))
}

func TestFormatFloat(t *testing.T) {
assert.Equal(t, "0", humanizeFloat(0.0, 0))
assert.Equal(t, "0.00", humanizeFloat(0.0, 2))
assert.Equal(t, "1", humanizeFloat(1.0, 0))
assert.Equal(t, "12", humanizeFloat(12.0, 0))
assert.Equal(t, "123", humanizeFloat(123.0, 0))
assert.Equal(t, "1,234", humanizeFloat(1234.0, 0))
assert.Equal(t, "12,345.0", humanizeFloat(12345.0, 1))
assert.Equal(t, "112,345.0", humanizeFloat(112345.0, 1))
assert.Equal(t, "1", humanizeFloat(1.123, 0))
assert.Equal(t, "-1", humanizeFloat(-1.123, 0))
assert.Equal(t, "1,123,123", humanizeFloat(1123123.123, 0))
assert.Equal(t, "-1,123,123", humanizeFloat(-1123123.123, 0))
assert.Equal(t, "1,123,123.12", humanizeFloat(1123123.123, 2))
assert.Equal(t, "1,123,123.123456", humanizeFloat(1123123.123456, 6))
assert.Equal(t, "-1,123,123.123456", humanizeFloat(-1123123.123456, 6))
assert.Equal(t, "-111,121,231,233,123.125000", humanizeFloat(-111121231233123.123456, 6))
assert.Equal(t, "111,121,231,233,123.125000", humanizeFloat(111121231233123.123456, 6))
assert.Equal(t, "28,446,744,073,709,551,616.0", humanizeFloat(28446744073709551615.0, 1))

assert.Equal(t, "NaN", humanizeFloat(math.NaN(), 2))
assert.Equal(t, "Inf", humanizeFloat(math.Inf(1), 2))
assert.Equal(t, "Inf", humanizeFloat(math.Inf(-1), 2))
}

func BenchmarkFormatInt(b *testing.B) {
for i := 0; i < b.N; i++ {
humanizeInt(10000)
}
}

func BenchmarkItoa(b *testing.B) {
for i := 0; i < b.N; i++ {
strconv.Itoa(10000)
}
}

// BenchmarkFormatFloat-4 2549425 473.6 ns/op 24 B/op 1 allocs/op
func BenchmarkFormatFloat(b *testing.B) {
for i := 0; i < b.N; i++ {
humanizeFloat(10000.123123123123, 10)
}
}