size.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package units
  2. import (
  3. "fmt"
  4. "strconv"
  5. "strings"
  6. )
  7. // See: http://en.wikipedia.org/wiki/Binary_prefix
  8. const (
  9. // Decimal
  10. KB = 1000
  11. MB = 1000 * KB
  12. GB = 1000 * MB
  13. TB = 1000 * GB
  14. PB = 1000 * TB
  15. // Binary
  16. KiB = 1024
  17. MiB = 1024 * KiB
  18. GiB = 1024 * MiB
  19. TiB = 1024 * GiB
  20. PiB = 1024 * TiB
  21. )
  22. type unitMap map[byte]int64
  23. var (
  24. decimalMap = unitMap{'k': KB, 'm': MB, 'g': GB, 't': TB, 'p': PB}
  25. binaryMap = unitMap{'k': KiB, 'm': MiB, 'g': GiB, 't': TiB, 'p': PiB}
  26. )
  27. var (
  28. decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
  29. binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
  30. )
  31. func getSizeAndUnit(size float64, base float64, _map []string) (float64, string) {
  32. i := 0
  33. unitsLimit := len(_map) - 1
  34. for size >= base && i < unitsLimit {
  35. size = size / base
  36. i++
  37. }
  38. return size, _map[i]
  39. }
  40. // CustomSize returns a human-readable approximation of a size
  41. // using custom format.
  42. func CustomSize(format string, size float64, base float64, _map []string) string {
  43. size, unit := getSizeAndUnit(size, base, _map)
  44. return fmt.Sprintf(format, size, unit)
  45. }
  46. // HumanSizeWithPrecision allows the size to be in any precision,
  47. // instead of 4 digit precision used in units.HumanSize.
  48. func HumanSizeWithPrecision(size float64, precision int) string {
  49. size, unit := getSizeAndUnit(size, 1000.0, decimapAbbrs)
  50. return fmt.Sprintf("%.*g%s", precision, size, unit)
  51. }
  52. // HumanSize returns a human-readable approximation of a size
  53. // capped at 4 valid numbers (eg. "2.746 MB", "796 KB").
  54. func HumanSize(size float64) string {
  55. return HumanSizeWithPrecision(size, 4)
  56. }
  57. // BytesSize returns a human-readable size in bytes, kibibytes,
  58. // mebibytes, gibibytes, or tebibytes (eg. "44kiB", "17MiB").
  59. func BytesSize(size float64) string {
  60. return CustomSize("%.4g%s", size, 1024.0, binaryAbbrs)
  61. }
  62. // FromHumanSize returns an integer from a human-readable specification of a
  63. // size using SI standard (eg. "44kB", "17MB").
  64. func FromHumanSize(size string) (int64, error) {
  65. return parseSize(size, decimalMap)
  66. }
  67. // RAMInBytes parses a human-readable string representing an amount of RAM
  68. // in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
  69. // returns the number of bytes, or -1 if the string is unparseable.
  70. // Units are case-insensitive, and the 'b' suffix is optional.
  71. func RAMInBytes(size string) (int64, error) {
  72. return parseSize(size, binaryMap)
  73. }
  74. // Parses the human-readable size string into the amount it represents.
  75. func parseSize(sizeStr string, uMap unitMap) (int64, error) {
  76. // TODO: rewrite to use strings.Cut if there's a space
  77. // once Go < 1.18 is deprecated.
  78. sep := strings.LastIndexAny(sizeStr, "01234567890. ")
  79. if sep == -1 {
  80. // There should be at least a digit.
  81. return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
  82. }
  83. var num, sfx string
  84. if sizeStr[sep] != ' ' {
  85. num = sizeStr[:sep+1]
  86. sfx = sizeStr[sep+1:]
  87. } else {
  88. // Omit the space separator.
  89. num = sizeStr[:sep]
  90. sfx = sizeStr[sep+1:]
  91. }
  92. size, err := strconv.ParseFloat(num, 64)
  93. if err != nil {
  94. return -1, err
  95. }
  96. // Backward compatibility: reject negative sizes.
  97. if size < 0 {
  98. return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
  99. }
  100. if len(sfx) == 0 {
  101. return int64(size), nil
  102. }
  103. // Process the suffix.
  104. if len(sfx) > 3 { // Too long.
  105. goto badSuffix
  106. }
  107. sfx = strings.ToLower(sfx)
  108. // Trivial case: b suffix.
  109. if sfx[0] == 'b' {
  110. if len(sfx) > 1 { // no extra characters allowed after b.
  111. goto badSuffix
  112. }
  113. return int64(size), nil
  114. }
  115. // A suffix from the map.
  116. if mul, ok := uMap[sfx[0]]; ok {
  117. size *= float64(mul)
  118. } else {
  119. goto badSuffix
  120. }
  121. // The suffix may have extra "b" or "ib" (e.g. KiB or MB).
  122. switch {
  123. case len(sfx) == 2 && sfx[1] != 'b':
  124. goto badSuffix
  125. case len(sfx) == 3 && sfx[1:] != "ib":
  126. goto badSuffix
  127. }
  128. return int64(size), nil
  129. badSuffix:
  130. return -1, fmt.Errorf("invalid suffix: '%s'", sfx)
  131. }