github.com/clubpay/ronykit/kit@v0.14.4-0.20240515065620-d0dace45cbc7/utils/transform.go (about)

     1  package utils
     2  
     3  import (
     4  	"strings"
     5  )
     6  
     7  /*
     8     Creation Time: 2020 - Apr - 09
     9     Created by:  (ehsan)
    10     Maintainers:
    11        1.  Ehsan N. Moosa (E2)
    12     Auditor: Ehsan N. Moosa (E2)
    13  */
    14  
    15  var uppercaseAcronym = map[string]string{
    16  	"ID":   "id",
    17  	"HTML": "html",
    18  	"RPC":  "rpc",
    19  }
    20  
    21  // Converts a string to CamelCase
    22  func toCamelInitCase(s string, initCase bool) string {
    23  	s = strings.TrimSpace(s)
    24  	if s == "" {
    25  		return s
    26  	}
    27  	if a, ok := uppercaseAcronym[s]; ok {
    28  		s = a
    29  	}
    30  
    31  	n := strings.Builder{}
    32  	n.Grow(len(s))
    33  	capNext := initCase
    34  	for i, v := range []byte(s) {
    35  		vIsCap := v >= 'A' && v <= 'Z'
    36  		vIsLow := v >= 'a' && v <= 'z'
    37  		if capNext {
    38  			if vIsLow {
    39  				v += 'A'
    40  				v -= 'a'
    41  			}
    42  		} else if i == 0 {
    43  			if vIsCap {
    44  				v += 'a'
    45  				v -= 'A'
    46  			}
    47  		}
    48  		if vIsCap || vIsLow {
    49  			n.WriteByte(v)
    50  			capNext = false
    51  		} else if vIsNum := v >= '0' && v <= '9'; vIsNum {
    52  			n.WriteByte(v)
    53  			capNext = true
    54  		} else {
    55  			capNext = v == '_' || v == ' ' || v == '-' || v == '.'
    56  		}
    57  	}
    58  
    59  	return n.String()
    60  }
    61  
    62  // ToCamel converts a string to CamelCase
    63  func ToCamel(s string) string {
    64  	return toCamelInitCase(s, true)
    65  }
    66  
    67  // ToLowerCamel converts a string to lowerCamelCase
    68  func ToLowerCamel(s string) string {
    69  	return toCamelInitCase(s, false)
    70  }
    71  
    72  // ToSnake converts a string to snake_case
    73  func ToSnake(s string) string {
    74  	return ToDelimited(s, '_')
    75  }
    76  
    77  func ToSnakeWithIgnore(s string, ignore uint8) string {
    78  	return ToScreamingDelimited(s, '_', ignore, false)
    79  }
    80  
    81  // ToScreamingSnake converts a string to SCREAMING_SNAKE_CASE
    82  func ToScreamingSnake(s string) string {
    83  	return ToScreamingDelimited(s, '_', 0, true)
    84  }
    85  
    86  // ToKebab converts a string to kebab-case
    87  func ToKebab(s string) string {
    88  	return ToDelimited(s, '-')
    89  }
    90  
    91  // ToScreamingKebab converts a string to SCREAMING-KEBAB-CASE
    92  func ToScreamingKebab(s string) string {
    93  	return ToScreamingDelimited(s, '-', 0, true)
    94  }
    95  
    96  // ToDelimited converts a string to delimited.snake.case
    97  // (in this case `delimiter = '.'`)
    98  func ToDelimited(s string, delimiter uint8) string {
    99  	return ToScreamingDelimited(s, delimiter, 0, false)
   100  }
   101  
   102  // ToScreamingDelimited converts a string to SCREAMING.DELIMITED.SNAKE.CASE
   103  // (in this case `delimiter = '.'; screaming = true`)
   104  // or delimited.snake.case
   105  // (in this case `delimiter = '.'; screaming = false`)
   106  func ToScreamingDelimited(s string, delimiter uint8, ignore uint8, screaming bool) string { //nolint:gocognit,gocyclo
   107  	n := strings.Builder{}
   108  	n.Grow(len(s) + 2) // nominal 2 bytes of extra space for inserted delimiters
   109  	start := true
   110  	spaces := 0
   111  	for i, v := range []byte(s) {
   112  		if v == ' ' {
   113  			spaces++
   114  
   115  			continue
   116  		} else if start {
   117  			start = false
   118  			spaces = 0
   119  		} else {
   120  			for ; spaces > 0; spaces-- {
   121  				if ignore == ' ' {
   122  					n.WriteByte(' ')
   123  				} else {
   124  					n.WriteByte(delimiter)
   125  				}
   126  			}
   127  		}
   128  
   129  		vIsCap := v >= 'A' && v <= 'Z'
   130  		vIsLow := v >= 'a' && v <= 'z'
   131  		if vIsLow && screaming {
   132  			v += 'A'
   133  			v -= 'a'
   134  		} else if vIsCap && !screaming {
   135  			v += 'a'
   136  			v -= 'A'
   137  		}
   138  
   139  		// treat acronyms as words, eg. for JSONData -> ToJSON is a whole word
   140  		//nolint:nestif
   141  		if i+1 < len(s) {
   142  			next := s[i+1]
   143  			vIsNum := v >= '0' && v <= '9'
   144  			nextIsCap := next >= 'A' && next <= 'Z'
   145  			nextIsLow := next >= 'a' && next <= 'z'
   146  			nextIsNum := next >= '0' && next <= '9'
   147  			// add underscore if next letter case type is changed
   148  			if (vIsCap && (nextIsLow || nextIsNum)) ||
   149  				(vIsLow && (nextIsCap || nextIsNum)) ||
   150  				(vIsNum && (nextIsCap || nextIsLow)) {
   151  				if prevIgnore := ignore > 0 && i > 0 && s[i-1] == ignore; !prevIgnore {
   152  					if vIsCap && nextIsLow {
   153  						if prevIsCap := i > 0 && s[i-1] >= 'A' && s[i-1] <= 'Z'; prevIsCap {
   154  							n.WriteByte(delimiter)
   155  						}
   156  					}
   157  					n.WriteByte(v)
   158  					if vIsLow || vIsNum || nextIsNum {
   159  						n.WriteByte(delimiter)
   160  					}
   161  
   162  					continue
   163  				}
   164  			}
   165  		}
   166  
   167  		if (v == ' ' || v == '_' || v == '-') && v != ignore {
   168  			// replace space/underscore/hyphen with delimiter
   169  			n.WriteByte(delimiter)
   170  		} else {
   171  			n.WriteByte(v)
   172  		}
   173  	}
   174  
   175  	return n.String()
   176  }