github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/countsuffix.go (about) 1 package fs 2 3 // CountSuffix is parsed by flag with k/M/G decimal suffixes 4 import ( 5 "errors" 6 "fmt" 7 "math" 8 "sort" 9 "strconv" 10 "strings" 11 ) 12 13 // CountSuffix is an int64 with a friendly way of printing setting 14 type CountSuffix int64 15 16 // Common multipliers for SizeSuffix 17 const ( 18 CountSuffixBase CountSuffix = 1 19 Kilo = 1000 * CountSuffixBase 20 Mega = 1000 * Kilo 21 Giga = 1000 * Mega 22 Tera = 1000 * Giga 23 Peta = 1000 * Tera 24 Exa = 1000 * Peta 25 ) 26 const ( 27 // CountSuffixMax is the largest CountSuffix multiplier 28 CountSuffixMax = Exa 29 // CountSuffixMaxValue is the largest value that can be used to create CountSuffix 30 CountSuffixMaxValue = math.MaxInt64 31 // CountSuffixMinValue is the smallest value that can be used to create CountSuffix 32 CountSuffixMinValue = math.MinInt64 33 ) 34 35 // Turn CountSuffix into a string and a suffix 36 func (x CountSuffix) string() (string, string) { 37 scaled := float64(0) 38 suffix := "" 39 switch { 40 case x < 0: 41 return "off", "" 42 case x == 0: 43 return "0", "" 44 case x < Kilo: 45 scaled = float64(x) 46 suffix = "" 47 case x < Mega: 48 scaled = float64(x) / float64(Kilo) 49 suffix = "k" 50 case x < Giga: 51 scaled = float64(x) / float64(Mega) 52 suffix = "M" 53 case x < Tera: 54 scaled = float64(x) / float64(Giga) 55 suffix = "G" 56 case x < Peta: 57 scaled = float64(x) / float64(Tera) 58 suffix = "T" 59 case x < Exa: 60 scaled = float64(x) / float64(Peta) 61 suffix = "P" 62 default: 63 scaled = float64(x) / float64(Exa) 64 suffix = "E" 65 } 66 if math.Floor(scaled) == scaled { 67 return fmt.Sprintf("%.0f", scaled), suffix 68 } 69 return fmt.Sprintf("%.3f", scaled), suffix 70 } 71 72 // String turns CountSuffix into a string 73 func (x CountSuffix) String() string { 74 val, suffix := x.string() 75 return val + suffix 76 } 77 78 // Unit turns CountSuffix into a string with a unit 79 func (x CountSuffix) Unit(unit string) string { 80 val, suffix := x.string() 81 if val == "off" { 82 return val 83 } 84 var suffixUnit string 85 if suffix != "" && unit != "" { 86 suffixUnit = suffix + unit 87 } else { 88 suffixUnit = suffix + unit 89 } 90 return val + " " + suffixUnit 91 } 92 93 func (x *CountSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) { 94 switch s { 95 case 'k', 'K': 96 return true, float64(Kilo) 97 case 'm', 'M': 98 return true, float64(Mega) 99 case 'g', 'G': 100 return true, float64(Giga) 101 case 't', 'T': 102 return true, float64(Tera) 103 case 'p', 'P': 104 return true, float64(Peta) 105 case 'e', 'E': 106 return true, float64(Exa) 107 default: 108 return false, float64(CountSuffixBase) 109 } 110 } 111 112 // Set a CountSuffix 113 func (x *CountSuffix) Set(s string) error { 114 if len(s) == 0 { 115 return errors.New("empty string") 116 } 117 if strings.ToLower(s) == "off" { 118 *x = -1 119 return nil 120 } 121 suffix := s[len(s)-1] 122 suffixLen := 1 123 multiplierFound := false 124 var multiplier float64 125 switch suffix { 126 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': 127 suffixLen = 0 128 multiplier = float64(Kilo) 129 case 'b', 'B': 130 if len(s) > 1 { 131 suffix = s[len(s)-2] 132 if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound { 133 suffixLen = 2 134 } 135 } else { 136 multiplier = float64(CountSuffixBase) 137 } 138 default: 139 if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound { 140 return fmt.Errorf("bad suffix %q", suffix) 141 } 142 } 143 s = s[:len(s)-suffixLen] 144 value, err := strconv.ParseFloat(s, 64) 145 if err != nil { 146 return err 147 } 148 if value < 0 { 149 return fmt.Errorf("size can't be negative %q", s) 150 } 151 value *= multiplier 152 *x = CountSuffix(value) 153 return nil 154 } 155 156 // Type of the value 157 func (x CountSuffix) Type() string { 158 return "CountSuffix" 159 } 160 161 // Scan implements the fmt.Scanner interface 162 func (x *CountSuffix) Scan(s fmt.ScanState, ch rune) error { 163 token, err := s.Token(true, nil) 164 if err != nil { 165 return err 166 } 167 return x.Set(string(token)) 168 } 169 170 // CountSuffixList is a slice CountSuffix values 171 type CountSuffixList []CountSuffix 172 173 func (l CountSuffixList) Len() int { return len(l) } 174 func (l CountSuffixList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 175 func (l CountSuffixList) Less(i, j int) bool { return l[i] < l[j] } 176 177 // Sort sorts the list 178 func (l CountSuffixList) Sort() { 179 sort.Sort(l) 180 } 181 182 // UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON 183 func (x *CountSuffix) UnmarshalJSON(in []byte) error { 184 return UnmarshalJSONFlag(in, x, func(i int64) error { 185 *x = CountSuffix(i) 186 return nil 187 }) 188 }