github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/man/bytes.go (about)

     1  package man
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"strconv"
     7  	"strings"
     8  	"unicode"
     9  )
    10  
    11  // These utilities convert a size in bytes to a human-readable string in either SI (decimal) or IEC (binary) format.
    12  // https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
    13  // |     Input     | ByteCountSI | ByteCountIEC |
    14  // |---------------|-------------|--------------|
    15  // |      999      |   "999B"   |   "999B"    |
    16  // |     1000      |  "1kB"   |   "1000B"   |
    17  // |     1023      |  "1kB"   |   "1023B"   |
    18  // |     1024      |  "1kB"   |  "1KiB"   |
    19  // |  987,654,321  | "987.7MB"  | "941.9MiB"  |
    20  // | math.MaxInt64 |  "9.2EB"   |  "8EiB"   |
    21  
    22  // ByteCount produces a human readable representation of an SI size(copy go version).
    23  func ByteCount(b int64) string {
    24  	const unit = 1000
    25  	if b < unit {
    26  		return fmt.Sprintf("%dB", b)
    27  	}
    28  
    29  	div, exp := int64(unit), 0
    30  	for n := b / unit; n >= unit; n /= unit {
    31  		div *= unit
    32  		exp++
    33  	}
    34  
    35  	f := strings.TrimSuffix(fmt.Sprintf("%.1f", float64(b)/float64(div)), ".0")
    36  	return fmt.Sprintf("%s%cB", f, "kMGTPE"[exp])
    37  }
    38  
    39  // IByteCount produces a human readable representation of an IEC size(copy go version).
    40  func IByteCount(b int64) string {
    41  	const unit = 1024
    42  	if b < unit {
    43  		return fmt.Sprintf("%dB", b)
    44  	}
    45  
    46  	div, exp := int64(unit), 0
    47  	for n := b / unit; n >= unit; n /= unit {
    48  		div *= unit
    49  		exp++
    50  	}
    51  
    52  	f := strings.TrimSuffix(fmt.Sprintf("%.1f", float64(b)/float64(div)), ".0")
    53  	return fmt.Sprintf("%s%ciB", f, "KMGTPE"[exp])
    54  }
    55  
    56  // IEC Sizes.
    57  // kibis of bits
    58  const (
    59  	Byte = 1 << (iota * 10)
    60  	KiByte
    61  	MiByte
    62  	GiByte
    63  	TiByte
    64  	PiByte
    65  	EiByte
    66  )
    67  
    68  // SI Sizes.
    69  const (
    70  	IByte = 1
    71  	KByte = IByte * 1000
    72  	MByte = KByte * 1000
    73  	GByte = MByte * 1000
    74  	TByte = GByte * 1000
    75  	PByte = TByte * 1000
    76  	EByte = PByte * 1000
    77  )
    78  
    79  var bytesSizeTable = map[string]uint64{
    80  	"b": Byte, "kib": KiByte, "kb": KByte, "mib": MiByte, "mb": MByte, "gib": GiByte, "gb": GByte,
    81  	"tib": TiByte, "tb": TByte, "pib": PiByte, "pb": PByte, "eib": EiByte, "eb": EByte,
    82  	// Without suffix
    83  	"": Byte, "ki": KiByte, "k": KByte, "mi": MiByte, "m": MByte, "gi": GiByte, "g": GByte,
    84  	"ti": TiByte, "t": TByte, "pi": PiByte, "p": PByte, "ei": EiByte, "e": EByte,
    85  }
    86  
    87  func logn(n, b float64) float64 {
    88  	return math.Log(n) / math.Log(b)
    89  }
    90  
    91  func humanateBytes(s uint64, base float64, sizes []string) string {
    92  	if s < 10 {
    93  		return fmt.Sprintf("%dB", s)
    94  	}
    95  	e := math.Floor(logn(float64(s), base))
    96  	suffix := sizes[int(e)]
    97  	val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
    98  	f := strings.TrimSuffix(fmt.Sprintf("%.1f", val), ".0")
    99  	return f + suffix
   100  }
   101  
   102  // Bytes produces a human readable representation of an SI size.
   103  //
   104  // See also: ParseBytes.
   105  //
   106  // Bytes(82854982) -> 83MB
   107  func Bytes(s uint64) string {
   108  	sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
   109  	return humanateBytes(s, 1000, sizes)
   110  }
   111  
   112  // IBytes produces a human readable representation of an IEC size.
   113  //
   114  // See also: ParseBytes.
   115  //
   116  // IBytes(82854982) -> 79MiB
   117  func IBytes(s uint64) string {
   118  	sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
   119  	return humanateBytes(s, 1024, sizes)
   120  }
   121  
   122  // ParseBytes parses a string representation of bytes into the number
   123  // of bytes it represents.
   124  //
   125  // See Also: Bytes, IBytes.
   126  //
   127  // ParseBytes("42 MB") -> 42000000, nil
   128  // ParseBytes("42 mib") -> 44040192, nil
   129  func ParseBytes(s string) (uint64, error) {
   130  	lastDigit := 0
   131  	hasComma := false
   132  	for _, r := range s {
   133  		if !(unicode.IsDigit(r) || r == '.' || r == ',') {
   134  			break
   135  		}
   136  		if r == ',' {
   137  			hasComma = true
   138  		}
   139  		lastDigit++
   140  	}
   141  
   142  	num := s[:lastDigit]
   143  	if hasComma {
   144  		num = strings.Replace(num, ",", "", -1)
   145  	}
   146  
   147  	f, err := strconv.ParseFloat(num, 64)
   148  	if err != nil {
   149  		return 0, err
   150  	}
   151  
   152  	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
   153  	if m, ok := bytesSizeTable[extra]; ok {
   154  		f *= float64(m)
   155  		if f >= math.MaxUint64 {
   156  			return 0, fmt.Errorf("too large: %v", s)
   157  		}
   158  		return uint64(f), nil
   159  	}
   160  
   161  	return 0, fmt.Errorf("unhandled size name: %v", extra)
   162  }