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/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 ""
|
||
|
}
|