Skip to content

Commit

Permalink
Merge pull request #1 from MyBlackJay/DEVELOP
Browse files Browse the repository at this point in the history
first version
  • Loading branch information
MyBlackJay authored Dec 27, 2023
2 parents 4e05531 + 245837b commit 1d656e1
Show file tree
Hide file tree
Showing 4 changed files with 406 additions and 0 deletions.
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Humansize
This library parses a human-readable format for writing data measurements in byte counts, or turns a byte count into a data measurement format string.
## Installation
```
go get github.com/MyBlackJay/humansize
```

## Example
[Usage](https://go.dev/play/p/pux9fwKARrG)
```go
package main

import (
"fmt"
"github.com/MyBlackJay/humansize"
)

func formatAndPrintKB() {
size := "100KB"

if parsing, err := humansize.Compile(size); err == nil {
fmt.Println(parsing.GetInput(), parsing.GetMeasure(), parsing.GetCompiledUInt64())
}
}

func formatAndPrintMiB() {
size := "1MiB"

if parsing, err := humansize.Compile(size); err == nil {
fmt.Println(parsing.GetInput(), parsing.GetMeasure(), parsing.GetCompiledUInt64())
}
}

func MustCompileMiWithError() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()

size := "1MR"

parsing := humansize.MustCompile(size)
fmt.Println(parsing.GetInput(), parsing.GetMeasure(), parsing.GetCompiledUInt64())
}

func validateMeasureAndPrint() {
measure := "EiR"
fmt.Println(humansize.ValidateMeasure(measure))
}

func TurnBytesIntoToSizeAndPrint() {
size := 2.596 * float64(1<<60)
if res, err := humansize.BytesToSize(size, 10); err == nil {
fmt.Println(res)
}
}

func main() {
formatAndPrintKB() // 100KB 1024 102400
formatAndPrintMiB() // 1MiB 1048576 1048576
MustCompileMiWithError() // unsupported data size format
validateMeasureAndPrint() // false
TurnBytesIntoToSizeAndPrint() // 2.5960000000EB
}
```

3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/MyBlackJay/humansize

go 1.21
137 changes: 137 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package humansize

import (
"errors"
"math"
"math/big"
"regexp"
"strconv"
"strings"
)

const (
measurePattern string = `^([bB]|[bB]ytes|[kmgtpeKMGTPE]|[kmgtpeKMGTPE]?[iI]|[kmgtpeKMGTPE][iI]?[bB])?$`
sizePattern string = `^([0-9]+|[0-9]*\.[0-9]+)([bB]|[bB]ytes|[kmgtpeKMGTPE]|[kmgtpeKMGTPE]?[iI]|[kmgtpeKMGTPE][iI]?[bB])?$`
)

var defaultMeasure = []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}

// ReadableSize is the representation of a compiled data size expression.
type ReadableSize struct {
input string
measure int64
compiled *big.Int
}

// GetInput returns original data size expression.
func (rs *ReadableSize) GetInput() string {
return rs.input
}

// GetMeasure returns the compiled data units in uint64.
func (rs *ReadableSize) GetMeasure() int64 {
return rs.measure
}

// Get returns the compiled data size in big.Int.
func (rs *ReadableSize) Get() big.Int {
return *rs.compiled
}

// GetCompiledUInt64 returns the compiled data size in uint64.
// Warning: Possible rounding overflow, use with relatively small numbers.
func (rs *ReadableSize) GetCompiledUInt64() uint64 {
return rs.compiled.Uint64()
}

// compileMeasuring returns a numeric representation of a data unit in int64.
// See the constant for the allowed options.
func compileMeasuring(measure string) int64 {
multiplier := int64(1)

if measure == "" {
return multiplier
}

switch strings.ToLower(string(measure[0])) {
case "k":
multiplier = 1 << 10
case "m":
multiplier = 1 << 20
case "g":
multiplier = 1 << 30
case "t":
multiplier = 1 << 40
case "p":
multiplier = 1 << 50
case "e":
multiplier = 1 << 60
}

return multiplier
}

// Compile parses a data size expression and returns, if successful, a ReadableSize object.
// For example: 100MB.
func Compile(input string) (*ReadableSize, error) {
parser := regexp.MustCompile(sizePattern)

if matches := parser.FindStringSubmatch(input); len(matches) == 3 {
if sz, err := strconv.ParseFloat(matches[1], 64); err == nil {
measure := compileMeasuring(matches[2])
result, _ := big.NewFloat(sz).Mul(big.NewFloat(sz), big.NewFloat(float64(measure))).Int(new(big.Int))

return &ReadableSize{
input: input,
measure: measure,
compiled: result,
}, nil
}
}

return nil, errors.New("unsupported data size format")
}

// MustCompile parses a data size expression and returns, if successful,
// a ReadableSize object or returns panic, if an error is found.
// For example: 100MB.
func MustCompile(input string) *ReadableSize {
res, err := Compile(input)

if err != nil {
panic(err)
}

return res
}

// ValidateMeasure parses a data size measure and returns true or false.
func ValidateMeasure(format string) bool {
if format == "" || !regexp.MustCompile(measurePattern).MatchString(format) {
return false
}

return true
}

// BytesToSize parses a number and returns a string of data size format.
// For example: 100MB.
func BytesToSize(size float64, precision uint) (string, error) {
rounder := func() float64 {
ratio := math.Pow(10, float64(precision))
return math.Round(size*ratio) / ratio
}

if size == 0 {
return "0B", nil
}

for i, v := range defaultMeasure {
if size < 1024 || i == len(defaultMeasure)-1 {
return strconv.FormatFloat(rounder(), 'f', int(precision), 64) + v, nil
}
size /= 1 << 10
}

return "", errors.New("unable convert")
}
Loading

0 comments on commit 1d656e1

Please sign in to comment.