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: // Named types: // // 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 }