github.com/git-lfs/git-lfs@v2.5.2+incompatible/tools/humanize/humanize.go (about)

     1  package humanize
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  	"unicode"
    10  
    11  	"github.com/git-lfs/git-lfs/errors"
    12  )
    13  
    14  const (
    15  	Byte = 1 << (iota * 10)
    16  	Kibibyte
    17  	Mebibyte
    18  	Gibibyte
    19  	Tebibyte
    20  	Pebibyte
    21  
    22  	Kilobyte = 1000 * Byte
    23  	Megabyte = 1000 * Kilobyte
    24  	Gigabyte = 1000 * Megabyte
    25  	Terabyte = 1000 * Gigabyte
    26  	Petabyte = 1000 * Terabyte
    27  
    28  	// eps is the machine epsilon, or a 64-bit floating point value
    29  	// reasonably close to zero.
    30  	eps float64 = 7.0/3 - 4.0/3 - 1.0
    31  )
    32  
    33  var bytesTable = map[string]uint64{
    34  	"":  Byte,
    35  	"b": Byte,
    36  
    37  	"kib": Kibibyte,
    38  	"mib": Mebibyte,
    39  	"gib": Gibibyte,
    40  	"tib": Tebibyte,
    41  	"pib": Pebibyte,
    42  
    43  	"kb": Kilobyte,
    44  	"mb": Megabyte,
    45  	"gb": Gigabyte,
    46  	"tb": Terabyte,
    47  	"pb": Petabyte,
    48  }
    49  
    50  // ParseBytes parses a given human-readable bytes or ibytes string into a number
    51  // of bytes, or an error if the string was unable to be parsed.
    52  func ParseBytes(str string) (uint64, error) {
    53  	var sep int
    54  	for _, r := range str {
    55  		if !(unicode.IsDigit(r) || r == '.' || r == ',') {
    56  			break
    57  		}
    58  
    59  		sep = sep + 1
    60  	}
    61  
    62  	var f float64
    63  
    64  	if s := strings.Replace(str[:sep], ",", "", -1); len(s) > 0 {
    65  		var err error
    66  
    67  		f, err = strconv.ParseFloat(s, 64)
    68  		if err != nil {
    69  			return 0, err
    70  		}
    71  	}
    72  
    73  	m, err := ParseByteUnit(str[sep:])
    74  	if err != nil {
    75  		return 0, err
    76  	}
    77  
    78  	f = f * float64(m)
    79  	if f >= math.MaxUint64 {
    80  		return 0, errors.New("number of bytes too large")
    81  	}
    82  	return uint64(f), nil
    83  }
    84  
    85  // ParseByteUnit returns the number of bytes in a given unit of storage, or an
    86  // error, if that unit is unrecognized.
    87  func ParseByteUnit(str string) (uint64, error) {
    88  	str = strings.TrimSpace(str)
    89  	str = strings.ToLower(str)
    90  
    91  	if u, ok := bytesTable[str]; ok {
    92  		return u, nil
    93  	}
    94  	return 0, errors.Errorf("unknown unit: %q", str)
    95  }
    96  
    97  var sizes = []string{"B", "KB", "MB", "GB", "TB", "PB"}
    98  
    99  // FormatBytes outputs the given number of bytes "s" as a human-readable string,
   100  // rounding to the nearest half within .01.
   101  func FormatBytes(s uint64) string {
   102  	var e float64
   103  	if s == 0 {
   104  		e = 0
   105  	} else {
   106  		e = math.Floor(log(float64(s), 1000))
   107  	}
   108  
   109  	unit := uint64(math.Pow(1000, e))
   110  	suffix := sizes[int(e)]
   111  
   112  	return fmt.Sprintf("%s %s",
   113  		FormatBytesUnit(s, unit), suffix)
   114  }
   115  
   116  // FormatBytesUnit outputs the given number of bytes "s" as a quantity of the
   117  // given units "u" to the nearest half within .01.
   118  func FormatBytesUnit(s, u uint64) string {
   119  	var rounded float64
   120  	if s < 10 {
   121  		rounded = float64(s)
   122  	} else {
   123  		rounded = math.Floor(float64(s)/float64(u)*10+.5) / 10
   124  	}
   125  
   126  	format := "%.0f"
   127  	if rounded < 10 && u > 1 {
   128  		format = "%.1f"
   129  	}
   130  
   131  	return fmt.Sprintf(format, rounded)
   132  }
   133  
   134  // FormatByteRate outputs the given rate of transfer "r" as the quotient of "s"
   135  // (the number of bytes transferred) over "d" (the duration of time that those
   136  // bytes were transferred in).
   137  //
   138  // It displays the output as a quantity of a "per-unit-time" unit (i.e., B/s,
   139  // MiB/s) in the most representative fashion possible, as above.
   140  func FormatByteRate(s uint64, d time.Duration) string {
   141  	// e is the index of the most representative unit of storage.
   142  	var e float64
   143  
   144  	// f is the floating-point equivalent of "s", so as to avoid more
   145  	// conversions than necessary.
   146  	f := float64(s)
   147  
   148  	if f != 0 {
   149  		f = f / d.Seconds()
   150  		e = math.Floor(log(f, 1000))
   151  		if e <= eps {
   152  			// The result of math.Floor(log(r, 1000)) can be
   153  			// "close-enough" to zero that it should be effectively
   154  			// considered zero.
   155  			e = 0
   156  		}
   157  	}
   158  
   159  	unit := uint64(math.Pow(1000, e))
   160  	suffix := sizes[int(e)]
   161  
   162  	return fmt.Sprintf("%s %s/s",
   163  		FormatBytesUnit(uint64(math.Ceil(f)), unit), suffix)
   164  }
   165  
   166  // log takes the log base "b" of "n" (\log_b{n})
   167  func log(n, b float64) float64 {
   168  	return math.Log(n) / math.Log(b)
   169  }