package generator import ( "strings" "unicode" "unicode/utf8" "github.com/jhump/protoreflect/desc" "google.golang.org/protobuf/proto" descriptor "google.golang.org/protobuf/types/descriptorpb" "google.golang.org/protobuf/types/pluginpb" gqlpb "go.unistack.org/micro-proto/v3/graphql" ) func GraphqlMethodOptions(opts proto.Message) *gqlpb.Rpc { if opts != nil { v := proto.GetExtension(opts, gqlpb.E_Rpc) if v != nil { return v.(*gqlpb.Rpc) } } return nil } func GraphqlServiceOptions(opts proto.Message) *gqlpb.Svc { if opts != nil { v := proto.GetExtension(opts, gqlpb.E_Svc) if v != nil { return v.(*gqlpb.Svc) } } return nil } func GraphqlFieldOptions(opts proto.Message) *gqlpb.Field { if opts != nil { v := proto.GetExtension(opts, gqlpb.E_Field) if v != nil && v.(*gqlpb.Field) != nil { return v.(*gqlpb.Field) } } return nil } func GraphqlOneofOptions(opts proto.Message) *gqlpb.Oneof { if opts != nil { v := proto.GetExtension(opts, gqlpb.E_Oneof) if v != nil && v.(*gqlpb.Oneof) != nil { return v.(*gqlpb.Oneof) } } return nil } // GoCamelCase camel-cases a protobuf name for use as a Go identifier. // // If there is an interior underscore followed by a lower case letter, // drop the underscore and convert the letter to upper case. func GoCamelCase(s string) string { // Invariant: if the next letter is lower case, it must be converted // to upper case. // That is, we process a word at a time, where words are marked by _ or // upper case letter. Digits are treated as words. var b []byte for i := 0; i < len(s); i++ { c := s[i] switch { case c == '.' && i+1 < len(s) && isASCIILower(s[i+1]): // Skip over '.' in ".{{lowercase}}". case c == '.': b = append(b, '_') // convert '.' to '_' case c == '_' && (i == 0 || s[i-1] == '.'): // Convert initial '_' to ensure we start with a capital letter. // Do the same for '_' after '.' to match historic behavior. b = append(b, 'X') // convert '_' to 'X' case c == '_' && i+1 < len(s) && isASCIILower(s[i+1]): // Skip over '_' in "_{{lowercase}}". case isASCIIDigit(c): b = append(b, c) default: // Assume we have a letter now - if not, it's a bogus identifier. // The next word is a sequence of characters that must start upper case. if isASCIILower(c) { c -= 'a' - 'A' // convert lowercase to uppercase } b = append(b, c) // Accept lower case sequence that follows. for ; i+1 < len(s) && isASCIILower(s[i+1]); i++ { b = append(b, s[i+1]) } } } return string(b) } func GetRequestType(rpcOpts *gqlpb.Rpc, svcOpts *gqlpb.Svc) gqlpb.Type { if rpcOpts != nil && rpcOpts.Type != nil { return *rpcOpts.Type } if svcOpts != nil && svcOpts.Type != nil { return *svcOpts.Type } return gqlpb.Type_DEFAULT } func CreateDescriptorsFromProto(req *pluginpb.CodeGeneratorRequest) (descs []*desc.FileDescriptor, err error) { dd, err := desc.CreateFileDescriptorsFromSet(&descriptor.FileDescriptorSet{ File: req.GetProtoFile(), }) if err != nil { return nil, err } for _, d := range dd { for _, filename := range req.FileToGenerate { if filename == d.GetName() { descs = append(descs, d) } } } return } func ResolveProtoFilesRecursively(descs []*desc.FileDescriptor) (files FileDescriptors) { for _, f := range descs { files = append(files, ResolveProtoFilesRecursively(f.GetDependencies())...) files = append(files, f) } return files } type FileDescriptors []*desc.FileDescriptor func (ds FileDescriptors) AsFileDescriptorProto() (files []*descriptor.FileDescriptorProto) { for _, d := range ds { files = append(files, d.AsFileDescriptorProto()) } return } // Split splits the camelcase word and returns a list of words. It also // supports digits. Both lower camel case and upper camel case are supported. // For more info please check: http://en.wikipedia.org/wiki/CamelCase // // Examples // // "" => [""] // "lowercase" => ["lowercase"] // "Class" => ["Class"] // "MyClass" => ["My", "Class"] // "MyC" => ["My", "C"] // "HTML" => ["HTML"] // "PDFLoader" => ["PDF", "Loader"] // "AString" => ["A", "String"] // "SimpleXMLParser" => ["Simple", "XML", "Parser"] // "vimRPCPlugin" => ["vim", "RPC", "Plugin"] // "GL11Version" => ["GL", "11", "Version"] // "99Bottles" => ["99", "Bottles"] // "May5" => ["May", "5"] // "BFG9000" => ["BFG", "9000"] // "BöseÜberraschung" => ["Böse", "Überraschung"] // "Two spaces" => ["Two", " ", "spaces"] // "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] // // Splitting rules // // 1. If string is not valid UTF-8, return it without splitting as // single item array. // 2. Assign all unicode characters into one of 4 sets: lower case // letters, upper case letters, numbers, and all other characters. // 3. Iterate through characters of string, introducing splits // between adjacent characters that belong to different sets. // 4. Iterate through array of split strings, and if a given string // is upper case: // if subsequent string is lower case: // move last character of upper case string to beginning of // lower case string func SplitCamelCase(src string) (entries []string) { // don't split invalid utf8 if !utf8.ValidString(src) { return []string{src} } entries = []string{} var runes [][]rune lastClass := 0 class := 0 // split into fields based on class of unicode character for _, r := range src { switch true { case unicode.IsLower(r): class = 1 case unicode.IsUpper(r): class = 2 case unicode.IsDigit(r): class = 3 default: class = 4 } if class == lastClass { runes[len(runes)-1] = append(runes[len(runes)-1], r) } else { runes = append(runes, []rune{r}) } lastClass = class } // handle upper case -> lower case sequences, e.g. // "PDFL", "oader" -> "PDF", "Loader" for i := 0; i < len(runes)-1; i++ { if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) runes[i] = runes[i][:len(runes[i])-1] } } // construct []string from results for _, s := range runes { if len(s) > 0 { entries = append(entries, string(s)) } } return } // CamelCase returns the CamelCased name. // If there is an interior underscore followed by a lower case letter, // drop the underscore and convert the letter to upper case. // There is a remote possibility of this rewrite causing a name collision, // but it's so remote we're prepared to pretend it's nonexistent - since the // C++ generator lowercases names, it's extremely unlikely to have two fields // with different capitalizations. // In short, _my_field_name_2 becomes XMyFieldName_2. func CamelCase(s string) string { if s == "" { return "" } t := make([]byte, 0, 32) i := 0 if s[0] == '_' { // Need a capital letter; drop the '_'. t = append(t, 'X') i++ } // Invariant: if the next letter is lower case, it must be converted // to upper case. // That is, we process a word at a time, where words are marked by _ or // upper case letter. Digits are treated as words. for ; i < len(s); i++ { c := s[i] if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { continue // Skip the underscore in s. } if isASCIIDigit(c) { t = append(t, c) continue } // Assume we have a letter now - if not, it's a bogus identifier. // The next word is a sequence of characters that must start upper case. if isASCIILower(c) { c ^= ' ' // Make it a capital letter. } t = append(t, c) // Guaranteed not lower case. // Accept lower case sequence that follows. for i+1 < len(s) && isASCIILower(s[i+1]) { i++ t = append(t, s[i]) } } return string(t) } // CamelCaseSlice is like CamelCase, but the argument is a slice of strings to // be joined with "_". func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) } // Is c an ASCII lower-case letter? func isASCIILower(c byte) bool { return 'a' <= c && c <= 'z' } // Is c an ASCII digit? func isASCIIDigit(c byte) bool { return '0' <= c && c <= '9' } func ToLowerFirst(s string) string { if len(s) > 0 { return string(unicode.ToLower(rune(s[0]))) + s[1:] } return "" }