Skip to content

Commit

Permalink
Add FromSize method
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cfergeau committed Dec 15, 2020
1 parent 27b1dec commit dc6d94f
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 6 deletions.
43 changes: 37 additions & 6 deletions size.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type unitMap map[string]int64
var (
decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
binaryMap = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)?) ?([kKmMgGtTpP])?[iI]?[bB]?$`)
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)?) ?([kKmMgGtTpP])?([iI])?[bB]?$`)
)

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

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

// FromSize returns an integer from a specification of a
// size using either SI standard (eg. "44kB", "17MB") or
// binary standard (eg. "37kiB", "97MiB")
func FromSize(size string) (int64, error) {
return parseSize(size, Auto)
}

type parsingMode int

const (
Auto parsingMode = iota
ForceBinary
ForceDecimal
)

// Parses the human-readable size string into the amount it represents.
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
func parseSize(sizeStr string, mode parsingMode) (int64, error) {
matches := sizeRegex.FindStringSubmatch(sizeStr)
if len(matches) != 4 {
if len(matches) != 5 {
return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
}

size, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return -1, err
}

unitPrefix := strings.ToLower(matches[3])

var uMap unitMap
switch mode {
case ForceBinary:
uMap = binaryMap
case ForceDecimal:
uMap = decimalMap
case Auto:
fallthrough
default:
if matches[4] != "" {
uMap = binaryMap
} else {
uMap = decimalMap
}
}

if mul, ok := uMap[unitPrefix]; ok {
size *= float64(mul)
}
Expand Down
29 changes: 29 additions & 0 deletions size_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,35 @@ func TestRAMInBytes(t *testing.T) {
assertError(t, RAMInBytes, "32bm")
}

func TestFromSize(t *testing.T) {
assertSuccessEquals(t, 32, FromSize, "32")
assertSuccessEquals(t, 32, FromSize, "32b")
assertSuccessEquals(t, 32, FromSize, "32B")
assertSuccessEquals(t, 32*KB, FromSize, "32k")
assertSuccessEquals(t, 32*KB, FromSize, "32K")
assertSuccessEquals(t, 32*KB, FromSize, "32kb")
assertSuccessEquals(t, 32*KB, FromSize, "32Kb")
assertSuccessEquals(t, 32*KiB, FromSize, "32Kib")
assertSuccessEquals(t, 32*KiB, FromSize, "32KIB")
assertSuccessEquals(t, 32*MB, FromSize, "32Mb")
assertSuccessEquals(t, 32*GB, FromSize, "32Gb")
assertSuccessEquals(t, 32*TB, FromSize, "32Tb")
assertSuccessEquals(t, 32*PB, FromSize, "32Pb")
assertSuccessEquals(t, 32*PB, FromSize, "32PB")
assertSuccessEquals(t, 32*PB, FromSize, "32P")

assertSuccessEquals(t, 32, FromSize, "32.3")
tmp := 32.3 * MiB
assertSuccessEquals(t, int64(tmp), FromSize, "32.3 MiB")

assertError(t, FromSize, "")
assertError(t, FromSize, "hello")
assertError(t, FromSize, "-32")
assertError(t, FromSize, " 32 ")
assertError(t, FromSize, "32m b")
assertError(t, FromSize, "32bm")
}

func assertEquals(t *testing.T, expected, actual interface{}) {
if expected != actual {
t.Errorf("Expected '%v' but got '%v'", expected, actual)
Expand Down

0 comments on commit dc6d94f

Please sign in to comment.