292 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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/v4/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 ""
 | |
| }
 |