Skip to content

Commit 6c3a524

Browse files
committed
v0.1.0
1 parent 622da74 commit 6c3a524

File tree

5 files changed

+176
-78
lines changed

5 files changed

+176
-78
lines changed

README.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# caps
2+
3+
caps is a case conversion library for Go. It was built with the following
4+
priorites: configurability, consistency, correctness, ergonomic, and performant
5+
in mind, in that order.
6+
7+
Out of the box, the following case conversion are supported:
8+
9+
- Camel Case (e.g. CamelCase)
10+
- Lower Camel Case (e.g. lowerCamelCase)
11+
- Snake Case (e.g. snake_case)
12+
- Screaming Snake Case (e.g. SCREAMING_SNAKE_CASE)
13+
- Kebab Case (e.g. kebab-case)
14+
- Screaming Kebab Case(e.g. SCREAMING-KEBAB-CASE)
15+
- Dot Notation Case (e.g. dot.notation.case)
16+
- Title Case (e.g. Title Case)
17+
- Other deliminations
18+
19+
Word boundaries are determined by the `caps.Formatter`. The provided implementation, `caps.FormatterImpl`,
20+
delegates the boundary detection to `caps.Tokenizer`. The provided implementation, `caps.TokenizerImpl`,
21+
uses the following tokens as delimiters: `" _.!?:;$-(){}[]#@&+~"`.
22+
23+
`caps.StdFormatter` also allows users to register `caps.Replacement`s for acronym replacement. The default list is:
24+
25+
```go
26+
{"Http", "HTTP"}
27+
{"Https", "HTTPS"}
28+
{"Id", "ID"}
29+
{"Ip", "IP"}
30+
{"Html", "HTML"}
31+
{"Xml", "XML"}
32+
{"Json", "JSON"}
33+
{"Csv", "CSV"}
34+
{"Aws", "AWS"}
35+
{"Gcp", "GCP"}
36+
{"Sql", "SQL"}
37+
```
38+
39+
If you would like to add or remove entries from that list, you have a few
40+
options.
41+
42+
You can pass a new instance of `caps.StdFormatter` with a new set of
43+
`caps.Replacement` (likely preferred).
44+
45+
You can create your own `caps.Formatter`. This could be as simple as
46+
implementing the single `Format` method, calling `caps.DefaultFormatter.Format`,
47+
and then modifying the result.
48+
49+
Finally, if you are so inclined, you can update `caps.DefaultFormatter`. Just be aware that the
50+
module was not built with thread-safety in mind so you should set it once.
51+
Otherwise, you'll need guard your usage of the library accordingly.
52+
53+
## License
54+
55+
MIT

caps.go

