Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
30 changes: 30 additions & 0 deletions size_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func TestFromHumanSize(t *testing.T) {
assertError(t, FromHumanSize, "hello")
assertError(t, FromHumanSize, "-32")
assertError(t, FromHumanSize, ".3kB")
assertError(t, FromHumanSize, "32.3.3kB")
assertError(t, FromHumanSize, " 32 ")
assertError(t, FromHumanSize, "32m b")
assertError(t, FromHumanSize, "32bm")
Expand Down Expand Up @@ -137,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