You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
2.5 KiB

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