Matthias Andreas Benkard | 832a54e | 2019-01-29 09:27:38 +0100 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2015 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package namer |
| 18 | |
| 19 | import ( |
| 20 | "path/filepath" |
| 21 | "strings" |
| 22 | |
| 23 | "k8s.io/gengo/types" |
| 24 | ) |
| 25 | |
| 26 | const ( |
| 27 | // GoSeperator is used to split go import paths. |
| 28 | // Forward slash is used instead of filepath.Seperator because it is the |
| 29 | // only universally-accepted path delimiter and the only delimiter not |
| 30 | // potentially forbidden by Go compilers. (In particular gc does not allow |
| 31 | // the use of backslashes in import paths.) |
| 32 | // See https://golang.org/ref/spec#Import_declarations. |
| 33 | // See also https://github.com/kubernetes/gengo/issues/83#issuecomment-367040772. |
| 34 | GoSeperator = "/" |
| 35 | ) |
| 36 | |
| 37 | // Returns whether a name is a private Go name. |
| 38 | func IsPrivateGoName(name string) bool { |
| 39 | return len(name) == 0 || strings.ToLower(name[:1]) == name[:1] |
| 40 | } |
| 41 | |
| 42 | // NewPublicNamer is a helper function that returns a namer that makes |
| 43 | // CamelCase names. See the NameStrategy struct for an explanation of the |
| 44 | // arguments to this constructor. |
| 45 | func NewPublicNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy { |
| 46 | n := &NameStrategy{ |
| 47 | Join: Joiner(IC, IC), |
| 48 | IgnoreWords: map[string]bool{}, |
| 49 | PrependPackageNames: prependPackageNames, |
| 50 | } |
| 51 | for _, w := range ignoreWords { |
| 52 | n.IgnoreWords[w] = true |
| 53 | } |
| 54 | return n |
| 55 | } |
| 56 | |
| 57 | // NewPrivateNamer is a helper function that returns a namer that makes |
| 58 | // camelCase names. See the NameStrategy struct for an explanation of the |
| 59 | // arguments to this constructor. |
| 60 | func NewPrivateNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy { |
| 61 | n := &NameStrategy{ |
| 62 | Join: Joiner(IL, IC), |
| 63 | IgnoreWords: map[string]bool{}, |
| 64 | PrependPackageNames: prependPackageNames, |
| 65 | } |
| 66 | for _, w := range ignoreWords { |
| 67 | n.IgnoreWords[w] = true |
| 68 | } |
| 69 | return n |
| 70 | } |
| 71 | |
| 72 | // NewRawNamer will return a Namer that makes a name by which you would |
| 73 | // directly refer to a type, optionally keeping track of the import paths |
| 74 | // necessary to reference the names it provides. Tracker may be nil. |
| 75 | // The 'pkg' is the full package name, in which the Namer is used - all |
| 76 | // types from that package will be referenced by just type name without |
| 77 | // referencing the package. |
| 78 | // |
| 79 | // For example, if the type is map[string]int, a raw namer will literally |
| 80 | // return "map[string]int". |
| 81 | // |
| 82 | // Or if the type, in package foo, is "type Bar struct { ... }", then the raw |
| 83 | // namer will return "foo.Bar" as the name of the type, and if 'tracker' was |
| 84 | // not nil, will record that package foo needs to be imported. |
| 85 | func NewRawNamer(pkg string, tracker ImportTracker) *rawNamer { |
| 86 | return &rawNamer{pkg: pkg, tracker: tracker} |
| 87 | } |
| 88 | |
| 89 | // Names is a map from Type to name, as defined by some Namer. |
| 90 | type Names map[*types.Type]string |
| 91 | |
| 92 | // Namer takes a type, and assigns a name. |
| 93 | // |
| 94 | // The purpose of this complexity is so that you can assign coherent |
| 95 | // side-by-side systems of names for the types. For example, you might want a |
| 96 | // public interface, a private implementation struct, and also to reference |
| 97 | // literally the type name. |
| 98 | // |
| 99 | // Note that it is safe to call your own Name() function recursively to find |
| 100 | // the names of keys, elements, etc. This is because anonymous types can't have |
| 101 | // cycles in their names, and named types don't require the sort of recursion |
| 102 | // that would be problematic. |
| 103 | type Namer interface { |
| 104 | Name(*types.Type) string |
| 105 | } |
| 106 | |
| 107 | // NameSystems is a map of a system name to a namer for that system. |
| 108 | type NameSystems map[string]Namer |
| 109 | |
| 110 | // NameStrategy is a general Namer. The easiest way to use it is to copy the |
| 111 | // Public/PrivateNamer variables, and modify the members you wish to change. |
| 112 | // |
| 113 | // The Name method produces a name for the given type, of the forms: |
| 114 | // Anonymous types: <Prefix><Type description><Suffix> |
| 115 | // Named types: <Prefix><Optional Prepended Package name(s)><Original name><Suffix> |
| 116 | // |
| 117 | // In all cases, every part of the name is run through the capitalization |
| 118 | // functions. |
| 119 | // |
| 120 | // The IgnoreWords map can be set if you have directory names that are |
| 121 | // semantically meaningless for naming purposes, e.g. "proto". |
| 122 | // |
| 123 | // Prefix and Suffix can be used to disambiguate parallel systems of type |
| 124 | // names. For example, if you want to generate an interface and an |
| 125 | // implementation, you might want to suffix one with "Interface" and the other |
| 126 | // with "Implementation". Another common use-- if you want to generate private |
| 127 | // types, and one of your source types could be "string", you can't use the |
| 128 | // default lowercase private namer. You'll have to add a suffix or prefix. |
| 129 | type NameStrategy struct { |
| 130 | Prefix, Suffix string |
| 131 | Join func(pre string, parts []string, post string) string |
| 132 | |
| 133 | // Add non-meaningful package directory names here (e.g. "proto") and |
| 134 | // they will be ignored. |
| 135 | IgnoreWords map[string]bool |
| 136 | |
| 137 | // If > 0, prepend exactly that many package directory names (or as |
| 138 | // many as there are). Package names listed in "IgnoreWords" will be |
| 139 | // ignored. |
| 140 | // |
| 141 | // For example, if Ignore words lists "proto" and type Foo is in |
| 142 | // pkg/server/frobbing/proto, then a value of 1 will give a type name |
| 143 | // of FrobbingFoo, 2 gives ServerFrobbingFoo, etc. |
| 144 | PrependPackageNames int |
| 145 | |
| 146 | // A cache of names thus far assigned by this namer. |
| 147 | Names |
| 148 | } |
| 149 | |
| 150 | // IC ensures the first character is uppercase. |
| 151 | func IC(in string) string { |
| 152 | if in == "" { |
| 153 | return in |
| 154 | } |
| 155 | return strings.ToUpper(in[:1]) + in[1:] |
| 156 | } |
| 157 | |
| 158 | // IL ensures the first character is lowercase. |
| 159 | func IL(in string) string { |
| 160 | if in == "" { |
| 161 | return in |
| 162 | } |
| 163 | return strings.ToLower(in[:1]) + in[1:] |
| 164 | } |
| 165 | |
| 166 | // Joiner lets you specify functions that preprocess the various components of |
| 167 | // a name before joining them. You can construct e.g. camelCase or CamelCase or |
| 168 | // any other way of joining words. (See the IC and IL convenience functions.) |
| 169 | func Joiner(first, others func(string) string) func(pre string, in []string, post string) string { |
| 170 | return func(pre string, in []string, post string) string { |
| 171 | tmp := []string{others(pre)} |
| 172 | for i := range in { |
| 173 | tmp = append(tmp, others(in[i])) |
| 174 | } |
| 175 | tmp = append(tmp, others(post)) |
| 176 | return first(strings.Join(tmp, "")) |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | func (ns *NameStrategy) removePrefixAndSuffix(s string) string { |
| 181 | // The join function may have changed capitalization. |
| 182 | lowerIn := strings.ToLower(s) |
| 183 | lowerP := strings.ToLower(ns.Prefix) |
| 184 | lowerS := strings.ToLower(ns.Suffix) |
| 185 | b, e := 0, len(s) |
| 186 | if strings.HasPrefix(lowerIn, lowerP) { |
| 187 | b = len(ns.Prefix) |
| 188 | } |
| 189 | if strings.HasSuffix(lowerIn, lowerS) { |
| 190 | e -= len(ns.Suffix) |
| 191 | } |
| 192 | return s[b:e] |
| 193 | } |
| 194 | |
| 195 | var ( |
| 196 | importPathNameSanitizer = strings.NewReplacer("-", "_", ".", "") |
| 197 | ) |
| 198 | |
| 199 | // filters out unwanted directory names and sanitizes remaining names. |
| 200 | func (ns *NameStrategy) filterDirs(path string) []string { |
| 201 | allDirs := strings.Split(path, GoSeperator) |
| 202 | dirs := make([]string, 0, len(allDirs)) |
| 203 | for _, p := range allDirs { |
| 204 | if ns.IgnoreWords == nil || !ns.IgnoreWords[p] { |
| 205 | dirs = append(dirs, importPathNameSanitizer.Replace(p)) |
| 206 | } |
| 207 | } |
| 208 | return dirs |
| 209 | } |
| 210 | |
| 211 | // See the comment on NameStrategy. |
| 212 | func (ns *NameStrategy) Name(t *types.Type) string { |
| 213 | if ns.Names == nil { |
| 214 | ns.Names = Names{} |
| 215 | } |
| 216 | if s, ok := ns.Names[t]; ok { |
| 217 | return s |
| 218 | } |
| 219 | |
| 220 | if t.Name.Package != "" { |
| 221 | dirs := append(ns.filterDirs(t.Name.Package), t.Name.Name) |
| 222 | i := ns.PrependPackageNames + 1 |
| 223 | dn := len(dirs) |
| 224 | if i > dn { |
| 225 | i = dn |
| 226 | } |
| 227 | name := ns.Join(ns.Prefix, dirs[dn-i:], ns.Suffix) |
| 228 | ns.Names[t] = name |
| 229 | return name |
| 230 | } |
| 231 | |
| 232 | // Only anonymous types remain. |
| 233 | var name string |
| 234 | switch t.Kind { |
| 235 | case types.Builtin: |
| 236 | name = ns.Join(ns.Prefix, []string{t.Name.Name}, ns.Suffix) |
| 237 | case types.Map: |
| 238 | name = ns.Join(ns.Prefix, []string{ |
| 239 | "Map", |
| 240 | ns.removePrefixAndSuffix(ns.Name(t.Key)), |
| 241 | "To", |
| 242 | ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
| 243 | }, ns.Suffix) |
| 244 | case types.Slice: |
| 245 | name = ns.Join(ns.Prefix, []string{ |
| 246 | "Slice", |
| 247 | ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
| 248 | }, ns.Suffix) |
| 249 | case types.Pointer: |
| 250 | name = ns.Join(ns.Prefix, []string{ |
| 251 | "Pointer", |
| 252 | ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
| 253 | }, ns.Suffix) |
| 254 | case types.Struct: |
| 255 | names := []string{"Struct"} |
| 256 | for _, m := range t.Members { |
| 257 | names = append(names, ns.removePrefixAndSuffix(ns.Name(m.Type))) |
| 258 | } |
| 259 | name = ns.Join(ns.Prefix, names, ns.Suffix) |
| 260 | case types.Chan: |
| 261 | name = ns.Join(ns.Prefix, []string{ |
| 262 | "Chan", |
| 263 | ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
| 264 | }, ns.Suffix) |
| 265 | case types.Interface: |
| 266 | // TODO: add to name test |
| 267 | names := []string{"Interface"} |
| 268 | for _, m := range t.Methods { |
| 269 | // TODO: include function signature |
| 270 | names = append(names, m.Name.Name) |
| 271 | } |
| 272 | name = ns.Join(ns.Prefix, names, ns.Suffix) |
| 273 | case types.Func: |
| 274 | // TODO: add to name test |
| 275 | parts := []string{"Func"} |
| 276 | for _, pt := range t.Signature.Parameters { |
| 277 | parts = append(parts, ns.removePrefixAndSuffix(ns.Name(pt))) |
| 278 | } |
| 279 | parts = append(parts, "Returns") |
| 280 | for _, rt := range t.Signature.Results { |
| 281 | parts = append(parts, ns.removePrefixAndSuffix(ns.Name(rt))) |
| 282 | } |
| 283 | name = ns.Join(ns.Prefix, parts, ns.Suffix) |
| 284 | default: |
| 285 | name = "unnameable_" + string(t.Kind) |
| 286 | } |
| 287 | ns.Names[t] = name |
| 288 | return name |
| 289 | } |
| 290 | |
| 291 | // ImportTracker allows a raw namer to keep track of the packages needed for |
| 292 | // import. You can implement yourself or use the one in the generation package. |
| 293 | type ImportTracker interface { |
| 294 | AddType(*types.Type) |
| 295 | LocalNameOf(packagePath string) string |
| 296 | PathOf(localName string) (string, bool) |
| 297 | ImportLines() []string |
| 298 | } |
| 299 | |
| 300 | type rawNamer struct { |
| 301 | pkg string |
| 302 | tracker ImportTracker |
| 303 | Names |
| 304 | } |
| 305 | |
| 306 | // Name makes a name the way you'd write it to literally refer to type t, |
| 307 | // making ordinary assumptions about how you've imported t's package (or using |
| 308 | // r.tracker to specifically track the package imports). |
| 309 | func (r *rawNamer) Name(t *types.Type) string { |
| 310 | if r.Names == nil { |
| 311 | r.Names = Names{} |
| 312 | } |
| 313 | if name, ok := r.Names[t]; ok { |
| 314 | return name |
| 315 | } |
| 316 | if t.Name.Package != "" { |
| 317 | var name string |
| 318 | if r.tracker != nil { |
| 319 | r.tracker.AddType(t) |
| 320 | if t.Name.Package == r.pkg { |
| 321 | name = t.Name.Name |
| 322 | } else { |
| 323 | name = r.tracker.LocalNameOf(t.Name.Package) + "." + t.Name.Name |
| 324 | } |
| 325 | } else { |
| 326 | if t.Name.Package == r.pkg { |
| 327 | name = t.Name.Name |
| 328 | } else { |
| 329 | name = filepath.Base(t.Name.Package) + "." + t.Name.Name |
| 330 | } |
| 331 | } |
| 332 | r.Names[t] = name |
| 333 | return name |
| 334 | } |
| 335 | var name string |
| 336 | switch t.Kind { |
| 337 | case types.Builtin: |
| 338 | name = t.Name.Name |
| 339 | case types.Map: |
| 340 | name = "map[" + r.Name(t.Key) + "]" + r.Name(t.Elem) |
| 341 | case types.Slice: |
| 342 | name = "[]" + r.Name(t.Elem) |
| 343 | case types.Pointer: |
| 344 | name = "*" + r.Name(t.Elem) |
| 345 | case types.Struct: |
| 346 | elems := []string{} |
| 347 | for _, m := range t.Members { |
| 348 | elems = append(elems, m.Name+" "+r.Name(m.Type)) |
| 349 | } |
| 350 | name = "struct{" + strings.Join(elems, "; ") + "}" |
| 351 | case types.Chan: |
| 352 | // TODO: include directionality |
| 353 | name = "chan " + r.Name(t.Elem) |
| 354 | case types.Interface: |
| 355 | // TODO: add to name test |
| 356 | elems := []string{} |
| 357 | for _, m := range t.Methods { |
| 358 | // TODO: include function signature |
| 359 | elems = append(elems, m.Name.Name) |
| 360 | } |
| 361 | name = "interface{" + strings.Join(elems, "; ") + "}" |
| 362 | case types.Func: |
| 363 | // TODO: add to name test |
| 364 | params := []string{} |
| 365 | for _, pt := range t.Signature.Parameters { |
| 366 | params = append(params, r.Name(pt)) |
| 367 | } |
| 368 | results := []string{} |
| 369 | for _, rt := range t.Signature.Results { |
| 370 | results = append(results, r.Name(rt)) |
| 371 | } |
| 372 | name = "func(" + strings.Join(params, ",") + ")" |
| 373 | if len(results) == 1 { |
| 374 | name += " " + results[0] |
| 375 | } else if len(results) > 1 { |
| 376 | name += " (" + strings.Join(results, ",") + ")" |
| 377 | } |
| 378 | default: |
| 379 | name = "unnameable_" + string(t.Kind) |
| 380 | } |
| 381 | r.Names[t] = name |
| 382 | return name |
| 383 | } |