khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 1 | package humanize |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "math" |
| 6 | "strconv" |
| 7 | "strings" |
| 8 | "unicode" |
| 9 | ) |
| 10 | |
| 11 | // IEC Sizes. |
| 12 | // kibis of bits |
| 13 | const ( |
| 14 | Byte = 1 << (iota * 10) |
| 15 | KiByte |
| 16 | MiByte |
| 17 | GiByte |
| 18 | TiByte |
| 19 | PiByte |
| 20 | EiByte |
| 21 | ) |
| 22 | |
| 23 | // SI Sizes. |
| 24 | const ( |
| 25 | IByte = 1 |
| 26 | KByte = IByte * 1000 |
| 27 | MByte = KByte * 1000 |
| 28 | GByte = MByte * 1000 |
| 29 | TByte = GByte * 1000 |
| 30 | PByte = TByte * 1000 |
| 31 | EByte = PByte * 1000 |
| 32 | ) |
| 33 | |
| 34 | var bytesSizeTable = map[string]uint64{ |
| 35 | "b": Byte, |
| 36 | "kib": KiByte, |
| 37 | "kb": KByte, |
| 38 | "mib": MiByte, |
| 39 | "mb": MByte, |
| 40 | "gib": GiByte, |
| 41 | "gb": GByte, |
| 42 | "tib": TiByte, |
| 43 | "tb": TByte, |
| 44 | "pib": PiByte, |
| 45 | "pb": PByte, |
| 46 | "eib": EiByte, |
| 47 | "eb": EByte, |
| 48 | // Without suffix |
| 49 | "": Byte, |
| 50 | "ki": KiByte, |
| 51 | "k": KByte, |
| 52 | "mi": MiByte, |
| 53 | "m": MByte, |
| 54 | "gi": GiByte, |
| 55 | "g": GByte, |
| 56 | "ti": TiByte, |
| 57 | "t": TByte, |
| 58 | "pi": PiByte, |
| 59 | "p": PByte, |
| 60 | "ei": EiByte, |
| 61 | "e": EByte, |
| 62 | } |
| 63 | |
| 64 | func logn(n, b float64) float64 { |
| 65 | return math.Log(n) / math.Log(b) |
| 66 | } |
| 67 | |
| 68 | func humanateBytes(s uint64, base float64, sizes []string) string { |
| 69 | if s < 10 { |
| 70 | return fmt.Sprintf("%d B", s) |
| 71 | } |
| 72 | e := math.Floor(logn(float64(s), base)) |
| 73 | suffix := sizes[int(e)] |
| 74 | val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 |
| 75 | f := "%.0f %s" |
| 76 | if val < 10 { |
| 77 | f = "%.1f %s" |
| 78 | } |
| 79 | |
| 80 | return fmt.Sprintf(f, val, suffix) |
| 81 | } |
| 82 | |
| 83 | // Bytes produces a human readable representation of an SI size. |
| 84 | // |
| 85 | // See also: ParseBytes. |
| 86 | // |
| 87 | // Bytes(82854982) -> 83 MB |
| 88 | func Bytes(s uint64) string { |
| 89 | sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} |
| 90 | return humanateBytes(s, 1000, sizes) |
| 91 | } |
| 92 | |
| 93 | // IBytes produces a human readable representation of an IEC size. |
| 94 | // |
| 95 | // See also: ParseBytes. |
| 96 | // |
| 97 | // IBytes(82854982) -> 79 MiB |
| 98 | func IBytes(s uint64) string { |
| 99 | sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} |
| 100 | return humanateBytes(s, 1024, sizes) |
| 101 | } |
| 102 | |
| 103 | // ParseBytes parses a string representation of bytes into the number |
| 104 | // of bytes it represents. |
| 105 | // |
| 106 | // See Also: Bytes, IBytes. |
| 107 | // |
| 108 | // ParseBytes("42 MB") -> 42000000, nil |
| 109 | // ParseBytes("42 mib") -> 44040192, nil |
| 110 | func ParseBytes(s string) (uint64, error) { |
| 111 | lastDigit := 0 |
| 112 | hasComma := false |
| 113 | for _, r := range s { |
| 114 | if !(unicode.IsDigit(r) || r == '.' || r == ',') { |
| 115 | break |
| 116 | } |
| 117 | if r == ',' { |
| 118 | hasComma = true |
| 119 | } |
| 120 | lastDigit++ |
| 121 | } |
| 122 | |
| 123 | num := s[:lastDigit] |
| 124 | if hasComma { |
| 125 | num = strings.Replace(num, ",", "", -1) |
| 126 | } |
| 127 | |
| 128 | f, err := strconv.ParseFloat(num, 64) |
| 129 | if err != nil { |
| 130 | return 0, err |
| 131 | } |
| 132 | |
| 133 | extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) |
| 134 | if m, ok := bytesSizeTable[extra]; ok { |
| 135 | f *= float64(m) |
| 136 | if f >= math.MaxUint64 { |
| 137 | return 0, fmt.Errorf("too large: %v", s) |
| 138 | } |
| 139 | return uint64(f), nil |
| 140 | } |
| 141 | |
| 142 | return 0, fmt.Errorf("unhandled size name: %v", extra) |
| 143 | } |