-
Notifications
You must be signed in to change notification settings - Fork 77
/
name.go
190 lines (154 loc) · 6.93 KB
/
name.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
package pgs
import (
"bytes"
"fmt"
"strings"
"unicode"
"unicode/utf8"
"path/filepath"
)
// A Name describes an identifier of an Entity (Message, Field, Enum, Service,
// Field). It can be converted to multiple forms using the provided helper
// methods, or a custom transform can be used to modify its behavior.
type Name string
// String satisfies the strings.Stringer interface.
func (n Name) String() string { return string(n) }
// UpperCamelCase converts Name n to upper camelcase, where each part is
// title-cased and concatenated with no separator.
func (n Name) UpperCamelCase() Name { return n.Transform(strings.Title, strings.Title, "") }
// LowerCamelCase converts Name n to lower camelcase, where each part is
// title-cased and concatenated with no separator except the first which is
// lower-cased.
func (n Name) LowerCamelCase() Name { return n.Transform(strings.Title, strings.ToLower, "") }
// ScreamingSnakeCase converts Name n to screaming-snake-case, where each part
// is all-caps and concatenated with underscores.
func (n Name) ScreamingSnakeCase() Name { return n.Transform(strings.ToUpper, strings.ToUpper, "_") }
// LowerSnakeCase converts Name n to lower-snake-case, where each part is
// lower-cased and concatenated with underscores.
func (n Name) LowerSnakeCase() Name { return n.Transform(strings.ToLower, strings.ToLower, "_") }
// UpperSnakeCase converts Name n to upper-snake-case, where each part is
// title-cased and concatenated with underscores.
func (n Name) UpperSnakeCase() Name { return n.Transform(strings.Title, strings.Title, "_") }
// SnakeCase converts Name n to snake-case, where each part preserves its
// capitalization and concatenated with underscores.
func (n Name) SnakeCase() Name { return n.Transform(ID, ID, "_") }
// LowerDotNotation converts Name n to lower dot notation, where each part is
// lower-cased and concatenated with periods.
func (n Name) LowerDotNotation() Name { return n.Transform(strings.ToLower, strings.ToLower, ".") }
// UpperDotNotation converts Name n to upper dot notation, where each part is
// title-cased and concatenated with periods.
func (n Name) UpperDotNotation() Name { return n.Transform(strings.Title, strings.Title, ".") }
// Split breaks apart Name n into its constituent components. Precedence
// follows dot notation, then underscores (excluding underscore prefixes), then
// camelcase. Numbers are treated as standalone components.
func (n Name) Split() (parts []string) {
ns := string(n)
switch {
case ns == "":
return []string{""}
case strings.LastIndex(ns, ".") >= 0:
return strings.Split(ns, ".")
case strings.LastIndex(ns, "_") > 0: // leading underscore does not count
parts = strings.Split(ns, "_")
if parts[0] == "" {
parts[1] = "_" + parts[1]
return parts[1:]
}
return
default: // camelCase
buf := &bytes.Buffer{}
var capt, lodash, num bool
for _, r := range ns {
uc := unicode.IsUpper(r) || unicode.IsTitle(r)
dg := unicode.IsDigit(r)
if r == '_' && buf.Len() == 0 && len(parts) == 0 {
lodash = true
}
if uc && !capt && buf.Len() > 0 && !lodash { // new upper letter
parts = append(parts, buf.String())
buf.Reset()
} else if dg && !num && buf.Len() > 0 && !lodash { // new digit
parts = append(parts, buf.String())
buf.Reset()
} else if !uc && capt && buf.Len() > 1 { // upper to lower
if ss := buf.String(); len(ss) > 1 &&
(len(ss) != 2 || ss[0] != '_') {
pr, _ := utf8.DecodeLastRuneInString(ss)
parts = append(parts, strings.TrimSuffix(ss, string(pr)))
buf.Reset()
buf.WriteRune(pr)
}
} else if !dg && num && buf.Len() >= 1 {
parts = append(parts, buf.String())
buf.Reset()
}
num = dg
capt = uc
buf.WriteRune(r)
}
parts = append(parts, buf.String())
return
}
}
// SplitOnDot breaks apart Name n into its constituent components using dot notation.
func (n Name) SplitOnDot() (parts []string) {
return strings.Split(string(n), ".")
}
// NameTransformer is a function that mutates a string. Many of the methods in
// the standard strings package satisfy this signature.
type NameTransformer func(string) string
// ID is a NameTransformer that does not mutate the string.
func ID(s string) string { return s }
// Chain combines the behavior of two Transformers into one. If multiple
// transformations need to be performed on a Name, this method should be used
// to reduce it to a single transformation before applying.
func (n NameTransformer) Chain(t NameTransformer) NameTransformer {
return func(s string) string { return t(n(s)) }
}
// Transform applies a transformation to the parts of Name n, returning a new
// Name. Transformer first is applied to the first part, with mod applied to
// all subsequent ones. The parts are then concatenated with the separator sep.
// For optimal efficiency, multiple NameTransformers should be Chained together
// before calling Transform.
func (n Name) Transform(mod, first NameTransformer, sep string) Name {
parts := n.Split()
for i, p := range parts {
if i == 0 {
parts[i] = first(p)
} else {
parts[i] = mod(p)
}
}
return Name(strings.Join(parts, sep))
}
// A FilePath describes the name of a file or directory. This type simplifies
// path related operations.
type FilePath string
// JoinPaths is an convenient alias around filepath.Join, to easily create
// FilePath types.
func JoinPaths(elem ...string) FilePath { return FilePath(filepath.Join(elem...)) }
// String satisfies the strings.Stringer interface.
func (n FilePath) String() string { return string(n) }
// Dir returns the parent directory of the current FilePath. This method is an
// alias around filepath.Dir.
func (n FilePath) Dir() FilePath { return FilePath(filepath.Dir(n.String())) }
// Base returns the base of the current FilePath (the last element in the
// path). This method is an alias around filepath.Base.
func (n FilePath) Base() string { return filepath.Base(n.String()) }
// Ext returns the extension of the current FilePath (starting at and including
// the last '.' in the FilePath). This method is an alias around filepath.Ext.
func (n FilePath) Ext() string { return filepath.Ext(n.String()) }
// BaseName returns the Base of the current FilePath without Ext.
func (n FilePath) BaseName() string { return strings.TrimSuffix(n.Base(), n.Ext()) }
// SetExt returns a new FilePath with the extension replaced with ext.
func (n FilePath) SetExt(ext string) FilePath { return n.SetBase(n.BaseName() + ext) }
// SetBase returns a new FilePath with the base element replaced with base.
func (n FilePath) SetBase(base string) FilePath { return n.Dir().Push(base) }
// Pop returns a new FilePath with the last element removed. Pop is an alias
// for the Dir method.
func (n FilePath) Pop() FilePath { return n.Dir() }
// Push returns a new FilePath with elem added to the end
func (n FilePath) Push(elem string) FilePath { return JoinPaths(n.String(), elem) }
func fullyQualifiedName(p, e Entity) string {
return fmt.Sprintf("%s.%s", p.FullyQualifiedName(), e.Name())
}