about summary refs log tree commit diff
path: root/vendor/github.com/dustin/go-humanize
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/dustin/go-humanize')
-rw-r--r--vendor/github.com/dustin/go-humanize/.travis.yml21
-rw-r--r--vendor/github.com/dustin/go-humanize/LICENSE21
-rw-r--r--vendor/github.com/dustin/go-humanize/README.markdown124
-rw-r--r--vendor/github.com/dustin/go-humanize/big.go31
-rw-r--r--vendor/github.com/dustin/go-humanize/bigbytes.go189
-rw-r--r--vendor/github.com/dustin/go-humanize/bytes.go143
-rw-r--r--vendor/github.com/dustin/go-humanize/comma.go116
-rw-r--r--vendor/github.com/dustin/go-humanize/commaf.go41
-rw-r--r--vendor/github.com/dustin/go-humanize/ftoa.go49
-rw-r--r--vendor/github.com/dustin/go-humanize/humanize.go8
-rw-r--r--vendor/github.com/dustin/go-humanize/number.go192
-rw-r--r--vendor/github.com/dustin/go-humanize/ordinals.go25
-rw-r--r--vendor/github.com/dustin/go-humanize/si.go127
-rw-r--r--vendor/github.com/dustin/go-humanize/times.go117
14 files changed, 1204 insertions, 0 deletions
diff --git a/vendor/github.com/dustin/go-humanize/.travis.yml b/vendor/github.com/dustin/go-humanize/.travis.yml
new file mode 100644
index 0000000..ac12e48
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/.travis.yml
@@ -0,0 +1,21 @@
+sudo: false
+language: go
+go_import_path: github.com/dustin/go-humanize
+go:
+  - 1.13.x
+  - 1.14.x
+  - 1.15.x
+  - 1.16.x
+  - stable
+  - master
+matrix:
+  allow_failures:
+    - go: master
+  fast_finish: true
+install:
+  - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
+script:
+  - diff -u <(echo -n) <(gofmt -d -s .)
+  - go vet .
+  - go install -v -race ./...
+  - go test -v -race ./...
diff --git a/vendor/github.com/dustin/go-humanize/LICENSE b/vendor/github.com/dustin/go-humanize/LICENSE
new file mode 100644
index 0000000..8d9a94a
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+<http://www.opensource.org/licenses/mit-license.php>
diff --git a/vendor/github.com/dustin/go-humanize/README.markdown b/vendor/github.com/dustin/go-humanize/README.markdown
new file mode 100644
index 0000000..7d0b16b
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/README.markdown
@@ -0,0 +1,124 @@
+# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
+
+Just a few functions for helping humanize times and sizes.
+
+`go get` it as `github.com/dustin/go-humanize`, import it as
+`"github.com/dustin/go-humanize"`, use it as `humanize`.
+
+See [godoc](https://pkg.go.dev/github.com/dustin/go-humanize) for
+complete documentation.
+
+## Sizes
+
+This lets you take numbers like `82854982` and convert them to useful
+strings like, `83 MB` or `79 MiB` (whichever you prefer).
+
+Example:
+
+```go
+fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
+```
+
+## Times
+
+This lets you take a `time.Time` and spit it out in relative terms.
+For example, `12 seconds ago` or `3 days from now`.
+
+Example:
+
+```go
+fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
+```
+
+Thanks to Kyle Lemons for the time implementation from an IRC
+conversation one day. It's pretty neat.
+
+## Ordinals
+
+From a [mailing list discussion][odisc] where a user wanted to be able
+to label ordinals.
+
+    0 -> 0th
+    1 -> 1st
+    2 -> 2nd
+    3 -> 3rd
+    4 -> 4th
+    [...]
+
+Example:
+
+```go
+fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
+```
+
+## Commas
+
+Want to shove commas into numbers? Be my guest.
+
+    0 -> 0
+    100 -> 100
+    1000 -> 1,000
+    1000000000 -> 1,000,000,000
+    -100000 -> -100,000
+
+Example:
+
+```go
+fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
+```
+
+## Ftoa
+
+Nicer float64 formatter that removes trailing zeros.
+
+```go
+fmt.Printf("%f", 2.24)                // 2.240000
+fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
+fmt.Printf("%f", 2.0)                 // 2.000000
+fmt.Printf("%s", humanize.Ftoa(2.0))  // 2
+```
+
+## SI notation
+
+Format numbers with [SI notation][sinotation].
+
+Example:
+
+```go
+humanize.SI(0.00000000223, "M") // 2.23 nM
+```
+
+## English-specific functions
+
+The following functions are in the `humanize/english` subpackage.
+
+### Plurals
+
+Simple English pluralization
+
+```go
+english.PluralWord(1, "object", "") // object
+english.PluralWord(42, "object", "") // objects
+english.PluralWord(2, "bus", "") // buses
+english.PluralWord(99, "locus", "loci") // loci
+
+english.Plural(1, "object", "") // 1 object
+english.Plural(42, "object", "") // 42 objects
+english.Plural(2, "bus", "") // 2 buses
+english.Plural(99, "locus", "loci") // 99 loci
+```
+
+### Word series
+
+Format comma-separated words lists with conjuctions:
+
+```go
+english.WordSeries([]string{"foo"}, "and") // foo
+english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
+english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
+
+english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
+```
+
+[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
+[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
diff --git a/vendor/github.com/dustin/go-humanize/big.go b/vendor/github.com/dustin/go-humanize/big.go
new file mode 100644
index 0000000..f49dc33
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/big.go
@@ -0,0 +1,31 @@
+package humanize
+
+import (
+	"math/big"
+)
+
+// order of magnitude (to a max order)
+func oomm(n, b *big.Int, maxmag int) (float64, int) {
+	mag := 0
+	m := &big.Int{}
+	for n.Cmp(b) >= 0 {
+		n.DivMod(n, b, m)
+		mag++
+		if mag == maxmag && maxmag >= 0 {
+			break
+		}
+	}
+	return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
+}
+
+// total order of magnitude
+// (same as above, but with no upper limit)
+func oom(n, b *big.Int) (float64, int) {
+	mag := 0
+	m := &big.Int{}
+	for n.Cmp(b) >= 0 {
+		n.DivMod(n, b, m)
+		mag++
+	}
+	return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
+}
diff --git a/vendor/github.com/dustin/go-humanize/bigbytes.go b/vendor/github.com/dustin/go-humanize/bigbytes.go
new file mode 100644
index 0000000..3b015fd
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/bigbytes.go
@@ -0,0 +1,189 @@
+package humanize
+
+import (
+	"fmt"
+	"math/big"
+	"strings"
+	"unicode"
+)
+
+var (
+	bigIECExp = big.NewInt(1024)
+
+	// BigByte is one byte in bit.Ints
+	BigByte = big.NewInt(1)
+	// BigKiByte is 1,024 bytes in bit.Ints
+	BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
+	// BigMiByte is 1,024 k bytes in bit.Ints
+	BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
+	// BigGiByte is 1,024 m bytes in bit.Ints
+	BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
+	// BigTiByte is 1,024 g bytes in bit.Ints
+	BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
+	// BigPiByte is 1,024 t bytes in bit.Ints
+	BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
+	// BigEiByte is 1,024 p bytes in bit.Ints
+	BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
+	// BigZiByte is 1,024 e bytes in bit.Ints
+	BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
+	// BigYiByte is 1,024 z bytes in bit.Ints
+	BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
+	// BigRiByte is 1,024 y bytes in bit.Ints
+	BigRiByte = (&big.Int{}).Mul(BigYiByte, bigIECExp)
+	// BigQiByte is 1,024 r bytes in bit.Ints
+	BigQiByte = (&big.Int{}).Mul(BigRiByte, bigIECExp)
+)
+
+var (
+	bigSIExp = big.NewInt(1000)
+
+	// BigSIByte is one SI byte in big.Ints
+	BigSIByte = big.NewInt(1)
+	// BigKByte is 1,000 SI bytes in big.Ints
+	BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
+	// BigMByte is 1,000 SI k bytes in big.Ints
+	BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
+	// BigGByte is 1,000 SI m bytes in big.Ints
+	BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
+	// BigTByte is 1,000 SI g bytes in big.Ints
+	BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
+	// BigPByte is 1,000 SI t bytes in big.Ints
+	BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
+	// BigEByte is 1,000 SI p bytes in big.Ints
+	BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
+	// BigZByte is 1,000 SI e bytes in big.Ints
+	BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
+	// BigYByte is 1,000 SI z bytes in big.Ints
+	BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
+	// BigRByte is 1,000 SI y bytes in big.Ints
+	BigRByte = (&big.Int{}).Mul(BigYByte, bigSIExp)
+	// BigQByte is 1,000 SI r bytes in big.Ints
+	BigQByte = (&big.Int{}).Mul(BigRByte, bigSIExp)
+)
+
+var bigBytesSizeTable = map[string]*big.Int{
+	"b":   BigByte,
+	"kib": BigKiByte,
+	"kb":  BigKByte,
+	"mib": BigMiByte,
+	"mb":  BigMByte,
+	"gib": BigGiByte,
+	"gb":  BigGByte,
+	"tib": BigTiByte,
+	"tb":  BigTByte,
+	"pib": BigPiByte,
+	"pb":  BigPByte,
+	"eib": BigEiByte,
+	"eb":  BigEByte,
+	"zib": BigZiByte,
+	"zb":  BigZByte,
+	"yib": BigYiByte,
+	"yb":  BigYByte,
+	"rib": BigRiByte,
+	"rb":  BigRByte,
+	"qib": BigQiByte,
+	"qb":  BigQByte,
+	// Without suffix
+	"":   BigByte,
+	"ki": BigKiByte,
+	"k":  BigKByte,
+	"mi": BigMiByte,
+	"m":  BigMByte,
+	"gi": BigGiByte,
+	"g":  BigGByte,
+	"ti": BigTiByte,
+	"t":  BigTByte,
+	"pi": BigPiByte,
+	"p":  BigPByte,
+	"ei": BigEiByte,
+	"e":  BigEByte,
+	"z":  BigZByte,
+	"zi": BigZiByte,
+	"y":  BigYByte,
+	"yi": BigYiByte,
+	"r":  BigRByte,
+	"ri": BigRiByte,
+	"q":  BigQByte,
+	"qi": BigQiByte,
+}
+
+var ten = big.NewInt(10)
+
+func humanateBigBytes(s, base *big.Int, sizes []string) string {
+	if s.Cmp(ten) < 0 {
+		return fmt.Sprintf("%d B", s)
+	}
+	c := (&big.Int{}).Set(s)
+	val, mag := oomm(c, base, len(sizes)-1)
+	suffix := sizes[mag]
+	f := "%.0f %s"
+	if val < 10 {
+		f = "%.1f %s"
+	}
+
+	return fmt.Sprintf(f, val, suffix)
+
+}
+
+// BigBytes produces a human readable representation of an SI size.
+//
+// See also: ParseBigBytes.
+//
+// BigBytes(82854982) -> 83 MB
+func BigBytes(s *big.Int) string {
+	sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"}
+	return humanateBigBytes(s, bigSIExp, sizes)
+}
+
+// BigIBytes produces a human readable representation of an IEC size.
+//
+// See also: ParseBigBytes.
+//
+// BigIBytes(82854982) -> 79 MiB
+func BigIBytes(s *big.Int) string {
+	sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"}
+	return humanateBigBytes(s, bigIECExp, sizes)
+}
+
+// ParseBigBytes parses a string representation of bytes into the number
+// of bytes it represents.
+//
+// See also: BigBytes, BigIBytes.
+//
+// ParseBigBytes("42 MB") -> 42000000, nil
+// ParseBigBytes("42 mib") -> 44040192, nil
+func ParseBigBytes(s string) (*big.Int, error) {
+	lastDigit := 0
+	hasComma := false
+	for _, r := range s {
+		if !(unicode.IsDigit(r) || r == '.' || r == ',') {
+			break
+		}
+		if r == ',' {
+			hasComma = true
+		}
+		lastDigit++
+	}
+
+	num := s[:lastDigit]
+	if hasComma {
+		num = strings.Replace(num, ",", "", -1)
+	}
+
+	val := &big.Rat{}
+	_, err := fmt.Sscanf(num, "%f", val)
+	if err != nil {
+		return nil, err
+	}
+
+	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
+	if m, ok := bigBytesSizeTable[extra]; ok {
+		mv := (&big.Rat{}).SetInt(m)
+		val.Mul(val, mv)
+		rv := &big.Int{}
+		rv.Div(val.Num(), val.Denom())
+		return rv, nil
+	}
+
+	return nil, fmt.Errorf("unhandled size name: %v", extra)
+}
diff --git a/vendor/github.com/dustin/go-humanize/bytes.go b/vendor/github.com/dustin/go-humanize/bytes.go
new file mode 100644
index 0000000..0b498f4
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/bytes.go
@@ -0,0 +1,143 @@
+package humanize
+
+import (
+	"fmt"
+	"math"
+	"strconv"
+	"strings"
+	"unicode"
+)
+
+// IEC Sizes.
+// kibis of bits
+const (
+	Byte = 1 << (iota * 10)
+	KiByte
+	MiByte
+	GiByte
+	TiByte
+	PiByte
+	EiByte
+)
+
+// SI Sizes.
+const (
+	IByte = 1
+	KByte = IByte * 1000
+	MByte = KByte * 1000
+	GByte = MByte * 1000
+	TByte = GByte * 1000
+	PByte = TByte * 1000
+	EByte = PByte * 1000
+)
+
+var bytesSizeTable = map[string]uint64{
+	"b":   Byte,
+	"kib": KiByte,
+	"kb":  KByte,
+	"mib": MiByte,
+	"mb":  MByte,
+	"gib": GiByte,
+	"gb":  GByte,
+	"tib": TiByte,
+	"tb":  TByte,
+	"pib": PiByte,
+	"pb":  PByte,
+	"eib": EiByte,
+	"eb":  EByte,
+	// Without suffix
+	"":   Byte,
+	"ki": KiByte,
+	"k":  KByte,
+	"mi": MiByte,
+	"m":  MByte,
+	"gi": GiByte,
+	"g":  GByte,
+	"ti": TiByte,
+	"t":  TByte,
+	"pi": PiByte,
+	"p":  PByte,
+	"ei": EiByte,
+	"e":  EByte,
+}
+
+func logn(n, b float64) float64 {
+	return math.Log(n) / math.Log(b)
+}
+
+func humanateBytes(s uint64, base float64, sizes []string) string {
+	if s < 10 {
+		return fmt.Sprintf("%d B", s)
+	}
+	e := math.Floor(logn(float64(s), base))
+	suffix := sizes[int(e)]
+	val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
+	f := "%.0f %s"
+	if val < 10 {
+		f = "%.1f %s"
+	}
+
+	return fmt.Sprintf(f, val, suffix)
+}
+
+// Bytes produces a human readable representation of an SI size.
+//
+// See also: ParseBytes.
+//
+// Bytes(82854982) -> 83 MB
+func Bytes(s uint64) string {
+	sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
+	return humanateBytes(s, 1000, sizes)
+}
+
+// IBytes produces a human readable representation of an IEC size.
+//
+// See also: ParseBytes.
+//
+// IBytes(82854982) -> 79 MiB
+func IBytes(s uint64) string {
+	sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
+	return humanateBytes(s, 1024, sizes)
+}
+
+// ParseBytes parses a string representation of bytes into the number
+// of bytes it represents.
+//
+// See Also: Bytes, IBytes.
+//
+// ParseBytes("42 MB") -> 42000000, nil
+// ParseBytes("42 mib") -> 44040192, nil
+func ParseBytes(s string) (uint64, error) {
+	lastDigit := 0
+	hasComma := false
+	for _, r := range s {
+		if !(unicode.IsDigit(r) || r == '.' || r == ',') {
+			break
+		}
+		if r == ',' {
+			hasComma = true
+		}
+		lastDigit++
+	}
+
+	num := s[:lastDigit]
+	if hasComma {
+		num = strings.Replace(num, ",", "", -1)
+	}
+
+	f, err := strconv.ParseFloat(num, 64)
+	if err != nil {
+		return 0, err
+	}
+
+	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
+	if m, ok := bytesSizeTable[extra]; ok {
+		f *= float64(m)
+		if f >= math.MaxUint64 {
+			return 0, fmt.Errorf("too large: %v", s)
+		}
+		return uint64(f), nil
+	}
+
+	return 0, fmt.Errorf("unhandled size name: %v", extra)
+}
diff --git a/vendor/github.com/dustin/go-humanize/comma.go b/vendor/github.com/dustin/go-humanize/comma.go
new file mode 100644
index 0000000..520ae3e
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/comma.go
@@ -0,0 +1,116 @@
+package humanize
+
+import (
+	"bytes"
+	"math"
+	"math/big"
+	"strconv"
+	"strings"
+)
+
+// Comma produces a string form of the given number in base 10 with
+// commas after every three orders of magnitude.
+//
+// e.g. Comma(834142) -> 834,142
+func Comma(v int64) string {
+	sign := ""
+
+	// Min int64 can't be negated to a usable value, so it has to be special cased.
+	if v == math.MinInt64 {
+		return "-9,223,372,036,854,775,808"
+	}
+
+	if v < 0 {
+		sign = "-"
+		v = 0 - v
+	}
+
+	parts := []string{"", "", "", "", "", "", ""}
+	j := len(parts) - 1
+
+	for v > 999 {
+		parts[j] = strconv.FormatInt(v%1000, 10)
+		switch len(parts[j]) {
+		case 2:
+			parts[j] = "0" + parts[j]
+		case 1:
+			parts[j] = "00" + parts[j]
+		}
+		v = v / 1000
+		j--
+	}
+	parts[j] = strconv.Itoa(int(v))
+	return sign + strings.Join(parts[j:], ",")
+}
+
+// Commaf produces a string form of the given number in base 10 with
+// commas after every three orders of magnitude.
+//
+// e.g. Commaf(834142.32) -> 834,142.32
+func Commaf(v float64) string {
+	buf := &bytes.Buffer{}
+	if v < 0 {
+		buf.Write([]byte{'-'})
+		v = 0 - v
+	}
+
+	comma := []byte{','}
+
+	parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
+	pos := 0
+	if len(parts[0])%3 != 0 {
+		pos += len(parts[0]) % 3
+		buf.WriteString(parts[0][:pos])
+		buf.Write(comma)
+	}
+	for ; pos < len(parts[0]); pos += 3 {
+		buf.WriteString(parts[0][pos : pos+3])
+		buf.Write(comma)
+	}
+	buf.Truncate(buf.Len() - 1)
+
+	if len(parts) > 1 {
+		buf.Write([]byte{'.'})
+		buf.WriteString(parts[1])
+	}
+	return buf.String()
+}
+
+// CommafWithDigits works like the Commaf but limits the resulting
+// string to the given number of decimal places.
+//
+// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
+func CommafWithDigits(f float64, decimals int) string {
+	return stripTrailingDigits(Commaf(f), decimals)
+}
+
+// BigComma produces a string form of the given big.Int in base 10
+// with commas after every three orders of magnitude.
+func BigComma(b *big.Int) string {
+	sign := ""
+	if b.Sign() < 0 {
+		sign = "-"
+		b.Abs(b)
+	}
+
+	athousand := big.NewInt(1000)
+	c := (&big.Int{}).Set(b)
+	_, m := oom(c, athousand)
+	parts := make([]string, m+1)
+	j := len(parts) - 1
+
+	mod := &big.Int{}
+	for b.Cmp(athousand) >= 0 {
+		b.DivMod(b, athousand, mod)
+		parts[j] = strconv.FormatInt(mod.Int64(), 10)
+		switch len(parts[j]) {
+		case 2:
+			parts[j] = "0" + parts[j]
+		case 1:
+			parts[j] = "00" + parts[j]
+		}
+		j--
+	}
+	parts[j] = strconv.Itoa(int(b.Int64()))
+	return sign + strings.Join(parts[j:], ",")
+}
diff --git a/vendor/github.com/dustin/go-humanize/commaf.go b/vendor/github.com/dustin/go-humanize/commaf.go
new file mode 100644
index 0000000..2bc83a0
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/commaf.go
@@ -0,0 +1,41 @@
+//go:build go1.6
+// +build go1.6
+
+package humanize
+
+import (
+	"bytes"
+	"math/big"
+	"strings"
+)
+
+// BigCommaf produces a string form of the given big.Float in base 10
+// with commas after every three orders of magnitude.
+func BigCommaf(v *big.Float) string {
+	buf := &bytes.Buffer{}
+	if v.Sign() < 0 {
+		buf.Write([]byte{'-'})
+		v.Abs(v)
+	}
+
+	comma := []byte{','}
+
+	parts := strings.Split(v.Text('f', -1), ".")
+	pos := 0
+	if len(parts[0])%3 != 0 {
+		pos += len(parts[0]) % 3
+		buf.WriteString(parts[0][:pos])
+		buf.Write(comma)
+	}
+	for ; pos < len(parts[0]); pos += 3 {
+		buf.WriteString(parts[0][pos : pos+3])
+		buf.Write(comma)
+	}
+	buf.Truncate(buf.Len() - 1)
+
+	if len(parts) > 1 {
+		buf.Write([]byte{'.'})
+		buf.WriteString(parts[1])
+	}
+	return buf.String()
+}
diff --git a/vendor/github.com/dustin/go-humanize/ftoa.go b/vendor/github.com/dustin/go-humanize/ftoa.go
new file mode 100644
index 0000000..bce923f
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/ftoa.go
@@ -0,0 +1,49 @@
+package humanize
+
+import (
+	"strconv"
+	"strings"
+)
+
+func stripTrailingZeros(s string) string {
+	if !strings.ContainsRune(s, '.') {
+		return s
+	}
+	offset := len(s) - 1
+	for offset > 0 {
+		if s[offset] == '.' {
+			offset--
+			break
+		}
+		if s[offset] != '0' {
+			break
+		}
+		offset--
+	}
+	return s[:offset+1]
+}
+
+func stripTrailingDigits(s string, digits int) string {
+	if i := strings.Index(s, "."); i >= 0 {
+		if digits <= 0 {
+			return s[:i]
+		}
+		i++
+		if i+digits >= len(s) {
+			return s
+		}
+		return s[:i+digits]
+	}
+	return s
+}
+
+// Ftoa converts a float to a string with no trailing zeros.
+func Ftoa(num float64) string {
+	return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
+}
+
+// FtoaWithDigits converts a float to a string but limits the resulting string
+// to the given number of decimal places, and no trailing zeros.
+func FtoaWithDigits(num float64, digits int) string {
+	return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
+}
diff --git a/vendor/github.com/dustin/go-humanize/humanize.go b/vendor/github.com/dustin/go-humanize/humanize.go
new file mode 100644
index 0000000..a2c2da3
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/humanize.go
@@ -0,0 +1,8 @@
+/*
+Package humanize converts boring ugly numbers to human-friendly strings and back.
+
+Durations can be turned into strings such as "3 days ago", numbers
+representing sizes like 82854982 into useful strings like, "83 MB" or
+"79 MiB" (whichever you prefer).
+*/
+package humanize
diff --git a/vendor/github.com/dustin/go-humanize/number.go b/vendor/github.com/dustin/go-humanize/number.go
new file mode 100644
index 0000000..6470d0d
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/number.go
@@ -0,0 +1,192 @@
+package humanize
+
+/*
+Slightly adapted from the source to fit go-humanize.
+
+Author: https://github.com/gorhill
+Source: https://gist.github.com/gorhill/5285193
+
+*/
+
+import (
+	"math"
+	"strconv"
+)
+
+var (
+	renderFloatPrecisionMultipliers = [...]float64{
+		1,
+		10,
+		100,
+		1000,
+		10000,
+		100000,
+		1000000,
+		10000000,
+		100000000,
+		1000000000,
+	}
+
+	renderFloatPrecisionRounders = [...]float64{
+		0.5,
+		0.05,
+		0.005,
+		0.0005,
+		0.00005,
+		0.000005,
+		0.0000005,
+		0.00000005,
+		0.000000005,
+		0.0000000005,
+	}
+)
+
+// FormatFloat produces a formatted number as string based on the following user-specified criteria:
+// * thousands separator
+// * decimal separator
+// * decimal precision
+//
+// Usage: s := RenderFloat(format, n)
+// The format parameter tells how to render the number n.
+//
+// See examples: http://play.golang.org/p/LXc1Ddm1lJ
+//
+// Examples of format strings, given n = 12345.6789:
+// "#,###.##" => "12,345.67"
+// "#,###." => "12,345"
+// "#,###" => "12345,678"
+// "#\u202F###,##" => "12 345,68"
+// "#.###,###### => 12.345,678900
+// "" (aka default format) => 12,345.67
+//
+// The highest precision allowed is 9 digits after the decimal symbol.
+// There is also a version for integer number, FormatInteger(),
+// which is convenient for calls within template.
+func FormatFloat(format string, n float64) string {
+	// Special cases:
+	//   NaN = "NaN"
+	//   +Inf = "+Infinity"
+	//   -Inf = "-Infinity"
+	if math.IsNaN(n) {
+		return "NaN"
+	}
+	if n > math.MaxFloat64 {
+		return "Infinity"
+	}
+	if n < (0.0 - math.MaxFloat64) {
+		return "-Infinity"
+	}
+
+	// default format
+	precision := 2
+	decimalStr := "."
+	thousandStr := ","
+	positiveStr := ""
+	negativeStr := "-"
+
+	if len(format) > 0 {
+		format := []rune(format)
+
+		// If there is an explicit format directive,
+		// then default values are these:
+		precision = 9
+		thousandStr = ""
+
+		// collect indices of meaningful formatting directives
+		formatIndx := []int{}
+		for i, char := range format {
+			if char != '#' && char != '0' {
+				formatIndx = append(formatIndx, i)
+			}
+		}
+
+		if len(formatIndx) > 0 {
+			// Directive at index 0:
+			//   Must be a '+'
+			//   Raise an error if not the case
+			// index: 0123456789
+			//        +0.000,000
+			//        +000,000.0
+			//        +0000.00
+			//        +0000
+			if formatIndx[0] == 0 {
+				if format[formatIndx[0]] != '+' {
+					panic("RenderFloat(): invalid positive sign directive")
+				}
+				positiveStr = "+"
+				formatIndx = formatIndx[1:]
+			}
+
+			// Two directives:
+			//   First is thousands separator
+			//   Raise an error if not followed by 3-digit
+			// 0123456789
+			// 0.000,000
+			// 000,000.00
+			if len(formatIndx) == 2 {
+				if (formatIndx[1] - formatIndx[0]) != 4 {
+					panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
+				}
+				thousandStr = string(format[formatIndx[0]])
+				formatIndx = formatIndx[1:]
+			}
+
+			// One directive:
+			//   Directive is decimal separator
+			//   The number of digit-specifier following the separator indicates wanted precision
+			// 0123456789
+			// 0.00
+			// 000,0000
+			if len(formatIndx) == 1 {
+				decimalStr = string(format[formatIndx[0]])
+				precision = len(format) - formatIndx[0] - 1
+			}
+		}
+	}
+
+	// generate sign part
+	var signStr string
+	if n >= 0.000000001 {
+		signStr = positiveStr
+	} else if n <= -0.000000001 {
+		signStr = negativeStr
+		n = -n
+	} else {
+		signStr = ""
+		n = 0.0
+	}
+
+	// split number into integer and fractional parts
+	intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
+
+	// generate integer part string
+	intStr := strconv.FormatInt(int64(intf), 10)
+
+	// add thousand separator if required
+	if len(thousandStr) > 0 {
+		for i := len(intStr); i > 3; {
+			i -= 3
+			intStr = intStr[:i] + thousandStr + intStr[i:]
+		}
+	}
+
+	// no fractional part, we can leave now
+	if precision == 0 {
+		return signStr + intStr
+	}
+
+	// generate fractional part
+	fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
+	// may need padding
+	if len(fracStr) < precision {
+		fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
+	}
+
+	return signStr + intStr + decimalStr + fracStr
+}
+
+// FormatInteger produces a formatted number as string.
+// See FormatFloat.
+func FormatInteger(format string, n int) string {
+	return FormatFloat(format, float64(n))
+}
diff --git a/vendor/github.com/dustin/go-humanize/ordinals.go b/vendor/github.com/dustin/go-humanize/ordinals.go
new file mode 100644
index 0000000..43d88a8
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/ordinals.go
@@ -0,0 +1,25 @@
+package humanize
+
+import "strconv"
+
+// Ordinal gives you the input number in a rank/ordinal format.
+//
+// Ordinal(3) -> 3rd
+func Ordinal(x int) string {
+	suffix := "th"
+	switch x % 10 {
+	case 1:
+		if x%100 != 11 {
+			suffix = "st"
+		}
+	case 2:
+		if x%100 != 12 {
+			suffix = "nd"
+		}
+	case 3:
+		if x%100 != 13 {
+			suffix = "rd"
+		}
+	}
+	return strconv.Itoa(x) + suffix
+}
diff --git a/vendor/github.com/dustin/go-humanize/si.go b/vendor/github.com/dustin/go-humanize/si.go
new file mode 100644
index 0000000..8b85019
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/si.go
@@ -0,0 +1,127 @@
+package humanize
+
+import (
+	"errors"
+	"math"
+	"regexp"
+	"strconv"
+)
+
+var siPrefixTable = map[float64]string{
+	-30: "q", // quecto
+	-27: "r", // ronto
+	-24: "y", // yocto
+	-21: "z", // zepto
+	-18: "a", // atto
+	-15: "f", // femto
+	-12: "p", // pico
+	-9:  "n", // nano
+	-6:  "µ", // micro
+	-3:  "m", // milli
+	0:   "",
+	3:   "k", // kilo
+	6:   "M", // mega
+	9:   "G", // giga
+	12:  "T", // tera
+	15:  "P", // peta
+	18:  "E", // exa
+	21:  "Z", // zetta
+	24:  "Y", // yotta
+	27:  "R", // ronna
+	30:  "Q", // quetta
+}
+
+var revSIPrefixTable = revfmap(siPrefixTable)
+
+// revfmap reverses the map and precomputes the power multiplier
+func revfmap(in map[float64]string) map[string]float64 {
+	rv := map[string]float64{}
+	for k, v := range in {
+		rv[v] = math.Pow(10, k)
+	}
+	return rv
+}
+
+var riParseRegex *regexp.Regexp
+
+func init() {
+	ri := `^([\-0-9.]+)\s?([`
+	for _, v := range siPrefixTable {
+		ri += v
+	}
+	ri += `]?)(.*)`
+
+	riParseRegex = regexp.MustCompile(ri)
+}
+
+// ComputeSI finds the most appropriate SI prefix for the given number
+// and returns the prefix along with the value adjusted to be within
+// that prefix.
+//
+// See also: SI, ParseSI.
+//
+// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
+func ComputeSI(input float64) (float64, string) {
+	if input == 0 {
+		return 0, ""
+	}
+	mag := math.Abs(input)
+	exponent := math.Floor(logn(mag, 10))
+	exponent = math.Floor(exponent/3) * 3
+
+	value := mag / math.Pow(10, exponent)
+
+	// Handle special case where value is exactly 1000.0
+	// Should return 1 M instead of 1000 k
+	if value == 1000.0 {
+		exponent += 3
+		value = mag / math.Pow(10, exponent)
+	}
+
+	value = math.Copysign(value, input)
+
+	prefix := siPrefixTable[exponent]
+	return value, prefix
+}
+
+// SI returns a string with default formatting.
+//
+// SI uses Ftoa to format float value, removing trailing zeros.
+//
+// See also: ComputeSI, ParseSI.
+//
+// e.g. SI(1000000, "B") -> 1 MB
+// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
+func SI(input float64, unit string) string {
+	value, prefix := ComputeSI(input)
+	return Ftoa(value) + " " + prefix + unit
+}
+
+// SIWithDigits works like SI but limits the resulting string to the
+// given number of decimal places.
+//
+// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
+// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
+func SIWithDigits(input float64, decimals int, unit string) string {
+	value, prefix := ComputeSI(input)
+	return FtoaWithDigits(value, decimals) + " " + prefix + unit
+}
+
+var errInvalid = errors.New("invalid input")
+
+// ParseSI parses an SI string back into the number and unit.
+//
+// See also: SI, ComputeSI.
+//
+// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
+func ParseSI(input string) (float64, string, error) {
+	found := riParseRegex.FindStringSubmatch(input)
+	if len(found) != 4 {
+		return 0, "", errInvalid
+	}
+	mag := revSIPrefixTable[found[2]]
+	unit := found[3]
+
+	base, err := strconv.ParseFloat(found[1], 64)
+	return base * mag, unit, err
+}
diff --git a/vendor/github.com/dustin/go-humanize/times.go b/vendor/github.com/dustin/go-humanize/times.go
new file mode 100644
index 0000000..dd3fbf5
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/times.go
@@ -0,0 +1,117 @@
+package humanize
+
+import (
+	"fmt"
+	"math"
+	"sort"
+	"time"
+)
+
+// Seconds-based time units
+const (
+	Day      = 24 * time.Hour
+	Week     = 7 * Day
+	Month    = 30 * Day
+	Year     = 12 * Month
+	LongTime = 37 * Year
+)
+
+// Time formats a time into a relative string.
+//
+// Time(someT) -> "3 weeks ago"
+func Time(then time.Time) string {
+	return RelTime(then, time.Now(), "ago", "from now")
+}
+
+// A RelTimeMagnitude struct contains a relative time point at which
+// the relative format of time will switch to a new format string.  A
+// slice of these in ascending order by their "D" field is passed to
+// CustomRelTime to format durations.
+//
+// The Format field is a string that may contain a "%s" which will be
+// replaced with the appropriate signed label (e.g. "ago" or "from
+// now") and a "%d" that will be replaced by the quantity.
+//
+// The DivBy field is the amount of time the time difference must be
+// divided by in order to display correctly.
+//
+// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
+// DivBy should be time.Minute so whatever the duration is will be
+// expressed in minutes.
+type RelTimeMagnitude struct {
+	D      time.Duration
+	Format string
+	DivBy  time.Duration
+}
+
+var defaultMagnitudes = []RelTimeMagnitude{
+	{time.Second, "now", time.Second},
+	{2 * time.Second, "1 second %s", 1},
+	{time.Minute, "%d seconds %s", time.Second},
+	{2 * time.Minute, "1 minute %s", 1},
+	{time.Hour, "%d minutes %s", time.Minute},
+	{2 * time.Hour, "1 hour %s", 1},
+	{Day, "%d hours %s", time.Hour},
+	{2 * Day, "1 day %s", 1},
+	{Week, "%d days %s", Day},
+	{2 * Week, "1 week %s", 1},
+	{Month, "%d weeks %s", Week},
+	{2 * Month, "1 month %s", 1},
+	{Year, "%d months %s", Month},
+	{18 * Month, "1 year %s", 1},
+	{2 * Year, "2 years %s", 1},
+	{LongTime, "%d years %s", Year},
+	{math.MaxInt64, "a long while %s", 1},
+}
+
+// RelTime formats a time into a relative string.
+//
+// It takes two times and two labels.  In addition to the generic time
+// delta string (e.g. 5 minutes), the labels are used applied so that
+// the label corresponding to the smaller time is applied.
+//
+// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
+func RelTime(a, b time.Time, albl, blbl string) string {
+	return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
+}
+
+// CustomRelTime formats a time into a relative string.
+//
+// It takes two times two labels and a table of relative time formats.
+// In addition to the generic time delta string (e.g. 5 minutes), the
+// labels are used applied so that the label corresponding to the
+// smaller time is applied.
+func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
+	lbl := albl
+	diff := b.Sub(a)
+
+	if a.After(b) {
+		lbl = blbl
+		diff = a.Sub(b)
+	}
+
+	n := sort.Search(len(magnitudes), func(i int) bool {
+		return magnitudes[i].D > diff
+	})
+
+	if n >= len(magnitudes) {
+		n = len(magnitudes) - 1
+	}
+	mag := magnitudes[n]
+	args := []interface{}{}
+	escaped := false
+	for _, ch := range mag.Format {
+		if escaped {
+			switch ch {
+			case 's':
+				args = append(args, lbl)
+			case 'd':
+				args = append(args, diff/mag.DivBy)
+			}
+			escaped = false
+		} else {
+			escaped = ch == '%'
+		}
+	}
+	return fmt.Sprintf(mag.Format, args...)
+}