diff --git a/acronyms.go b/acronyms.go index 3b06064..ecb533e 100644 --- a/acronyms.go +++ b/acronyms.go @@ -1,5 +1,5 @@ package strcase -var uppercaseAcronym = map[string]bool{ - "ID": true, +var uppercaseAcronym = map[string]string{ + "ID": "id", } diff --git a/camel.go b/camel.go index bd55a1d..ab2d261 100644 --- a/camel.go +++ b/camel.go @@ -31,40 +31,47 @@ import ( // Converts a string to CamelCase func toCamelInitCase(s string, initCase bool) string { - s = addWordBoundariesToNumbers(s) - n := "" - capNext := initCase - for _, v := range s { - if capNext && v >= 'a' && v <= 'z' { - v = int32(strings.ToUpper(string(v))[0]) - } - if (v >= 'A' && v <= 'Z') || - (v >= 'a' && v <= 'z') || - (v >= '0' && v <= '9') { - n += string(v) - } - capNext = v == '_' || v == ' ' || v == '-' || v == '.' + if s == "" { + return s } - return n + if a, ok := uppercaseAcronym[s]; ok { + s = a + } + + s = addWordBoundariesToNumbers(s) + n := strings.Builder{} + n.Grow(len(s)) + capNext := initCase + for i, v := range []byte(s) { + vIsCap := v >= 'A' && v <= 'Z' + vIsLow := v >= 'a' && v <= 'z' + if capNext { + if vIsLow { + v += 'A' + v -= 'a' + } + } else if i == 0 { + if vIsCap { + v += 'a' + v -= 'A' + } + } + if vIsCap || vIsLow || (v >= '0' && v <= '9') { + n.WriteByte(v) + capNext = false + } else { + capNext = v == '_' || v == ' ' || v == '-' || v == '.' + } + } + return n.String() } // ToCamel converts a string to CamelCase func ToCamel(s string) string { - if uppercaseAcronym[s] { - s = strings.ToLower(s) - } return toCamelInitCase(s, true) } // ToLowerCamel converts a string to lowerCamelCase func ToLowerCamel(s string) string { - if s == "" { - return s - } - if uppercaseAcronym[s] { - s = strings.ToLower(s) - } else if r := s[0]; r >= 'A' && r <= 'Z' { - s = strings.ToLower(string(r)) + s[1:] - } return toCamelInitCase(s, false) } diff --git a/snake.go b/snake.go index 159c1b5..fa1f8f4 100644 --- a/snake.go +++ b/snake.go @@ -65,14 +65,40 @@ func ToDelimited(s string, delimiter uint8) string { // (in this case `delimiter = '.'; screaming = false`) func ToScreamingDelimited(s string, delimiter uint8, ignore uint8, screaming bool) string { s = addWordBoundariesToNumbers(s) - s = strings.Trim(s, " ") - n := "" - for i, v := range s { + n := strings.Builder{} + n.Grow(len(s) + 2) // nominal 2 bytes of extra space for inserted delimiters + start := true + spaces := 0 + for i, v := range []byte(s) { + if v == ' ' { + spaces++ + continue + } else if start { + start = false + spaces = 0 + } else { + for ; spaces > 0; spaces-- { + if ignore == ' ' { + n.WriteByte(' ') + } else { + n.WriteByte(delimiter) + } + } + } + + vIsCap := v >= 'A' && v <= 'Z' + vIsLow := v >= 'a' && v <= 'z' + if vIsLow && screaming { + v += 'A' + v -= 'a' + } else if vIsCap && !screaming { + v += 'a' + v -= 'A' + } + // treat acronyms as words, eg for JSONData -> JSON is a whole word if i+1 < len(s) { next := s[i+1] - vIsCap := v >= 'A' && v <= 'Z' - vIsLow := v >= 'a' && v <= 'z' nextIsCap := next >= 'A' && next <= 'Z' nextIsLow := next >= 'a' && next <= 'z' // add underscore if next letter case type is changed @@ -80,12 +106,12 @@ func ToScreamingDelimited(s string, delimiter uint8, ignore uint8, screaming boo if prevIgnore := ignore > 0 && i > 0 && s[i-1] == ignore; !prevIgnore { if vIsCap && nextIsLow { if prevIsCap := i > 0 && s[i-1] >= 'A' && s[i-1] <= 'Z'; prevIsCap { - n += string(delimiter) + n.WriteByte(delimiter) } } - n += string(v) + n.WriteByte(v) if vIsLow { - n += string(delimiter) + n.WriteByte(delimiter) } continue } @@ -94,16 +120,11 @@ func ToScreamingDelimited(s string, delimiter uint8, ignore uint8, screaming boo if (v == ' ' || v == '_' || v == '-') && uint8(v) != ignore { // replace space/underscore/hyphen with delimiter - n += string(delimiter) + n.WriteByte(delimiter) } else { - n += string(v) + n.WriteByte(v) } } - if screaming { - n = strings.ToUpper(n) - } else { - n = strings.ToLower(n) - } - return n + return n.String() }