+31-26
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type Tokenizer interface {
9090
//
9191
// style: Expected output caps.Style of the string.
9292
// repStyle: The caps.ReplaceStyle to use if a word needs to be replaced.
93-
// join: The delimiter to use when joining the words. For CamelCase, this is an empty string.
93+
// join: The delimiter to use when joining the words. For Camel, this is an empty string.
9494
// allowedSymbols: The set of allowed symbols. If set, these should take precedence over any delimiters
9595
// numberRules: Any custom rules dictating how to handle special characters in numbers.
9696
type Formatter interface {
@@ -153,8 +153,6 @@ func (t TokenizerImpl) Tokenize(str string, allowedSymbols []rune, numberRules m
153153
allowed := newRunes(allowedSymbols)
154154

155155
for i, r := range str {
156-
rStr := string(r)
157-
_ = rStr
158156
switch {
159157
case unicode.IsUpper(r):
160158
if foundLower && current.Len() > 0 {
@@ -304,10 +302,10 @@ type ReplaceStyle uint8
304302

305303
type (
306304
Replacement struct {
307-
// Camelcase variant of the word which should be replaced.
305+
// Camel case variant of the word which should be replaced.
308306
// e.g. "Http"
309307
Camel string
310-
// Screaming (all uppercase) representation of the word to replace.
308+
// Screaming (all upper case) representation of the word to replace.
311309
// e.g. "HTTP"
312310
Screaming string
313311
}
@@ -318,8 +316,8 @@ type Style uint8
318316
const (
319317
StyleLower Style = iota // The output should be lowercase (e.g. "an_example")
320318
StyleScreaming // The output should be screaming (e.g. "AN_EXAMPLE")
321-
StyleCamel // The output should be camelcase (e.g. "AnExample")
322-
StyleLowerCamel // The output should be lower camelcase (e.g. "anExample")
319+
StyleCamel // The output should be camel case (e.g. "AnExample")
320+
StyleLowerCamel // The output should be lower camel case (e.g. "anExample")
323321
)
324322

325323
const (
@@ -616,7 +614,7 @@ func loadOpts(opts []Opts) Opts {
616614
result := Opts{
617615
AllowedSymbols: "",
618616
Formatter: DefaultFormatter,
619-
ReplaceStyle: ReplaceStyleNotSpecified,
617+
ReplaceStyle: ReplaceStyleScreaming,
620618
}
621619
if len(opts) == 0 {
622620
return result
@@ -628,6 +626,13 @@ func loadOpts(opts []Opts) Opts {
628626
if opts[0].Formatter != nil {
629627
result.Formatter = opts[0].Formatter
630628
}
629+
if opts[0].ReplaceStyle != ReplaceStyleNotSpecified {
630+
result.ReplaceStyle = opts[0].ReplaceStyle
631+
}
632+
if opts[0].NumberRules != nil {
633+
result.NumberRules = opts[0].NumberRules
634+
}
635+
631636
return result
632637
}
633638

@@ -653,22 +658,22 @@ func WithoutNumbers[T ~string](s T) T {
653658
}, string(s)))
654659
}
655660

656-
// ToCamel transforms the case of str into Camelcase (e.g. AnExampleString) using
661+
// ToCamel transforms the case of str into Camel Case (e.g. AnExampleString) using
657662
// either the provided Formatter or the DefaultFormatter otherwise.
658663
//
659664
// The default Formatter detects case so that "AN_EXAMPLE_STRING" becomes "AnExampleString".
660665
// It also has a configurable set of replacements, such that "some_json" becomes "SomeJSON"
661666
// so long as opts.ReplacementStyle is set to ReplaceStyleScreaming. A ReplaceStyle of
662667
// ReplaceStyleCamel would result in "SomeJson".
663668
//
664-
// caps.ToCamel("This is [an] {example}${id32}.") // thisIsAnExampleID32
665-
// caps.ToCamel("AN_EXAMPLE_STRING", ) // anExampleString
669+
// caps.ToCamel("This is [an] {example}${id32}.") // ThisIsAnExampleID32
670+
// caps.ToCamel("AN_EXAMPLE_STRING", ) // AnExampleString
666671
func ToCamel[T ~string](str T, options ...Opts) T {
667672
opts := loadOpts(options)
668673
return T(opts.Formatter.Format(StyleCamel, opts.ReplaceStyle, string(str), "", []rune(opts.AllowedSymbols), opts.NumberRules))
669674
}
670675

671-
// ToLowerCamel transforms the case of str into Lower Camelcase (e.g. anExampleString) using
676+
// ToLowerCamel transforms the case of str into Lower Camel Case (e.g. anExampleString) using
672677
// either the provided Formatter or the DefaultFormatter otherwise.
673678
//
674679
// The default Formatter detects case so that "AN_EXAMPLE_STRING" becomes "anExampleString".
@@ -682,15 +687,15 @@ func ToLowerCamel[T ~string](str T, options ...Opts) T {
682687
return T(opts.Formatter.Format(StyleLowerCamel, opts.ReplaceStyle, string(str), "", []rune(opts.AllowedSymbols), opts.NumberRules))
683688
}
684689

685-
// ToSnake transforms the case of str into Lower Snakecase (e.g. an_example_string) using
690+
// ToSnake transforms the case of str into Lower Snake Case (e.g. an_example_string) using
686691
// either the provided Formatter or the DefaultFormatter otherwise.
687692
//
688693
// caps.ToSnake("This is [an] {example}${id32}.") // this_is_an_example_id_32
689694
func ToSnake[T ~string](str T, options ...Opts) T {
690695
return ToDelimited(str, '_', true, options...)
691696
}
692697

693-
// ToScreamingSnake transforms the case of str into Screaming Snakecase (e.g.
698+
// ToScreamingSnake transforms the case of str into Screaming Snake Case (e.g.
694699
// AN_EXAMPLE_STRING) using either the provided Formatter or the
695700
// DefaultFormatter otherwise.
696701
//
@@ -699,41 +704,41 @@ func ToScreamingSnake[T ~string](str T, options ...Opts) T {
699704
return ToDelimited(str, '_', false, options...)
700705
}
701706

702-
// ToKebab transforms the case of str into Lower Kebabcase (e.g. an-example-string) using
707+
// ToKebab transforms the case of str into Lower Kebab Case (e.g. an-example-string) using
703708
// either the provided Formatter or the DefaultFormatter otherwise.
704709
//
705710
// caps.ToKebab("This is [an] {example}${id32}.") // this-is-an-example-id-32
706711
func ToKebab[T ~string](str T, options ...Opts) T {
707-
return ToDelimited(str, '_', true, options...)
712+
return ToDelimited(str, '-', true, options...)
708713
}
709714

710-
// ToScreamingKebab transforms the case of str into Screaming Kebab (e.g.
715+
// ToScreamingKebab transforms the case of str into Screaming Kebab Snake (e.g.
711716
// AN-EXAMPLE-STRING) using either the provided Formatter or the
712717
// DefaultFormatter otherwise.
713718
//
714-
// caps.ToScreamingSnake("This is [an] {example}${id32}.") // THIS-IS-AN-EXAMPLE-ID-32
719+
// caps.ToScreamingKebab("This is [an] {example}${id32}.") // THIS-IS-AN-EXAMPLE-ID-32
715720
func ToScreamingKebab[T ~string](str T, options ...Opts) T {
716-
return ToDelimited(str, '_', false, options...)
721+
return ToDelimited(str, '-', false, options...)
717722
}
718723

719-
// ToDot transforms the case of str into Lower Dot notation case (e.g. an.example.string) using
724+
// ToDot transforms the case of str into Lower Dot Notation Case (e.g. an.example.string) using
720725
// either the provided Formatter or the DefaultFormatter otherwise.
721726
//
722727
// caps.ToDot("This is [an] {example}${id32}.") // this.is.an.example.id.32
723728
func ToDot[T ~string](str T, options ...Opts) T {
724-
return ToDelimited(str, '_', true, options...)
729+
return ToDelimited(str, '.', true, options...)
725730
}
726731

727-
// ToScreamingKebab transforms the case of str into Screaming Kebab (e.g.
732+
// ToScreamingKebab transforms the case of str into Screaming Kebab Case (e.g.
728733
// AN-EXAMPLE-STRING) using either the provided Formatter or the
729734
// DefaultFormatter otherwise.
730735
//
731736
// caps.ToScreamingDot("This is [an] {example}${id32}.") // THIS.IS.AN.EXAMPLE.ID.32
732737
func ToScreamingDot[T ~string](str T, options ...Opts) T {
733-
return ToDelimited(str, '_', false, options...)
738+
return ToDelimited(str, '.', false, options...)
734739
}
735740

736-
// ToTitle transforms the case of str into Lower Dot notation case (e.g. An Example String) using
741+
// ToTitle transforms the case of str into Title Case (e.g. An Example String) using
737742
// either the provided Formatter or the DefaultFormatter otherwise.
738743
//
739744
// caps.ToTitle("This is [an] {example}${id32}.") // This Is An Example ID 32
@@ -752,8 +757,8 @@ func ToTitle[T ~string](str T, options ...Opts) T {
752757
// # Example
753758
//
754759
// caps.ToDelimited("This is [an] {example}${id}.#32", '.', true) // this.is.an.example.id.32
755-
// caps.ToDelimited("This is [an] {example}${id32}.break32", '.', false) // THIS.IS.AN.EXAMPLE.ID.BREAK.32
756-
// caps.ToDelimited("This is [an] {example}${id32}.v32", '.', true, caps.Opts{AllowedSymbols: "$" }) // this.is.an.example.id.$.v32
760+
// caps.ToDelimited("This is [an] {example}${id}.break32", '.', false) // THIS.IS.AN.EXAMPLE.ID.BREAK.32
761+
// caps.ToDelimited("This is [an] {example}${id}.v32", '.', true, caps.Opts{AllowedSymbols: "$"}) // this.is.an.example.$.id.v32
757762
func ToDelimited[T ~string](str T, delimiter rune, lowercase bool, options ...Opts) T {
758763
opts := loadOpts(options)
759764
var style Style

examples_test.go

+65-27
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,76 @@
11
package caps_test
22

3-
func ExampleToCamel() {
4-
// fmt.Println(caps.ToCamel("This is [an] {example}${id32}."))
5-
// fmt.Println(caps.ToCamel("This is [an] {example}${id32}.break32"))
6-
// fmt.Println(caps.ToCamel("This example allows for $ symbols", caps.Opts{AllowedSymbols: "$"}))
3+
import (
4+
"fmt"
75

8-
// customReplacer := caps.NewFormatter([]caps.R{{"Http", "HTTP"}, {"Https", "HTTPS"}})
9-
// fmt.Println(caps.ToCamel("No Id just http And Https", caps.Opts{Formatter: customReplacer}))
6+
"github.com/chanced/caps"
7+
)
108

11-
// Outputx:
9+
func ExampleToCamel() {
10+
fmt.Println(caps.ToCamel("This is [an] {example}${id32}."))
11+
fmt.Println(caps.ToCamel("AN_EXAMPLE_STRING"))
12+
// Output:
13+
// ThisIsAnExampleID32
14+
// AnExampleString
15+
}
16+
17+
func ExampleToLowerCamel() {
18+
fmt.Println(caps.ToLowerCamel("This is [an] {example}${id32}."))
19+
// Output:
1220
// thisIsAnExampleID32
13-
// thisIsAnExampleID32Break32
14-
// thisExampleAllowsFor$symbols
15-
// noIdJustHTTPAndHTTPS
21+
}
22+
23+
func ExampleToSnake() {
24+
fmt.Println(caps.ToSnake("This is [an] {example}${id32}."))
25+
fmt.Println(caps.ToSnake("v3.2.2"))
26+
// Output:
27+
// this_is_an_example_id_32
28+
// v3_2_2
29+
}
30+
31+
func ExampleToScreamingSnake() {
32+
fmt.Println(caps.ToScreamingSnake("This is [an] {example}${id32}."))
33+
// Output:
34+
// THIS_IS_AN_EXAMPLE_ID_32
35+
}
36+
37+
func ExampleToKebab() {
38+
fmt.Println(caps.ToKebab("This is [an] {example}${id32}."))
39+
// Output:
40+
// this-is-an-example-id-32
41+
}
42+
43+
func ExampleToScreamingKebab() {
44+
fmt.Println(caps.ToScreamingKebab("This is [an] {example}${id32}."))
45+
// Output:
46+
// THIS-IS-AN-EXAMPLE-ID-32
47+
}
48+
49+
func ExampleToDot() {
50+
fmt.Println(caps.ToDot("This is [an] {example}${id32}."))
51+
// Output:
52+
// this.is.an.example.id.32
53+
}
54+
55+
func ExampleToScreamingDot() {
56+
fmt.Println(caps.ToScreamingDot("This is [an] {example}${id32}."))
57+
// Output:
58+
// THIS.IS.AN.EXAMPLE.ID.32
1659
}
1760

1861
func ExampleToDelimited() {
19-
// fmt.Println(caps.ToDelimited("A # B _ C", '.', true))
20-
// fmt.Println(caps.ToDelimited("$id", '.', false))
21-
// fmt.Println(caps.ToDelimited("$id", '.', true, caps.Opts{AllowedSymbols: "$"}))
22-
// fmt.Println(caps.ToDelimited("fromCamelcaseString", '.', true))
23-
// Outputx:
24-
// a.b.c
25-
// ID
26-
// $id
27-
// from.camelcase.string
62+
fmt.Println(caps.ToDelimited("This is [an] {example}${id}.#32", '.', true))
63+
fmt.Println(caps.ToDelimited("This is [an] {example}${id}.break32", '.', false))
64+
fmt.Println(caps.ToDelimited("This is [an] {example}${id}.v32", '.', true, caps.Opts{AllowedSymbols: "$"}))
65+
66+
// Output:
67+
// this.is.an.example.id.32
68+
// THIS.IS.AN.EXAMPLE.ID.BREAK.32
69+
// this.is.an.example.$.id.v32
2870
}
2971

30-
func ExampleToSnake() {
31-
// fmt.Println(caps.ToSnake("A long string with spaces"))
32-
// fmt.Println(caps.ToSnake(strings.ToLower("A_SCREAMING_SNAKE_MUST_BE_LOWERED_FIRST")))
33-
// fmt.Println(caps.ToSnake("$word", caps.Opts{AllowedSymbols: "$"}))
34-
// OutputX:
35-
// a_long_string_with_spaces
36-
// a_screaming_snake_must_be_lowered_first
37-
// $word
72+
func ExampleToTitle() {
73+
fmt.Println(caps.ToTitle("This is [an] {example}${id32}."))
74+
// Output:
75+
// This Is An Example ID 32
3876
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/chanced/caps
22

3-
go 1.19
3+
go 1.18

text/text.go

+24-24
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
package text
22

3-
import "strings"
3+
// import "strings"
44

5-
type Text string
5+
// type Text string
66

7-
func (t Text) String() string {
8-
return string(t)
9-
}
7+
// func (t Text) String() string {
8+
// return string(t)
9+
// }
1010

11-
func (t Text) ToLower() Text {
12-
return Text(strings.ToLower(t.String()))
13-
}
11+
// func (t Text) ToLower() Text {
12+
// return Text(strings.ToLower(t.String()))
13+
// }
1414

15-
func (t Text) ToUpper() Text {
16-
return Text(strings.ToUpper(t.String()))
17-
}
15+
// func (t Text) ToUpper() Text {
16+
// return Text(strings.ToUpper(t.String()))
17+
// }
1818

19-
func (t Text) ToSnake() Text {
20-
panic("not implemented")
21-
}
19+
// func (t Text) ToSnake() Text {
20+
// panic("not implemented")
21+
// }
2222

23-
func (t Text) ToScreamingSnake() Text {
24-
// return Text(strcase.ToScreamingSnake(t.String()))
25-
panic("not implemented")
26-
}
23+
// func (t Text) ToScreamingSnake() Text {
24+
// // return Text(strcase.ToScreamingSnake(t.String()))
25+
// panic("not implemented")
26+
// }
2727

28-
func (t Text) ReplaceAll(old string, new string) Text {
29-
return Text(strings.Replace(t.String(), old, new, -1))
30-
}
28+
// func (t Text) ReplaceAll(old string, new string) Text {
29+
// return Text(strings.Replace(t.String(), old, new, -1))
30+
// }
3131

32-
func (t Text) Replace(old, new string, n int) Text {
33-
return Text(strings.Replace(t.String(), old, new, n))
34-
}
32+
// func (t Text) Replace(old, new string, n int) Text {
33+
// return Text(strings.Replace(t.String(), old, new, n))
34+
// }

0 commit comments

Comments
 (0)