github.com/saferwall/pe@v1.5.2/cmd/size.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  )
     8  
     9  // See: http://en.wikipedia.org/wiki/Binary_prefix
    10  const (
    11  	// Decimal
    12  
    13  	KB = 1000
    14  	MB = 1000 * KB
    15  	GB = 1000 * MB
    16  	TB = 1000 * GB
    17  	PB = 1000 * TB
    18  
    19  	// Binary
    20  
    21  	KiB = 1024
    22  	MiB = 1024 * KiB
    23  	GiB = 1024 * MiB
    24  	TiB = 1024 * GiB
    25  	PiB = 1024 * TiB
    26  )
    27  
    28  type unitMap map[byte]int64
    29  
    30  var (
    31  	decimalMap = unitMap{'k': KB, 'm': MB, 'g': GB, 't': TB, 'p': PB}
    32  	binaryMap  = unitMap{'k': KiB, 'm': MiB, 'g': GiB, 't': TiB, 'p': PiB}
    33  )
    34  
    35  var (
    36  	decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
    37  	binaryAbbrs  = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
    38  )
    39  
    40  func getSizeAndUnit(size float64, base float64, _map []string) (float64, string) {
    41  	i := 0
    42  	unitsLimit := len(_map) - 1
    43  	for size >= base && i < unitsLimit {
    44  		size = size / base
    45  		i++
    46  	}
    47  	return size, _map[i]
    48  }
    49  
    50  // CustomSize returns a human-readable approximation of a size
    51  // using custom format.
    52  func CustomSize(format string, size float64, base float64, _map []string) string {
    53  	size, unit := getSizeAndUnit(size, base, _map)
    54  	return fmt.Sprintf(format, size, unit)
    55  }
    56  
    57  // HumanSizeWithPrecision allows the size to be in any precision,
    58  // instead of 4 digit precision used in units.HumanSize.
    59  func HumanSizeWithPrecision(size float64, precision int) string {
    60  	size, unit := getSizeAndUnit(size, 1000.0, decimapAbbrs)
    61  	return fmt.Sprintf("%.*g%s", precision, size, unit)
    62  }
    63  
    64  // HumanSize returns a human-readable approximation of a size
    65  // capped at 4 valid numbers (eg. "2.746 MB", "796 KB").
    66  func HumanSize(size float64) string {
    67  	return HumanSizeWithPrecision(size, 4)
    68  }
    69  
    70  // BytesSize returns a human-readable size in bytes, kibibytes,
    71  // mebibytes, gibibytes, or tebibytes (eg. "44kiB", "17MiB").
    72  func BytesSize(size float64) string {
    73  	return CustomSize("%.4g%s", size, 1024.0, binaryAbbrs)
    74  }
    75  
    76  // FromHumanSize returns an integer from a human-readable specification of a
    77  // size using SI standard (eg. "44kB", "17MB").
    78  func FromHumanSize(size string) (int64, error) {
    79  	return parseSize(size, decimalMap)
    80  }
    81  
    82  // RAMInBytes parses a human-readable string representing an amount of RAM
    83  // in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
    84  // returns the number of bytes, or -1 if the string is unparseable.
    85  // Units are case-insensitive, and the 'b' suffix is optional.
    86  func RAMInBytes(size string) (int64, error) {
    87  	return parseSize(size, binaryMap)
    88  }
    89  
    90  // Parses the human-readable size string into the amount it represents.
    91  func parseSize(sizeStr string, uMap unitMap) (int64, error) {
    92  	// TODO: rewrite to use strings.Cut if there's a space
    93  	// once Go < 1.18 is deprecated.
    94  	sep := strings.LastIndexAny(sizeStr, "01234567890. ")
    95  	if sep == -1 {
    96  		// There should be at least a digit.
    97  		return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
    98  	}
    99  	var num, sfx string
   100  	if sizeStr[sep] != ' ' {
   101  		num = sizeStr[:sep+1]
   102  		sfx = sizeStr[sep+1:]
   103  	} else {
   104  		// Omit the space separator.
   105  		num = sizeStr[:sep]
   106  		sfx = sizeStr[sep+1:]
   107  	}
   108  
   109  	size, err := strconv.ParseFloat(num, 64)
   110  	if err != nil {
   111  		return -1, err
   112  	}
   113  	// Backward compatibility: reject negative sizes.
   114  	if size < 0 {
   115  		return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
   116  	}
   117  
   118  	if len(sfx) == 0 {
   119  		return int64(size), nil
   120  	}
   121  
   122  	// Process the suffix.
   123  
   124  	if len(sfx) > 3 { // Too long.
   125  		goto badSuffix
   126  	}
   127  	sfx = strings.ToLower(sfx)
   128  	// Trivial case: b suffix.
   129  	if sfx[0] == 'b' {
   130  		if len(sfx) > 1 { // no extra characters allowed after b.
   131  			goto badSuffix
   132  		}
   133  		return int64(size), nil
   134  	}
   135  	// A suffix from the map.
   136  	if mul, ok := uMap[sfx[0]]; ok {
   137  		size *= float64(mul)
   138  	} else {
   139  		goto badSuffix
   140  	}
   141  
   142  	// The suffix may have extra "b" or "ib" (e.g. KiB or MB).
   143  	switch {
   144  	case len(sfx) == 2 && sfx[1] != 'b':
   145  		goto badSuffix
   146  	case len(sfx) == 3 && sfx[1:] != "ib":
   147  		goto badSuffix
   148  	}
   149  
   150  	return int64(size), nil
   151  
   152  badSuffix:
   153  	return -1, fmt.Errorf("invalid suffix: '%s'", sfx)
   154  }