gstr_case.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // Copyright 2019 gf Author(https://github.com/gogf/gf). 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. // | SnakeCase(s) | any_kind_of_string |
  10. // | SnakeScreamingCase(s) | ANY_KIND_OF_STRING |
  11. // | KebabCase(s) | any-kind-of-string |
  12. // | KebabScreamingCase(s) | ANY-KIND-OF-STRING |
  13. // | DelimitedCase(s, '.') | any.kind.of.string |
  14. // | DelimitedScreamingCase(s, '.') | ANY.KIND.OF.STRING |
  15. // | CamelCase(s) | AnyKindOfString |
  16. // | CamelLowerCase(s) | anyKindOfString |
  17. // | SnakeFirstUpperCase(RGBCodeMd5) | rgb_code_md5 |
  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. // CamelCase converts a string to CamelCase.
  29. func CamelCase(s string) string {
  30. return toCamelInitCase(s, true)
  31. }
  32. // CamelLowerCase converts a string to lowerCamelCase.
  33. func CamelLowerCase(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. // SnakeCase converts a string to snake_case.
  43. func SnakeCase(s string) string {
  44. return DelimitedCase(s, '_')
  45. }
  46. // SnakeScreamingCase converts a string to SNAKE_CASE_SCREAMING.
  47. func SnakeScreamingCase(s string) string {
  48. return DelimitedScreamingCase(s, '_', true)
  49. }
  50. // SnakeFirstUpperCase converts a string from RGBCodeMd5 to rgb_code_md5.
  51. // The length of word should not be too long
  52. // TODO for efficiency should change regexp to traversing string in future
  53. func SnakeFirstUpperCase(word string, underscore ...string) string {
  54. replace := "_"
  55. if len(underscore) > 0 {
  56. replace = underscore[0]
  57. }
  58. m := firstCamelCaseEnd.FindAllStringSubmatch(word, 1)
  59. if len(m) > 0 {
  60. word = m[0][1] + replace + TrimLeft(ToLower(m[0][2]), replace)
  61. }
  62. for {
  63. m := firstCamelCaseStart.FindAllStringSubmatch(word, 1)
  64. if len(m) > 0 && m[0][1] != "" {
  65. w := strings.ToLower(m[0][1])
  66. w = string(w[:len(w)-1]) + replace + string(w[len(w)-1])
  67. word = strings.Replace(word, m[0][1], w, 1)
  68. } else {
  69. break
  70. }
  71. }
  72. return TrimLeft(word, replace)
  73. }
  74. // KebabCase converts a string to kebab-case
  75. func KebabCase(s string) string {
  76. return DelimitedCase(s, '-')
  77. }
  78. // KebabScreamingCase converts a string to KEBAB-CASE-SCREAMING.
  79. func KebabScreamingCase(s string) string {
  80. return DelimitedScreamingCase(s, '-', true)
  81. }
  82. // DelimitedCase converts a string to snake.case.delimited.
  83. func DelimitedCase(s string, del uint8) string {
  84. return DelimitedScreamingCase(s, del, false)
  85. }
  86. // DelimitedScreamingCase converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case.
  87. func DelimitedScreamingCase(s string, del uint8, screaming bool) string {
  88. s = addWordBoundariesToNumbers(s)
  89. s = strings.Trim(s, " ")
  90. n := ""
  91. for i, v := range s {
  92. // treat acronyms as words, eg for JSONData -> JSON is a whole word
  93. nextCaseIsChanged := false
  94. if i+1 < len(s) {
  95. next := s[i+1]
  96. if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') {
  97. nextCaseIsChanged = true
  98. }
  99. }
  100. if i > 0 && n[len(n)-1] != del && nextCaseIsChanged {
  101. // add underscore if next letter case type is changed
  102. if v >= 'A' && v <= 'Z' {
  103. n += string(del) + string(v)
  104. } else if v >= 'a' && v <= 'z' {
  105. n += string(v) + string(del)
  106. }
  107. } else if v == ' ' || v == '_' || v == '-' || v == '.' {
  108. // replace spaces/underscores with delimiters
  109. n += string(del)
  110. } else {
  111. n = n + string(v)
  112. }
  113. }
  114. if screaming {
  115. n = strings.ToUpper(n)
  116. } else {
  117. n = strings.ToLower(n)
  118. }
  119. return n
  120. }
  121. func addWordBoundariesToNumbers(s string) string {
  122. r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte {
  123. var result []byte
  124. match := numberSequence.FindSubmatch(bytes)
  125. if len(match[1]) > 0 {
  126. result = append(result, match[1]...)
  127. result = append(result, []byte(" ")...)
  128. }
  129. result = append(result, match[2]...)
  130. if len(match[3]) > 0 {
  131. result = append(result, []byte(" ")...)
  132. result = append(result, match[3]...)
  133. }
  134. return result
  135. })
  136. return string(r)
  137. }
  138. // Converts a string to CamelCase
  139. func toCamelInitCase(s string, initCase bool) string {
  140. s = addWordBoundariesToNumbers(s)
  141. s = strings.Trim(s, " ")
  142. n := ""
  143. capNext := initCase
  144. for _, v := range s {
  145. if v >= 'A' && v <= 'Z' {
  146. n += string(v)
  147. }
  148. if v >= '0' && v <= '9' {
  149. n += string(v)
  150. }
  151. if v >= 'a' && v <= 'z' {
  152. if capNext {
  153. n += strings.ToUpper(string(v))
  154. } else {
  155. n += string(v)
  156. }
  157. }
  158. if v == '_' || v == ' ' || v == '-' || v == '.' {
  159. capNext = true
  160. } else {
  161. capNext = false
  162. }
  163. }
  164. return n
  165. }