Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FromSize method #37

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
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