diff --git a/Makefile b/Makefile
index ac98b4a..41ab05b 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,6 @@ test:
lint:
which golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1
golangci-lint run
- golangci-lint run benchmark/*.go
go mod tidy
benchmark:
diff --git a/README.md b/README.md
index a984da8..862ed09 100644
--- a/README.md
+++ b/README.md
@@ -514,11 +514,13 @@ const (
-## type [WordCase](./convert.go#L6)
+## type [WordCase](./convert.go#L11)
``` go
type WordCase int
```
WordCase is an enumeration of the ways to format a word.
+The first 16 bits are base casers
+The second 16 bits are options
``` go
@@ -535,10 +537,39 @@ const (
// Notably, even if the first word is an initialism, it will be lower
// cased. This is important for code generators where capital letters
// mean exported functions. i.e. jsonString(), not JSONString()
+ //
+ // Use CamelCase|InitialismFirstWord (see options below) if you want to
+ // have initialisms like JSONString
CamelCase
)
```
+``` go
+const (
+
+ // InitialismFirstWord will allow CamelCase to start with an upper case
+ // letter if it is an initialism. Only impacts CamelCase.
+ // LowerCase will initialize all specified initialisms, regardless of position.
+ //
+ // e.g ToGoCase("jsonString", CamelCase|InitialismFirstWord, 0) == "JSONString"
+ InitialismFirstWord WordCase = 1 << 17
+
+ // PreserveInitialism will treat any capitalized words as initialisms
+ // If the entire word is all upper case, keep them upper case.
+ //
+ // Note that you may also use InitialismFirstWord with PreserveInitialism
+ // e.g. CamelCase|InitialismFirstWord|PreserveInitialism: NASA-rocket -> NASARocket
+ // e.g. CamelCase|PreserveInitialism: NASA-rocket -> nasaRocket
+ //
+ // Works for LowerCase, TitleCase, and CamelCase. No impact on Original
+ // and UpperCase.
+ //
+ // Not recommended when the input is in SCREAMING_SNAKE_CASE
+ // as all words will be treated as initialisms.
+ PreserveInitialism WordCase = 1 << 16
+)
+```
+
diff --git a/convert.go b/convert.go
index cb901d0..1346f1d 100644
--- a/convert.go
+++ b/convert.go
@@ -1,8 +1,13 @@
package strcase
-import "strings"
+import (
+ "strings"
+ "unicode"
+)
// WordCase is an enumeration of the ways to format a word.
+// The first 16 bits are base casers
+// The second 16 bits are options
type WordCase int
const (
@@ -18,9 +23,36 @@ const (
// Notably, even if the first word is an initialism, it will be lower
// cased. This is important for code generators where capital letters
// mean exported functions. i.e. jsonString(), not JSONString()
+ //
+ // Use CamelCase|InitialismFirstWord (see options below) if you want to
+ // have initialisms like JSONString
CamelCase
)
+const (
+ wordCaseMask = 0xFFFF
+ // InitialismFirstWord will allow CamelCase to start with an upper case
+ // letter if it is an initialism. Only impacts CamelCase.
+ // LowerCase will initialize all specified initialisms, regardless of position.
+ //
+ // e.g ToGoCase("jsonString", CamelCase|InitialismFirstWord, 0) == "JSONString"
+ InitialismFirstWord WordCase = 1 << 17
+
+ // PreserveInitialism will treat any capitalized words as initialisms
+ // If the entire word is all upper case, keep them upper case.
+ //
+ // Note that you may also use InitialismFirstWord with PreserveInitialism
+ // e.g. CamelCase|InitialismFirstWord|PreserveInitialism: NASA-rocket -> NASARocket
+ // e.g. CamelCase|PreserveInitialism: NASA-rocket -> nasaRocket
+ //
+ // Works for LowerCase, TitleCase, and CamelCase. No impact on Original
+ // and UpperCase.
+ //
+ // Not recommended when the input is in SCREAMING_SNAKE_CASE
+ // as all words will be treated as initialisms.
+ PreserveInitialism WordCase = 1 << 16
+)
+
// We have 3 convert functions for performance reasons
// The general convert could handle everything, but is not optimized
//
@@ -67,7 +99,7 @@ func convertWithoutInitialisms(input string, delimiter rune, wordCase WordCase)
}
inWord = false
}
- switch wordCase {
+ switch wordCase & wordCaseMask {
case UpperCase:
b.WriteRune(toUpper(curr))
case LowerCase:
@@ -131,7 +163,7 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s
}
w := word.String()
if golintInitialisms[w] {
- if !firstWord || wordCase != CamelCase {
+ if !firstWord || wordCase&wordCaseMask != CamelCase || wordCase&InitialismFirstWord != 0 {
b.WriteString(w)
firstWord = false
return
@@ -141,7 +173,7 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s
for i := start; i < end; i++ {
r := runes[i]
- switch wordCase {
+ switch wordCase & wordCaseMask {
case UpperCase:
panic("use convertWithoutInitialisms instead")
case LowerCase:
@@ -235,13 +267,30 @@ func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase,
}
w := word.String()
if initialisms[w] {
- if !firstWord || wordCase != CamelCase {
+ if !firstWord || wordCase&wordCaseMask != CamelCase || wordCase&InitialismFirstWord != 0 {
b.WriteString(w)
firstWord = false
return
}
}
}
+ // If we're preserving initialism, check to see if the entire word is
+ // an initialism.
+ // Note we don't support preserving initialisms if they are followed
+ // by a number and we're not spliting before numbers
+ if !firstWord || wordCase&InitialismFirstWord != 0 || wordCase&wordCaseMask != CamelCase {
+ if wordCase&PreserveInitialism != 0 {
+ allCaps := true
+ for i := start; i < end; i++ {
+ allCaps = allCaps && (isUpper(runes[i]) || !unicode.IsLetter(runes[i]))
+ }
+ if allCaps {
+ b.WriteString(string(runes[start:end]))
+ firstWord = false
+ return
+ }
+ }
+ }
skipIdx := 0
for i := start; i < end; i++ {
@@ -250,7 +299,7 @@ func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase,
continue
}
r := runes[i]
- switch wordCase {
+ switch wordCase & wordCaseMask {
case UpperCase:
b.WriteRune(toUpper(r))
case LowerCase:
diff --git a/strcase_test.go b/strcase_test.go
index ce56809..1736cec 100644
--- a/strcase_test.go
+++ b/strcase_test.go
@@ -23,6 +23,51 @@ func TestEdges(t *testing.T) {
})
}
+func TestOrignal(t *testing.T) {
+ // In the plain ToCase, we don't support any initialisms
+ assertEqual(t, "nativeOrgUrl", ToCase("NativeOrgURL", CamelCase, 0))
+ assertEqual(t, "nativeOrgUrl", ToCase("NativeOrgUrl", CamelCase|PreserveInitialism, 0))
+ assertEqual(t, "nativeOrgUrl", ToCase("NativeOrgUrl", CamelCase|InitialismFirstWord, 0))
+
+ // For ToGoCase, preserve initialism will do nothing since we ony initialize
+ // Go initialisms
+ assertEqual(t, "nativeOrgURL", ToGoCase("NativeOrgUrl", CamelCase, 0))
+ assertEqual(t, "nativeOrgURL", ToGoCase("NativeOrgURL", CamelCase, 0))
+ assertEqual(t, "nativeOrgURL", ToGoCase("NativeOrgURL", CamelCase|PreserveInitialism, 0))
+ // But InitialismFirstWord will impact camelcase
+ assertEqual(t, "JSONString", ToGoCase("jsonString", CamelCase|InitialismFirstWord, 0))
+ // LowerCase and others will initialize all words already
+ assertEqual(t, "JSON-string", ToGoCase("jsonString", LowerCase, '-'))
+
+ caser := NewCaser(false, nil, nil)
+ assertEqual(t, "native-org-url", caser.ToCase("NativeOrgURL", LowerCase, '-'))
+ assertEqual(t, "native-org-URL", caser.ToCase("NativeOrgURL", LowerCase|PreserveInitialism, '-'))
+ assertEqual(t, "native-org-url", caser.ToCase("NativeOrgUrl", LowerCase|PreserveInitialism, '-'))
+ assertEqual(t, "JSON-string", caser.ToCase("JSONString", LowerCase|PreserveInitialism, '-'))
+ assertEqual(t, "json-string", caser.ToCase("jsonString", LowerCase|PreserveInitialism, '-'))
+
+ assertEqual(t, "nativeOrgUrl", caser.ToCase("NativeOrgURL", CamelCase, 0))
+ assertEqual(t, "nativeOrgUrl", caser.ToCase("NativeOrgUrl", CamelCase|PreserveInitialism, 0))
+ assertEqual(t, "nativeOrgURL", caser.ToCase("NativeOrgURL", CamelCase|PreserveInitialism, 0))
+
+ assertEqual(t, "jsonString", caser.ToCase("JSONString", CamelCase|PreserveInitialism, 0))
+ assertEqual(t, "jsonString", caser.ToCase("jsonString", CamelCase|PreserveInitialism, 0))
+ assertEqual(t, "JSONString", caser.ToCase("JSONString", CamelCase|PreserveInitialism|InitialismFirstWord, 0))
+ assertEqual(t, "jsonString", caser.ToCase("JSONString", CamelCase|InitialismFirstWord, 0))
+
+ assertEqual(t, "NASA-rocket", caser.ToCase("NASARocket", LowerCase|PreserveInitialism, '-'))
+ assertEqual(t, "nasa-rocket", caser.ToCase("NasaRocket", LowerCase|PreserveInitialism, '-'))
+ assertEqual(t, "nasa-rocket", caser.ToCase("NASARocket", LowerCase, '-'))
+
+ assertEqual(t, "ps4", caser.ToCase("ps4", LowerCase, '-'))
+ assertEqual(t, "PS4", caser.ToCase("PS4", LowerCase|PreserveInitialism, '-'))
+ assertEqual(t, "ps4", caser.ToCase("Ps4", LowerCase|PreserveInitialism, '-'))
+ assertEqual(t, "ps4", caser.ToCase("ps4", LowerCase, '-'))
+
+ // Not a great option if you're coming from an all-caps case
+ assertEqual(t, "SCREAMING-CASE", caser.ToCase("SCREAMING_CASE", LowerCase|PreserveInitialism, '-'))
+}
+
func TestAll(t *testing.T) {
// Instead of testing, we can generate the outputs to make it easier to
// add more test cases or functions