gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

bytesize.go (7499B)


      1 package bytesize
      2 
      3 import (
      4 	"errors"
      5 	"math/bits"
      6 	_ "strconv"
      7 	"unsafe"
      8 )
      9 
     10 const (
     11 	// SI units
     12 	KB Size = 1e3
     13 	MB Size = 1e6
     14 	GB Size = 1e9
     15 	TB Size = 1e12
     16 	PB Size = 1e15
     17 	EB Size = 1e18
     18 
     19 	// IEC units
     20 	KiB Size = 1024
     21 	MiB Size = KiB * 1024
     22 	GiB Size = MiB * 1024
     23 	TiB Size = GiB * 1024
     24 	PiB Size = TiB * 1024
     25 	EiB Size = PiB * 1024
     26 )
     27 
     28 var (
     29 	// ErrInvalidUnit is returned when an invalid IEC/SI is provided.
     30 	ErrInvalidUnit = errors.New("bytesize: invalid unit")
     31 
     32 	// ErrInvalidFormat is returned when an invalid size value is provided.
     33 	ErrInvalidFormat = errors.New("bytesize: invalid format")
     34 
     35 	// iecpows is a precomputed table of 1024^n.
     36 	iecpows = [...]float64{
     37 		float64(KiB),
     38 		float64(MiB),
     39 		float64(GiB),
     40 		float64(TiB),
     41 		float64(PiB),
     42 		float64(EiB),
     43 	}
     44 
     45 	// sipows is a precomputed table of 1000^n.
     46 	sipows = [...]float64{
     47 		float64(KB),
     48 		float64(MB),
     49 		float64(GB),
     50 		float64(TB),
     51 		float64(PB),
     52 		float64(EB),
     53 	}
     54 
     55 	// bvals is a precomputed table of IEC unit values.
     56 	iecvals = [...]float64{
     57 		'k': float64(KiB),
     58 		'K': float64(KiB),
     59 		'M': float64(MiB),
     60 		'G': float64(GiB),
     61 		'T': float64(TiB),
     62 		'P': float64(PiB),
     63 		'E': float64(EiB),
     64 	}
     65 
     66 	// sivals is a precomputed table of SI unit values.
     67 	sivals = [...]float64{
     68 		// ASCII numbers _aren't_ valid SI unit values,
     69 		// BUT if the space containing a possible unit
     70 		// char is checked with this table -- it is valid
     71 		// to provide no unit char so unit=1 works.
     72 		'0': 1,
     73 		'1': 1,
     74 		'2': 1,
     75 		'3': 1,
     76 		'4': 1,
     77 		'5': 1,
     78 		'6': 1,
     79 		'7': 1,
     80 		'8': 1,
     81 		'9': 1,
     82 
     83 		'k': float64(KB),
     84 		'K': float64(KB),
     85 		'M': float64(MB),
     86 		'G': float64(GB),
     87 		'T': float64(TB),
     88 		'P': float64(PB),
     89 		'E': float64(EB),
     90 	}
     91 )
     92 
     93 // Size is a casting for uint64 types that provides formatting
     94 // methods for byte sizes in both IEC and SI units.
     95 type Size uint64
     96 
     97 // ParseSize will parse a valid Size from given string. Both IEC and SI units are supported.
     98 func ParseSize(s string) (Size, error) {
     99 	// Parse units from string
    100 	unit, l, err := parseUnit(s)
    101 	if err != nil {
    102 		return 0, err
    103 	}
    104 
    105 	// Parse remaining string as float
    106 	f, n, err := atof64(s[:l])
    107 	if err != nil || n != l {
    108 		return 0, ErrInvalidFormat
    109 	}
    110 
    111 	return Size(f * unit), nil
    112 }
    113 
    114 // Set implements flag.Value{}.
    115 func (sz *Size) Set(in string) error {
    116 	s, err := ParseSize(in)
    117 	if err != nil {
    118 		return err
    119 	}
    120 	*sz = s
    121 	return nil
    122 }
    123 
    124 // MarshalText implements encoding.TextMarshaler{}.
    125 func (sz *Size) MarshalText() ([]byte, error) {
    126 	const maxLen = 7 // max IEC string length
    127 	return sz.AppendFormatIEC(make([]byte, 0, maxLen)), nil
    128 }
    129 
    130 // UnmarshalText implements encoding.TextUnmarshaler{}.
    131 func (sz *Size) UnmarshalText(text []byte) error {
    132 	return sz.Set(*(*string)(unsafe.Pointer(&text)))
    133 }
    134 
    135 // AppendFormat defaults to using Size.AppendFormatIEC().
    136 func (sz Size) AppendFormat(dst []byte) []byte {
    137 	return sz.AppendFormatIEC(dst) // default
    138 }
    139 
    140 // AppendFormatSI will append SI formatted size to 'dst'.
    141 func (sz Size) AppendFormatSI(dst []byte) []byte {
    142 	if uint64(sz) < 1000 {
    143 		dst = itoa(dst, uint64(sz))
    144 		dst = append(dst, 'B')
    145 		return dst
    146 	} // above is fast-path, .appendFormat() is outlined
    147 	return sz.appendFormat(dst, 1000, &sipows, "B")
    148 }
    149 
    150 // AppendFormatIEC will append IEC formatted size to 'dst'.
    151 func (sz Size) AppendFormatIEC(dst []byte) []byte {
    152 	if uint64(sz) < 1024 {
    153 		dst = itoa(dst, uint64(sz))
    154 		dst = append(dst, 'B')
    155 		return dst
    156 	} // above is fast-path, .appendFormat() is outlined
    157 	return sz.appendFormat(dst, 1024, &iecpows, "iB")
    158 }
    159 
    160 // appendFormat will append formatted Size to 'dst', depending on base, powers table and single unit suffix.
    161 func (sz Size) appendFormat(dst []byte, base uint64, pows *[6]float64, sunit string) []byte {
    162 	const (
    163 		// min "small" unit threshold
    164 		min = 0.75
    165 
    166 		// binary unit chars.
    167 		units = `kMGTPE`
    168 	)
    169 
    170 	// Larger number: get value of
    171 	// i / unit size. We have a 'min'
    172 	// threshold after which we prefer
    173 	// using the unit 1 down
    174 	n := bits.Len64(uint64(sz)) / 10
    175 	f := float64(sz) / pows[n-1]
    176 	if f < min {
    177 		f *= float64(base)
    178 		n--
    179 	}
    180 
    181 	// Append formatted float with units
    182 	dst = ftoa(dst, f)
    183 	dst = append(dst, units[n-1])
    184 	dst = append(dst, sunit...)
    185 	return dst
    186 }
    187 
    188 // StringSI returns an SI unit string format of Size.
    189 func (sz Size) StringSI() string {
    190 	const maxLen = 6 // max SI string length
    191 	b := sz.AppendFormatSI(make([]byte, 0, maxLen))
    192 	return *(*string)(unsafe.Pointer(&b))
    193 }
    194 
    195 // StringIEC returns an IEC unit string format of Size.
    196 func (sz Size) StringIEC() string {
    197 	const maxLen = 7 // max IEC string length
    198 	b := sz.AppendFormatIEC(make([]byte, 0, maxLen))
    199 	return *(*string)(unsafe.Pointer(&b))
    200 }
    201 
    202 // String returns a string format of Size, defaults to IEC unit format.
    203 func (sz Size) String() string {
    204 	return sz.StringIEC()
    205 }
    206 
    207 // parseUnit will parse the byte size unit from string 's'.
    208 func parseUnit(s string) (float64, int, error) {
    209 	// Check for string
    210 	if len(s) < 1 {
    211 		return 0, 0, ErrInvalidFormat
    212 	}
    213 
    214 	// Strip 'byte' unit suffix
    215 	if l := len(s) - 1; s[l] == 'B' {
    216 		s = s[:l]
    217 
    218 		if len(s) < 1 {
    219 			// No remaining str before unit suffix
    220 			return 0, 0, ErrInvalidFormat
    221 		}
    222 	}
    223 
    224 	// Strip IEC binary unit suffix
    225 	if l := len(s) - 1; s[l] == 'i' {
    226 		s = s[:l]
    227 
    228 		if len(s) < 1 {
    229 			// No remaining str before unit suffix
    230 			return 0, 0, ErrInvalidFormat
    231 		}
    232 
    233 		// Location of unit char.
    234 		l := len(s) - 1
    235 		c := int(s[l])
    236 
    237 		// Check valid unit char was provided
    238 		if len(iecvals) < c || iecvals[c] == 0 {
    239 			return 0, 0, ErrInvalidUnit
    240 		}
    241 
    242 		// Return parsed IEC unit size
    243 		return iecvals[c], l, nil
    244 	}
    245 
    246 	// Location of unit char.
    247 	l := len(s) - 1
    248 	c := int(s[l])
    249 
    250 	switch {
    251 	// Check valid unit char provided
    252 	case len(sivals) < c || sivals[c] == 0:
    253 		return 0, 0, ErrInvalidUnit
    254 
    255 	// No unit char (only ascii number)
    256 	case sivals[c] == 1:
    257 		l++
    258 	}
    259 
    260 	// Return parsed SI unit size
    261 	return sivals[c], l, nil
    262 }
    263 
    264 // ftoa appends string formatted 'f' to 'dst', assumed < ~800.
    265 func ftoa(dst []byte, f float64) []byte {
    266 	switch i := uint64(f); {
    267 	// Append with 2 d.p.
    268 	case i < 10:
    269 		f *= 10
    270 
    271 		// Calculate next dec. value
    272 		d1 := uint8(uint64(f) % 10)
    273 
    274 		f *= 10
    275 
    276 		// Calculate next dec. value
    277 		d2 := uint8(uint64(f) % 10)
    278 
    279 		// Round the final value
    280 		if uint64(f*10)%10 > 4 {
    281 			d2++
    282 
    283 			// Overflow, incr 'd1'
    284 			if d2 == 10 {
    285 				d2 = 0
    286 				d1++
    287 
    288 				// Overflow, incr 'i'
    289 				if d1 == 10 {
    290 					d1 = 0
    291 					i++
    292 				}
    293 			}
    294 		}
    295 
    296 		// Append decimal value
    297 		dst = itoa(dst, i)
    298 		dst = append(dst,
    299 			'.',
    300 			'0'+d1,
    301 			'0'+d2,
    302 		)
    303 
    304 	// Append with 1 d.p.
    305 	case i < 100:
    306 		f *= 10
    307 
    308 		// Calculate next dec. value
    309 		d1 := uint8(uint64(f) % 10)
    310 
    311 		// Round the final value
    312 		if uint64(f*10)%10 > 4 {
    313 			d1++
    314 
    315 			// Overflow, incr 'i'
    316 			if d1 == 10 {
    317 				d1 = 0
    318 				i++
    319 			}
    320 		}
    321 
    322 		// Append decimal value
    323 		dst = itoa(dst, i)
    324 		dst = append(dst, '.', '0'+d1)
    325 
    326 	// No decimal places
    327 	default:
    328 		dst = itoa(dst, i)
    329 	}
    330 
    331 	return dst
    332 }
    333 
    334 // itoa appends string formatted 'i' to 'dst'.
    335 func itoa(dst []byte, i uint64) []byte {
    336 	// Assemble uint in reverse order.
    337 	var b [4]byte
    338 	bp := len(b) - 1
    339 
    340 	// Append integer
    341 	for i >= 10 {
    342 		q := i / 10
    343 		b[bp] = byte('0' + i - q*10)
    344 		bp--
    345 		i = q
    346 	} // i < 10
    347 	b[bp] = byte('0' + i)
    348 
    349 	return append(dst, b[bp:]...)
    350 }
    351 
    352 // We use the following internal strconv function usually
    353 // used internally to parse float values, as we know that
    354 // are value passed will always be of 64bit type, and knowing
    355 // the returned float string length is very helpful!
    356 //
    357 //go:linkname atof64 strconv.atof64
    358 func atof64(string) (float64, int, error)