Skip to content

Commit b1194a4

Browse files
authored
Merge pull request #2 from Splode/feature/case-options
feat: add support for configuring phrase casing
2 parents 65a2ea2 + d211135 commit b1194a4

5 files changed

Lines changed: 142 additions & 20 deletions

File tree

cmd/fname/fname.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func main() {
4242
pflag.Usage = generateUsage
4343

4444
var (
45+
casing string = "lower"
4546
delimiter string
4647
help bool
4748
ver bool
@@ -51,6 +52,7 @@ func main() {
5152
// TODO: add option to use custom dictionary
5253
)
5354

55+
pflag.StringVarP(&casing, "casing", "c", casing, "case of generated names: lower, upper, or title")
5456
pflag.StringVarP(&delimiter, "delimiter", "d", delimiter, "delimiter to use between words")
5557
pflag.IntVarP(&quantity, "quantity", "q", quantity, "number of name phrases to generate")
5658
pflag.UintVarP(&size, "size", "z", size, "number of words per phrase (minimum 2, maximum 4)")
@@ -70,6 +72,14 @@ func main() {
7072
}
7173

7274
opts := []fname.GeneratorOption{}
75+
76+
c, err := fname.ParseCasing(casing)
77+
if err != nil {
78+
fmt.Fprintf(os.Stderr, "error: %s", err)
79+
os.Exit(1)
80+
}
81+
opts = append(opts, fname.WithCasing(c))
82+
7383
if delimiter != "" {
7484
opts = append(opts, fname.WithDelimiter(delimiter))
7585
}

generator.go

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,22 @@ import (
66
"math/rand"
77
"strings"
88
"time"
9+
10+
"golang.org/x/text/cases"
11+
"golang.org/x/text/language"
12+
)
13+
14+
type Casing string
15+
16+
const (
17+
Lower Casing = "lower"
18+
Upper Casing = "upper"
19+
Title Casing = "title"
920
)
1021

1122
// Generator is a random name generator.
1223
type Generator struct {
24+
casing Casing
1325
dict *Dictionary
1426
delimiter string
1527
rand *rand.Rand
@@ -19,61 +31,100 @@ type Generator struct {
1931
// GeneratorOption is a function that configures a Generator.
2032
type GeneratorOption func(*Generator)
2133

34+
// WithCasing sets the casing used to format the generated name.
35+
func WithCasing(casing Casing) GeneratorOption {
36+
return func(g *Generator) {
37+
g.casing = casing
38+
}
39+
}
40+
2241
// WithDelimiter sets the delimiter used to join words.
2342
func WithDelimiter(delimiter string) GeneratorOption {
24-
return func(r *Generator) {
25-
r.delimiter = delimiter
43+
return func(g *Generator) {
44+
g.delimiter = delimiter
2645
}
2746
}
2847

2948
// WithSeed sets the seed used to generate random numbers.
3049
func WithSeed(seed int64) GeneratorOption {
31-
return func(r *Generator) {
32-
r.rand.Seed(seed)
50+
return func(g *Generator) {
51+
g.rand.Seed(seed)
3352
}
3453
}
3554

3655
// WithSize sets the number of words in the generated name.
3756
func WithSize(size uint) GeneratorOption {
38-
return func(r *Generator) {
39-
r.size = size
57+
return func(g *Generator) {
58+
g.size = size
4059
}
4160
}
4261

4362
// NewGenerator creates a new Generator.
4463
func NewGenerator(opts ...GeneratorOption) *Generator {
45-
r := &Generator{
64+
g := &Generator{
65+
casing: Lower,
4666
dict: NewDictionary(),
4767
delimiter: "-",
4868
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
4969
size: 2,
5070
}
5171
for _, opt := range opts {
52-
opt(r)
72+
opt(g)
5373
}
54-
return r
74+
return g
5575
}
5676

5777
// Generate generates a random name.
58-
func (r *Generator) Generate() (string, error) {
78+
func (g *Generator) Generate() (string, error) {
5979
// TODO: address case where adjective and noun are the same, such as "orange-orange" or "sound-sound"
60-
adjective := r.dict.adectives[r.rand.Intn(r.dict.LengthAdjective())]
61-
noun := r.dict.nouns[r.rand.Intn(r.dict.LengthNoun())]
80+
adjective := g.dict.adectives[g.rand.Intn(g.dict.LengthAdjective())]
81+
noun := g.dict.nouns[g.rand.Intn(g.dict.LengthNoun())]
6282
words := []string{adjective, noun}
6383

64-
switch r.size {
84+
switch g.size {
6585
case 2:
66-
return strings.Join(words, r.delimiter), nil
86+
// do nothing
6787
case 3:
68-
verb := r.dict.verbs[r.rand.Intn(r.dict.LengthVerb())]
88+
verb := g.dict.verbs[g.rand.Intn(g.dict.LengthVerb())]
6989
words = append(words, verb)
7090
case 4:
71-
verb := r.dict.verbs[r.rand.Intn(r.dict.LengthVerb())]
91+
verb := g.dict.verbs[g.rand.Intn(g.dict.LengthVerb())]
7292
words = append(words, verb)
73-
adverb := r.dict.adverbs[r.rand.Intn(r.dict.LengthAdverb())]
93+
adverb := g.dict.adverbs[g.rand.Intn(g.dict.LengthAdverb())]
7494
words = append(words, adverb)
7595
default:
76-
return "", fmt.Errorf("invalid size: %d", r.size)
96+
return "", fmt.Errorf("invalid size: %d", g.size)
97+
}
98+
return strings.Join(g.applyCasing(words...), g.delimiter), nil
99+
}
100+
101+
// ParseCasing parses a string into a casing.
102+
func ParseCasing(casing string) (Casing, error) {
103+
switch casing {
104+
case "lower":
105+
return Lower, nil
106+
case "upper":
107+
return Upper, nil
108+
case "title":
109+
return Title, nil
110+
default:
111+
return "", fmt.Errorf("invalid casing: %s", casing)
112+
}
113+
}
114+
115+
var titleCaser = cases.Title(language.English)
116+
117+
var casingMap = map[Casing]func(string) string{
118+
Lower: strings.ToLower,
119+
Upper: strings.ToUpper,
120+
Title: titleCaser.String,
121+
}
122+
123+
func (g *Generator) applyCasing(words ...string) []string {
124+
if fn, ok := casingMap[g.casing]; ok {
125+
for i, word := range words {
126+
words[i] = fn(word)
127+
}
77128
}
78-
return strings.Join(words, r.delimiter), nil
129+
return words
79130
}

generator_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,16 @@ func TestNewGenerator(t *testing.T) {
2424

2525
t.Log("\tWhen creating a new Generator with custom values")
2626
{
27-
g := NewGenerator(WithDelimiter("_"), WithSize(3), WithSeed(12345))
27+
g := NewGenerator(WithCasing(Title), WithDelimiter("_"), WithSize(3), WithSeed(12345))
2828
if g == nil {
2929
t.Fatal("\t\tShould be able to create a Generator instance.")
3030
}
3131
t.Log("\t\tShould be able to create a Generator instance.")
3232

33+
if g.casing != Title {
34+
t.Error("\t\tShould be able to set the casing.")
35+
}
36+
3337
if g.size != 3 {
3438
t.Fatal("\t\tShould be able to set the size of the phrase.")
3539
}
@@ -67,6 +71,21 @@ func TestGenerate(t *testing.T) {
6771
t.Log("\t\tShould be able to generate a phrase with 2 parts.")
6872
}
6973

74+
t.Log("\tWhen generating a phrase with a custom case")
75+
{
76+
g := NewGenerator(WithCasing(Title))
77+
phrase, err := g.Generate()
78+
if err != nil {
79+
t.Fatal("\t\tShould be able to generate a phrase without error.")
80+
}
81+
t.Log("\t\tShould be able to generate a phrase without error.")
82+
83+
c := phrase[0]
84+
if c < 'A' || c > 'Z' {
85+
t.Fatal("\t\tShould be able to generate a phrase with a title case.")
86+
}
87+
}
88+
7089
t.Log("\tWhen generating a phrase with a custom delimiter")
7190
{
7291
g := NewGenerator(WithDelimiter("_"))
@@ -165,3 +184,41 @@ func TestGenerate(t *testing.T) {
165184
}
166185
}
167186
}
187+
188+
func TestParseCasing(t *testing.T) {
189+
t.Log("Given the need to parse casing strings")
190+
{
191+
t.Log("\tWhen parsing a valid casing string")
192+
{
193+
testCases := []struct {
194+
name string
195+
c Casing
196+
}{
197+
{"lower", Lower},
198+
{"upper", Upper},
199+
{"title", Title},
200+
}
201+
for _, tc := range testCases {
202+
c, err := ParseCasing(tc.name)
203+
if err != nil {
204+
t.Fatalf("\t\tShould be able to parse a valid casing string : %v", err)
205+
}
206+
t.Log("\t\tShould be able to parse a valid casing string")
207+
208+
if c != tc.c {
209+
t.Fatalf("\t\tShould be able to parse a valid casing string : got %v, want %v", c, tc.c)
210+
}
211+
t.Log("\t\tShould be able to parse a valid casing string")
212+
}
213+
}
214+
215+
t.Log("\tWhen parsing an invalid casing string")
216+
{
217+
_, err := ParseCasing("invalid")
218+
if err == nil {
219+
t.Fatal("\t\tShould not be able to parse an invalid casing string")
220+
}
221+
t.Log("\t\tShould not be able to parse an invalid casing string")
222+
}
223+
}
224+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ module github.com/splode/fname
33
go 1.19
44

55
require github.com/spf13/pflag v1.0.5
6+
7+
require golang.org/x/text v0.3.7

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
22
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
3+
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
4+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

0 commit comments

Comments
 (0)