podman

Форк
0
590 строк · 11.5 Кб
1
// Copyright 2015 Huan Du. All rights reserved.
2
// Licensed under the MIT license that can be found in the LICENSE file.
3

4
package xstrings
5

6
import (
7
	"math/rand"
8
	"unicode"
9
	"unicode/utf8"
10
)
11

12
// ToCamelCase is to convert words separated by space, underscore and hyphen to camel case.
13
//
14
// Some samples.
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 {
21
	if len(str) == 0 {
22
		return ""
23
	}
24

25
	buf := &stringBuilder{}
26
	var r0, r1 rune
27
	var size int
28

29
	// leading connector will appear in output.
30
	for len(str) > 0 {
31
		r0, size = utf8.DecodeRuneInString(str)
32
		str = str[size:]
33

34
		if !isConnector(r0) {
35
			r0 = unicode.ToUpper(r0)
36
			break
37
		}
38

39
		buf.WriteRune(r0)
40
	}
41

42
	if len(str) == 0 {
43
		// A special case for a string contains only 1 rune.
44
		if size != 0 {
45
			buf.WriteRune(r0)
46
		}
47

48
		return buf.String()
49
	}
50

51
	for len(str) > 0 {
52
		r1 = r0
53
		r0, size = utf8.DecodeRuneInString(str)
54
		str = str[size:]
55

56
		if isConnector(r0) && isConnector(r1) {
57
			buf.WriteRune(r1)
58
			continue
59
		}
60

61
		if isConnector(r1) {
62
			r0 = unicode.ToUpper(r0)
63
		} else {
64
			r0 = unicode.ToLower(r0)
65
			buf.WriteRune(r1)
66
		}
67
	}
68

69
	buf.WriteRune(r0)
70
	return buf.String()
71
}
72

73
// ToSnakeCase can convert all upper case characters in a string to
74
// snake case format.
75
//
76
// Some samples.
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, '_')
89
}
90

91
// ToKebabCase can convert all upper case characters in a string to
92
// kebab case format.
93
//
94
// Some samples.
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, '-')
107
}
108

109
func camelCaseToLowerCase(str string, connector rune) string {
110
	if len(str) == 0 {
111
		return ""
112
	}
113

114
	buf := &stringBuilder{}
115
	wt, word, remaining := nextWord(str)
116

117
	for len(remaining) > 0 {
118
		if wt != connectorWord {
119
			toLower(buf, wt, word, connector)
120
		}
121

122
		prev := wt
123
		last := word
124
		wt, word, remaining = nextWord(remaining)
125

126
		switch prev {
127
		case numberWord:
128
			for wt == alphabetWord || wt == numberWord {
129
				toLower(buf, wt, word, connector)
130
				wt, word, remaining = nextWord(remaining)
131
			}
132

133
			if wt != invalidWord && wt != punctWord && wt != connectorWord {
134
				buf.WriteRune(connector)
135
			}
136

137
		case connectorWord:
138
			toLower(buf, prev, last, connector)
139

140
		case punctWord:
141
			// nothing.
142

143
		default:
144
			if wt != numberWord {
145
				if wt != connectorWord && wt != punctWord {
146
					buf.WriteRune(connector)
147
				}
148

149
				break
150
			}
151

152
			if len(remaining) == 0 {
153
				break
154
			}
155

156
			last := word
157
			wt, word, remaining = nextWord(remaining)
158

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)
163

164
				if wt != connectorWord && wt != punctWord {
165
					buf.WriteRune(connector)
166
				}
167

168
				break
169
			}
170

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)
176

177
			for wt == alphabetWord || wt == numberWord {
178
				toLower(buf, wt, word, connector)
179
				wt, word, remaining = nextWord(remaining)
180
			}
181

182
			if wt != invalidWord && wt != connectorWord && wt != punctWord {
183
				buf.WriteRune(connector)
184
			}
185
		}
186
	}
187

188
	toLower(buf, wt, word, connector)
189
	return buf.String()
190
}
191

192
func isConnector(r rune) bool {
193
	return r == '-' || r == '_' || unicode.IsSpace(r)
194
}
195

196
type wordType int
197

198
const (
199
	invalidWord wordType = iota
200
	numberWord
201
	upperCaseWord
202
	alphabetWord
203
	connectorWord
204
	punctWord
205
	otherWord
206
)
207

