123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- 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
- }
|