Test Failed
Pull Request — master (#36)
by Frank
03:43 queued 02:03
created

strcase.ToScreamingDelimited   F

Complexity

Conditions 38

Size

Total Lines 49
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 38
eloc 34
nop 4
dl 0
loc 49
rs 0
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like strcase.ToScreamingDelimited often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*
2
 * The MIT License (MIT)
3
 *
4
 * Copyright (c) 2015 Ian Coleman
5
 * Copyright (c) 2018 Ma_124, <github.com/Ma124>
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, Subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or Substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25
26
package strcase
27
28
import (
29
	"strings"
30
)
31
32
// ToSnake converts a string to snake_case
33
func ToSnake(s string) string {
34
	return ToDelimited(s, '_')
35
}
36
37
func ToSnakeWithIgnore(s string, ignore string) string {
38
	return ToScreamingDelimited(s, '_', ignore, false)
39
}
40
41
// ToScreamingSnake converts a string to SCREAMING_SNAKE_CASE
42
func ToScreamingSnake(s string) string {
43
	return ToScreamingDelimited(s, '_', "", true)
44
}
45
46
// ToKebab converts a string to kebab-case
47
func ToKebab(s string) string {
48
	return ToDelimited(s, '-')
49
}
50
51
// ToScreamingKebab converts a string to SCREAMING-KEBAB-CASE
52
func ToScreamingKebab(s string) string {
53
	return ToScreamingDelimited(s, '-', "", true)
54
}
55
56
// ToDelimited converts a string to delimited.snake.case
57
// (in this case `delimiter = '.'`)
58
func ToDelimited(s string, delimiter uint8) string {
59
	return ToScreamingDelimited(s, delimiter, "", false)
60
}
61
62
// ToScreamingDelimited converts a string to SCREAMING.DELIMITED.SNAKE.CASE
63
// (in this case `delimiter = '.'; screaming = true`)
64
// or delimited.snake.case
65
// (in this case `delimiter = '.'; screaming = false`)
66
func ToScreamingDelimited(s string, delimiter uint8, ignore string, screaming bool) string {
67
	s = strings.TrimSpace(s)
68
	n := strings.Builder{}
69
	n.Grow(len(s) + 2) // nominal 2 bytes of extra space for inserted delimiters
70
	for i, v := range []byte(s) {
71
		vIsCap := v >= 'A' && v <= 'Z'
72
		vIsLow := v >= 'a' && v <= 'z'
73
		if vIsLow && screaming {
74
			v += 'A'
75
			v -= 'a'
76
		} else if vIsCap && !screaming {
77
			v += 'a'
78
			v -= 'A'
79
		}
80
81
		// treat acronyms as words, eg for JSONData -> JSON is a whole word
82
		if i+1 < len(s) {
83
			next := s[i+1]
84
			vIsNum := v >= '0' && v <= '9'
85
			nextIsCap := next >= 'A' && next <= 'Z'
86
			nextIsLow := next >= 'a' && next <= 'z'
87
			nextIsNum := next >= '0' && next <= '9'
88
			// add underscore if next letter case type is changed
89
			if (vIsCap && (nextIsLow || nextIsNum)) || (vIsLow && (nextIsCap || nextIsNum)) || (vIsNum && (nextIsCap || nextIsLow)) {
90
				prevIgnore := ignore != "" && i > 0 && strings.ContainsAny(string(s[i-1]), ignore)
91
				if !prevIgnore {
92
					if vIsCap && nextIsLow {
93
						if prevIsCap := i > 0 && s[i-1] >= 'A' && s[i-1] <= 'Z'; prevIsCap {
94
							n.WriteByte(delimiter)
95
						}
96
					}
97
					n.WriteByte(v)
98
					if vIsLow || vIsNum || nextIsNum {
99
						n.WriteByte(delimiter)
100
					}
101
					continue
102
				}
103
			}
104
		}
105
106
		if (v == ' ' || v == '_' || v == '-' || v == '.') && !strings.ContainsAny(string(v), ignore) {
107
			// replace space/underscore/hyphen/dot with delimiter
108
			n.WriteByte(delimiter)
109
		} else {
110
			n.WriteByte(v)
111
		}
112
	}
113
114
	return n.String()
115
}
116