mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-14 01:32:27 +08:00
feat: add feature for humanize byte unit
This commit is contained in:
178
formatter/byte.go
Normal file
178
formatter/byte.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package formatter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// code logic come from:
|
||||||
|
// https://github.com/docker/go-units/blob/master/size.go
|
||||||
|
|
||||||
|
// http://en.wikipedia.org/wiki/Binary_prefix
|
||||||
|
const (
|
||||||
|
// Decimal
|
||||||
|
UnitB = 1
|
||||||
|
UnitKB = 1000
|
||||||
|
UnitMB = 1000 * UnitKB
|
||||||
|
UnitGB = 1000 * UnitMB
|
||||||
|
UnitTB = 1000 * UnitGB
|
||||||
|
UnitPB = 1000 * UnitTB
|
||||||
|
UnitEB = 1000 * UnitPB
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
UnitBiB = 1
|
||||||
|
UnitKiB = 1024
|
||||||
|
UnitMiB = 1024 * UnitKiB
|
||||||
|
UnitGiB = 1024 * UnitMiB
|
||||||
|
UnitTiB = 1024 * UnitGiB
|
||||||
|
UnitPiB = 1024 * UnitTiB
|
||||||
|
UnitEiB = 1024 * UnitPiB
|
||||||
|
)
|
||||||
|
|
||||||
|
// type byteUnitMap map[byte]int64
|
||||||
|
|
||||||
|
var (
|
||||||
|
decimalByteMap = map[string]uint64{
|
||||||
|
"b": UnitB,
|
||||||
|
"kb": UnitKB,
|
||||||
|
"mb": UnitMB,
|
||||||
|
"gb": UnitGB,
|
||||||
|
"tb": UnitTB,
|
||||||
|
"pb": UnitPB,
|
||||||
|
"eb": UnitEB,
|
||||||
|
|
||||||
|
// Without suffix
|
||||||
|
"": UnitB,
|
||||||
|
"k": UnitKB,
|
||||||
|
"m": UnitMB,
|
||||||
|
"g": UnitGB,
|
||||||
|
"t": UnitTB,
|
||||||
|
"p": UnitPB,
|
||||||
|
"e": UnitEB,
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryByteMap = map[string]uint64{
|
||||||
|
"bi": UnitBiB,
|
||||||
|
"kib": UnitKiB,
|
||||||
|
"mib": UnitMiB,
|
||||||
|
"gib": UnitGiB,
|
||||||
|
"tib": UnitTiB,
|
||||||
|
"pib": UnitPiB,
|
||||||
|
"eib": UnitEiB,
|
||||||
|
|
||||||
|
// Without suffix
|
||||||
|
"": UnitBiB,
|
||||||
|
"ki": UnitKiB,
|
||||||
|
"mi": UnitMiB,
|
||||||
|
"gi": UnitGiB,
|
||||||
|
"ti": UnitTiB,
|
||||||
|
"pi": UnitPiB,
|
||||||
|
"ei": UnitEiB,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
decimalByteUnits = []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||||
|
binaryByteUnits = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecimalBytes returns a human readable byte size under decimal standard (base 1000)
|
||||||
|
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
|
||||||
|
// Play: todo
|
||||||
|
func DecimalBytes(size float64, precision ...int) string {
|
||||||
|
p := 5
|
||||||
|
if len(precision) > 0 {
|
||||||
|
p = precision[0] + 1
|
||||||
|
}
|
||||||
|
size, unit := calculateByteSize(size, 1000.0, decimalByteUnits)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%.*g%s", p, size, unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BinaryBytes returns a human-readable byte size under decimal standard (base 1024)
|
||||||
|
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
|
||||||
|
// Play: todo
|
||||||
|
func BinaryBytes(size float64, precision ...int) string {
|
||||||
|
p := 5
|
||||||
|
if len(precision) > 0 {
|
||||||
|
p = precision[0] + 1
|
||||||
|
}
|
||||||
|
size, unit := calculateByteSize(size, 1024.0, binaryByteUnits)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%.*g%s", p, size, unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateByteSize(size float64, base float64, byteUnits []string) (float64, string) {
|
||||||
|
i := 0
|
||||||
|
unitsLimit := len(byteUnits) - 1
|
||||||
|
for size >= base && i < unitsLimit {
|
||||||
|
size = size / base
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return size, byteUnits[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDecimalBytes the human readable bytes size string into the amount it represents(base 1000).
|
||||||
|
// ParseDecimalBytes("42 MB") -> 42000000, nil
|
||||||
|
// Play: todo
|
||||||
|
func ParseDecimalBytes(size string) (uint64, error) {
|
||||||
|
return parseBytes(size, "decimal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBinaryBytes the human readable bytes size string into the amount it represents(base 1024).
|
||||||
|
// ParseBinaryBytes("42 mib") -> 44040192, nil
|
||||||
|
// Play: todo
|
||||||
|
func ParseBinaryBytes(size string) (uint64, error) {
|
||||||
|
return parseBytes(size, "binary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// see https://github.com/dustin/go-humanize/blob/master/bytes.go
|
||||||
|
func parseBytes(s string, kind 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 kind == "decimal" {
|
||||||
|
if m, ok := decimalByteMap[extra]; ok {
|
||||||
|
f *= float64(m)
|
||||||
|
if f >= math.MaxUint64 {
|
||||||
|
return 0, fmt.Errorf("too large: %v", s)
|
||||||
|
}
|
||||||
|
return uint64(f), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if m, ok := binaryByteMap[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)
|
||||||
|
}
|
||||||
87
formatter/byte_test.go
Normal file
87
formatter/byte_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package formatter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecimalBytes(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestDecimalBytes")
|
||||||
|
|
||||||
|
assert.Equal("1KB", DecimalBytes(1000))
|
||||||
|
assert.Equal("1.024KB", DecimalBytes(1024))
|
||||||
|
assert.Equal("1.2346MB", DecimalBytes(1234567))
|
||||||
|
assert.Equal("1.235MB", DecimalBytes(1234567, 3))
|
||||||
|
assert.Equal("1.123GB", DecimalBytes(float64(1.123*UnitGB)))
|
||||||
|
assert.Equal("2.123TB", DecimalBytes(float64(2.123*UnitTB)))
|
||||||
|
assert.Equal("3.123PB", DecimalBytes(float64(3.123*UnitPB)))
|
||||||
|
assert.Equal("4.123EB", DecimalBytes(float64(4.123*UnitEB)))
|
||||||
|
assert.Equal("1EB", DecimalBytes(float64(1000*UnitPB)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBinaryBytes(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestBinaryBytes")
|
||||||
|
|
||||||
|
assert.Equal("1KiB", BinaryBytes(1024))
|
||||||
|
assert.Equal("1MiB", BinaryBytes(1024*1024))
|
||||||
|
assert.Equal("1.1774MiB", BinaryBytes(1234567))
|
||||||
|
assert.Equal("1.18MiB", BinaryBytes(1234567, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDecimalBytes(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestParseDecimalBytes")
|
||||||
|
|
||||||
|
cases := map[string]uint64{
|
||||||
|
"12": uint64(12),
|
||||||
|
"12 k": uint64(12000),
|
||||||
|
"12 kb": uint64(12000),
|
||||||
|
"12kb": uint64(12000),
|
||||||
|
"12k": uint64(12000),
|
||||||
|
"12K": uint64(12000),
|
||||||
|
"12KB": uint64(12000),
|
||||||
|
"12 K": uint64(12000),
|
||||||
|
"12 KB": uint64(12000),
|
||||||
|
"12 Kb": uint64(12000),
|
||||||
|
"12 kB": uint64(12000),
|
||||||
|
"12.2 KB": uint64(12200),
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range cases {
|
||||||
|
result, err := ParseDecimalBytes(k)
|
||||||
|
assert.Equal(v, result)
|
||||||
|
assert.IsNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseDecimalBytes("12 AB")
|
||||||
|
assert.IsNotNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBinaryBytes(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestParseBinaryBytes")
|
||||||
|
|
||||||
|
cases := map[string]uint64{
|
||||||
|
"12": uint64(12),
|
||||||
|
"12 ki": uint64(12288),
|
||||||
|
"12 kib": uint64(12288),
|
||||||
|
"12kib": uint64(12288),
|
||||||
|
"12ki": uint64(12288),
|
||||||
|
"12KI": uint64(12288),
|
||||||
|
"12KIB": uint64(12288),
|
||||||
|
"12KiB": uint64(12288),
|
||||||
|
"12 Ki": uint64(12288),
|
||||||
|
"12 KiB": uint64(12288),
|
||||||
|
"12 Kib": uint64(12288),
|
||||||
|
"12 kiB": uint64(12288),
|
||||||
|
"12.2 KiB": uint64(12492),
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range cases {
|
||||||
|
result, err := ParseBinaryBytes(k)
|
||||||
|
assert.Equal(v, result)
|
||||||
|
assert.IsNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseDecimalBytes("12 AB")
|
||||||
|
assert.IsNotNil(err)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user