208
func nextWord(str string) (wt wordType, word, remaining string) {
209
	if len(str) == 0 {
210
		return
211
	}
212

213
	var offset int
214
	remaining = str
215
	r, size := nextValidRune(remaining, utf8.RuneError)
216
	offset += size
217

218
	if r == utf8.RuneError {
219
		wt = invalidWord
220
		word = str[:offset]
221
		remaining = str[offset:]
222
		return
223
	}
224

225
	switch {
226
	case isConnector(r):
227
		wt = connectorWord
228
		remaining = remaining[size:]
229

230
		for len(remaining) > 0 {
231
			r, size = nextValidRune(remaining, r)
232

233
			if !isConnector(r) {
234
				break
235
			}
236

237
			offset += size
238
			remaining = remaining[size:]
239
		}
240

241
	case unicode.IsPunct(r):
242
		wt = punctWord
243
		remaining = remaining[size:]
244

245
		for len(remaining) > 0 {
246
			r, size = nextValidRune(remaining, r)
247

248
			if !unicode.IsPunct(r) {
249
				break
250
			}
251

252
			offset += size
253
			remaining = remaining[size:]
254
		}
255

256
	case unicode.IsUpper(r):
257
		wt = upperCaseWord
258
		remaining = remaining[size:]
259

260
		if len(remaining) == 0 {
261
			break
262
		}
263

264
		r, size = nextValidRune(remaining, r)
265

266
		switch {
267
		case unicode.IsUpper(r):
268
			prevSize := size
269
			offset += size
270
			remaining = remaining[size:]
271

272
			for len(remaining) > 0 {
273
				r, size = nextValidRune(remaining, r)
274

275
				if !unicode.IsUpper(r) {
276
					break
277
				}
278

279
				prevSize = size
280
				offset += size
281
				remaining = remaining[size:]
282
			}
283

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) {
288
				offset -= prevSize
289
				remaining = str[offset:]
290
			}
291

292
		case isAlphabet(r):
293
			offset += size
294
			remaining = remaining[size:]
295

296
			for len(remaining) > 0 {
297
				r, size = nextValidRune(remaining, r)
298

299
				if !isAlphabet(r) || unicode.IsUpper(r) {
300
					break
301
				}
302

303
				offset += size
304
				remaining = remaining[size:]
305
			}
306
		}
307

308
	case isAlphabet(r):
309
		wt = alphabetWord
310
		remaining = remaining[size:]
311

312
		for len(remaining) > 0 {
313
			r, size = nextValidRune(remaining, r)
314

315
			if !isAlphabet(r) || unicode.IsUpper(r) {
316
				break
317
			}
318

319
			offset += size
320
			remaining = remaining[size:]
321
		}
322

323
	case unicode.IsNumber(r):
324
		wt = numberWord
325
		remaining = remaining[size:]
326

327
		for len(remaining) > 0 {
328
			r, size = nextValidRune(remaining, r)
329

330
			if !unicode.IsNumber(r) {
331
				break
332
			}
333

334
			offset += size
335
			remaining = remaining[size:]
336
		}
337

338
	default:
339
		wt = otherWord
340
		remaining = remaining[size:]
341

342
		for len(remaining) > 0 {
343
			r, size = nextValidRune(remaining, r)
344

345
			if size == 0 || isConnector(r) || isAlphabet(r) || unicode.IsNumber(r) || unicode.IsPunct(r) {
346
				break
347
			}
348

349
			offset += size
350
			remaining = remaining[size:]
351
		}
352
	}
353

354
	word = str[:offset]
355
	return
356
}
357

358
func nextValidRune(str string, prev rune) (r rune, size int) {
359
	var sz int
360

361
	for len(str) > 0 {
362
		r, sz = utf8.DecodeRuneInString(str)
363
		size += sz
364

365
		if r != utf8.RuneError {
366
			return
367
		}
368

369
		str = str[sz:]
370
	}
371

372
	r = prev
373
	return
374
}
375

376
func toLower(buf *stringBuilder, wt wordType, str string, connector rune) {
377
	buf.Grow(buf.Len() + len(str))
378

379
	if wt != upperCaseWord && wt != connectorWord {
380
		buf.WriteString(str)
381
		return
382
	}
383

384
	for len(str) > 0 {
385
		r, size := utf8.DecodeRuneInString(str)
386
		str = str[size:]
387

388
		if isConnector(r) {
389
			buf.WriteRune(connector)
390
		} else if unicode.IsUpper(r) {
391
			buf.WriteRune(unicode.ToLower(r))
392
		} else {
393
			buf.WriteRune(r)
394
		}
395
	}
396
}
397

