gstr_case.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. //
  7. // | Function | Result |
  8. // |-----------------------------------|--------------------|
  9. // | CaseSnake(s) | any_kind_of_string |
  10. // | CaseSnakeScreaming(s) | ANY_KIND_OF_STRING |
  11. // | CaseSnakeFirstUpper("RGBCodeMd5") | rgb_code_md5 |
  12. // | CaseKebab(s) | any-kind-of-string |
  13. // | CaseKebabScreaming(s) | ANY-KIND-OF-STRING |
  14. // | CaseDelimited(s, '.') | any.kind.of.string |
  15. // | CaseDelimitedScreaming(s, '.') | ANY.KIND.OF.STRING |
  16. // | CaseCamel(s) | AnyKindOfString |
  17. // | CaseCamelLower(s) | anyKindOfString |
  18. package gstr
  19. import (
  20. "regexp"
  21. "strings"
  22. )
  23. var (
  24. numberSequence = regexp.MustCompile(`([a-zA-Z]{0,1})(\d+)([a-zA-Z]{0,1})`)
  25. firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`)
  26. firstCamelCaseEnd = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`)
  27. )
  28. // CaseCamel converts a string to CamelCase.
  29. func CaseCamel(s string) string {
  30. return toCamelInitCase(s, true)
  31. }
  32. // CaseCamelLower converts a string to lowerCamelCase.
  33. func CaseCamelLower(s string) string {
  34. if s == "" {
  35. return s
  36. }
  37. if r := rune(s[0]); r >= 'A' && r <= 'Z' {
  38. s = strings.ToLower(string(r)) + s[1:]
  39. }
  40. return toCamelInitCase(s, false)
  41. }
  42. // CaseSnake converts a string to snake_case.
  43. func CaseSnake(s string) string {
  44. return CaseDelimited(s, '_')
  45. }
  46. // CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING.
  47. func CaseSnakeScreaming(s string) string {
  48. return CaseDelimitedScreaming(s, '_', true)
  49. }
  50. // CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5".
  51. // TODO for efficiency should change regexp to traversing string in future.
  52. func CaseSnakeFirstUpper(word string, underscore ...string) string {
  53. replace := "_"
  54. if len(underscore) > 0 {
  55. replace = underscore[0]
  56. }
  57. m := firstCamelCaseEnd.FindAllStringSubmatch(word, 1)
  58. if len(m) > 0 {
  59. word = m[0][1] + replace + TrimLeft(ToLower(m[0][2]), replace)
  60. }
  61. for {
  62. m = firstCamelCaseStart.FindAllStringSubmatch(word, 1)
  63. if len(m) > 0 && m[0][1] != "" {
  64. w := strings.ToLower(m[0][1])
  65. w = w[:len(w)-1] + replace + string(w[len(w)-1])
  66. word = strings.Replace(word, m[0][1], w, 1)
  67. } else {
  68. break
  69. }
  70. }
  71. return TrimLeft(word, replace)
  72. }
  73. // CaseKebab converts a string to kebab-case
  74. func CaseKebab(s string) string {
  75. return CaseDelimited(s, '-')
  76. }
  77. // CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING.
  78. func CaseKebabScreaming(s string) string {
  79. return CaseDelimitedScreaming(s, '-', true)
  80. }
  81. // CaseDelimited converts a string to snake.case.delimited.
  82. func CaseDelimited(s string, del byte) string {
  83. return CaseDelimitedScreaming(s, del, false)
  84. }
  85. // CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case.
  86. func CaseDelimitedScreaming(s string, del uint8, screaming bool) string {
  87. s = addWordBoundariesToNumbers(s)
  88. s = strings.Trim(s, " ")
  89. n := ""
  90. for i, v := range s {
  91. // treat acronyms as words, eg for JSONData -> JSON is a whole word
  92. nextCaseIsChanged := false
  93. if i+1 < len(s) {
  94. next := s[i+1]
  95. if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') {
  96. nextCaseIsChanged = true
  97. }
  98. }
  99. if i > 0 && n[len(n)-1] != del && nextCaseIsChanged {
  100. // add underscore if next letter case type is changed
  101. if v >= 'A' && v <= 'Z' {
  102. n += string(del) + string(v)
  103. } else if v >= 'a' && v <= 'z' {
  104. n += string(v) + string(del)
  105. }
  106. } else if v == ' ' || v == '_' || v == '-' || v == '.' {
  107. // replace spaces/underscores with delimiters
  108. n += string(del)
  109. } else {
  110. n = n + string(v)
  111. }
  112. }
  113. if screaming {
  114. n = strings.ToUpper(n)
  115. } else {
  116. n = strings.ToLower(n)
  117. }
  118. return n
  119. }
  120. func addWordBoundariesToNumbers(s string) string {
  121. r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte {
  122. var result []byte
  123. match := numberSequence.FindSubmatch(bytes)
  124. if len(match[1]) > 0 {
  125. result = append(result, match[1]...)
  126. result = append(result, []byte(" ")...)
  127. }
  128. result = append(result, match[2]...)
  129. if len(match[3]) > 0 {
  130. result = append(result, []byte(" ")...)
  131. result = append(result, match[3]...)
  132. }
  133. return result
  134. })
  135. return string(r)
  136. }
  137. // Converts a string to CamelCase
  138. func toCamelInitCase(s string, initCase bool) string {
  139. s = addWordBoundariesToNumbers(s)
  140. s = strings.Trim(s, " ")
  141. n := ""
  142. capNext := initCase
  143. for _, v := range s {
  144. if v >= 'A' && v <= 'Z' {
  145. n += string(v)
  146. }
  147. if v >= '0' && v <= '9' {
  148. n += string(v)
  149. }
  150. if v >= 'a' && v <= 'z' {
  151. if capNext {
  152. n += strings.ToUpper(string(v))
  153. } else {
  154. n += string(v)
  155. }
  156. }
  157. if v == '_' || v == ' ' || v == '-' || v == '.' {
  158. capNext = true
  159. } else {
  160. capNext = false
  161. }
  162. }
  163. return n
  164. }