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  }