Skip to content

Commit b026717

Browse files
committed
feat: first commit
0 parents  commit b026717

10 files changed

Lines changed: 508 additions & 0 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2020 LoopContext, <github.com/LoopContext>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, Subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or Substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# anycase
2+
3+
[![Godoc Reference](https://godoc.org/github.com/loopcontext/anycase?status.svg)](http://godoc.org/github.com/loopcontext/anycase)
4+
[![Build Status](https://travis-ci.org/loopcontext/anycase.svg)](https://travis-ci.org/loopcontext/anycase)
5+
[![Coverage](http://gocover.io/_badge/github.com/loopcontext/anycase?0)](http://gocover.io/github.com/loopcontext/anycase)
6+
[![Go Report Card](https://goreportcard.com/badge/github.com/loopcontext/anycase)](https://goreportcard.com/report/github.com/loopcontext/anycase)
7+
8+
anycase is a go package for converting string case to various cases (e.g.
9+
[snake case](https://en.wikipedia.org/wiki/Snake_case) or
10+
[camel case](https://en.wikipedia.org/wiki/CamelCase)) or
11+
[kebab case](https://en.wiktionary.org/wiki/kebab_case)) to see the full conversion table below.
12+
13+
## Install
14+
15+
```bash
16+
go get -u github.com/loopcontext/anycase
17+
```
18+
19+
## Example
20+
21+
```go
22+
s := "AnyKind of_string"
23+
```
24+
25+
| Function | Result |
26+
| ----------------------------------------- | -------------------- |
27+
| `ToSnake(s)` | `any_kind_of_string` |
28+
| `ToSnakeAndIgnore(s, '.')` | `any_kind.of_string` |
29+
| `ToSnakeUppercase(s)` | `ANY_KIND_OF_STRING` |
30+
| `ToKebab(s)` | `any-kind-of-string` |
31+
| `ToKebabUppercase(s)` | `ANY-KIND-OF-STRING` |
32+
| `ToCamel(s)` | `AnyKindOfString` |
33+
| `ToLowerCamel(s)` | `anyKindOfString` |
34+
| `ToDelimited(s, '.')` | `any.kind.of.string` |
35+
| `ToDelimitedUppercase(s, '.', ' ', true)` | `ANY.KIND OF.STRING` |
36+
| `ToDelimitedUppercase(s, '.', '', true)` | `ANY.KIND.OF.STRING` |

acronyms.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package anycase
2+
3+
var uppercaseAcronym = map[string]bool{
4+
"ID": true,
5+
}

camel.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 anycase
27+
28+
import (
29+
"strings"
30+
)
31+
32+
// Converts a string to CamelCase
33+
func toCamelInitCase(s string, initCase bool) string {
34+
s = addWordBoundariesToNumbers(s)
35+
s = strings.Trim(s, " ")
36+
n := ""
37+
capNext := initCase
38+
for _, v := range s {
39+
if v >= 'A' && v <= 'Z' {
40+
n += string(v)
41+
}
42+
if v >= '0' && v <= '9' {
43+
n += string(v)
44+
}
45+
if v >= 'a' && v <= 'z' {
46+
if capNext {
47+
n += strings.ToUpper(string(v))
48+
} else {
49+
n += string(v)
50+
}
51+
}
52+
if v == '_' || v == ' ' || v == '-' || v == '.' {
53+
capNext = true
54+
} else {
55+
capNext = false
56+
}
57+
}
58+
return n
59+
}
60+
61+
// ToCamel converts a string to CamelCase
62+
func ToCamel(s string) string {
63+
if uppercaseAcronym[s] {
64+
s = strings.ToLower(s)
65+
}
66+
return toCamelInitCase(s, true)
67+
}
68+
69+
// ToLowerCamel converts a string to lowerCamelCase
70+
func ToLowerCamel(s string) string {
71+
if s == "" {
72+
return s
73+
}
74+
if uppercaseAcronym[s] {
75+
s = strings.ToLower(s)
76+
}
77+
if r := rune(s[0]); r >= 'A' && r <= 'Z' {
78+
s = strings.ToLower(string(r)) + s[1:]
79+
}
80+
return toCamelInitCase(s, false)
81+
}

camel_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 Ian Coleman
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, Subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or Substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package anycase
26+
27+
import (
28+
"testing"
29+
)
30+
31+
func TestToCamel(t *testing.T) {
32+
cases := [][]string{
33+
{"test_case", "TestCase"},
34+
{"test.case", "TestCase"},
35+
{"test", "Test"},
36+
{"TestCase", "TestCase"},
37+
{" test case ", "TestCase"},
38+
{"", ""},
39+
{"many_many_words", "ManyManyWords"},
40+
{"AnyKind of_string", "AnyKindOfString"},
41+
{"odd-fix", "OddFix"},
42+
{"numbers2And55with000", "Numbers2And55With000"},
43+
{"ID", "Id"},
44+
}
45+
for _, i := range cases {
46+
in := i[0]
47+
out := i[1]
48+
result := ToCamel(in)
49+
if result != out {
50+
t.Error("'" + result + "' != '" + out + "'")
51+
}
52+
}
53+
}
54+
55+
func TestToLowerCamel(t *testing.T) {
56+
cases := [][]string{
57+
{"foo-bar", "fooBar"},
58+
{"TestCase", "testCase"},
59+
{"", ""},
60+
{"AnyKind of_string", "anyKindOfString"},
61+
{"AnyKind.of-string", "anyKindOfString"},
62+
{"ID", "id"},
63+
}
64+
for _, i := range cases {
65+
in := i[0]
66+
out := i[1]
67+
result := ToLowerCamel(in)
68+
if result != out {
69+
t.Error("'" + result + "' != '" + out + "'")
70+
}
71+
}
72+
}

doc.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Package anycase converts strings to various cases. See the conversion table below:
2+
// | Function | Result |
3+
// | ----------------------------------------- | -------------------- |
4+
// | `ToSnake(s)` | `any_kind_of_string` |
5+
// | `ToSnakeAndIgnore(s, '.')` | `any_kind.of_string` |
6+
// | `ToSnakeUppercase(s)` | `ANY_KIND_OF_STRING` |
7+
// | `ToKebab(s)` | `any-kind-of-string` |
8+
// | `ToKebabUppercase(s)` | `ANY-KIND-OF-STRING` |
9+
// | `ToCamel(s)` | `AnyKindOfString` |
10+
// | `ToLowerCamel(s)` | `anyKindOfString` |
11+
// | `ToDelimited(s, '.')` | `any.kind.of.string` |
12+
// | `ToDelimitedUppercase(s, '.', ' ', true)` | `ANY.KIND OF.STRING` |
13+
// | `ToDelimitedUppercase(s, '.', '', true)` | `ANY.KIND.OF.STRING` |
14+
package anycase

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/loopcontext/anycase
2+
3+
go 1.14

numbers.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package anycase
2+
3+
import (
4+
"regexp"
5+
)
6+
7+
var numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
8+
var numberReplacement = []byte(`$1 $2 $3`)
9+
10+
func addWordBoundariesToNumbers(s string) string {
11+
b := []byte(s)
12+
b = numberSequence.ReplaceAll(b, numberReplacement)
13+
return string(b)
14+
}

snake.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package anycase
2+
3+
import (
4+
"strings"
5+
)
6+
7+
// ToSnake converts a string to snake_case
8+
func ToSnake(s string) string {
9+
10+
return ToDelimited(s, '_')
11+
}
12+
13+
// ToSnakeAndIgnore converts to snake_case and ignores the following characters arg
14+
func ToSnakeAndIgnore(s string, ignore uint8) string {
15+
16+
return ToDelimitedUppercase(s, '_', ignore, false)
17+
}
18+
19+
// ToSnakeUppercase converts a string to UPPERCASE_SNAKE_CASE
20+
func ToSnakeUppercase(s string) string {
21+
return ToDelimitedUppercase(s, '_', 0, true)
22+
}
23+
24+
// ToKebab converts a string to kebab-case
25+
func ToKebab(s string) string {
26+
return ToDelimited(s, '-')
27+
}
28+
29+
// ToKebabUppercase converts a string to UPPERCASE-KEBAB-CASE
30+
func ToKebabUppercase(s string) string {
31+
return ToDelimitedUppercase(s, '-', 0, true)
32+
}
33+
34+
// ToDelimited converts a string to delimited.snake.case
35+
// (in this case `delimiter = '.'`)
36+
func ToDelimited(s string, delimiter uint8) string {
37+
return ToDelimitedUppercase(s, delimiter, 0, false)
38+
}
39+
40+
// ToDelimitedUppercase converts a string to UPPERCASE.DELIMITED.SNAKE.CASE
41+
// (in this case `delimiter = '.'; uppercase = true`)
42+
// or delimited.snake.case
43+
// (in this case `delimiter = '.'; uppercase = false`)
44+
func ToDelimitedUppercase(s string, delimiter uint8, ignore uint8, uppercase bool) string {
45+
s = addWordBoundariesToNumbers(s)
46+
s = strings.Trim(s, " ")
47+
n := ""
48+
for i, v := range s {
49+
// treat acronyms as words, eg for JSONData -> JSON is a whole word
50+
nextCaseIsChanged := false
51+
if i+1 < len(s) {
52+
next := s[i+1]
53+
vIsCap := v >= 'A' && v <= 'Z'
54+
vIsLow := v >= 'a' && v <= 'z'
55+
nextIsCap := next >= 'A' && next <= 'Z'
56+
nextIsLow := next >= 'a' && next <= 'z'
57+
if (vIsCap && nextIsLow) || (vIsLow && nextIsCap) {
58+
nextCaseIsChanged = true
59+
}
60+
if ignore > 0 && i-1 >= 0 && s[i-1] == ignore && nextCaseIsChanged {
61+
nextCaseIsChanged = false
62+
}
63+
}
64+
65+
if i > 0 && n[len(n)-1] != delimiter && nextCaseIsChanged {
66+
// add underscore if next letter case type is changed
67+
if v >= 'A' && v <= 'Z' {
68+
n += string(delimiter) + string(v)
69+
} else if v >= 'a' && v <= 'z' {
70+
n += string(v) + string(delimiter)
71+
}
72+
} else if v == ' ' || v == '_' || v == '-' {
73+
// replace spaces/underscores with delimiters
74+
if uint8(v) == ignore {
75+
n += string(v)
76+
} else {
77+
n += string(delimiter)
78+
}
79+
} else {
80+
n = n + string(v)
81+
}
82+
}
83+
84+
if uppercase {
85+
n = strings.ToUpper(n)
86+
} else {
87+
n = strings.ToLower(n)
88+
}
89+
return n
90+
}

0 commit comments

Comments
 (0)