diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a8865410c..ba12af788 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -5,6 +5,10 @@ "./..." ], "Deps": [ + { + "ImportPath": "github.com/dustin/go-humanize", + "Rev": "8cc1aaa2d955ee82833337cfb10babc42be6bce6" + }, { "ImportPath": "github.com/gorilla/context", "Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da" @@ -22,10 +26,6 @@ "Comment": "1.2.0-102-gecb385c", "Rev": "ecb385c3fefd53678e3b6beba6a608fb7c8dfac1" }, - { - "ImportPath": "github.com/minio-io/iodine", - "Rev": "9a63d02ce3934e159d00732f15e096e4f86a6dbb" - }, { "ImportPath": "github.com/stretchr/objx", "Rev": "cbeaeb16a013161a98496fad62933b1d21786672" diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/.gitignore b/Godeps/_workspace/src/github.com/dustin/go-humanize/.gitignore new file mode 100644 index 000000000..05b40514a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/.gitignore @@ -0,0 +1,6 @@ +#* +*.[568] +*.a +*~ +[568].out +_* diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/LICENSE b/Godeps/_workspace/src/github.com/dustin/go-humanize/LICENSE new file mode 100644 index 000000000..8d9a94a90 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2005-2008 Dustin Sallings + +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. + + diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/README.markdown b/Godeps/_workspace/src/github.com/dustin/go-humanize/README.markdown new file mode 100644 index 000000000..079bc89a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/README.markdown @@ -0,0 +1,78 @@ +# Humane Units + +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` + +## Sizes + +This lets you take numbers like `82854982` and convert them to useful +strings like, `83MB` or `79MiB` (whichever you prefer). + +Example: + + fmt.Printf("That file is %s.", humanize.Bytes(82854982)) + +## 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: + + fmt.Printf("This was touched %s", humanize.Time(someTimeInstance)) + +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: + + fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) + +## 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: + + fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) + +## Ftoa + +Nicer float64 formatter that removes trailing zeros. + + 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: + + humanize.SI(0.00000000223, "M") // 2.23nM + + +[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion +[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/big.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/big.go new file mode 100644 index 000000000..f49dc337d --- /dev/null +++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/dustin/go-humanize/bigbytes.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/bigbytes.go new file mode 100644 index 000000000..6876e92ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/bigbytes.go @@ -0,0 +1,164 @@ +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) +) + +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) +) + +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, + // 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, +} + +var ten = big.NewInt(10) + +func humanateBigBytes(s, base *big.Int, sizes []string) string { + if s.Cmp(ten) < 0 { + return fmt.Sprintf("%dB", 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) -> 83MB +func BigBytes(s *big.Int) string { + sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} + return humanateBigBytes(s, bigSIExp, sizes) +} + +// BigIBytes produces a human readable representation of an IEC size. +// +// See also: ParseBigBytes. +// +// BigIBytes(82854982) -> 79MiB +func BigIBytes(s *big.Int) string { + sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} + return humanateBigBytes(s, bigIECExp, sizes) +} + +// ParseBigBytes parses a string representation of bytes into the number +// of bytes it represents. +// +// See also: BigBytes, BigIBytes. +// +// ParseBigBytes("42MB") -> 42000000, nil +// ParseBigBytes("42mib") -> 44040192, nil +func ParseBigBytes(s string) (*big.Int, error) { + lastDigit := 0 + for _, r := range s { + if !(unicode.IsDigit(r) || r == '.') { + break + } + lastDigit++ + } + + val := &big.Rat{} + _, err := fmt.Sscanf(s[:lastDigit], "%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/Godeps/_workspace/src/github.com/dustin/go-humanize/bigbytes_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/bigbytes_test.go new file mode 100644 index 000000000..a0f977a6e --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/bigbytes_test.go @@ -0,0 +1,219 @@ +package humanize + +import ( + "math/big" + "testing" +) + +func TestBigByteParsing(t *testing.T) { + tests := []struct { + in string + exp uint64 + }{ + {"42", 42}, + {"42MB", 42000000}, + {"42MiB", 44040192}, + {"42mb", 42000000}, + {"42mib", 44040192}, + {"42MIB", 44040192}, + {"42 MB", 42000000}, + {"42 MiB", 44040192}, + {"42 mb", 42000000}, + {"42 mib", 44040192}, + {"42 MIB", 44040192}, + {"42.5MB", 42500000}, + {"42.5MiB", 44564480}, + {"42.5 MB", 42500000}, + {"42.5 MiB", 44564480}, + // No need to say B + {"42M", 42000000}, + {"42Mi", 44040192}, + {"42m", 42000000}, + {"42mi", 44040192}, + {"42MI", 44040192}, + {"42 M", 42000000}, + {"42 Mi", 44040192}, + {"42 m", 42000000}, + {"42 mi", 44040192}, + {"42 MI", 44040192}, + {"42.5M", 42500000}, + {"42.5Mi", 44564480}, + {"42.5 M", 42500000}, + {"42.5 Mi", 44564480}, + // Large testing, breaks when too much larger than + // this. + {"12.5 EB", uint64(12.5 * float64(EByte))}, + {"12.5 E", uint64(12.5 * float64(EByte))}, + {"12.5 EiB", uint64(12.5 * float64(EiByte))}, + } + + for _, p := range tests { + got, err := ParseBigBytes(p.in) + if err != nil { + t.Errorf("Couldn't parse %v: %v", p.in, err) + } else { + if got.Uint64() != p.exp { + t.Errorf("Expected %v for %v, got %v", + p.exp, p.in, got) + } + } + } +} + +func TestBigByteErrors(t *testing.T) { + got, err := ParseBigBytes("84 JB") + if err == nil { + t.Errorf("Expected error, got %v", got) + } + got, err = ParseBigBytes("") + if err == nil { + t.Errorf("Expected error parsing nothing") + } +} + +func bbyte(in uint64) string { + return BigBytes((&big.Int{}).SetUint64(in)) +} + +func bibyte(in uint64) string { + return BigIBytes((&big.Int{}).SetUint64(in)) +} + +func TestBigBytes(t *testing.T) { + testList{ + {"bytes(0)", bbyte(0), "0B"}, + {"bytes(1)", bbyte(1), "1B"}, + {"bytes(803)", bbyte(803), "803B"}, + {"bytes(999)", bbyte(999), "999B"}, + + {"bytes(1024)", bbyte(1024), "1.0KB"}, + {"bytes(1MB - 1)", bbyte(MByte - Byte), "1000KB"}, + + {"bytes(1MB)", bbyte(1024 * 1024), "1.0MB"}, + {"bytes(1GB - 1K)", bbyte(GByte - KByte), "1000MB"}, + + {"bytes(1GB)", bbyte(GByte), "1.0GB"}, + {"bytes(1TB - 1M)", bbyte(TByte - MByte), "1000GB"}, + + {"bytes(1TB)", bbyte(TByte), "1.0TB"}, + {"bytes(1PB - 1T)", bbyte(PByte - TByte), "999TB"}, + + {"bytes(1PB)", bbyte(PByte), "1.0PB"}, + {"bytes(1PB - 1T)", bbyte(EByte - PByte), "999PB"}, + + {"bytes(1EB)", bbyte(EByte), "1.0EB"}, + // Overflows. + // {"bytes(1EB - 1P)", Bytes((KByte*EByte)-PByte), "1023EB"}, + + {"bytes(0)", bibyte(0), "0B"}, + {"bytes(1)", bibyte(1), "1B"}, + {"bytes(803)", bibyte(803), "803B"}, + {"bytes(1023)", bibyte(1023), "1023B"}, + + {"bytes(1024)", bibyte(1024), "1.0KiB"}, + {"bytes(1MB - 1)", bibyte(MiByte - IByte), "1024KiB"}, + + {"bytes(1MB)", bibyte(1024 * 1024), "1.0MiB"}, + {"bytes(1GB - 1K)", bibyte(GiByte - KiByte), "1024MiB"}, + + {"bytes(1GB)", bibyte(GiByte), "1.0GiB"}, + {"bytes(1TB - 1M)", bibyte(TiByte - MiByte), "1024GiB"}, + + {"bytes(1TB)", bibyte(TiByte), "1.0TiB"}, + {"bytes(1PB - 1T)", bibyte(PiByte - TiByte), "1023TiB"}, + + {"bytes(1PB)", bibyte(PiByte), "1.0PiB"}, + {"bytes(1PB - 1T)", bibyte(EiByte - PiByte), "1023PiB"}, + + {"bytes(1EiB)", bibyte(EiByte), "1.0EiB"}, + // Overflows. + // {"bytes(1EB - 1P)", bibyte((KIByte*EIByte)-PiByte), "1023EB"}, + + {"bytes(5.5GiB)", bibyte(5.5 * GiByte), "5.5GiB"}, + + {"bytes(5.5GB)", bbyte(5.5 * GByte), "5.5GB"}, + }.validate(t) +} + +func TestVeryBigBytes(t *testing.T) { + b, _ := (&big.Int{}).SetString("15347691069326346944512", 10) + s := BigBytes(b) + if s != "15ZB" { + t.Errorf("Expected 15ZB, got %v", s) + } + s = BigIBytes(b) + if s != "13ZiB" { + t.Errorf("Expected 13ZiB, got %v", s) + } + + b, _ = (&big.Int{}).SetString("15716035654990179271180288", 10) + s = BigBytes(b) + if s != "16YB" { + t.Errorf("Expected 16YB, got %v", s) + } + s = BigIBytes(b) + if s != "13YiB" { + t.Errorf("Expected 13YiB, got %v", s) + } +} + +func TestVeryVeryBigBytes(t *testing.T) { + b, _ := (&big.Int{}).SetString("16093220510709943573688614912", 10) + s := BigBytes(b) + if s != "16093YB" { + t.Errorf("Expected 16093YB, got %v", s) + } + s = BigIBytes(b) + if s != "13312YiB" { + t.Errorf("Expected 13312YiB, got %v", s) + } +} + +func TestParseVeryBig(t *testing.T) { + tests := []struct { + in string + out string + }{ + {"16ZB", "16000000000000000000000"}, + {"16ZiB", "18889465931478580854784"}, + {"16.5ZB", "16500000000000000000000"}, + {"16.5ZiB", "19479761741837286506496"}, + {"16Z", "16000000000000000000000"}, + {"16Zi", "18889465931478580854784"}, + {"16.5Z", "16500000000000000000000"}, + {"16.5Zi", "19479761741837286506496"}, + + {"16YB", "16000000000000000000000000"}, + {"16YiB", "19342813113834066795298816"}, + {"16.5YB", "16500000000000000000000000"}, + {"16.5YiB", "19947276023641381382651904"}, + {"16Y", "16000000000000000000000000"}, + {"16Yi", "19342813113834066795298816"}, + {"16.5Y", "16500000000000000000000000"}, + {"16.5Yi", "19947276023641381382651904"}, + } + + for _, test := range tests { + x, err := ParseBigBytes(test.in) + if err != nil { + t.Errorf("Error parsing %q: %v", test.in, err) + continue + } + + if x.String() != test.out { + t.Errorf("Expected %q for %q, got %v", test.out, test.in, x) + } + } +} + +func BenchmarkParseBigBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + ParseBigBytes("16.5Z") + } +} + +func BenchmarkBigBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + bibyte(16.5 * GByte) + } +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/bytes.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/bytes.go new file mode 100644 index 000000000..4c4b5af15 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/bytes.go @@ -0,0 +1,134 @@ +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("%dB", 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) -> 83MB +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) -> 79MiB +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("42MB") -> 42000000, nil +// ParseBytes("42mib") -> 44040192, nil +func ParseBytes(s string) (uint64, error) { + lastDigit := 0 + for _, r := range s { + if !(unicode.IsDigit(r) || r == '.') { + break + } + lastDigit++ + } + + f, err := strconv.ParseFloat(s[:lastDigit], 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/Godeps/_workspace/src/github.com/dustin/go-humanize/bytes_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/bytes_test.go new file mode 100644 index 000000000..76a594c1a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/bytes_test.go @@ -0,0 +1,144 @@ +package humanize + +import ( + "testing" +) + +func TestByteParsing(t *testing.T) { + tests := []struct { + in string + exp uint64 + }{ + {"42", 42}, + {"42MB", 42000000}, + {"42MiB", 44040192}, + {"42mb", 42000000}, + {"42mib", 44040192}, + {"42MIB", 44040192}, + {"42 MB", 42000000}, + {"42 MiB", 44040192}, + {"42 mb", 42000000}, + {"42 mib", 44040192}, + {"42 MIB", 44040192}, + {"42.5MB", 42500000}, + {"42.5MiB", 44564480}, + {"42.5 MB", 42500000}, + {"42.5 MiB", 44564480}, + // No need to say B + {"42M", 42000000}, + {"42Mi", 44040192}, + {"42m", 42000000}, + {"42mi", 44040192}, + {"42MI", 44040192}, + {"42 M", 42000000}, + {"42 Mi", 44040192}, + {"42 m", 42000000}, + {"42 mi", 44040192}, + {"42 MI", 44040192}, + {"42.5M", 42500000}, + {"42.5Mi", 44564480}, + {"42.5 M", 42500000}, + {"42.5 Mi", 44564480}, + // Large testing, breaks when too much larger than + // this. + {"12.5 EB", uint64(12.5 * float64(EByte))}, + {"12.5 E", uint64(12.5 * float64(EByte))}, + {"12.5 EiB", uint64(12.5 * float64(EiByte))}, + } + + for _, p := range tests { + got, err := ParseBytes(p.in) + if err != nil { + t.Errorf("Couldn't parse %v: %v", p.in, err) + } + if got != p.exp { + t.Errorf("Expected %v for %v, got %v", + p.exp, p.in, got) + } + } +} + +func TestByteErrors(t *testing.T) { + got, err := ParseBytes("84 JB") + if err == nil { + t.Errorf("Expected error, got %v", got) + } + got, err = ParseBytes("") + if err == nil { + t.Errorf("Expected error parsing nothing") + } + got, err = ParseBytes("16 EiB") + if err == nil { + t.Errorf("Expected error, got %v", got) + } +} + +func TestBytes(t *testing.T) { + testList{ + {"bytes(0)", Bytes(0), "0B"}, + {"bytes(1)", Bytes(1), "1B"}, + {"bytes(803)", Bytes(803), "803B"}, + {"bytes(999)", Bytes(999), "999B"}, + + {"bytes(1024)", Bytes(1024), "1.0KB"}, + {"bytes(9999)", Bytes(9999), "10KB"}, + {"bytes(1MB - 1)", Bytes(MByte - Byte), "1000KB"}, + + {"bytes(1MB)", Bytes(1024 * 1024), "1.0MB"}, + {"bytes(1GB - 1K)", Bytes(GByte - KByte), "1000MB"}, + + {"bytes(1GB)", Bytes(GByte), "1.0GB"}, + {"bytes(1TB - 1M)", Bytes(TByte - MByte), "1000GB"}, + {"bytes(10MB)", Bytes(9999 * 1000), "10MB"}, + + {"bytes(1TB)", Bytes(TByte), "1.0TB"}, + {"bytes(1PB - 1T)", Bytes(PByte - TByte), "999TB"}, + + {"bytes(1PB)", Bytes(PByte), "1.0PB"}, + {"bytes(1PB - 1T)", Bytes(EByte - PByte), "999PB"}, + + {"bytes(1EB)", Bytes(EByte), "1.0EB"}, + // Overflows. + // {"bytes(1EB - 1P)", Bytes((KByte*EByte)-PByte), "1023EB"}, + + {"bytes(0)", IBytes(0), "0B"}, + {"bytes(1)", IBytes(1), "1B"}, + {"bytes(803)", IBytes(803), "803B"}, + {"bytes(1023)", IBytes(1023), "1023B"}, + + {"bytes(1024)", IBytes(1024), "1.0KiB"}, + {"bytes(1MB - 1)", IBytes(MiByte - IByte), "1024KiB"}, + + {"bytes(1MB)", IBytes(1024 * 1024), "1.0MiB"}, + {"bytes(1GB - 1K)", IBytes(GiByte - KiByte), "1024MiB"}, + + {"bytes(1GB)", IBytes(GiByte), "1.0GiB"}, + {"bytes(1TB - 1M)", IBytes(TiByte - MiByte), "1024GiB"}, + + {"bytes(1TB)", IBytes(TiByte), "1.0TiB"}, + {"bytes(1PB - 1T)", IBytes(PiByte - TiByte), "1023TiB"}, + + {"bytes(1PB)", IBytes(PiByte), "1.0PiB"}, + {"bytes(1PB - 1T)", IBytes(EiByte - PiByte), "1023PiB"}, + + {"bytes(1EiB)", IBytes(EiByte), "1.0EiB"}, + // Overflows. + // {"bytes(1EB - 1P)", IBytes((KIByte*EIByte)-PiByte), "1023EB"}, + + {"bytes(5.5GiB)", IBytes(5.5 * GiByte), "5.5GiB"}, + + {"bytes(5.5GB)", Bytes(5.5 * GByte), "5.5GB"}, + }.validate(t) +} + +func BenchmarkParseBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + ParseBytes("16.5GB") + } +} + +func BenchmarkBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + Bytes(16.5 * GByte) + } +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/comma.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/comma.go new file mode 100644 index 000000000..9d0d2ed18 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/comma.go @@ -0,0 +1,101 @@ +package humanize + +import ( + "bytes" + "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 := "" + 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:len(parts)], ",") +} + +// Commaf produces a string form of the given number in base 10 with +// commas after every three orders of magnitude. +// +// e.g. Comma(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() +} + +// 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:len(parts)], ",") +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/comma_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/comma_test.go new file mode 100644 index 000000000..49040fb71 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/comma_test.go @@ -0,0 +1,134 @@ +package humanize + +import ( + "math" + "math/big" + "testing" +) + +func TestCommas(t *testing.T) { + testList{ + {"0", Comma(0), "0"}, + {"10", Comma(10), "10"}, + {"100", Comma(100), "100"}, + {"1,000", Comma(1000), "1,000"}, + {"10,000", Comma(10000), "10,000"}, + {"100,000", Comma(100000), "100,000"}, + {"10,000,000", Comma(10000000), "10,000,000"}, + {"10,100,000", Comma(10100000), "10,100,000"}, + {"10,010,000", Comma(10010000), "10,010,000"}, + {"10,001,000", Comma(10001000), "10,001,000"}, + {"123,456,789", Comma(123456789), "123,456,789"}, + {"maxint", Comma(9.223372e+18), "9,223,372,000,000,000,000"}, + {"minint", Comma(-9.223372e+18), "-9,223,372,000,000,000,000"}, + {"-123,456,789", Comma(-123456789), "-123,456,789"}, + {"-10,100,000", Comma(-10100000), "-10,100,000"}, + {"-10,010,000", Comma(-10010000), "-10,010,000"}, + {"-10,001,000", Comma(-10001000), "-10,001,000"}, + {"-10,000,000", Comma(-10000000), "-10,000,000"}, + {"-100,000", Comma(-100000), "-100,000"}, + {"-10,000", Comma(-10000), "-10,000"}, + {"-1,000", Comma(-1000), "-1,000"}, + {"-100", Comma(-100), "-100"}, + {"-10", Comma(-10), "-10"}, + }.validate(t) +} + +func TestCommafs(t *testing.T) { + testList{ + {"0", Commaf(0), "0"}, + {"10.11", Commaf(10.11), "10.11"}, + {"100", Commaf(100), "100"}, + {"1,000", Commaf(1000), "1,000"}, + {"10,000", Commaf(10000), "10,000"}, + {"100,000", Commaf(100000), "100,000"}, + {"834,142.32", Commaf(834142.32), "834,142.32"}, + {"10,000,000", Commaf(10000000), "10,000,000"}, + {"10,100,000", Commaf(10100000), "10,100,000"}, + {"10,010,000", Commaf(10010000), "10,010,000"}, + {"10,001,000", Commaf(10001000), "10,001,000"}, + {"123,456,789", Commaf(123456789), "123,456,789"}, + {"maxf64", Commaf(math.MaxFloat64), "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000"}, + {"minf64", Commaf(math.SmallestNonzeroFloat64), "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"}, + {"-123,456,789", Commaf(-123456789), "-123,456,789"}, + {"-10,100,000", Commaf(-10100000), "-10,100,000"}, + {"-10,010,000", Commaf(-10010000), "-10,010,000"}, + {"-10,001,000", Commaf(-10001000), "-10,001,000"}, + {"-10,000,000", Commaf(-10000000), "-10,000,000"}, + {"-100,000", Commaf(-100000), "-100,000"}, + {"-10,000", Commaf(-10000), "-10,000"}, + {"-1,000", Commaf(-1000), "-1,000"}, + {"-100.11", Commaf(-100.11), "-100.11"}, + {"-10", Commaf(-10), "-10"}, + }.validate(t) +} + +func BenchmarkCommas(b *testing.B) { + for i := 0; i < b.N; i++ { + Comma(1234567890) + } +} + +func BenchmarkCommaf(b *testing.B) { + for i := 0; i < b.N; i++ { + Commaf(1234567890.83584) + } +} + +func BenchmarkBigCommas(b *testing.B) { + for i := 0; i < b.N; i++ { + BigComma(big.NewInt(1234567890)) + } +} + +func bigComma(i int64) string { + return BigComma(big.NewInt(i)) +} + +func TestBigCommas(t *testing.T) { + testList{ + {"0", bigComma(0), "0"}, + {"10", bigComma(10), "10"}, + {"100", bigComma(100), "100"}, + {"1,000", bigComma(1000), "1,000"}, + {"10,000", bigComma(10000), "10,000"}, + {"100,000", bigComma(100000), "100,000"}, + {"10,000,000", bigComma(10000000), "10,000,000"}, + {"10,100,000", bigComma(10100000), "10,100,000"}, + {"10,010,000", bigComma(10010000), "10,010,000"}, + {"10,001,000", bigComma(10001000), "10,001,000"}, + {"123,456,789", bigComma(123456789), "123,456,789"}, + {"maxint", bigComma(9.223372e+18), "9,223,372,000,000,000,000"}, + {"minint", bigComma(-9.223372e+18), "-9,223,372,000,000,000,000"}, + {"-123,456,789", bigComma(-123456789), "-123,456,789"}, + {"-10,100,000", bigComma(-10100000), "-10,100,000"}, + {"-10,010,000", bigComma(-10010000), "-10,010,000"}, + {"-10,001,000", bigComma(-10001000), "-10,001,000"}, + {"-10,000,000", bigComma(-10000000), "-10,000,000"}, + {"-100,000", bigComma(-100000), "-100,000"}, + {"-10,000", bigComma(-10000), "-10,000"}, + {"-1,000", bigComma(-1000), "-1,000"}, + {"-100", bigComma(-100), "-100"}, + {"-10", bigComma(-10), "-10"}, + }.validate(t) +} + +func TestVeryBigCommas(t *testing.T) { + tests := []struct{ in, exp string }{ + { + "84889279597249724975972597249849757294578485", + "84,889,279,597,249,724,975,972,597,249,849,757,294,578,485", + }, + { + "-84889279597249724975972597249849757294578485", + "-84,889,279,597,249,724,975,972,597,249,849,757,294,578,485", + }, + } + for _, test := range tests { + n, _ := (&big.Int{}).SetString(test.in, 10) + got := BigComma(n) + if test.exp != got { + t.Errorf("Expected %q, got %q", test.exp, got) + } + } +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/common_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/common_test.go new file mode 100644 index 000000000..fc7db1516 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/common_test.go @@ -0,0 +1,18 @@ +package humanize + +import ( + "testing" +) + +type testList []struct { + name, got, exp string +} + +func (tl testList) validate(t *testing.T) { + for _, test := range tl { + if test.got != test.exp { + t.Errorf("On %v, expected '%v', but got '%v'", + test.name, test.exp, test.got) + } + } +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/ftoa.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/ftoa.go new file mode 100644 index 000000000..c76190b10 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/ftoa.go @@ -0,0 +1,23 @@ +package humanize + +import "strconv" + +func stripTrailingZeros(s string) string { + offset := len(s) - 1 + for offset > 0 { + if s[offset] == '.' { + offset-- + break + } + if s[offset] != '0' { + break + } + offset-- + } + return s[:offset+1] +} + +// Ftoa converts a float to a string with no trailing zeros. +func Ftoa(num float64) string { + return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64)) +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/ftoa_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/ftoa_test.go new file mode 100644 index 000000000..40d13bd71 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/ftoa_test.go @@ -0,0 +1,55 @@ +package humanize + +import ( + "fmt" + "regexp" + "strconv" + "testing" +) + +func TestFtoa(t *testing.T) { + testList{ + {"200", Ftoa(200), "200"}, + {"2", Ftoa(2), "2"}, + {"2.2", Ftoa(2.2), "2.2"}, + {"2.02", Ftoa(2.02), "2.02"}, + {"200.02", Ftoa(200.02), "200.02"}, + }.validate(t) +} + +func BenchmarkFtoaRegexTrailing(b *testing.B) { + trailingZerosRegex := regexp.MustCompile(`\.?0+$`) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + trailingZerosRegex.ReplaceAllString("2.00000", "") + trailingZerosRegex.ReplaceAllString("2.0000", "") + trailingZerosRegex.ReplaceAllString("2.000", "") + trailingZerosRegex.ReplaceAllString("2.00", "") + trailingZerosRegex.ReplaceAllString("2.0", "") + trailingZerosRegex.ReplaceAllString("2", "") + } +} + +func BenchmarkFtoaFunc(b *testing.B) { + for i := 0; i < b.N; i++ { + stripTrailingZeros("2.00000") + stripTrailingZeros("2.0000") + stripTrailingZeros("2.000") + stripTrailingZeros("2.00") + stripTrailingZeros("2.0") + stripTrailingZeros("2") + } +} + +func BenchmarkFmtF(b *testing.B) { + for i := 0; i < b.N; i++ { + fmt.Sprintf("%f", 2.03584) + } +} + +func BenchmarkStrconvF(b *testing.B) { + for i := 0; i < b.N; i++ { + strconv.FormatFloat(2.03584, 'f', 6, 64) + } +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/humanize.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/humanize.go new file mode 100644 index 000000000..a69540a06 --- /dev/null +++ b/Godeps/_workspace/src/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, "83MB" or +"79MiB" (whichever you prefer). +*/ +package humanize diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/ordinals.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/ordinals.go new file mode 100644 index 000000000..43d88a861 --- /dev/null +++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/dustin/go-humanize/ordinals_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/ordinals_test.go new file mode 100644 index 000000000..51d85ee7a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/ordinals_test.go @@ -0,0 +1,22 @@ +package humanize + +import ( + "testing" +) + +func TestOrdinals(t *testing.T) { + testList{ + {"0", Ordinal(0), "0th"}, + {"1", Ordinal(1), "1st"}, + {"2", Ordinal(2), "2nd"}, + {"3", Ordinal(3), "3rd"}, + {"4", Ordinal(4), "4th"}, + {"10", Ordinal(10), "10th"}, + {"11", Ordinal(11), "11th"}, + {"12", Ordinal(12), "12th"}, + {"13", Ordinal(13), "13th"}, + {"101", Ordinal(101), "101st"}, + {"102", Ordinal(102), "102nd"}, + {"103", Ordinal(103), "103rd"}, + }.validate(t) +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/si.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/si.go new file mode 100644 index 000000000..dee8b765a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/si.go @@ -0,0 +1,110 @@ +package humanize + +import ( + "errors" + "math" + "regexp" + "strconv" +) + +var siPrefixTable = map[float64]string{ + -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 +} + +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.]+)([` + 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, "" + } + exponent := math.Floor(logn(input, 10)) + exponent = math.Floor(exponent/3) * 3 + + value := input / math.Pow(10, exponent) + + // Handle special case where value is exactly 1000.0 + // Should return 1M instead of 1000k + if value == 1000.0 { + exponent += 3 + value = input / math.Pow(10, exponent) + } + + 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) -> 1MB +// e.g. SI(2.2345e-12, "F") -> 2.2345pF +func SI(input float64, unit string) string { + value, prefix := ComputeSI(input) + return Ftoa(value) + 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.2345pF) -> (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/Godeps/_workspace/src/github.com/dustin/go-humanize/si_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/si_test.go new file mode 100644 index 000000000..32fb386b5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/si_test.go @@ -0,0 +1,98 @@ +package humanize + +import ( + "math" + "testing" +) + +func TestSI(t *testing.T) { + tests := []struct { + name string + num float64 + formatted string + }{ + {"e-24", 1e-24, "1yF"}, + {"e-21", 1e-21, "1zF"}, + {"e-18", 1e-18, "1aF"}, + {"e-15", 1e-15, "1fF"}, + {"e-12", 1e-12, "1pF"}, + {"e-12", 2.2345e-12, "2.2345pF"}, + {"e-12", 2.23e-12, "2.23pF"}, + {"e-11", 2.23e-11, "22.3pF"}, + {"e-10", 2.2e-10, "220pF"}, + {"e-9", 2.2e-9, "2.2nF"}, + {"e-8", 2.2e-8, "22nF"}, + {"e-7", 2.2e-7, "220nF"}, + {"e-6", 2.2e-6, "2.2µF"}, + {"e-6", 1e-6, "1µF"}, + {"e-5", 2.2e-5, "22µF"}, + {"e-4", 2.2e-4, "220µF"}, + {"e-3", 2.2e-3, "2.2mF"}, + {"e-2", 2.2e-2, "22mF"}, + {"e-1", 2.2e-1, "220mF"}, + {"e+0", 2.2e-0, "2.2F"}, + {"e+0", 2.2, "2.2F"}, + {"e+1", 2.2e+1, "22F"}, + {"0", 0, "0F"}, + {"e+1", 22, "22F"}, + {"e+2", 2.2e+2, "220F"}, + {"e+2", 220, "220F"}, + {"e+3", 2.2e+3, "2.2kF"}, + {"e+3", 2200, "2.2kF"}, + {"e+4", 2.2e+4, "22kF"}, + {"e+4", 22000, "22kF"}, + {"e+5", 2.2e+5, "220kF"}, + {"e+6", 2.2e+6, "2.2MF"}, + {"e+6", 1e+6, "1MF"}, + {"e+7", 2.2e+7, "22MF"}, + {"e+8", 2.2e+8, "220MF"}, + {"e+9", 2.2e+9, "2.2GF"}, + {"e+10", 2.2e+10, "22GF"}, + {"e+11", 2.2e+11, "220GF"}, + {"e+12", 2.2e+12, "2.2TF"}, + {"e+15", 2.2e+15, "2.2PF"}, + {"e+18", 2.2e+18, "2.2EF"}, + {"e+21", 2.2e+21, "2.2ZF"}, + {"e+24", 2.2e+24, "2.2YF"}, + + // special case + {"1F", 1000 * 1000, "1MF"}, + {"1F", 1e6, "1MF"}, + } + + for _, test := range tests { + got := SI(test.num, "F") + if got != test.formatted { + t.Errorf("On %v (%v), got %v, wanted %v", + test.name, test.num, got, test.formatted) + } + + gotf, gotu, err := ParseSI(test.formatted) + if err != nil { + t.Errorf("Error parsing %v (%v): %v", test.name, test.formatted, err) + continue + } + + if math.Abs(1-(gotf/test.num)) > 0.01 { + t.Errorf("On %v (%v), got %v, wanted %v (±%v)", + test.name, test.formatted, gotf, test.num, + math.Abs(1-(gotf/test.num))) + } + if gotu != "F" { + t.Errorf("On %v (%v), expected unit F, got %v", + test.name, test.formatted, gotu) + } + } + + // Parse error + gotf, gotu, err := ParseSI("x1.21JW") // 1.21 jigga whats + if err == nil { + t.Errorf("Expected error on x1.21JW, got %v %v", gotf, gotu) + } +} + +func BenchmarkParseSI(b *testing.B) { + for i := 0; i < b.N; i++ { + ParseSI("2.2346ZB") + } +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/times.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/times.go new file mode 100644 index 000000000..592ebe1d6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/times.go @@ -0,0 +1,91 @@ +package humanize + +import ( + "fmt" + "math" + "sort" + "time" +) + +// Seconds-based time units +const ( + Minute = 60 + Hour = 60 * Minute + Day = 24 * 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") +} + +var magnitudes = []struct { + d int64 + format string + divby int64 +}{ + {1, "now", 1}, + {2, "1 second %s", 1}, + {Minute, "%d seconds %s", 1}, + {2 * Minute, "1 minute %s", 1}, + {Hour, "%d minutes %s", Minute}, + {2 * Hour, "1 hour %s", 1}, + {Day, "%d hours %s", 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 { + lbl := albl + diff := b.Unix() - a.Unix() + + after := a.After(b) + if after { + lbl = blbl + diff = a.Unix() - b.Unix() + } + + n := sort.Search(len(magnitudes), func(i int) bool { + return magnitudes[i].d > diff + }) + + mag := magnitudes[n] + args := []interface{}{} + escaped := false + for _, ch := range mag.format { + if escaped { + switch ch { + case '%': + 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...) +} diff --git a/Godeps/_workspace/src/github.com/dustin/go-humanize/times_test.go b/Godeps/_workspace/src/github.com/dustin/go-humanize/times_test.go new file mode 100644 index 000000000..528daa4ec --- /dev/null +++ b/Godeps/_workspace/src/github.com/dustin/go-humanize/times_test.go @@ -0,0 +1,71 @@ +package humanize + +import ( + "math" + "testing" + "time" +) + +func TestPast(t *testing.T) { + now := time.Now().Unix() + testList{ + {"now", Time(time.Unix(now, 0)), "now"}, + {"1 second ago", Time(time.Unix(now-1, 0)), "1 second ago"}, + {"12 seconds ago", Time(time.Unix(now-12, 0)), "12 seconds ago"}, + {"30 seconds ago", Time(time.Unix(now-30, 0)), "30 seconds ago"}, + {"45 seconds ago", Time(time.Unix(now-45, 0)), "45 seconds ago"}, + {"1 minute ago", Time(time.Unix(now-63, 0)), "1 minute ago"}, + {"15 minutes ago", Time(time.Unix(now-15*Minute, 0)), "15 minutes ago"}, + {"1 hour ago", Time(time.Unix(now-63*Minute, 0)), "1 hour ago"}, + {"2 hours ago", Time(time.Unix(now-2*Hour, 0)), "2 hours ago"}, + {"21 hours ago", Time(time.Unix(now-21*Hour, 0)), "21 hours ago"}, + {"1 day ago", Time(time.Unix(now-26*Hour, 0)), "1 day ago"}, + {"2 days ago", Time(time.Unix(now-49*Hour, 0)), "2 days ago"}, + {"3 days ago", Time(time.Unix(now-3*Day, 0)), "3 days ago"}, + {"1 week ago (1)", Time(time.Unix(now-7*Day, 0)), "1 week ago"}, + {"1 week ago (2)", Time(time.Unix(now-12*Day, 0)), "1 week ago"}, + {"2 weeks ago", Time(time.Unix(now-15*Day, 0)), "2 weeks ago"}, + {"1 month ago", Time(time.Unix(now-39*Day, 0)), "1 month ago"}, + {"3 months ago", Time(time.Unix(now-99*Day, 0)), "3 months ago"}, + {"1 year ago (1)", Time(time.Unix(now-365*Day, 0)), "1 year ago"}, + {"1 year ago (1)", Time(time.Unix(now-400*Day, 0)), "1 year ago"}, + {"2 years ago (1)", Time(time.Unix(now-548*Day, 0)), "2 years ago"}, + {"2 years ago (2)", Time(time.Unix(now-725*Day, 0)), "2 years ago"}, + {"2 years ago (3)", Time(time.Unix(now-800*Day, 0)), "2 years ago"}, + {"3 years ago", Time(time.Unix(now-3*Year, 0)), "3 years ago"}, + {"long ago", Time(time.Unix(now-LongTime, 0)), "a long while ago"}, + }.validate(t) +} + +func TestFuture(t *testing.T) { + now := time.Now().Unix() + testList{ + {"now", Time(time.Unix(now, 0)), "now"}, + {"1 second from now", Time(time.Unix(now+1, 0)), "1 second from now"}, + {"12 seconds from now", Time(time.Unix(now+12, 0)), "12 seconds from now"}, + {"30 seconds from now", Time(time.Unix(now+30, 0)), "30 seconds from now"}, + {"45 seconds from now", Time(time.Unix(now+45, 0)), "45 seconds from now"}, + {"15 minutes from now", Time(time.Unix(now+15*Minute, 0)), "15 minutes from now"}, + {"2 hours from now", Time(time.Unix(now+2*Hour, 0)), "2 hours from now"}, + {"21 hours from now", Time(time.Unix(now+21*Hour, 0)), "21 hours from now"}, + {"1 day from now", Time(time.Unix(now+26*Hour, 0)), "1 day from now"}, + {"2 days from now", Time(time.Unix(now+49*Hour, 0)), "2 days from now"}, + {"3 days from now", Time(time.Unix(now+3*Day, 0)), "3 days from now"}, + {"1 week from now (1)", Time(time.Unix(now+7*Day, 0)), "1 week from now"}, + {"1 week from now (2)", Time(time.Unix(now+12*Day, 0)), "1 week from now"}, + {"2 weeks from now", Time(time.Unix(now+15*Day, 0)), "2 weeks from now"}, + {"1 month from now", Time(time.Unix(now+30*Day, 0)), "1 month from now"}, + {"1 year from now", Time(time.Unix(now+365*Day, 0)), "1 year from now"}, + {"2 years from now", Time(time.Unix(now+2*Year, 0)), "2 years from now"}, + {"a while from now", Time(time.Unix(now+LongTime, 0)), "a long while from now"}, + }.validate(t) +} + +func TestRange(t *testing.T) { + start := time.Time{} + end := time.Unix(math.MaxInt64, math.MaxInt64) + x := RelTime(start, end, "ago", "from now") + if x != "a long while from now" { + t.Errorf("Expected a long while from now, got %q", x) + } +} diff --git a/main.go b/main.go index 1908c0bd1..8149c4f1d 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "time" "github.com/minio-io/cli" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/server" "github.com/minio-io/minio/pkg/utils/log" ) diff --git a/pkg/api/api_bucket_handlers.go b/pkg/api/api_bucket_handlers.go index 6bfc795a1..23958c945 100644 --- a/pkg/api/api_bucket_handlers.go +++ b/pkg/api/api_bucket_handlers.go @@ -20,7 +20,7 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/storage/drivers" "github.com/minio-io/minio/pkg/utils/log" ) diff --git a/pkg/api/api_object_handlers.go b/pkg/api/api_object_handlers.go index 508494085..9b5a48772 100644 --- a/pkg/api/api_object_handlers.go +++ b/pkg/api/api_object_handlers.go @@ -20,7 +20,7 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/storage/drivers" "github.com/minio-io/minio/pkg/utils/log" ) diff --git a/pkg/api/api_policy_handlers.go b/pkg/api/api_policy_handlers.go index 02f08e706..e4d41308c 100644 --- a/pkg/api/api_policy_handlers.go +++ b/pkg/api/api_policy_handlers.go @@ -21,7 +21,7 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/storage/drivers" "github.com/minio-io/minio/pkg/utils/log" ) diff --git a/pkg/api/api_router.go b/pkg/api/api_router.go index 66d8834d1..0e5813a52 100644 --- a/pkg/api/api_router.go +++ b/pkg/api/api_router.go @@ -21,8 +21,8 @@ import ( "net/http" router "github.com/gorilla/mux" - "github.com/minio-io/iodine" "github.com/minio-io/minio/pkg/api/config" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/storage/drivers" ) diff --git a/pkg/api/config/config.go b/pkg/api/config/config.go index 90e613b92..8c850d667 100644 --- a/pkg/api/config/config.go +++ b/pkg/api/config/config.go @@ -24,7 +24,7 @@ import ( "path" "sync" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // Config context diff --git a/pkg/api/web/web.go b/pkg/api/web/web.go index 2117fdb3e..4c8ec0a70 100644 --- a/pkg/api/web/web.go +++ b/pkg/api/web/web.go @@ -23,8 +23,8 @@ import ( "path" "github.com/gorilla/mux" - "github.com/minio-io/iodine" "github.com/minio-io/minio/pkg/api/config" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/utils/crypto/keys" "github.com/minio-io/minio/pkg/utils/log" ) diff --git a/Godeps/_workspace/src/github.com/minio-io/iodine/.gitignore b/pkg/iodine/.gitignore similarity index 100% rename from Godeps/_workspace/src/github.com/minio-io/iodine/.gitignore rename to pkg/iodine/.gitignore diff --git a/Godeps/_workspace/src/github.com/minio-io/iodine/LICENSE b/pkg/iodine/LICENSE similarity index 100% rename from Godeps/_workspace/src/github.com/minio-io/iodine/LICENSE rename to pkg/iodine/LICENSE diff --git a/Godeps/_workspace/src/github.com/minio-io/iodine/README.md b/pkg/iodine/README.md similarity index 100% rename from Godeps/_workspace/src/github.com/minio-io/iodine/README.md rename to pkg/iodine/README.md diff --git a/Godeps/_workspace/src/github.com/minio-io/iodine/iodine.go b/pkg/iodine/iodine.go similarity index 97% rename from Godeps/_workspace/src/github.com/minio-io/iodine/iodine.go rename to pkg/iodine/iodine.go index cee287f93..c37453827 100644 --- a/Godeps/_workspace/src/github.com/minio-io/iodine/iodine.go +++ b/pkg/iodine/iodine.go @@ -31,7 +31,7 @@ import ( "github.com/dustin/go-humanize" ) -// WrappedError is the iodine error which contains a pointer to the original error +// Error is the iodine error which contains a pointer to the original error // and stack traces. type Error struct { EmbeddedError error `json:"-"` @@ -93,7 +93,7 @@ func GetGlobalStateKey(k string) string { return result } -// Error - instantiate an error, turning it into an iodine error. +// New - instantiate an error, turning it into an iodine error. // Adds an initial stack trace. func New(err error, data map[string]string) error { if err != nil { diff --git a/Godeps/_workspace/src/github.com/minio-io/iodine/iodine_test.go b/pkg/iodine/iodine_test.go similarity index 100% rename from Godeps/_workspace/src/github.com/minio-io/iodine/iodine_test.go rename to pkg/iodine/iodine_test.go diff --git a/pkg/server/server.go b/pkg/server/server.go index f68bc82dd..faa54f333 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -24,9 +24,9 @@ import ( "errors" - "github.com/minio-io/iodine" "github.com/minio-io/minio/pkg/api" "github.com/minio-io/minio/pkg/api/web" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/server/httpserver" "github.com/minio-io/minio/pkg/storage/drivers" "github.com/minio-io/minio/pkg/storage/drivers/donut" diff --git a/pkg/storage/donut/donut.go b/pkg/storage/donut/donut.go index 89e8b60dc..e9888516f 100644 --- a/pkg/storage/donut/donut.go +++ b/pkg/storage/donut/donut.go @@ -19,7 +19,7 @@ package donut import ( "errors" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // donut struct internal data diff --git a/pkg/storage/donut/donut_bucket.go b/pkg/storage/donut/donut_bucket.go index f824bf46c..5566cc83d 100644 --- a/pkg/storage/donut/donut_bucket.go +++ b/pkg/storage/donut/donut_bucket.go @@ -29,7 +29,7 @@ import ( "crypto/md5" "encoding/hex" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // internal struct carrying bucket specific information diff --git a/pkg/storage/donut/donut_bucket_internal.go b/pkg/storage/donut/donut_bucket_internal.go index aa78d35f3..95f3e5704 100644 --- a/pkg/storage/donut/donut_bucket_internal.go +++ b/pkg/storage/donut/donut_bucket_internal.go @@ -29,7 +29,7 @@ import ( "strconv" "strings" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/utils/split" ) diff --git a/pkg/storage/donut/donut_disk.go b/pkg/storage/donut/donut_disk.go index 47f8b5f87..447910b28 100644 --- a/pkg/storage/donut/donut_disk.go +++ b/pkg/storage/donut/donut_disk.go @@ -24,7 +24,7 @@ import ( "io/ioutil" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // internal disk struct diff --git a/pkg/storage/donut/donut_encoder.go b/pkg/storage/donut/donut_encoder.go index fe549c57b..f4977e023 100644 --- a/pkg/storage/donut/donut_encoder.go +++ b/pkg/storage/donut/donut_encoder.go @@ -20,8 +20,8 @@ import ( "errors" "strconv" - "github.com/minio-io/iodine" encoding "github.com/minio-io/minio/pkg/erasure" + "github.com/minio-io/minio/pkg/iodine" ) // encoder internal struct diff --git a/pkg/storage/donut/donut_node.go b/pkg/storage/donut/donut_node.go index 3b69b638c..bc4316522 100644 --- a/pkg/storage/donut/donut_node.go +++ b/pkg/storage/donut/donut_node.go @@ -19,7 +19,7 @@ package donut import ( "errors" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // node struct internal diff --git a/pkg/storage/donut/donut_rebalance.go b/pkg/storage/donut/donut_rebalance.go index 391b8bc57..0bddfce2a 100644 --- a/pkg/storage/donut/donut_rebalance.go +++ b/pkg/storage/donut/donut_rebalance.go @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // Rebalance - diff --git a/pkg/storage/donut/management.go b/pkg/storage/donut/management.go index 653b2cddb..56b3bfcc3 100644 --- a/pkg/storage/donut/management.go +++ b/pkg/storage/donut/management.go @@ -5,7 +5,7 @@ import ( "errors" "path" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // Heal - heal a donut and fix bad data blocks diff --git a/pkg/storage/donut/objectstorage.go b/pkg/storage/donut/objectstorage.go index 110da763b..58ef79684 100644 --- a/pkg/storage/donut/objectstorage.go +++ b/pkg/storage/donut/objectstorage.go @@ -24,7 +24,7 @@ import ( "strings" "time" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) // MakeBucket - make a new bucket diff --git a/pkg/storage/donut/objectstorage_internal.go b/pkg/storage/donut/objectstorage_internal.go index e4712e8c1..d679df606 100644 --- a/pkg/storage/donut/objectstorage_internal.go +++ b/pkg/storage/donut/objectstorage_internal.go @@ -22,7 +22,7 @@ import ( "path" "strings" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" ) func (d donut) makeDonutBucket(bucketName string) error { diff --git a/pkg/storage/drivers/donut/donut.go b/pkg/storage/drivers/donut/donut.go index c3207c30b..a8886dd56 100644 --- a/pkg/storage/drivers/donut/donut.go +++ b/pkg/storage/drivers/donut/donut.go @@ -30,7 +30,7 @@ import ( "io/ioutil" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/storage/donut" "github.com/minio-io/minio/pkg/storage/drivers" "github.com/minio-io/minio/pkg/utils/log" diff --git a/pkg/storage/drivers/mocks/Driver.go b/pkg/storage/drivers/mocks/Driver.go index 1d9bd9ebf..28e081ee5 100644 --- a/pkg/storage/drivers/mocks/Driver.go +++ b/pkg/storage/drivers/mocks/Driver.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/minio-io/iodine" + "github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/storage/drivers" "github.com/stretchr/testify/mock" )