diff --git a/units/size.go b/units/size.go index 99c8800..480ec2f 100644 --- a/units/size.go +++ b/units/size.go @@ -21,6 +21,42 @@ func HumanSize(size int64) string { return fmt.Sprintf("%.4g %s", sizef, units[i]) } +// FromHumanSize returns an integer from a human-readable specification of a size +// using SI standard (eg. "44kB", "17MB") +func FromHumanSize(size string) (int64, error) { + re, error := regexp.Compile("^(\\d+)([kKmMgGtTpP])?[bB]?$") + if error != nil { + return -1, fmt.Errorf("%s does not specify not a size", size) + } + + matches := re.FindStringSubmatch(size) + + if len(matches) != 3 { + return -1, fmt.Errorf("Invalid size: '%s'", size) + } + + theSize, error := strconv.ParseInt(matches[1], 10, 0) + if error != nil { + return -1, error + } + + unit := strings.ToLower(matches[2]) + + if unit == "k" { + theSize *= 1000 + } else if unit == "m" { + theSize *= 1000 * 1000 + } else if unit == "g" { + theSize *= 1000 * 1000 * 1000 + } else if unit == "t" { + theSize *= 1000 * 1000 * 1000 * 1000 + } else if unit == "p" { + theSize *= 1000 * 1000 * 1000 * 1000 * 1000 + } + + return theSize, nil +} + // Parses a human-readable string representing an amount of RAM // in bytes, kibibytes, mebibytes or gibibytes, and returns the // number of bytes, or -1 if the string is unparseable. diff --git a/units/size_test.go b/units/size_test.go index 958a4ca..5240bbd 100644 --- a/units/size_test.go +++ b/units/size_test.go @@ -20,6 +20,41 @@ func TestHumanSize(t *testing.T) { } } +func TestFromHumanSize(t *testing.T) { + assertFromHumanSize(t, "32", false, 32) + assertFromHumanSize(t, "32b", false, 32) + assertFromHumanSize(t, "32B", false, 32) + assertFromHumanSize(t, "32k", false, 32*1000) + assertFromHumanSize(t, "32K", false, 32*1000) + assertFromHumanSize(t, "32kb", false, 32*1000) + assertFromHumanSize(t, "32Kb", false, 32*1000) + assertFromHumanSize(t, "32Mb", false, 32*1000*1000) + assertFromHumanSize(t, "32Gb", false, 32*1000*1000*1000) + assertFromHumanSize(t, "32Tb", false, 32*1000*1000*1000*1000) + assertFromHumanSize(t, "8Pb", false, 8*1000*1000*1000*1000*1000) + + assertFromHumanSize(t, "", true, -1) + assertFromHumanSize(t, "hello", true, -1) + assertFromHumanSize(t, "-32", true, -1) + assertFromHumanSize(t, " 32 ", true, -1) + assertFromHumanSize(t, "32 mb", true, -1) + assertFromHumanSize(t, "32m b", true, -1) + assertFromHumanSize(t, "32bm", true, -1) +} + +func assertFromHumanSize(t *testing.T, size string, expectError bool, expectedBytes int64) { + actualBytes, err := FromHumanSize(size) + if (err != nil) && !expectError { + t.Errorf("Unexpected error parsing '%s': %s", size, err) + } + if (err == nil) && expectError { + t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes) + } + if actualBytes != expectedBytes { + t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes) + } +} + func TestRAMInBytes(t *testing.T) { assertRAMInBytes(t, "32", false, 32) assertRAMInBytes(t, "32b", false, 32)