1
// Copyright 2015 Huan Du. All rights reserved.
2
// Licensed under the MIT license that can be found in the LICENSE file.
12
// ToCamelCase is to convert words separated by space, underscore and hyphen to camel case.
15
// "some_words" => "SomeWords"
16
// "http_server" => "HttpServer"
17
// "no_https" => "NoHttps"
18
// "_complex__case_" => "_Complex_Case_"
19
// "some words" => "SomeWords"
20
func ToCamelCase(str string) string {
25
buf := &stringBuilder{}
29
// leading connector will appear in output.
31
r0, size = utf8.DecodeRuneInString(str)
35
r0 = unicode.ToUpper(r0)
43
// A special case for a string contains only 1 rune.
53
r0, size = utf8.DecodeRuneInString(str)
56
if isConnector(r0) && isConnector(r1) {
62
r0 = unicode.ToUpper(r0)
64
r0 = unicode.ToLower(r0)
73
// ToSnakeCase can convert all upper case characters in a string to
77
// "FirstName" => "first_name"
78
// "HTTPServer" => "http_server"
79
// "NoHTTPS" => "no_https"
80
// "GO_PATH" => "go_path"
81
// "GO PATH" => "go_path" // space is converted to underscore.
82
// "GO-PATH" => "go_path" // hyphen is converted to underscore.
83
// "http2xx" => "http_2xx" // insert an underscore before a number and after an alphabet.
84
// "HTTP20xOK" => "http_20x_ok"
85
// "Duration2m3s" => "duration_2m3s"
86
// "Bld4Floor3rd" => "bld4_floor_3rd"
87
func ToSnakeCase(str string) string {
88
return camelCaseToLowerCase(str, '_')
91
// ToKebabCase can convert all upper case characters in a string to
95
// "FirstName" => "first-name"
96
// "HTTPServer" => "http-server"
97
// "NoHTTPS" => "no-https"
98
// "GO_PATH" => "go-path"
99
// "GO PATH" => "go-path" // space is converted to '-'.
100
// "GO-PATH" => "go-path" // hyphen is converted to '-'.
101
// "http2xx" => "http-2xx" // insert an underscore before a number and after an alphabet.
102
// "HTTP20xOK" => "http-20x-ok"
103
// "Duration2m3s" => "duration-2m3s"
104
// "Bld4Floor3rd" => "bld4-floor-3rd"
105
func ToKebabCase(str string) string {
106
return camelCaseToLowerCase(str, '-')
109
func camelCaseToLowerCase(str string, connector rune) string {
114
buf := &stringBuilder{}
115
wt, word, remaining := nextWord(str)
117
for len(remaining) > 0 {
118
if wt != connectorWord {
119
toLower(buf, wt, word, connector)
124
wt, word, remaining = nextWord(remaining)
128
for wt == alphabetWord || wt == numberWord {
129
toLower(buf, wt, word, connector)
130
wt, word, remaining = nextWord(remaining)
133
if wt != invalidWord && wt != punctWord && wt != connectorWord {
134
buf.WriteRune(connector)
138
toLower(buf, prev, last, connector)
144
if wt != numberWord {
145
if wt != connectorWord && wt != punctWord {
146
buf.WriteRune(connector)
152
if len(remaining) == 0 {
157
wt, word, remaining = nextWord(remaining)
159
// consider number as a part of previous word.
160
// e.g. "Bld4Floor" => "bld4_floor"
161
if wt != alphabetWord {
162
toLower(buf, numberWord, last, connector)
164
if wt != connectorWord && wt != punctWord {
165
buf.WriteRune(connector)
171
// if there are some lower case letters following a number,
172
// add connector before the number.
173
// e.g. "HTTP2xx" => "http_2xx"
174
buf.WriteRune(connector)
175
toLower(buf, numberWord, last, connector)
177
for wt == alphabetWord || wt == numberWord {
178
toLower(buf, wt, word, connector)
179
wt, word, remaining = nextWord(remaining)
182
if wt != invalidWord && wt != connectorWord && wt != punctWord {
183
buf.WriteRune(connector)
188
toLower(buf, wt, word, connector)
192
func isConnector(r rune) bool {
193
return r == '-' || r == '_' || unicode.IsSpace(r)
199
invalidWord wordType = iota
208
func nextWord(str string) (wt wordType, word, remaining string) {
215
r, size := nextValidRune(remaining, utf8.RuneError)
218
if r == utf8.RuneError {
221
remaining = str[offset:]
228
remaining = remaining[size:]
230
for len(remaining) > 0 {
231
r, size = nextValidRune(remaining, r)
238
remaining = remaining[size:]
241
case unicode.IsPunct(r):
243
remaining = remaining[size:]
245
for len(remaining) > 0 {
246
r, size = nextValidRune(remaining, r)
248
if !unicode.IsPunct(r) {
253
remaining = remaining[size:]
256
case unicode.IsUpper(r):
258
remaining = remaining[size:]
260
if len(remaining) == 0 {
264
r, size = nextValidRune(remaining, r)
267
case unicode.IsUpper(r):
270
remaining = remaining[size:]
272
for len(remaining) > 0 {
273
r, size = nextValidRune(remaining, r)
275
if !unicode.IsUpper(r) {
281
remaining = remaining[size:]
284
// it's a bit complex when dealing with a case like "HTTPStatus".
285
// it's expected to be splitted into "HTTP" and "Status".
286
// Therefore "S" should be in remaining instead of word.
287
if len(remaining) > 0 && isAlphabet(r) {
289
remaining = str[offset:]
294
remaining = remaining[size:]
296
for len(remaining) > 0 {
297
r, size = nextValidRune(remaining, r)
299
if !isAlphabet(r) || unicode.IsUpper(r) {
304
remaining = remaining[size:]
310
remaining = remaining[size:]
312
for len(remaining) > 0 {
313
r, size = nextValidRune(remaining, r)
315
if !isAlphabet(r) || unicode.IsUpper(r) {
320
remaining = remaining[size:]
323
case unicode.IsNumber(r):
325
remaining = remaining[size:]
327
for len(remaining) > 0 {
328
r, size = nextValidRune(remaining, r)
330
if !unicode.IsNumber(r) {
335
remaining = remaining[size:]
340
remaining = remaining[size:]
342
for len(remaining) > 0 {
343
r, size = nextValidRune(remaining, r)
345
if size == 0 || isConnector(r) || isAlphabet(r) || unicode.IsNumber(r) || unicode.IsPunct(r) {
350
remaining = remaining[size:]
358
func nextValidRune(str string, prev rune) (r rune, size int) {
362
r, sz = utf8.DecodeRuneInString(str)
365
if r != utf8.RuneError {
376
func toLower(buf *stringBuilder, wt wordType, str string, connector rune) {
377
buf.Grow(buf.Len() + len(str))
379
if wt != upperCaseWord && wt != connectorWord {
385
r, size := utf8.DecodeRuneInString(str)
389
buf.WriteRune(connector)
390
} else if unicode.IsUpper(r) {
391
buf.WriteRune(unicode.ToLower(r))
398
// SwapCase will swap characters case from upper to lower or lower to upper.
399
func SwapCase(str string) string {
403
buf := &stringBuilder{}
406
r, size = utf8.DecodeRuneInString(str)
409
case unicode.IsUpper(r):
410
buf.WriteRune(unicode.ToLower(r))
412
case unicode.IsLower(r):
413
buf.WriteRune(unicode.ToUpper(r))
425
// FirstRuneToUpper converts first rune to upper case if necessary.
426
func FirstRuneToUpper(str string) string {
431
r, size := utf8.DecodeRuneInString(str)
433
if !unicode.IsLower(r) {
437
buf := &stringBuilder{}
438
buf.WriteRune(unicode.ToUpper(r))
439
buf.WriteString(str[size:])
443
// FirstRuneToLower converts first rune to lower case if necessary.
444
func FirstRuneToLower(str string) string {
449
r, size := utf8.DecodeRuneInString(str)
451
if !unicode.IsUpper(r) {
455
buf := &stringBuilder{}
456
buf.WriteRune(unicode.ToLower(r))
457
buf.WriteString(str[size:])
461
// Shuffle randomizes runes in a string and returns the result.
462
// It uses default random source in `math/rand`.
463
func Shuffle(str string) string {
471
for i := len(runes) - 1; i > 0; i-- {
472
index = rand.Intn(i + 1)
475
runes[i], runes[index] = runes[index], runes[i]
482
// ShuffleSource randomizes runes in a string with given random source.
483
func ShuffleSource(str string, src rand.Source) string {
492
for i := len(runes) - 1; i > 0; i-- {
493
index = r.Intn(i + 1)
496
runes[i], runes[index] = runes[index], runes[i]
503
// Successor returns the successor to string.
505
// If there is one alphanumeric rune is found in string, increase the rune by 1.
506
// If increment generates a "carry", the rune to the left of it is incremented.
507
// This process repeats until there is no carry, adding an additional rune if necessary.
509
// If there is no alphanumeric rune, the rightmost rune will be increased by 1
510
// regardless whether the result is a valid rune or not.
512
// Only following characters are alphanumeric.
517
// Samples (borrowed from ruby's String#succ document):
519
// "THX1138" => "THX1139"
520
// "<<koala>>" => "<<koalb>>"
521
// "1999zzz" => "2000aaa"
522
// "ZZZ9999" => "AAAA0000"
524
func Successor(str string) string {
534
lastAlphanumeric := l
536
for i = l - 1; i >= 0; i-- {
539
if ('a' <= r && r <= 'y') ||
540
('A' <= r && r <= 'Y') ||
541
('0' <= r && r <= '8') {
566
// Needs to add one character for carry.
567
if i < 0 && carry != ' ' {
568
buf := &stringBuilder{}
569
buf.Grow(l + 4) // Reserve enough space for write.
571
if lastAlphanumeric != 0 {
572
buf.WriteString(str[:lastAlphanumeric])
577
for _, r = range runes[lastAlphanumeric:] {
584
// No alphanumeric character. Simply increase last rune's value.
585
if lastAlphanumeric == l {