forked from hbarnardt/hb_migrations
-
Notifications
You must be signed in to change notification settings - Fork 1
/
casing.go
209 lines (182 loc) · 5.32 KB
/
casing.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package migrations
import (
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/pkg/errors"
)
var (
// ErrUnknownNamingConvention indicates that an attempt was made to
// create a migration, without specifying a name.
ErrUnknownNamingConvention = errors.New("unknown naming convention")
)
// MigrationNameConvention represents a naming convention in terms of
// casing and underscores for a Migrator.
type MigrationNameConvention string
const (
// CamelCase represents a camelCase naming convention.
CamelCase MigrationNameConvention = "camelCase"
// SnakeCase represents a snake_case naming convention.
SnakeCase MigrationNameConvention = "snakeCase"
)
// Caser is intended to convert a description to a particular naming
// convention. It should take spaces into account.
type Caser interface {
// ToFileCase converts a description to the casing required for
// a filename. There should not be any spaces in the output.
ToFileCase(time.Time, string) string
// ToFileCase converts a description to the casing required for
// a function name. There should not be any spaces in the output.
ToFuncCase(time.Time, string) string
}
// GetCaser returns the appropriate caser for the given naming convention.
func GetCaser(convention MigrationNameConvention) (Caser, error) {
switch convention {
case SnakeCase:
return SnakeCaser{}, nil
case CamelCase:
return CamelCaser{}, nil
default:
err := errors.Wrapf(
ErrUnknownNamingConvention,
"unknown convention %s",
convention,
)
return nil, err
}
}
var _ Caser = (*SnakeCaser)(nil)
// SnakeCaser will attempt to use snake_case for filenames.
type SnakeCaser struct{}
func (cc SnakeCaser) ToFileCase(date time.Time, input string) string {
// Panicking here is acceptable, because builder.WriteString
// should only ever return an error when out of memory.
builder := strings.Builder{}
_, err := builder.WriteString(date.Format("20060102150405"))
if err != nil {
panic(err)
}
_, err = builder.WriteRune('_')
if err != nil {
panic(err)
}
description := ConvertCamelCaseToSnakeCase(input)
_, err = builder.WriteString(description)
if err != nil {
panic(err)
}
return builder.String()
}
func (cc SnakeCaser) ToFuncCase(date time.Time, input string) string {
// Panicking here is acceptable, because builder.WriteString
// should only ever return an error when out of memory.
builder := strings.Builder{}
_, err := builder.WriteString(date.Format("20060102150405"))
if err != nil {
panic(err)
}
description := ConvertSnakeCaseToCamelCase(input)
_, err = builder.WriteString(description)
if err != nil {
panic(err)
}
return builder.String()
}
var _ Caser = (*CamelCaser)(nil)
// SnakeCaser will attempt to use camelCase for filenames.
type CamelCaser struct{}
func (cc CamelCaser) ToFileCase(date time.Time, input string) string {
// Panicking here is acceptable, because builder.WriteString
// should only ever return an error when out of memory.
builder := strings.Builder{}
_, err := builder.WriteString(date.Format("20060102150405"))
if err != nil {
panic(err)
}
description := ConvertSnakeCaseToCamelCase(input)
_, err = builder.WriteString(description)
if err != nil {
panic(err)
}
return builder.String()
}
func (cc CamelCaser) ToFuncCase(date time.Time, input string) string {
// Panicking here is acceptable, because builder.WriteString
// should only ever return an error when out of memory.
builder := strings.Builder{}
_, err := builder.WriteString(date.Format("20060102150405"))
if err != nil {
panic(err)
}
description := ConvertSnakeCaseToCamelCase(input)
_, err = builder.WriteString(description)
if err != nil {
panic(err)
}
return builder.String()
}
// ConvertCamelCaseToSnakeCase converts a potentially camel-case
// string to snake-case. Should be Unicode-safe.
//
// Spaces are converted to underscores and any uppercase letters
// are replaced with an underscore and the lowercase version of
// the same letter.
func ConvertCamelCaseToSnakeCase(word string) (result string) {
if len(word) == 0 {
return ""
}
var err error
builder := &strings.Builder{}
char, _ := utf8.DecodeRuneInString(word)
_, err = builder.WriteRune(unicode.ToLower(char))
if err != nil {
panic(err)
}
var prevWordBoundary bool
for _, char := range word[1:] {
if char == '_' || unicode.IsSpace(char) {
prevWordBoundary = true
continue
}
if prevWordBoundary || unicode.IsUpper(char) {
prevWordBoundary = false
_, err = builder.WriteRune('_')
if err != nil {
panic(err)
}
}
_, err = builder.WriteRune(unicode.ToLower(char))
if err != nil {
panic(err)
}
}
return builder.String()
}
// ConvertSnakeCaseToCamelCase converts a potentially snake-case
// string to camel-case. Should be Unicode-safe.
//
// Spaces and underscores are removed and any letter following
// immediately after these removed characters will be converted
// to uppercase.
func ConvertSnakeCaseToCamelCase(word string) (result string) {
builder := &strings.Builder{}
var prevWordBoundary bool
for _, char := range word {
if char == '_' || unicode.IsSpace(char) {
prevWordBoundary = true
continue
}
var err error
if prevWordBoundary {
prevWordBoundary = false
_, err = builder.WriteRune(unicode.ToUpper(char))
} else {
_, err = builder.WriteRune(unicode.ToLower(char))
}
if err != nil {
panic(err)
}
}
return builder.String()
}