package humanize import ( "bytes" "fmt" "git.nspix.com/golang/kos/util/bs" "math" "strconv" "strings" "unicode" ) type Size uint64 // 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) } func (b *Size) UnmarshalJSON(buf []byte) error { var ( n uint64 err error ) buf = bytes.TrimFunc(buf, func(r rune) bool { if r == '"' { return true } return false }) if n, err = ParseBytes(bs.BytesToString(buf)); err == nil { *b = Size(n) } return err } func (b Size) MarshalJSON() ([]byte, error) { s := `"` + IBytes(uint64(b)) + `"` return bs.StringToBytes(s), nil } func (b Size) String() string { return IBytes(uint64(b)) }