398
// SwapCase will swap characters case from upper to lower or lower to upper.
399
func SwapCase(str string) string {
400
	var r rune
401
	var size int
402

403
	buf := &stringBuilder{}
404

405
	for len(str) > 0 {
406
		r, size = utf8.DecodeRuneInString(str)
407

408
		switch {
409
		case unicode.IsUpper(r):
410
			buf.WriteRune(unicode.ToLower(r))
411

412
		case unicode.IsLower(r):
413
			buf.WriteRune(unicode.ToUpper(r))
414

415
		default:
416
			buf.WriteRune(r)
417
		}
418

419
		str = str[size:]
420
	}
421

422
	return buf.String()
423
}
424

425
// FirstRuneToUpper converts first rune to upper case if necessary.
426
func FirstRuneToUpper(str string) string {
427
	if str == "" {
428
		return str
429
	}
430

431
	r, size := utf8.DecodeRuneInString(str)
432

433
	if !unicode.IsLower(r) {
434
		return str
435
	}
436

437
	buf := &stringBuilder{}
438
	buf.WriteRune(unicode.ToUpper(r))
439
	buf.WriteString(str[size:])
440
	return buf.String()
441
}
442

443
// FirstRuneToLower converts first rune to lower case if necessary.
444
func FirstRuneToLower(str string) string {
445
	if str == "" {
446
		return str
447
	}
448

449
	r, size := utf8.DecodeRuneInString(str)
450

451
	if !unicode.IsUpper(r) {
452
		return str
453
	}
454

455
	buf := &stringBuilder{}
456
	buf.WriteRune(unicode.ToLower(r))
457
	buf.WriteString(str[size:])
458
	return buf.String()
459
}
460

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 {
464
	if str == "" {
465
		return str
466
	}
467

468
	runes := []rune(str)
469
	index := 0
470

471
	for i := len(runes) - 1; i > 0; i-- {
472
		index = rand.Intn(i + 1)
473

474
		if i != index {
475
			runes[i], runes[index] = runes[index], runes[i]
476
		}
477
	}
478

479
	return string(runes)
480
}
481

482
// ShuffleSource randomizes runes in a string with given random source.
483
func ShuffleSource(str string, src rand.Source) string {
484
	if str == "" {
485
		return str
486
	}
487

488
	runes := []rune(str)
489
	index := 0
490
	r := rand.New(src)
491

492
	for i := len(runes) - 1; i > 0; i-- {
493
		index = r.Intn(i + 1)
494

495
		if i != index {
496
			runes[i], runes[index] = runes[index], runes[i]
497
		}
498
	}
499

500
	return string(runes)
501
}
502

503
// Successor returns the successor to string.
504
//
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.
508
//
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.
511
//
512
// Only following characters are alphanumeric.
513
//     * a - z
514
//     * A - Z
515
//     * 0 - 9
516
//
517
// Samples (borrowed from ruby's String#succ document):
518
//     "abcd"      => "abce"
519
//     "THX1138"   => "THX1139"
520
//     "<<koala>>" => "<<koalb>>"
521
//     "1999zzz"   => "2000aaa"
522
//     "ZZZ9999"   => "AAAA0000"
523
//     "***"       => "**+"
524
func Successor(str string) string {
525
	if str == "" {
526
		return str
527
	}
528

529
	var r rune
530
	var i int
531
	carry := ' '
532
	runes := []rune(str)
533
	l := len(runes)
534
	lastAlphanumeric := l
535

536
	for i = l - 1; i >= 0; i-- {
537
		r = runes[i]
538

539
		if ('a' <= r && r <= 'y') ||
540
			('A' <= r && r <= 'Y') ||
541
			('0' <= r && r <= '8') {
542
			runes[i]++
543
			carry = ' '
544
			lastAlphanumeric = i
545
			break
546
		}
547

548
		switch r {
549
		case 'z':
550
			runes[i] = 'a'
551
			carry = 'a'
552
			lastAlphanumeric = i
553

554
		case 'Z':
555
			runes[i] = 'A'
556
			carry = 'A'
557
			lastAlphanumeric = i
558

559
		case '9':
560
			runes[i] = '0'
561
			carry = '0'
562
			lastAlphanumeric = i
563
		}
564
	}
565

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.
570

571
		if lastAlphanumeric != 0 {
572
			buf.WriteString(str[:lastAlphanumeric])
573
		}
574

575
		buf.WriteRune(carry)
576

577
		for _, r = range runes[lastAlphanumeric:] {
578
			buf.WriteRune(r)
579
		}
580

581
		return buf.String()
582
	}
583

584
	// No alphanumeric character. Simply increase last rune's value.
585
	if lastAlphanumeric == l {
586
		runes[l-1]++
587
	}
588

589
	return string(runes)
590
}
591

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.