Skip to content

Commit d403ec6

Browse files
committed
Add FromSize method
go-units API is missing a method which would parse "32kB" as 32000 bytes, and "32kiB" as 32768 bytes. FromHumanSize() parses both as 32000 bytes, while RAMInBytes() parses both as 32768 bytes. This commit introduces a FromSize method a more litteral parsing of the unit is needed. Modifiers without a unit ('32k') will be assumed to be using a decimal unit, so they'll equivalent to 32 kB/32000 bytes. This fixes #31
1 parent 27b1dec commit d403ec6

2 files changed

Lines changed: 69 additions & 6 deletions

File tree

size.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type unitMap map[string]int64
3131
var (
3232
decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
3333
binaryMap = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
34-
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)?) ?([kKmMgGtTpP])?[iI]?[bB]?$`)
34+
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)?) ?([kKmMgGtTpP])?([iI])?[bB]?$`)
3535
)
3636

3737
var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
@@ -76,30 +76,64 @@ func BytesSize(size float64) string {
7676
// FromHumanSize returns an integer from a human-readable specification of a
7777
// size using SI standard (eg. "44kB", "17MB").
7878
func FromHumanSize(size string) (int64, error) {
79-
return parseSize(size, decimalMap)
79+
return parseSize(size, ForceDecimal)
8080
}
8181

8282
// RAMInBytes parses a human-readable string representing an amount of RAM
8383
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
8484
// returns the number of bytes, or -1 if the string is unparseable.
8585
// Units are case-insensitive, and the 'b' suffix is optional.
8686
func RAMInBytes(size string) (int64, error) {
87-
return parseSize(size, binaryMap)
87+
return parseSize(size, ForceBinary)
8888
}
8989

90+
// FromSize returns an integer from a specification of a
91+
// size using either SI standard (eg. "44kB", "17MB") or
92+
// binary standard (eg. "37kiB", "97MiB")
93+
func FromSize(size string) (int64, error) {
94+
return parseSize(size, Auto)
95+
}
96+
97+
type parsingMode int
98+
99+
const (
100+
Auto parsingMode = iota
101+
ForceBinary
102+
ForceDecimal
103+
)
104+
90105
// Parses the human-readable size string into the amount it represents.
91-
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
106+
func parseSize(sizeStr string, mode parsingMode) (int64, error) {
92107
matches := sizeRegex.FindStringSubmatch(sizeStr)
93-
if len(matches) != 4 {
108+
if len(matches) != 5 {
94109
return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
95110
}
96-
97111
size, err := strconv.ParseFloat(matches[1], 64)
98112
if err != nil {
99113
return -1, err
100114
}
101115

102116
unitPrefix := strings.ToLower(matches[3])
117+
118+
var uMap unitMap
119+
switch mode {
120+
case ForceBinary:
121+
uMap = binaryMap
122+
case ForceDecimal:
123+
uMap = decimalMap
124+
case Auto:
125+
fallthrough
126+
default:
127+
if mode == Auto {
128+
fmt.Printf("size: %s i: %s\n", sizeStr, matches[4])
129+
}
130+
if matches[4] != "" {
131+
uMap = binaryMap
132+
} else {
133+
uMap = decimalMap
134+
}
135+
}
136+
103137
if mul, ok := uMap[unitPrefix]; ok {
104138
size *= float64(mul)
105139
}

size_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,35 @@ func TestRAMInBytes(t *testing.T) {
138138
assertError(t, RAMInBytes, "32bm")
139139
}
140140

141+
func TestFromSize(t *testing.T) {
142+
assertSuccessEquals(t, 32, FromSize, "32")
143+
assertSuccessEquals(t, 32, FromSize, "32b")
144+
assertSuccessEquals(t, 32, FromSize, "32B")
145+
assertSuccessEquals(t, 32*KB, FromSize, "32k")
146+
assertSuccessEquals(t, 32*KB, FromSize, "32K")
147+
assertSuccessEquals(t, 32*KB, FromSize, "32kb")
148+
assertSuccessEquals(t, 32*KB, FromSize, "32Kb")
149+
assertSuccessEquals(t, 32*KiB, FromSize, "32Kib")
150+
assertSuccessEquals(t, 32*KiB, FromSize, "32KIB")
151+
assertSuccessEquals(t, 32*MB, FromSize, "32Mb")
152+
assertSuccessEquals(t, 32*GB, FromSize, "32Gb")
153+
assertSuccessEquals(t, 32*TB, FromSize, "32Tb")
154+
assertSuccessEquals(t, 32*PB, FromSize, "32Pb")
155+
assertSuccessEquals(t, 32*PB, FromSize, "32PB")
156+
assertSuccessEquals(t, 32*PB, FromSize, "32P")
157+
158+
assertSuccessEquals(t, 32, FromSize, "32.3")
159+
tmp := 32.3 * MiB
160+
assertSuccessEquals(t, int64(tmp), FromSize, "32.3 MiB")
161+
162+
assertError(t, FromSize, "")
163+
assertError(t, FromSize, "hello")
164+
assertError(t, FromSize, "-32")
165+
assertError(t, FromSize, " 32 ")
166+
assertError(t, FromSize, "32m b")
167+
assertError(t, FromSize, "32bm")
168+
}
169+
141170
func assertEquals(t *testing.T, expected, actual interface{}) {
142171
if expected != actual {
143172
t.Errorf("Expected '%v' but got '%v'", expected, actual)

0 commit comments

Comments
 (0)