pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/fmtutil/fmtutil.go (about) 1 // Package fmtutil provides methods for output formatting 2 package fmtutil 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "bytes" 13 "fmt" 14 "math" 15 "strconv" 16 "strings" 17 18 "pkg.re/essentialkaos/ek.v12/ansi" 19 "pkg.re/essentialkaos/ek.v12/mathutil" 20 "pkg.re/essentialkaos/ek.v12/strutil" 21 ) 22 23 // ////////////////////////////////////////////////////////////////////////////////// // 24 25 const ( 26 _KILO = 1024 27 _MEGA = 1048576 28 _GIGA = 1073741824 29 _TERA = 1099511627776 30 ) 31 32 // ////////////////////////////////////////////////////////////////////////////////// // 33 34 type wrapper struct { 35 Indent string 36 MaxLineLength int 37 38 result bytes.Buffer 39 line bytes.Buffer 40 lineLen int 41 } 42 43 // ////////////////////////////////////////////////////////////////////////////////// // 44 45 // OrderSeparator is a default order separator 46 var OrderSeparator = "," 47 48 // SizeSeparator is a default size separator 49 var SizeSeparator = "" 50 51 // ////////////////////////////////////////////////////////////////////////////////// // 52 53 // PrettyNum formats number to "pretty" form (e.g 1234567 -> 1,234,567) 54 func PrettyNum(i interface{}, separator ...string) string { 55 var str string 56 57 sep := OrderSeparator 58 59 if len(separator) > 0 { 60 sep = separator[0] 61 } 62 63 switch v := i.(type) { 64 case int, int32, int64, uint, uint32, uint64: 65 str = fmt.Sprintf("%d", v) 66 67 return appendPrettySymbol(str, sep) 68 69 case float32, float64: 70 str = fmt.Sprintf("%.3f", v) 71 72 if str == "NaN" { 73 return "0" 74 } 75 76 return formatPrettyFloat(str, sep) 77 } 78 79 // Return value for unsupported types as is 80 return fmt.Sprintf("%v", i) 81 } 82 83 // PrettyDiff formats number to "pretty" form with + or - symbol at the begining 84 func PrettyDiff(i int, separator ...string) string { 85 if i > 0 { 86 return "+" + PrettyNum(i, separator...) 87 } 88 89 return PrettyNum(i, separator...) 90 } 91 92 // PrettyNum formats float value to "pretty" percent form (e.g 12.3423 -> 12.3%) 93 func PrettyPerc(i float64) string { 94 i = Float(i) 95 96 if i < 0.01 { 97 return "< 0.01%" 98 } 99 100 return PrettyNum(i) + "%" 101 } 102 103 // PrettySize formats value to "pretty" size (e.g 1478182 -> 1.34 Mb) 104 func PrettySize(i interface{}, separator ...string) string { 105 var f float64 106 107 sep := SizeSeparator 108 109 if len(separator) > 0 { 110 sep = separator[0] 111 } 112 113 switch u := i.(type) { 114 case int: 115 f = float64(u) 116 case int32: 117 f = float64(u) 118 case int64: 119 f = float64(u) 120 case uint: 121 f = float64(u) 122 case uint32: 123 f = float64(u) 124 case uint64: 125 f = float64(u) 126 case float32: 127 f = float64(u) 128 case float64: 129 f = i.(float64) 130 } 131 132 if math.IsNaN(f) { 133 return "0" + sep + "B" 134 } 135 136 switch { 137 case f >= _TERA: 138 return fmt.Sprintf("%g", Float(f/_TERA)) + sep + "TB" 139 case f >= _GIGA: 140 return fmt.Sprintf("%g", Float(f/_GIGA)) + sep + "GB" 141 case f >= _MEGA: 142 return fmt.Sprintf("%g", Float(f/_MEGA)) + sep + "MB" 143 case f >= _KILO: 144 return fmt.Sprintf("%g", Float(f/_KILO)) + sep + "KB" 145 default: 146 return fmt.Sprintf("%g", mathutil.Round(f, 0)) + sep + "B" 147 } 148 } 149 150 // PrettyBool formats boolean to "pretty" form (e.g true/false -> Y/N) 151 func PrettyBool(b bool, vals ...string) string { 152 switch { 153 case b && len(vals) >= 1: 154 return vals[0] 155 case !b && len(vals) >= 2: 156 return vals[1] 157 case b: 158 return "Y" 159 } 160 161 return "N" 162 } 163 164 // ParseSize parses size and return it in bytes (e.g 1.34 Mb -> 1478182) 165 func ParseSize(size string) uint64 { 166 ns := strings.ToLower(strings.Replace(size, " ", "", -1)) 167 mlt, sfx := extractSizeInfo(ns) 168 169 if sfx == "" { 170 num, err := strconv.ParseUint(size, 10, 64) 171 172 if err != nil { 173 return 0 174 } 175 176 return num 177 } 178 179 ns = strings.TrimRight(ns, sfx) 180 numFlt, err := strconv.ParseFloat(ns, 64) 181 182 if err != nil { 183 return 0 184 } 185 186 return uint64(numFlt * float64(mlt)) 187 } 188 189 // Float formats float numbers more nicely 190 func Float(f float64) float64 { 191 if math.IsNaN(f) { 192 return 0.0 193 } 194 195 if f < 10.0 { 196 return mathutil.Round(f, 2) 197 } 198 199 return mathutil.Round(f, 1) 200 } 201 202 // Wrap wraps text using max line length 203 func Wrap(text, indent string, maxLineLength int) string { 204 var word bytes.Buffer 205 var isNewLine bool 206 207 w := &wrapper{ 208 Indent: indent, 209 MaxLineLength: maxLineLength, 210 } 211 212 reader := strings.NewReader(text) 213 214 for i := int64(0); i < reader.Size(); i++ { 215 r, _, _ := reader.ReadRune() 216 217 switch r { 218 case ' ': 219 // break 220 case '\n', '\r': 221 if !isNewLine { 222 isNewLine = true 223 w.AddWord(word) 224 word.Reset() 225 } else { 226 w.NewLine() 227 } 228 default: 229 isNewLine = false 230 word.WriteRune(r) 231 continue 232 } 233 234 w.AddWord(word) 235 word.Reset() 236 } 237 238 w.AddWord(word) 239 word.Reset() 240 241 return w.Result() 242 } 243 244 // ColorizePassword adds different fmtc color tags for numbers and letters 245 func ColorizePassword(password, letterTag, numTag, specialTag string) string { 246 var result, curTag, prevTag string 247 248 prevTag = "-" 249 250 for _, r := range password { 251 switch { 252 case r >= 48 && r <= 57: 253 curTag = numTag 254 case r >= 91 && r <= 96: 255 curTag = specialTag 256 case r >= 65 && r <= 122: 257 curTag = letterTag 258 default: 259 curTag = specialTag 260 } 261 262 if curTag != prevTag { 263 if curTag == "" { 264 result += "{!}" + string(r) 265 } else { 266 result += curTag + string(r) 267 } 268 prevTag = curTag 269 } else { 270 result += string(r) 271 } 272 } 273 274 return result + "{!}" 275 } 276 277 // CountDigits returns number of digits in integer 278 func CountDigits(i int) int { 279 if i < 0 { 280 return int(math.Log10(math.Abs(float64(i)))) + 2 281 } 282 283 return int(math.Log10(float64(i))) + 1 284 } 285 286 // ////////////////////////////////////////////////////////////////////////////////// // 287 288 func formatPrettyFloat(str, sep string) string { 289 flt := strings.TrimRight(strutil.ReadField(str, 1, false, "."), "0") 290 291 if flt == "" { 292 return appendPrettySymbol(strutil.ReadField(str, 0, false, "."), sep) 293 } 294 295 return appendPrettySymbol(strutil.ReadField(str, 0, false, "."), sep) + "." + flt 296 } 297 298 func appendPrettySymbol(str, sep string) string { 299 if len(str) < 3 { 300 return str 301 } 302 303 var b strings.Builder 304 305 if str[0] == '-' { 306 b.WriteRune('-') 307 str = str[1:] 308 } 309 310 if len(str)%3 == 0 { 311 b.Grow(len(str) + (len(str) / 3) - 1) 312 } else { 313 b.Grow(len(str) + len(str)/3) 314 b.WriteString(str[:(len(str) % 3)]) 315 b.WriteString(sep) 316 } 317 318 for i := len(str) % 3; i < len(str); i++ { 319 b.WriteByte(str[i]) 320 if (1+i-len(str)%3)%3 == 0 && i+1 != len(str) { 321 b.WriteString(sep) 322 } 323 } 324 325 return b.String() 326 } 327 328 func extractSizeInfo(s string) (uint64, string) { 329 var mlt uint64 330 var sfx string 331 332 switch { 333 case strings.HasSuffix(s, "tb"): 334 mlt = 1024 * 1024 * 1024 * 1024 335 sfx = "tb" 336 case strings.HasSuffix(s, "t"): 337 mlt = 1000 * 1000 * 1000 * 1000 338 sfx = "t" 339 case strings.HasSuffix(s, "gb"): 340 mlt = 1024 * 1024 * 1024 341 sfx = "gb" 342 case strings.HasSuffix(s, "g"): 343 mlt = 1000 * 1000 * 1000 344 sfx = "g" 345 case strings.HasSuffix(s, "mb"): 346 mlt = 1024 * 1024 347 sfx = "mb" 348 case strings.HasSuffix(s, "m"): 349 mlt = 1000 * 1000 350 sfx = "m" 351 case strings.HasSuffix(s, "kb"): 352 mlt = 1024 353 sfx = "kb" 354 case strings.HasSuffix(s, "k"): 355 mlt = 1000 356 sfx = "k" 357 case strings.HasSuffix(s, "b"): 358 mlt = 1 359 sfx = "b" 360 } 361 362 return mlt, sfx 363 } 364 365 // ////////////////////////////////////////////////////////////////////////////////// // 366 367 // AddWord appends word to the line 368 func (w *wrapper) AddWord(word bytes.Buffer) { 369 if word.Len() == 0 { 370 return 371 } 372 373 var wordLen int 374 375 if ansi.HasCodesBytes(word.Bytes()) { 376 wordLen = len(ansi.RemoveCodesBytes(word.Bytes())) 377 } else { 378 wordLen = len(word.String()) 379 } 380 381 if w.line.Len() != 0 && len(w.Indent)+w.lineLen+wordLen > w.MaxLineLength { 382 w.result.WriteString(w.Indent) 383 w.result.Write(w.line.Bytes()) 384 w.result.WriteRune('\n') 385 w.line.Reset() 386 w.lineLen = wordLen + 1 387 } else { 388 w.lineLen += wordLen + 1 389 if w.line.Len() != 0 { 390 w.line.WriteRune(' ') 391 } 392 } 393 394 w.line.Write(word.Bytes()) 395 } 396 397 // NewLine adds new line 398 func (w *wrapper) NewLine() { 399 w.result.WriteString(w.Indent) 400 w.result.Write(w.line.Bytes()) 401 w.result.WriteRune('\n') 402 w.result.WriteRune('\n') 403 w.line.Reset() 404 w.lineLen = 0 405 } 406 407 // Result returns result as a string 408 func (w *wrapper) Result() string { 409 // If line buffer isn't empty append it to the result 410 if w.line.Len() != 0 { 411 w.result.WriteString(w.Indent) 412 w.result.Write(w.line.Bytes()) 413 } 414 415 w.line.Reset() 416 417 return w.result.String() 418 }