2018-01-12 17:30:54 +01:00
|
|
|
package fs
|
|
|
|
|
|
|
|
// SizeSuffix is parsed by flag with k/M/G suffixes
|
|
|
|
import (
|
2020-12-11 18:48:09 +01:00
|
|
|
"encoding/json"
|
2018-01-12 17:30:54 +01:00
|
|
|
"fmt"
|
|
|
|
"math"
|
2018-10-09 09:42:45 +02:00
|
|
|
"sort"
|
2018-01-12 17:30:54 +01:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// SizeSuffix is an int64 with a friendly way of printing setting
|
|
|
|
type SizeSuffix int64
|
|
|
|
|
2018-07-22 11:27:34 +02:00
|
|
|
// Common multipliers for SizeSuffix
|
|
|
|
const (
|
|
|
|
Byte SizeSuffix = 1 << (iota * 10)
|
|
|
|
KibiByte
|
|
|
|
MebiByte
|
|
|
|
GibiByte
|
|
|
|
TebiByte
|
|
|
|
PebiByte
|
|
|
|
ExbiByte
|
|
|
|
)
|
|
|
|
|
2018-01-12 17:30:54 +01:00
|
|
|
// Turn SizeSuffix into a string and a suffix
|
|
|
|
func (x SizeSuffix) string() (string, string) {
|
|
|
|
scaled := float64(0)
|
|
|
|
suffix := ""
|
|
|
|
switch {
|
|
|
|
case x < 0:
|
|
|
|
return "off", ""
|
|
|
|
case x == 0:
|
|
|
|
return "0", ""
|
2018-04-17 22:47:20 +02:00
|
|
|
case x < 1<<10:
|
2018-01-12 17:30:54 +01:00
|
|
|
scaled = float64(x)
|
|
|
|
suffix = ""
|
2018-04-17 22:47:20 +02:00
|
|
|
case x < 1<<20:
|
|
|
|
scaled = float64(x) / (1 << 10)
|
2018-01-12 17:30:54 +01:00
|
|
|
suffix = "k"
|
2018-04-17 22:47:20 +02:00
|
|
|
case x < 1<<30:
|
|
|
|
scaled = float64(x) / (1 << 20)
|
2018-01-12 17:30:54 +01:00
|
|
|
suffix = "M"
|
2018-04-17 22:47:20 +02:00
|
|
|
case x < 1<<40:
|
|
|
|
scaled = float64(x) / (1 << 30)
|
2018-01-12 17:30:54 +01:00
|
|
|
suffix = "G"
|
2018-04-17 22:47:20 +02:00
|
|
|
case x < 1<<50:
|
|
|
|
scaled = float64(x) / (1 << 40)
|
|
|
|
suffix = "T"
|
|
|
|
default:
|
|
|
|
scaled = float64(x) / (1 << 50)
|
|
|
|
suffix = "P"
|
2018-01-12 17:30:54 +01:00
|
|
|
}
|
|
|
|
if math.Floor(scaled) == scaled {
|
|
|
|
return fmt.Sprintf("%.0f", scaled), suffix
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%.3f", scaled), suffix
|
|
|
|
}
|
|
|
|
|
|
|
|
// String turns SizeSuffix into a string
|
|
|
|
func (x SizeSuffix) String() string {
|
|
|
|
val, suffix := x.string()
|
|
|
|
return val + suffix
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unit turns SizeSuffix into a string with a unit
|
|
|
|
func (x SizeSuffix) Unit(unit string) string {
|
|
|
|
val, suffix := x.string()
|
|
|
|
if val == "off" {
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
return val + " " + suffix + unit
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a SizeSuffix
|
|
|
|
func (x *SizeSuffix) Set(s string) error {
|
|
|
|
if len(s) == 0 {
|
|
|
|
return errors.New("empty string")
|
|
|
|
}
|
|
|
|
if strings.ToLower(s) == "off" {
|
|
|
|
*x = -1
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
suffix := s[len(s)-1]
|
|
|
|
suffixLen := 1
|
|
|
|
var multiplier float64
|
|
|
|
switch suffix {
|
|
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
|
|
|
|
suffixLen = 0
|
|
|
|
multiplier = 1 << 10
|
|
|
|
case 'b', 'B':
|
|
|
|
multiplier = 1
|
|
|
|
case 'k', 'K':
|
|
|
|
multiplier = 1 << 10
|
|
|
|
case 'm', 'M':
|
|
|
|
multiplier = 1 << 20
|
|
|
|
case 'g', 'G':
|
|
|
|
multiplier = 1 << 30
|
2018-04-17 22:47:20 +02:00
|
|
|
case 't', 'T':
|
|
|
|
multiplier = 1 << 40
|
|
|
|
case 'p', 'P':
|
|
|
|
multiplier = 1 << 50
|
2018-01-12 17:30:54 +01:00
|
|
|
default:
|
|
|
|
return errors.Errorf("bad suffix %q", suffix)
|
|
|
|
}
|
|
|
|
s = s[:len(s)-suffixLen]
|
|
|
|
value, err := strconv.ParseFloat(s, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if value < 0 {
|
|
|
|
return errors.Errorf("size can't be negative %q", s)
|
|
|
|
}
|
|
|
|
value *= multiplier
|
|
|
|
*x = SizeSuffix(value)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type of the value
|
|
|
|
func (x *SizeSuffix) Type() string {
|
2019-02-07 12:57:26 +01:00
|
|
|
return "SizeSuffix"
|
2018-01-12 17:30:54 +01:00
|
|
|
}
|
2018-05-14 17:32:27 +02:00
|
|
|
|
|
|
|
// Scan implements the fmt.Scanner interface
|
|
|
|
func (x *SizeSuffix) Scan(s fmt.ScanState, ch rune) error {
|
|
|
|
token, err := s.Token(true, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return x.Set(string(token))
|
|
|
|
}
|
2018-10-09 09:42:45 +02:00
|
|
|
|
2019-04-30 14:06:24 +02:00
|
|
|
// SizeSuffixList is a slice SizeSuffix values
|
2018-10-09 09:42:45 +02:00
|
|
|
type SizeSuffixList []SizeSuffix
|
|
|
|
|
|
|
|
func (l SizeSuffixList) Len() int { return len(l) }
|
|
|
|
func (l SizeSuffixList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
|
|
|
func (l SizeSuffixList) Less(i, j int) bool { return l[i] < l[j] }
|
|
|
|
|
|
|
|
// Sort sorts the list
|
|
|
|
func (l SizeSuffixList) Sort() {
|
|
|
|
sort.Sort(l)
|
|
|
|
}
|
2020-12-11 18:48:09 +01:00
|
|
|
|
|
|
|
// UnmarshalJSONFlag unmarshals a JSON input for a flag. If the input
|
|
|
|
// is a string then it calls the Set method on the flag otherwise it
|
|
|
|
// calls the setInt function with a parsed int64.
|
|
|
|
func UnmarshalJSONFlag(in []byte, x interface{ Set(string) error }, setInt func(int64) error) error {
|
|
|
|
// Try to parse as string first
|
|
|
|
var s string
|
|
|
|
err := json.Unmarshal(in, &s)
|
|
|
|
if err == nil {
|
|
|
|
return x.Set(s)
|
|
|
|
}
|
|
|
|
// If that fails parse as integer
|
|
|
|
var i int64
|
|
|
|
err = json.Unmarshal(in, &i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return setInt(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
|
|
|
func (x *SizeSuffix) UnmarshalJSON(in []byte) error {
|
|
|
|
return UnmarshalJSONFlag(in, x, func(i int64) error {
|
|
|
|
*x = SizeSuffix(i)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|