You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

convert.go 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. // Copyright 2015 Huan Du. All rights reserved.
  2. // Licensed under the MIT license that can be found in the LICENSE file.
  3. package xstrings
  4. import (
  5. "bytes"
  6. "math/rand"
  7. "unicode"
  8. "unicode/utf8"
  9. )
  10. // ToCamelCase is to convert words separated by space, underscore and hyphen to camel case.
  11. //
  12. // Some samples.
  13. // "some_words" => "SomeWords"
  14. // "http_server" => "HttpServer"
  15. // "no_https" => "NoHttps"
  16. // "_complex__case_" => "_Complex_Case_"
  17. // "some words" => "SomeWords"
  18. func ToCamelCase(str string) string {
  19. if len(str) == 0 {
  20. return ""
  21. }
  22. buf := &bytes.Buffer{}
  23. var r0, r1 rune
  24. var size int
  25. // leading connector will appear in output.
  26. for len(str) > 0 {
  27. r0, size = utf8.DecodeRuneInString(str)
  28. str = str[size:]
  29. if !isConnector(r0) {
  30. r0 = unicode.ToUpper(r0)
  31. break
  32. }
  33. buf.WriteRune(r0)
  34. }
  35. if len(str) == 0 {
  36. // A special case for a string contains only 1 rune.
  37. if size != 0 {
  38. buf.WriteRune(r0)
  39. }
  40. return buf.String()
  41. }
  42. for len(str) > 0 {
  43. r1 = r0
  44. r0, size = utf8.DecodeRuneInString(str)
  45. str = str[size:]
  46. if isConnector(r0) && isConnector(r1) {
  47. buf.WriteRune(r1)
  48. continue
  49. }
  50. if isConnector(r1) {
  51. r0 = unicode.ToUpper(r0)
  52. } else {
  53. r0 = unicode.ToLower(r0)
  54. buf.WriteRune(r1)
  55. }
  56. }
  57. buf.WriteRune(r0)
  58. return buf.String()
  59. }
  60. // ToSnakeCase can convert all upper case characters in a string to
  61. // snake case format.
  62. //
  63. // Some samples.
  64. // "FirstName" => "first_name"
  65. // "HTTPServer" => "http_server"
  66. // "NoHTTPS" => "no_https"
  67. // "GO_PATH" => "go_path"
  68. // "GO PATH" => "go_path" // space is converted to underscore.
  69. // "GO-PATH" => "go_path" // hyphen is converted to underscore.
  70. // "HTTP2XX" => "http_2xx" // insert an underscore before a number and after an alphabet.
  71. // "http2xx" => "http_2xx"
  72. // "HTTP20xOK" => "http_20x_ok"
  73. func ToSnakeCase(str string) string {
  74. return camelCaseToLowerCase(str, '_')
  75. }
  76. // ToKebabCase can convert all upper case characters in a string to
  77. // kebab case format.
  78. //
  79. // Some samples.
  80. // "FirstName" => "first-name"
  81. // "HTTPServer" => "http-server"
  82. // "NoHTTPS" => "no-https"
  83. // "GO_PATH" => "go-path"
  84. // "GO PATH" => "go-path" // space is converted to '-'.
  85. // "GO-PATH" => "go-path" // hyphen is converted to '-'.
  86. // "HTTP2XX" => "http-2xx" // insert a '-' before a number and after an alphabet.
  87. // "http2xx" => "http-2xx"
  88. // "HTTP20xOK" => "http-20x-ok"
  89. func ToKebabCase(str string) string {
  90. return camelCaseToLowerCase(str, '-')
  91. }
  92. func camelCaseToLowerCase(str string, connector rune) string {
  93. if len(str) == 0 {
  94. return ""
  95. }
  96. buf := &bytes.Buffer{}
  97. var prev, r0, r1 rune
  98. var size int
  99. r0 = connector
  100. for len(str) > 0 {
  101. prev = r0
  102. r0, size = utf8.DecodeRuneInString(str)
  103. str = str[size:]
  104. switch {
  105. case r0 == utf8.RuneError:
  106. buf.WriteRune(r0)
  107. case unicode.IsUpper(r0):
  108. if prev != connector && !unicode.IsNumber(prev) {
  109. buf.WriteRune(connector)
  110. }
  111. buf.WriteRune(unicode.ToLower(r0))
  112. if len(str) == 0 {
  113. break
  114. }
  115. r0, size = utf8.DecodeRuneInString(str)
  116. str = str[size:]
  117. if !unicode.IsUpper(r0) {
  118. buf.WriteRune(r0)
  119. break
  120. }
  121. // find next non-upper-case character and insert connector properly.
  122. // it's designed to convert `HTTPServer` to `http_server`.
  123. // if there are more than 2 adjacent upper case characters in a word,
  124. // treat them as an abbreviation plus a normal word.
  125. for len(str) > 0 {
  126. r1 = r0
  127. r0, size = utf8.DecodeRuneInString(str)
  128. str = str[size:]
  129. if r0 == utf8.RuneError {
  130. buf.WriteRune(unicode.ToLower(r1))
  131. buf.WriteRune(r0)
  132. break
  133. }
  134. if !unicode.IsUpper(r0) {
  135. if isConnector(r0) {
  136. r0 = connector
  137. buf.WriteRune(unicode.ToLower(r1))
  138. } else if unicode.IsNumber(r0) {
  139. // treat a number as an upper case rune
  140. // so that both `http2xx` and `HTTP2XX` can be converted to `http_2xx`.
  141. buf.WriteRune(unicode.ToLower(r1))
  142. buf.WriteRune(connector)
  143. buf.WriteRune(r0)
  144. } else {
  145. buf.WriteRune(connector)
  146. buf.WriteRune(unicode.ToLower(r1))
  147. buf.WriteRune(r0)
  148. }
  149. break
  150. }
  151. buf.WriteRune(unicode.ToLower(r1))
  152. }
  153. if len(str) == 0 || r0 == connector {
  154. buf.WriteRune(unicode.ToLower(r0))
  155. }
  156. case unicode.IsNumber(r0):
  157. if prev != connector && !unicode.IsNumber(prev) {
  158. buf.WriteRune(connector)
  159. }
  160. buf.WriteRune(r0)
  161. default:
  162. if isConnector(r0) {
  163. r0 = connector
  164. }
  165. buf.WriteRune(r0)
  166. }
  167. }
  168. return buf.String()
  169. }
  170. func isConnector(r rune) bool {
  171. return r == '-' || r == '_' || unicode.IsSpace(r)
  172. }
  173. // SwapCase will swap characters case from upper to lower or lower to upper.
  174. func SwapCase(str string) string {
  175. var r rune
  176. var size int
  177. buf := &bytes.Buffer{}
  178. for len(str) > 0 {
  179. r, size = utf8.DecodeRuneInString(str)
  180. switch {
  181. case unicode.IsUpper(r):
  182. buf.WriteRune(unicode.ToLower(r))
  183. case unicode.IsLower(r):
  184. buf.WriteRune(unicode.ToUpper(r))
  185. default:
  186. buf.WriteRune(r)
  187. }
  188. str = str[size:]
  189. }
  190. return buf.String()
  191. }
  192. // FirstRuneToUpper converts first rune to upper case if necessary.
  193. func FirstRuneToUpper(str string) string {
  194. if str == "" {
  195. return str
  196. }
  197. r, size := utf8.DecodeRuneInString(str)
  198. if !unicode.IsLower(r) {
  199. return str
  200. }
  201. buf := &bytes.Buffer{}
  202. buf.WriteRune(unicode.ToUpper(r))
  203. buf.WriteString(str[size:])
  204. return buf.String()
  205. }
  206. // FirstRuneToLower converts first rune to lower case if necessary.
  207. func FirstRuneToLower(str string) string {
  208. if str == "" {
  209. return str
  210. }
  211. r, size := utf8.DecodeRuneInString(str)
  212. if !unicode.IsUpper(r) {
  213. return str
  214. }
  215. buf := &bytes.Buffer{}
  216. buf.WriteRune(unicode.ToLower(r))
  217. buf.WriteString(str[size:])
  218. return buf.String()
  219. }
  220. // Shuffle randomizes runes in a string and returns the result.
  221. // It uses default random source in `math/rand`.
  222. func Shuffle(str string) string {
  223. if str == "" {
  224. return str
  225. }
  226. runes := []rune(str)
  227. index := 0
  228. for i := len(runes) - 1; i > 0; i-- {
  229. index = rand.Intn(i + 1)
  230. if i != index {
  231. runes[i], runes[index] = runes[index], runes[i]
  232. }
  233. }
  234. return string(runes)
  235. }
  236. // ShuffleSource randomizes runes in a string with given random source.
  237. func ShuffleSource(str string, src rand.Source) string {
  238. if str == "" {
  239. return str
  240. }
  241. runes := []rune(str)
  242. index := 0
  243. r := rand.New(src)
  244. for i := len(runes) - 1; i > 0; i-- {
  245. index = r.Intn(i + 1)
  246. if i != index {
  247. runes[i], runes[index] = runes[index], runes[i]
  248. }
  249. }
  250. return string(runes)
  251. }
  252. // Successor returns the successor to string.
  253. //
  254. // If there is one alphanumeric rune is found in string, increase the rune by 1.
  255. // If increment generates a "carry", the rune to the left of it is incremented.
  256. // This process repeats until there is no carry, adding an additional rune if necessary.
  257. //
  258. // If there is no alphanumeric rune, the rightmost rune will be increased by 1
  259. // regardless whether the result is a valid rune or not.
  260. //
  261. // Only following characters are alphanumeric.
  262. // * a - z
  263. // * A - Z
  264. // * 0 - 9
  265. //
  266. // Samples (borrowed from ruby's String#succ document):
  267. // "abcd" => "abce"
  268. // "THX1138" => "THX1139"
  269. // "<<koala>>" => "<<koalb>>"
  270. // "1999zzz" => "2000aaa"
  271. // "ZZZ9999" => "AAAA0000"
  272. // "***" => "**+"
  273. func Successor(str string) string {
  274. if str == "" {
  275. return str
  276. }
  277. var r rune
  278. var i int
  279. carry := ' '
  280. runes := []rune(str)
  281. l := len(runes)
  282. lastAlphanumeric := l
  283. for i = l - 1; i >= 0; i-- {
  284. r = runes[i]
  285. if ('a' <= r && r <= 'y') ||
  286. ('A' <= r && r <= 'Y') ||
  287. ('0' <= r && r <= '8') {
  288. runes[i]++
  289. carry = ' '
  290. lastAlphanumeric = i
  291. break
  292. }
  293. switch r {
  294. case 'z':
  295. runes[i] = 'a'
  296. carry = 'a'
  297. lastAlphanumeric = i
  298. case 'Z':
  299. runes[i] = 'A'
  300. carry = 'A'
  301. lastAlphanumeric = i
  302. case '9':
  303. runes[i] = '0'
  304. carry = '0'
  305. lastAlphanumeric = i
  306. }
  307. }
  308. // Needs to add one character for carry.
  309. if i < 0 && carry != ' ' {
  310. buf := &bytes.Buffer{}
  311. buf.Grow(l + 4) // Reserve enough space for write.
  312. if lastAlphanumeric != 0 {
  313. buf.WriteString(str[:lastAlphanumeric])
  314. }
  315. buf.WriteRune(carry)
  316. for _, r = range runes[lastAlphanumeric:] {
  317. buf.WriteRune(r)
  318. }
  319. return buf.String()
  320. }
  321. // No alphanumeric character. Simply increase last rune's value.
  322. if lastAlphanumeric == l {
  323. runes[l-1]++
  324. }
  325. return string(runes)
  326. }