github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/strcase/snake.go (about)

     1  package strcase
     2  
     3  import (
     4  	"strings"
     5  )
     6  
     7  // ToSnake converts a string to snake_case
     8  func ToSnake(s string) string {
     9  	return ToDelimited(s, '_')
    10  }
    11  
    12  // ToSnakeUpper converts a string to SCREAMING_SNAKE_CASE
    13  func ToSnakeUpper(s string) string {
    14  	return ToDelimitedScreaming(s, '_', true)
    15  }
    16  
    17  // ToKebab converts a string to kebab-case
    18  func ToKebab(s string) string {
    19  	return ToDelimited(s, '-')
    20  }
    21  
    22  // ToKebabUpper converts a string to SCREAMING-KEBAB-CASE
    23  func ToKebabUpper(s string) string {
    24  	return ToDelimitedScreaming(s, '-', true)
    25  }
    26  
    27  // ToDelimited converts a string to delimited.snake.case (in this case `del = '.'`)
    28  func ToDelimited(s string, del uint8) string {
    29  	return ToDelimitedScreaming(s, del, false)
    30  }
    31  
    32  // ToDelimitedUpper converts a string to SCREAMING.DELIMITED.SNAKE.CASE
    33  // (in this case `del = '.'; screaming = true`) or delimited.snake.case (in this case `del = '.'; screaming = false`)
    34  func ToDelimitedUpper(s string, del uint8) string {
    35  	return ToDelimitedScreaming(s, del, true)
    36  }
    37  
    38  // ToDelimitedScreaming converts a string to SCREAMING.DELIMITED.SNAKE.CASE
    39  // (in this case `del = '.'; screaming = true`) or delimited.snake.case (in this case `del = '.'; screaming = false`)
    40  func ToDelimitedScreaming(s string, del uint8, screaming bool) string {
    41  	s = addWordBoundariesToNumbers(s)
    42  	s = strings.Trim(s, " ")
    43  	n := ""
    44  
    45  	for i, v := range s {
    46  		// treat acronyms as words, eg for JSONData -> JSON is a whole word
    47  		nextCaseIsChanged := false
    48  
    49  		if i+1 < len(s) {
    50  			next := s[i+1]
    51  			if isCaseChanged(v, int32(next)) {
    52  				nextCaseIsChanged = true
    53  			}
    54  		}
    55  
    56  		switch {
    57  		case i > 0 && n[len(n)-1] != del && nextCaseIsChanged:
    58  			// add underscore if next letter case type is changed
    59  			if IsA2Z(v) {
    60  				n += string(del) + string(v)
    61  			} else if Isa2z(v) {
    62  				n += string(v) + string(del)
    63  			}
    64  		case anyOf(v, ' ', '_', '-'):
    65  			if len(n) > 0 && n[len(n)-1] == del {
    66  				continue
    67  			}
    68  			// replace spaces/underscores with delimiters
    69  			n += string(del)
    70  		default:
    71  			n += string(v)
    72  		}
    73  	}
    74  
    75  	if screaming {
    76  		return strings.ToUpper(n)
    77  	}
    78  
    79  	return strings.ToLower(n)
    80  }
    81  
    82  func anyOf(v int32, oneOfs ...int32) bool {
    83  	for _, one := range oneOfs {
    84  		if v == one {
    85  			return true
    86  		}
    87  	}
    88  
    89  	return false
    90  }
    91  
    92  func isCaseChanged(v, next int32) bool {
    93  	return IsA2Z(v) && Isa2z(next) ||
    94  		Isa2z(v) && IsA2Z(next) ||
    95  		Is029(v) && IsA2Z(next)
    96  }
    97  
    98  // Is029 tells v is 0-9
    99  func Is029(v int32) bool {
   100  	return v >= '0' && v <= '9'
   101  }
   102  
   103  // Isa2z tells v is a-z
   104  func Isa2z(v int32) bool {
   105  	return v >= 'a' && v <= 'z'
   106  }
   107  
   108  // IsA2Z tells v is A-Z
   109  func IsA2Z(v int32) bool {
   110  	return v >= 'A' && v <= 'Z'
   111  }