...

Source file src/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor/registry.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor

     1  package descriptor
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/golang/glog"
    10  	descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
    11  	plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
    12  	"google.golang.org/genproto/googleapis/api/annotations"
    13  )
    14  
    15  // Registry is a registry of information extracted from plugin.CodeGeneratorRequest.
    16  type Registry struct {
    17  	// msgs is a mapping from fully-qualified message name to descriptor
    18  	msgs map[string]*Message
    19  
    20  	// enums is a mapping from fully-qualified enum name to descriptor
    21  	enums map[string]*Enum
    22  
    23  	// files is a mapping from file path to descriptor
    24  	files map[string]*File
    25  
    26  	// prefix is a prefix to be inserted to golang package paths generated from proto package names.
    27  	prefix string
    28  
    29  	// importPath is used as the package if no input files declare go_package. If it contains slashes, everything up to the rightmost slash is ignored.
    30  	importPath string
    31  
    32  	// pkgMap is a user-specified mapping from file path to proto package.
    33  	pkgMap map[string]string
    34  
    35  	// pkgAliases is a mapping from package aliases to package paths in go which are already taken.
    36  	pkgAliases map[string]string
    37  
    38  	// allowDeleteBody permits http delete methods to have a body
    39  	allowDeleteBody bool
    40  
    41  	// externalHttpRules is a mapping from fully qualified service method names to additional HttpRules applicable besides the ones found in annotations.
    42  	externalHTTPRules map[string][]*annotations.HttpRule
    43  
    44  	// allowMerge generation one swagger file out of multiple protos
    45  	allowMerge bool
    46  
    47  	// mergeFileName target swagger file name after merge
    48  	mergeFileName string
    49  
    50  	// allowRepeatedFieldsInBody permits repeated field in body field path of `google.api.http` annotation option
    51  	allowRepeatedFieldsInBody bool
    52  
    53  	// includePackageInTags controls whether the package name defined in the `package` directive
    54  	// in the proto file can be prepended to the gRPC service name in the `Tags` field of every operation.
    55  	includePackageInTags bool
    56  
    57  	// repeatedPathParamSeparator specifies how path parameter repeated fields are separated
    58  	repeatedPathParamSeparator repeatedFieldSeparator
    59  
    60  	// useJSONNamesForFields if true json tag name is used for generating fields in swagger definitions,
    61  	// otherwise the original proto name is used. It's helpful for synchronizing the swagger definition
    62  	// with grpc-gateway response, if it uses json tags for marshaling.
    63  	useJSONNamesForFields bool
    64  
    65  	// useFQNForSwaggerName if true swagger names will use the full qualified name (FQN) from proto definition,
    66  	// and generate a dot-separated swagger name concatenating all elements from the proto FQN.
    67  	// If false, the default behavior is to concat the last 2 elements of the FQN if they are unique, otherwise concat
    68  	// all the elements of the FQN without any separator
    69  	useFQNForSwaggerName bool
    70  
    71  	// allowColonFinalSegments determines whether colons are permitted
    72  	// in the final segment of a path.
    73  	allowColonFinalSegments bool
    74  
    75  	// useGoTemplate determines whether you want to use GO templates
    76  	// in your protofile comments
    77  	useGoTemplate bool
    78  
    79  	// enumsAsInts render enum as integer, as opposed to string
    80  	enumsAsInts bool
    81  
    82  	// disableDefaultErrors disables the generation of the default error types.
    83  	// This is useful for users who have defined custom error handling.
    84  	disableDefaultErrors bool
    85  
    86  	// simpleOperationIDs removes the service prefix from the generated
    87  	// operationIDs. This risks generating duplicate operationIDs.
    88  	simpleOperationIDs bool
    89  
    90  	// warnOnUnboundMethods causes the registry to emit warning logs if an RPC method
    91  	// has no HttpRule annotation.
    92  	warnOnUnboundMethods bool
    93  
    94  	// generateUnboundMethods causes the registry to generate proxy methods even for
    95  	// RPC methods that have no HttpRule annotation.
    96  	generateUnboundMethods bool
    97  
    98  	// omitPackageDoc, if false, causes a package comment to be included in the generated code.
    99  	omitPackageDoc bool
   100  }
   101  
   102  type repeatedFieldSeparator struct {
   103  	name string
   104  	sep  rune
   105  }
   106  
   107  // NewRegistry returns a new Registry.
   108  func NewRegistry() *Registry {
   109  	return &Registry{
   110  		msgs:              make(map[string]*Message),
   111  		enums:             make(map[string]*Enum),
   112  		files:             make(map[string]*File),
   113  		pkgMap:            make(map[string]string),
   114  		pkgAliases:        make(map[string]string),
   115  		externalHTTPRules: make(map[string][]*annotations.HttpRule),
   116  		repeatedPathParamSeparator: repeatedFieldSeparator{
   117  			name: "csv",
   118  			sep:  ',',
   119  		},
   120  	}
   121  }
   122  
   123  // Load loads definitions of services, methods, messages, enumerations and fields from "req".
   124  func (r *Registry) Load(req *plugin.CodeGeneratorRequest) error {
   125  	for _, file := range req.GetProtoFile() {
   126  		r.loadFile(file)
   127  	}
   128  
   129  	var targetPkg string
   130  	for _, name := range req.FileToGenerate {
   131  		target := r.files[name]
   132  		if target == nil {
   133  			return fmt.Errorf("no such file: %s", name)
   134  		}
   135  		name := r.packageIdentityName(target.FileDescriptorProto)
   136  		if targetPkg == "" {
   137  			targetPkg = name
   138  		} else {
   139  			if targetPkg != name {
   140  				return fmt.Errorf("inconsistent package names: %s %s", targetPkg, name)
   141  			}
   142  		}
   143  
   144  		if err := r.loadServices(target); err != nil {
   145  			return err
   146  		}
   147  	}
   148  	return nil
   149  }
   150  
   151  // loadFile loads messages, enumerations and fields from "file".
   152  // It does not loads services and methods in "file".  You need to call
   153  // loadServices after loadFiles is called for all files to load services and methods.
   154  func (r *Registry) loadFile(file *descriptor.FileDescriptorProto) {
   155  	pkg := GoPackage{
   156  		Path: r.goPackagePath(file),
   157  		Name: r.defaultGoPackageName(file),
   158  	}
   159  	if err := r.ReserveGoPackageAlias(pkg.Name, pkg.Path); err != nil {
   160  		for i := 0; ; i++ {
   161  			alias := fmt.Sprintf("%s_%d", pkg.Name, i)
   162  			if err := r.ReserveGoPackageAlias(alias, pkg.Path); err == nil {
   163  				pkg.Alias = alias
   164  				break
   165  			}
   166  		}
   167  	}
   168  	f := &File{
   169  		FileDescriptorProto: file,
   170  		GoPkg:               pkg,
   171  	}
   172  
   173  	r.files[file.GetName()] = f
   174  	r.registerMsg(f, nil, file.GetMessageType())
   175  	r.registerEnum(f, nil, file.GetEnumType())
   176  }
   177  
   178  func (r *Registry) registerMsg(file *File, outerPath []string, msgs []*descriptor.DescriptorProto) {
   179  	for i, md := range msgs {
   180  		m := &Message{
   181  			File:            file,
   182  			Outers:          outerPath,
   183  			DescriptorProto: md,
   184  			Index:           i,
   185  		}
   186  		for _, fd := range md.GetField() {
   187  			m.Fields = append(m.Fields, &Field{
   188  				Message:              m,
   189  				FieldDescriptorProto: fd,
   190  			})
   191  		}
   192  		file.Messages = append(file.Messages, m)
   193  		r.msgs[m.FQMN()] = m
   194  		glog.V(1).Infof("register name: %s", m.FQMN())
   195  
   196  		var outers []string
   197  		outers = append(outers, outerPath...)
   198  		outers = append(outers, m.GetName())
   199  		r.registerMsg(file, outers, m.GetNestedType())
   200  		r.registerEnum(file, outers, m.GetEnumType())
   201  	}
   202  }
   203  
   204  func (r *Registry) registerEnum(file *File, outerPath []string, enums []*descriptor.EnumDescriptorProto) {
   205  	for i, ed := range enums {
   206  		e := &Enum{
   207  			File:                file,
   208  			Outers:              outerPath,
   209  			EnumDescriptorProto: ed,
   210  			Index:               i,
   211  		}
   212  		file.Enums = append(file.Enums, e)
   213  		r.enums[e.FQEN()] = e
   214  		glog.V(1).Infof("register enum name: %s", e.FQEN())
   215  	}
   216  }
   217  
   218  // LookupMsg looks up a message type by "name".
   219  // It tries to resolve "name" from "location" if "name" is a relative message name.
   220  func (r *Registry) LookupMsg(location, name string) (*Message, error) {
   221  	glog.V(1).Infof("lookup %s from %s", name, location)
   222  	if strings.HasPrefix(name, ".") {
   223  		m, ok := r.msgs[name]
   224  		if !ok {
   225  			return nil, fmt.Errorf("no message found: %s", name)
   226  		}
   227  		return m, nil
   228  	}
   229  
   230  	if !strings.HasPrefix(location, ".") {
   231  		location = fmt.Sprintf(".%s", location)
   232  	}
   233  	components := strings.Split(location, ".")
   234  	for len(components) > 0 {
   235  		fqmn := strings.Join(append(components, name), ".")
   236  		if m, ok := r.msgs[fqmn]; ok {
   237  			return m, nil
   238  		}
   239  		components = components[:len(components)-1]
   240  	}
   241  	return nil, fmt.Errorf("no message found: %s", name)
   242  }
   243  
   244  // LookupEnum looks up a enum type by "name".
   245  // It tries to resolve "name" from "location" if "name" is a relative enum name.
   246  func (r *Registry) LookupEnum(location, name string) (*Enum, error) {
   247  	glog.V(1).Infof("lookup enum %s from %s", name, location)
   248  	if strings.HasPrefix(name, ".") {
   249  		e, ok := r.enums[name]
   250  		if !ok {
   251  			return nil, fmt.Errorf("no enum found: %s", name)
   252  		}
   253  		return e, nil
   254  	}
   255  
   256  	if !strings.HasPrefix(location, ".") {
   257  		location = fmt.Sprintf(".%s", location)
   258  	}
   259  	components := strings.Split(location, ".")
   260  	for len(components) > 0 {
   261  		fqen := strings.Join(append(components, name), ".")
   262  		if e, ok := r.enums[fqen]; ok {
   263  			return e, nil
   264  		}
   265  		components = components[:len(components)-1]
   266  	}
   267  	return nil, fmt.Errorf("no enum found: %s", name)
   268  }
   269  
   270  // LookupFile looks up a file by name.
   271  func (r *Registry) LookupFile(name string) (*File, error) {
   272  	f, ok := r.files[name]
   273  	if !ok {
   274  		return nil, fmt.Errorf("no such file given: %s", name)
   275  	}
   276  	return f, nil
   277  }
   278  
   279  // LookupExternalHTTPRules looks up external http rules by fully qualified service method name
   280  func (r *Registry) LookupExternalHTTPRules(qualifiedMethodName string) []*annotations.HttpRule {
   281  	return r.externalHTTPRules[qualifiedMethodName]
   282  }
   283  
   284  // AddExternalHTTPRule adds an external http rule for the given fully qualified service method name
   285  func (r *Registry) AddExternalHTTPRule(qualifiedMethodName string, rule *annotations.HttpRule) {
   286  	r.externalHTTPRules[qualifiedMethodName] = append(r.externalHTTPRules[qualifiedMethodName], rule)
   287  }
   288  
   289  // UnboundExternalHTTPRules returns the list of External HTTPRules
   290  // which does not have a matching method in the registry
   291  func (r *Registry) UnboundExternalHTTPRules() []string {
   292  	allServiceMethods := make(map[string]struct{})
   293  	for _, f := range r.files {
   294  		for _, s := range f.GetService() {
   295  			svc := &Service{File: f, ServiceDescriptorProto: s}
   296  			for _, m := range s.GetMethod() {
   297  				method := &Method{Service: svc, MethodDescriptorProto: m}
   298  				allServiceMethods[method.FQMN()] = struct{}{}
   299  			}
   300  		}
   301  	}
   302  
   303  	var missingMethods []string
   304  	for httpRuleMethod := range r.externalHTTPRules {
   305  		if _, ok := allServiceMethods[httpRuleMethod]; !ok {
   306  			missingMethods = append(missingMethods, httpRuleMethod)
   307  		}
   308  	}
   309  	return missingMethods
   310  }
   311  
   312  // AddPkgMap adds a mapping from a .proto file to proto package name.
   313  func (r *Registry) AddPkgMap(file, protoPkg string) {
   314  	r.pkgMap[file] = protoPkg
   315  }
   316  
   317  // SetPrefix registers the prefix to be added to go package paths generated from proto package names.
   318  func (r *Registry) SetPrefix(prefix string) {
   319  	r.prefix = prefix
   320  }
   321  
   322  // SetImportPath registers the importPath which is used as the package if no
   323  // input files declare go_package. If it contains slashes, everything up to the
   324  // rightmost slash is ignored.
   325  func (r *Registry) SetImportPath(importPath string) {
   326  	r.importPath = importPath
   327  }
   328  
   329  // ReserveGoPackageAlias reserves the unique alias of go package.
   330  // If succeeded, the alias will be never used for other packages in generated go files.
   331  // If failed, the alias is already taken by another package, so you need to use another
   332  // alias for the package in your go files.
   333  func (r *Registry) ReserveGoPackageAlias(alias, pkgpath string) error {
   334  	if taken, ok := r.pkgAliases[alias]; ok {
   335  		if taken == pkgpath {
   336  			return nil
   337  		}
   338  		return fmt.Errorf("package name %s is already taken. Use another alias", alias)
   339  	}
   340  	r.pkgAliases[alias] = pkgpath
   341  	return nil
   342  }
   343  
   344  // goPackagePath returns the go package path which go files generated from "f" should have.
   345  // It respects the mapping registered by AddPkgMap if exists. Or use go_package as import path
   346  // if it includes a slash,  Otherwide, it generates a path from the file name of "f".
   347  func (r *Registry) goPackagePath(f *descriptor.FileDescriptorProto) string {
   348  	name := f.GetName()
   349  	if pkg, ok := r.pkgMap[name]; ok {
   350  		return path.Join(r.prefix, pkg)
   351  	}
   352  
   353  	gopkg := f.Options.GetGoPackage()
   354  	idx := strings.LastIndex(gopkg, "/")
   355  	if idx >= 0 {
   356  		if sc := strings.LastIndex(gopkg, ";"); sc > 0 {
   357  			gopkg = gopkg[:sc+1-1]
   358  		}
   359  		return gopkg
   360  	}
   361  
   362  	return path.Join(r.prefix, path.Dir(name))
   363  }
   364  
   365  // GetAllFQMNs returns a list of all FQMNs
   366  func (r *Registry) GetAllFQMNs() []string {
   367  	var keys []string
   368  	for k := range r.msgs {
   369  		keys = append(keys, k)
   370  	}
   371  	return keys
   372  }
   373  
   374  // GetAllFQENs returns a list of all FQENs
   375  func (r *Registry) GetAllFQENs() []string {
   376  	var keys []string
   377  	for k := range r.enums {
   378  		keys = append(keys, k)
   379  	}
   380  	return keys
   381  }
   382  
   383  // SetAllowDeleteBody controls whether http delete methods may have a
   384  // body or fail loading if encountered.
   385  func (r *Registry) SetAllowDeleteBody(allow bool) {
   386  	r.allowDeleteBody = allow
   387  }
   388  
   389  // SetAllowMerge controls whether generation one swagger file out of multiple protos
   390  func (r *Registry) SetAllowMerge(allow bool) {
   391  	r.allowMerge = allow
   392  }
   393  
   394  // IsAllowMerge whether generation one swagger file out of multiple protos
   395  func (r *Registry) IsAllowMerge() bool {
   396  	return r.allowMerge
   397  }
   398  
   399  // SetMergeFileName controls the target swagger file name out of multiple protos
   400  func (r *Registry) SetMergeFileName(mergeFileName string) {
   401  	r.mergeFileName = mergeFileName
   402  }
   403  
   404  // SetAllowRepeatedFieldsInBody controls whether repeated field can be used
   405  // in `body` and `response_body` (`google.api.http` annotation option) field path or not
   406  func (r *Registry) SetAllowRepeatedFieldsInBody(allow bool) {
   407  	r.allowRepeatedFieldsInBody = allow
   408  }
   409  
   410  // IsAllowRepeatedFieldsInBody checks if repeated field can be used
   411  // in `body` and `response_body` (`google.api.http` annotation option) field path or not
   412  func (r *Registry) IsAllowRepeatedFieldsInBody() bool {
   413  	return r.allowRepeatedFieldsInBody
   414  }
   415  
   416  // SetIncludePackageInTags controls whether the package name defined in the `package` directive
   417  // in the proto file can be prepended to the gRPC service name in the `Tags` field of every operation.
   418  func (r *Registry) SetIncludePackageInTags(allow bool) {
   419  	r.includePackageInTags = allow
   420  }
   421  
   422  // IsIncludePackageInTags checks whether the package name defined in the `package` directive
   423  // in the proto file can be prepended to the gRPC service name in the `Tags` field of every operation.
   424  func (r *Registry) IsIncludePackageInTags() bool {
   425  	return r.includePackageInTags
   426  }
   427  
   428  // GetRepeatedPathParamSeparator returns a rune spcifying how
   429  // path parameter repeated fields are separated.
   430  func (r *Registry) GetRepeatedPathParamSeparator() rune {
   431  	return r.repeatedPathParamSeparator.sep
   432  }
   433  
   434  // GetRepeatedPathParamSeparatorName returns the name path parameter repeated
   435  // fields repeatedFieldSeparator. I.e. 'csv', 'pipe', 'ssv' or 'tsv'
   436  func (r *Registry) GetRepeatedPathParamSeparatorName() string {
   437  	return r.repeatedPathParamSeparator.name
   438  }
   439  
   440  // SetRepeatedPathParamSeparator sets how path parameter repeated fields are
   441  // separated. Allowed names are 'csv', 'pipe', 'ssv' and 'tsv'.
   442  func (r *Registry) SetRepeatedPathParamSeparator(name string) error {
   443  	var sep rune
   444  	switch name {
   445  	case "csv":
   446  		sep = ','
   447  	case "pipes":
   448  		sep = '|'
   449  	case "ssv":
   450  		sep = ' '
   451  	case "tsv":
   452  		sep = '\t'
   453  	default:
   454  		return fmt.Errorf("unknown repeated path parameter separator: %s", name)
   455  	}
   456  	r.repeatedPathParamSeparator = repeatedFieldSeparator{
   457  		name: name,
   458  		sep:  sep,
   459  	}
   460  	return nil
   461  }
   462  
   463  // SetUseJSONNamesForFields sets useJSONNamesForFields
   464  func (r *Registry) SetUseJSONNamesForFields(use bool) {
   465  	r.useJSONNamesForFields = use
   466  }
   467  
   468  // GetUseJSONNamesForFields returns useJSONNamesForFields
   469  func (r *Registry) GetUseJSONNamesForFields() bool {
   470  	return r.useJSONNamesForFields
   471  }
   472  
   473  // SetUseFQNForSwaggerName sets useFQNForSwaggerName
   474  func (r *Registry) SetUseFQNForSwaggerName(use bool) {
   475  	r.useFQNForSwaggerName = use
   476  }
   477  
   478  // GetAllowColonFinalSegments returns allowColonFinalSegments
   479  func (r *Registry) GetAllowColonFinalSegments() bool {
   480  	return r.allowColonFinalSegments
   481  }
   482  
   483  // SetAllowColonFinalSegments sets allowColonFinalSegments
   484  func (r *Registry) SetAllowColonFinalSegments(use bool) {
   485  	r.allowColonFinalSegments = use
   486  }
   487  
   488  // GetUseFQNForSwaggerName returns useFQNForSwaggerName
   489  func (r *Registry) GetUseFQNForSwaggerName() bool {
   490  	return r.useFQNForSwaggerName
   491  }
   492  
   493  // GetMergeFileName return the target merge swagger file name
   494  func (r *Registry) GetMergeFileName() string {
   495  	return r.mergeFileName
   496  }
   497  
   498  // SetUseGoTemplate sets useGoTemplate
   499  func (r *Registry) SetUseGoTemplate(use bool) {
   500  	r.useGoTemplate = use
   501  }
   502  
   503  // GetUseGoTemplate returns useGoTemplate
   504  func (r *Registry) GetUseGoTemplate() bool {
   505  	return r.useGoTemplate
   506  }
   507  
   508  // SetEnumsAsInts set enumsAsInts
   509  func (r *Registry) SetEnumsAsInts(enumsAsInts bool) {
   510  	r.enumsAsInts = enumsAsInts
   511  }
   512  
   513  // GetEnumsAsInts returns enumsAsInts
   514  func (r *Registry) GetEnumsAsInts() bool {
   515  	return r.enumsAsInts
   516  }
   517  
   518  // SetDisableDefaultErrors sets disableDefaultErrors
   519  func (r *Registry) SetDisableDefaultErrors(use bool) {
   520  	r.disableDefaultErrors = use
   521  }
   522  
   523  // GetDisableDefaultErrors returns disableDefaultErrors
   524  func (r *Registry) GetDisableDefaultErrors() bool {
   525  	return r.disableDefaultErrors
   526  }
   527  
   528  // SetSimpleOperationIDs sets simpleOperationIDs
   529  func (r *Registry) SetSimpleOperationIDs(use bool) {
   530  	r.simpleOperationIDs = use
   531  }
   532  
   533  // GetSimpleOperationIDs returns simpleOperationIDs
   534  func (r *Registry) GetSimpleOperationIDs() bool {
   535  	return r.simpleOperationIDs
   536  }
   537  
   538  // SetWarnOnUnboundMethods sets warnOnUnboundMethods
   539  func (r *Registry) SetWarnOnUnboundMethods(warn bool) {
   540  	r.warnOnUnboundMethods = warn
   541  }
   542  
   543  // SetGenerateUnboundMethods sets generateUnboundMethods
   544  func (r *Registry) SetGenerateUnboundMethods(generate bool) {
   545  	r.generateUnboundMethods = generate
   546  }
   547  
   548  // SetOmitPackageDoc controls whether the generated code contains a package comment (if set to false, it will contain one)
   549  func (r *Registry) SetOmitPackageDoc(omit bool) {
   550  	r.omitPackageDoc = omit
   551  }
   552  
   553  // GetOmitPackageDoc returns whether a package comment will be omitted from the generated code
   554  func (r *Registry) GetOmitPackageDoc() bool {
   555  	return r.omitPackageDoc
   556  }
   557  
   558  // sanitizePackageName replaces unallowed character in package name
   559  // with allowed character.
   560  func sanitizePackageName(pkgName string) string {
   561  	pkgName = strings.Replace(pkgName, ".", "_", -1)
   562  	pkgName = strings.Replace(pkgName, "-", "_", -1)
   563  	return pkgName
   564  }
   565  
   566  // defaultGoPackageName returns the default go package name to be used for go files generated from "f".
   567  // You might need to use an unique alias for the package when you import it.  Use ReserveGoPackageAlias to get a unique alias.
   568  func (r *Registry) defaultGoPackageName(f *descriptor.FileDescriptorProto) string {
   569  	name := r.packageIdentityName(f)
   570  	return sanitizePackageName(name)
   571  }
   572  
   573  // packageIdentityName returns the identity of packages.
   574  // protoc-gen-grpc-gateway rejects CodeGenerationRequests which contains more than one packages
   575  // as protoc-gen-go does.
   576  func (r *Registry) packageIdentityName(f *descriptor.FileDescriptorProto) string {
   577  	if f.Options != nil && f.Options.GoPackage != nil {
   578  		gopkg := f.Options.GetGoPackage()
   579  		idx := strings.LastIndex(gopkg, "/")
   580  		if idx < 0 {
   581  			gopkg = gopkg[idx+1:]
   582  		}
   583  
   584  		gopkg = gopkg[idx+1:]
   585  		// package name is overrided with the string after the
   586  		// ';' character
   587  		sc := strings.IndexByte(gopkg, ';')
   588  		if sc < 0 {
   589  			return sanitizePackageName(gopkg)
   590  
   591  		}
   592  		return sanitizePackageName(gopkg[sc+1:])
   593  	}
   594  	if p := r.importPath; len(p) != 0 {
   595  		if i := strings.LastIndex(p, "/"); i >= 0 {
   596  			p = p[i+1:]
   597  		}
   598  		return p
   599  	}
   600  
   601  	if f.Package == nil {
   602  		base := filepath.Base(f.GetName())
   603  		ext := filepath.Ext(base)
   604  		return strings.TrimSuffix(base, ext)
   605  	}
   606  	return f.GetPackage()
   607  }
   608  

View as plain text