You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
11 KiB
367 lines
11 KiB
package namer |
|
|
|
import ( |
|
"path/filepath" |
|
"strings" |
|
|
|
"go-common/app/tool/gengo/types" |
|
) |
|
|
|
const ( |
|
// GoSeperator is used to split go import paths. |
|
// Forward slash is used instead of filepath.Seperator because it is the |
|
// only universally-accepted path delimiter and the only delimiter not |
|
// potentially forbidden by Go compilers. (In particular gc does not allow |
|
// the use of backslashes in import paths.) |
|
// See https://golang.org/ref/spec#Import_declarations. |
|
// See also https://github.com/kubernetes/gengo/issues/83#issuecomment-367040772. |
|
GoSeperator = "/" |
|
) |
|
|
|
// IsPrivateGoName returns whether a name is a private Go name. |
|
func IsPrivateGoName(name string) bool { |
|
return len(name) == 0 || strings.ToLower(name[:1]) == name[:1] |
|
} |
|
|
|
// NewPublicNamer is a helper function that returns a namer that makes |
|
// CamelCase names. See the NameStrategy struct for an explanation of the |
|
// arguments to this constructor. |
|
func NewPublicNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy { |
|
n := &NameStrategy{ |
|
Join: Joiner(IC, IC), |
|
IgnoreWords: map[string]bool{}, |
|
PrependPackageNames: prependPackageNames, |
|
} |
|
for _, w := range ignoreWords { |
|
n.IgnoreWords[w] = true |
|
} |
|
return n |
|
} |
|
|
|
// NewPrivateNamer is a helper function that returns a namer that makes |
|
// camelCase names. See the NameStrategy struct for an explanation of the |
|
// arguments to this constructor. |
|
func NewPrivateNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy { |
|
n := &NameStrategy{ |
|
Join: Joiner(IL, IC), |
|
IgnoreWords: map[string]bool{}, |
|
PrependPackageNames: prependPackageNames, |
|
} |
|
for _, w := range ignoreWords { |
|
n.IgnoreWords[w] = true |
|
} |
|
return n |
|
} |
|
|
|
// NewRawNamer will return a Namer that makes a name by which you would |
|
// directly refer to a type, optionally keeping track of the import paths |
|
// necessary to reference the names it provides. Tracker may be nil. |
|
// The 'pkg' is the full package name, in which the Namer is used - all |
|
// types from that package will be referenced by just type name without |
|
// referencing the package. |
|
// |
|
// For example, if the type is map[string]int, a raw namer will literally |
|
// return "map[string]int". |
|
// |
|
// Or if the type, in package foo, is "type Bar struct { ... }", then the raw |
|
// namer will return "foo.Bar" as the name of the type, and if 'tracker' was |
|
// not nil, will record that package foo needs to be imported. |
|
func NewRawNamer(pkg string, tracker ImportTracker) *rawNamer { |
|
return &rawNamer{pkg: pkg, tracker: tracker} |
|
} |
|
|
|
// Names is a map from Type to name, as defined by some Namer. |
|
type Names map[*types.Type]string |
|
|
|
// Namer takes a type, and assigns a name. |
|
// |
|
// The purpose of this complexity is so that you can assign coherent |
|
// side-by-side systems of names for the types. For example, you might want a |
|
// public interface, a private implementation struct, and also to reference |
|
// literally the type name. |
|
// |
|
// Note that it is safe to call your own Name() function recursively to find |
|
// the names of keys, elements, etc. This is because anonymous types can't have |
|
// cycles in their names, and named types don't require the sort of recursion |
|
// that would be problematic. |
|
type Namer interface { |
|
Name(*types.Type) string |
|
} |
|
|
|
// NameSystems is a map of a system name to a namer for that system. |
|
type NameSystems map[string]Namer |
|
|
|
// NameStrategy is a general Namer. The easiest way to use it is to copy the |
|
// Public/PrivateNamer variables, and modify the members you wish to change. |
|
// |
|
// The Name method produces a name for the given type, of the forms: |
|
// Anonymous types: <Prefix><Type description><Suffix> |
|
// Named types: <Prefix><Optional Prepended Package name(s)><Original name><Suffix> |
|
// |
|
// In all cases, every part of the name is run through the capitalization |
|
// functions. |
|
// |
|
// The IgnoreWords map can be set if you have directory names that are |
|
// semantically meaningless for naming purposes, e.g. "proto". |
|
// |
|
// Prefix and Suffix can be used to disambiguate parallel systems of type |
|
// names. For example, if you want to generate an interface and an |
|
// implementation, you might want to suffix one with "Interface" and the other |
|
// with "Implementation". Another common use-- if you want to generate private |
|
// types, and one of your source types could be "string", you can't use the |
|
// default lowercase private namer. You'll have to add a suffix or prefix. |
|
type NameStrategy struct { |
|
Prefix, Suffix string |
|
Join func(pre string, parts []string, post string) string |
|
|
|
// Add non-meaningful package directory names here (e.g. "proto") and |
|
// they will be ignored. |
|
IgnoreWords map[string]bool |
|
|
|
// If > 0, prepend exactly that many package directory names (or as |
|
// many as there are). Package names listed in "IgnoreWords" will be |
|
// ignored. |
|
// |
|
// For example, if Ignore words lists "proto" and type Foo is in |
|
// pkg/server/frobbing/proto, then a value of 1 will give a type name |
|
// of FrobbingFoo, 2 gives ServerFrobbingFoo, etc. |
|
PrependPackageNames int |
|
|
|
// A cache of names thus far assigned by this namer. |
|
Names |
|
} |
|
|
|
// IC ensures the first character is uppercase. |
|
func IC(in string) string { |
|
if in == "" { |
|
return in |
|
} |
|
return strings.ToUpper(in[:1]) + in[1:] |
|
} |
|
|
|
// IL ensures the first character is lowercase. |
|
func IL(in string) string { |
|
if in == "" { |
|
return in |
|
} |
|
return strings.ToLower(in[:1]) + in[1:] |
|
} |
|
|
|
// Joiner lets you specify functions that preprocess the various components of |
|
// a name before joining them. You can construct e.g. camelCase or CamelCase or |
|
// any other way of joining words. (See the IC and IL convenience functions.) |
|
func Joiner(first, others func(string) string) func(pre string, in []string, post string) string { |
|
return func(pre string, in []string, post string) string { |
|
tmp := []string{others(pre)} |
|
for i := range in { |
|
tmp = append(tmp, others(in[i])) |
|
} |
|
tmp = append(tmp, others(post)) |
|
return first(strings.Join(tmp, "")) |
|
} |
|
} |
|
|
|
func (ns *NameStrategy) removePrefixAndSuffix(s string) string { |
|
// The join function may have changed capitalization. |
|
lowerIn := strings.ToLower(s) |
|
lowerP := strings.ToLower(ns.Prefix) |
|
lowerS := strings.ToLower(ns.Suffix) |
|
b, e := 0, len(s) |
|
if strings.HasPrefix(lowerIn, lowerP) { |
|
b = len(ns.Prefix) |
|
} |
|
if strings.HasSuffix(lowerIn, lowerS) { |
|
e -= len(ns.Suffix) |
|
} |
|
return s[b:e] |
|
} |
|
|
|
var ( |
|
importPathNameSanitizer = strings.NewReplacer("-", "_", ".", "") |
|
) |
|
|
|
// filters out unwanted directory names and sanitizes remaining names. |
|
func (ns *NameStrategy) filterDirs(path string) []string { |
|
allDirs := strings.Split(path, GoSeperator) |
|
dirs := make([]string, 0, len(allDirs)) |
|
for _, p := range allDirs { |
|
if ns.IgnoreWords == nil || !ns.IgnoreWords[p] { |
|
dirs = append(dirs, importPathNameSanitizer.Replace(p)) |
|
} |
|
} |
|
return dirs |
|
} |
|
|
|
// Name See the comment on NameStrategy. |
|
func (ns *NameStrategy) Name(t *types.Type) string { |
|
if ns.Names == nil { |
|
ns.Names = Names{} |
|
} |
|
if s, ok := ns.Names[t]; ok { |
|
return s |
|
} |
|
|
|
if t.Name.Package != "" { |
|
dirs := append(ns.filterDirs(t.Name.Package), t.Name.Name) |
|
i := ns.PrependPackageNames + 1 |
|
dn := len(dirs) |
|
if i > dn { |
|
i = dn |
|
} |
|
name := ns.Join(ns.Prefix, dirs[dn-i:], ns.Suffix) |
|
ns.Names[t] = name |
|
return name |
|
} |
|
|
|
// Only anonymous types remain. |
|
var name string |
|
switch t.Kind { |
|
case types.Builtin: |
|
name = ns.Join(ns.Prefix, []string{t.Name.Name}, ns.Suffix) |
|
case types.Map: |
|
name = ns.Join(ns.Prefix, []string{ |
|
"Map", |
|
ns.removePrefixAndSuffix(ns.Name(t.Key)), |
|
"To", |
|
ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
|
}, ns.Suffix) |
|
case types.Slice: |
|
name = ns.Join(ns.Prefix, []string{ |
|
"Slice", |
|
ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
|
}, ns.Suffix) |
|
case types.Pointer: |
|
name = ns.Join(ns.Prefix, []string{ |
|
"Pointer", |
|
ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
|
}, ns.Suffix) |
|
case types.Struct: |
|
names := []string{"Struct"} |
|
for _, m := range t.Members { |
|
names = append(names, ns.removePrefixAndSuffix(ns.Name(m.Type))) |
|
} |
|
name = ns.Join(ns.Prefix, names, ns.Suffix) |
|
case types.Chan: |
|
name = ns.Join(ns.Prefix, []string{ |
|
"Chan", |
|
ns.removePrefixAndSuffix(ns.Name(t.Elem)), |
|
}, ns.Suffix) |
|
case types.Interface: |
|
// TODO: add to name test |
|
names := []string{"Interface"} |
|
for _, m := range t.Methods { |
|
// TODO: include function signature |
|
names = append(names, m.Name.Name) |
|
} |
|
name = ns.Join(ns.Prefix, names, ns.Suffix) |
|
case types.Func: |
|
// TODO: add to name test |
|
parts := []string{"Func"} |
|
for _, pt := range t.Signature.Parameters { |
|
parts = append(parts, ns.removePrefixAndSuffix(ns.Name(pt))) |
|
} |
|
parts = append(parts, "Returns") |
|
for _, rt := range t.Signature.Results { |
|
parts = append(parts, ns.removePrefixAndSuffix(ns.Name(rt))) |
|
} |
|
name = ns.Join(ns.Prefix, parts, ns.Suffix) |
|
default: |
|
name = "unnameable_" + string(t.Kind) |
|
} |
|
ns.Names[t] = name |
|
return name |
|
} |
|
|
|
// ImportTracker allows a raw namer to keep track of the packages needed for |
|
// import. You can implement yourself or use the one in the generation package. |
|
type ImportTracker interface { |
|
AddType(*types.Type) |
|
LocalNameOf(packagePath string) string |
|
PathOf(localName string) (string, bool) |
|
ImportLines() []string |
|
} |
|
|
|
type rawNamer struct { |
|
pkg string |
|
tracker ImportTracker |
|
Names |
|
} |
|
|
|
// Name makes a name the way you'd write it to literally refer to type t, |
|
// making ordinary assumptions about how you've imported t's package (or using |
|
// r.tracker to specifically track the package imports). |
|
func (r *rawNamer) Name(t *types.Type) string { |
|
if r.Names == nil { |
|
r.Names = Names{} |
|
} |
|
if name, ok := r.Names[t]; ok { |
|
return name |
|
} |
|
if t.Name.Package != "" { |
|
var name string |
|
if r.tracker != nil { |
|
r.tracker.AddType(t) |
|
if t.Name.Package == r.pkg { |
|
name = t.Name.Name |
|
} else { |
|
name = r.tracker.LocalNameOf(t.Name.Package) + "." + t.Name.Name |
|
} |
|
} else { |
|
if t.Name.Package == r.pkg { |
|
name = t.Name.Name |
|
} else { |
|
name = filepath.Base(t.Name.Package) + "." + t.Name.Name |
|
} |
|
} |
|
r.Names[t] = name |
|
return name |
|
} |
|
var name string |
|
switch t.Kind { |
|
case types.Builtin: |
|
name = t.Name.Name |
|
case types.Map: |
|
name = "map[" + r.Name(t.Key) + "]" + r.Name(t.Elem) |
|
case types.Slice: |
|
name = "[]" + r.Name(t.Elem) |
|
case types.Pointer: |
|
name = "*" + r.Name(t.Elem) |
|
case types.Struct: |
|
elems := []string{} |
|
for _, m := range t.Members { |
|
elems = append(elems, m.Name+" "+r.Name(m.Type)) |
|
} |
|
name = "struct{" + strings.Join(elems, "; ") + "}" |
|
case types.Chan: |
|
// TODO: include directionality |
|
name = "chan " + r.Name(t.Elem) |
|
case types.Interface: |
|
// TODO: add to name test |
|
elems := []string{} |
|
for _, m := range t.Methods { |
|
// TODO: include function signature |
|
elems = append(elems, m.Name.Name) |
|
} |
|
name = "interface{" + strings.Join(elems, "; ") + "}" |
|
case types.Func: |
|
// TODO: add to name test |
|
params := []string{} |
|
for _, pt := range t.Signature.Parameters { |
|
params = append(params, r.Name(pt)) |
|
} |
|
results := []string{} |
|
for _, rt := range t.Signature.Results { |
|
results = append(results, r.Name(rt)) |
|
} |
|
name = "func(" + strings.Join(params, ",") + ")" |
|
if len(results) == 1 { |
|
name += " " + results[0] |
|
} else if len(results) > 1 { |
|
name += " (" + strings.Join(results, ",") + ")" |
|
} |
|
default: |
|
name = "unnameable_" + string(t.Kind) |
|
} |
|
r.Names[t] = name |
|
return name |
|
}
|
|
|