github.com/divyam234/rclone@v1.64.1/fs/sizesuffix.go (about) 1 package fs 2 3 // SizeSuffix is parsed by flag with K/M/G binary suffixes 4 import ( 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math" 9 "sort" 10 "strconv" 11 "strings" 12 ) 13 14 // SizeSuffix is an int64 with a friendly way of printing setting 15 type SizeSuffix int64 16 17 // Common multipliers for SizeSuffix 18 const ( 19 SizeSuffixBase SizeSuffix = 1 << (iota * 10) 20 Kibi 21 Mebi 22 Gibi 23 Tebi 24 Pebi 25 Exbi 26 ) 27 const ( 28 // SizeSuffixMax is the largest SizeSuffix multiplier 29 SizeSuffixMax = Exbi 30 // SizeSuffixMaxValue is the largest value that can be used to create SizeSuffix 31 SizeSuffixMaxValue = math.MaxInt64 32 // SizeSuffixMinValue is the smallest value that can be used to create SizeSuffix 33 SizeSuffixMinValue = math.MinInt64 34 ) 35 36 // Turn SizeSuffix into a string and a suffix 37 func (x SizeSuffix) string() (string, string) { 38 scaled := float64(0) 39 suffix := "" 40 switch { 41 case x < 0: 42 return "off", "" 43 case x == 0: 44 return "0", "" 45 case x < Kibi: 46 scaled = float64(x) 47 suffix = "" 48 case x < Mebi: 49 scaled = float64(x) / float64(Kibi) 50 suffix = "Ki" 51 case x < Gibi: 52 scaled = float64(x) / float64(Mebi) 53 suffix = "Mi" 54 case x < Tebi: 55 scaled = float64(x) / float64(Gibi) 56 suffix = "Gi" 57 case x < Pebi: 58 scaled = float64(x) / float64(Tebi) 59 suffix = "Ti" 60 case x < Exbi: 61 scaled = float64(x) / float64(Pebi) 62 suffix = "Pi" 63 default: 64 scaled = float64(x) / float64(Exbi) 65 suffix = "Ei" 66 } 67 if math.Floor(scaled) == scaled { 68 return fmt.Sprintf("%.0f", scaled), suffix 69 } 70 return fmt.Sprintf("%.3f", scaled), suffix 71 } 72 73 // String turns SizeSuffix into a string 74 func (x SizeSuffix) String() string { 75 val, suffix := x.string() 76 return val + suffix 77 } 78 79 // Unit turns SizeSuffix into a string with a unit 80 func (x SizeSuffix) unit(unit string) string { 81 val, suffix := x.string() 82 if val == "off" { 83 return val 84 } 85 var suffixUnit string 86 if suffix != "" && unit != "" { 87 suffixUnit = suffix + unit 88 } else { 89 suffixUnit = suffix + unit 90 } 91 return val + " " + suffixUnit 92 } 93 94 // BitUnit turns SizeSuffix into a string with bit unit 95 func (x SizeSuffix) BitUnit() string { 96 return x.unit("bit") 97 } 98 99 // BitRateUnit turns SizeSuffix into a string with bit rate unit 100 func (x SizeSuffix) BitRateUnit() string { 101 return x.unit("bit/s") 102 } 103 104 // ByteUnit turns SizeSuffix into a string with byte unit 105 func (x SizeSuffix) ByteUnit() string { 106 return x.unit("B") 107 } 108 109 // ByteRateUnit turns SizeSuffix into a string with byte rate unit 110 func (x SizeSuffix) ByteRateUnit() string { 111 return x.unit("B/s") 112 } 113 114 func (x *SizeSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) { 115 switch s { 116 case 'k', 'K': 117 return true, float64(Kibi) 118 case 'm', 'M': 119 return true, float64(Mebi) 120 case 'g', 'G': 121 return true, float64(Gibi) 122 case 't', 'T': 123 return true, float64(Tebi) 124 case 'p', 'P': 125 return true, float64(Pebi) 126 case 'e', 'E': 127 return true, float64(Exbi) 128 default: 129 return false, float64(SizeSuffixBase) 130 } 131 } 132 133 // Set a SizeSuffix 134 func (x *SizeSuffix) Set(s string) error { 135 if len(s) == 0 { 136 return errors.New("empty string") 137 } 138 if strings.ToLower(s) == "off" { 139 *x = -1 140 return nil 141 } 142 suffix := s[len(s)-1] 143 suffixLen := 1 144 multiplierFound := false 145 var multiplier float64 146 switch suffix { 147 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': 148 suffixLen = 0 149 multiplier = float64(Kibi) 150 case 'b', 'B': 151 if len(s) > 2 && s[len(s)-2] == 'i' { 152 suffix = s[len(s)-3] 153 suffixLen = 3 154 if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound { 155 return fmt.Errorf("bad suffix %q", suffix) 156 } 157 // Could also support SI form MB, and treat it equivalent to MiB, but perhaps better to reserve it for CountSuffix? 158 //} else if len(s) > 1 { 159 // suffix = s[len(s)-2] 160 // if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound { 161 // suffixLen = 2 162 // } 163 //} 164 } else { 165 multiplier = float64(SizeSuffixBase) 166 } 167 case 'i', 'I': 168 if len(s) > 1 { 169 suffix = s[len(s)-2] 170 suffixLen = 2 171 multiplierFound, multiplier = x.multiplierFromSymbol(suffix) 172 } 173 if !multiplierFound { 174 return fmt.Errorf("bad suffix %q", suffix) 175 } 176 default: 177 if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound { 178 return fmt.Errorf("bad suffix %q", suffix) 179 } 180 } 181 s = s[:len(s)-suffixLen] 182 value, err := strconv.ParseFloat(s, 64) 183 if err != nil { 184 return err 185 } 186 if value < 0 { 187 return fmt.Errorf("size can't be negative %q", s) 188 } 189 value *= multiplier 190 *x = SizeSuffix(value) 191 return nil 192 } 193 194 // Type of the value 195 func (x *SizeSuffix) Type() string { 196 return "SizeSuffix" 197 } 198 199 // Scan implements the fmt.Scanner interface 200 func (x *SizeSuffix) Scan(s fmt.ScanState, ch rune) error { 201 token, err := s.Token(true, nil) 202 if err != nil { 203 return err 204 } 205 return x.Set(string(token)) 206 } 207 208 // SizeSuffixList is a slice SizeSuffix values 209 type SizeSuffixList []SizeSuffix 210 211 func (l SizeSuffixList) Len() int { return len(l) } 212 func (l SizeSuffixList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 213 func (l SizeSuffixList) Less(i, j int) bool { return l[i] < l[j] } 214 215 // Sort sorts the list 216 func (l SizeSuffixList) Sort() { 217 sort.Sort(l) 218 } 219 220 // UnmarshalJSONFlag unmarshals a JSON input for a flag. If the input 221 // is a string then it calls the Set method on the flag otherwise it 222 // calls the setInt function with a parsed int64. 223 func UnmarshalJSONFlag(in []byte, x interface{ Set(string) error }, setInt func(int64) error) error { 224 // Try to parse as string first 225 var s string 226 err := json.Unmarshal(in, &s) 227 if err == nil { 228 return x.Set(s) 229 } 230 // If that fails parse as integer 231 var i int64 232 err = json.Unmarshal(in, &i) 233 if err != nil { 234 return err 235 } 236 return setInt(i) 237 } 238 239 // UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON 240 func (x *SizeSuffix) UnmarshalJSON(in []byte) error { 241 return UnmarshalJSONFlag(in, x, func(i int64) error { 242 *x = SizeSuffix(i) 243 return nil 244 }) 245 }