1 package casing 2 3 import ( 4 "path/filepath" 5 "strings" 6 ) 7 8 // Camel returns the CamelCased name. 9 // 10 // This was moved from the now deprecated github.com/golang/protobuf/protoc-gen-go/generator package 11 // 12 // If there is an interior underscore followed by a lower case letter, 13 // drop the underscore and convert the letter to upper case. 14 // There is a remote possibility of this rewrite causing a name collision, 15 // but it's so remote we're prepared to pretend it's nonexistent - since the 16 // C++ generator lowercases names, it's extremely unlikely to have two fields 17 // with different capitalizations. 18 // In short, _my_field_name_2 becomes XMyFieldName_2. 19 func Camel(s string) string { 20 if s == "" { 21 return "" 22 } 23 t := make([]byte, 0, 32) 24 i := 0 25 if s[0] == '_' { 26 // Need a capital letter; drop the '_'. 27 t = append(t, 'X') 28 i++ 29 } 30 // Invariant: if the next letter is lower case, it must be converted 31 // to upper case. 32 // That is, we process a word at a time, where words are marked by _ or 33 // upper case letter. Digits are treated as words. 34 for ; i < len(s); i++ { 35 c := s[i] 36 if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { 37 continue // Skip the underscore in s. 38 } 39 if isASCIIDigit(c) { 40 t = append(t, c) 41 continue 42 } 43 // Assume we have a letter now - if not, it's a bogus identifier. 44 // The next word is a sequence of characters that must start upper case. 45 if isASCIILower(c) { 46 c ^= ' ' // Make it a capital letter. 47 } 48 t = append(t, c) // Guaranteed not lower case. 49 // Accept lower case sequence that follows. 50 for i+1 < len(s) && isASCIILower(s[i+1]) { 51 i++ 52 t = append(t, s[i]) 53 } 54 } 55 return string(t) 56 } 57 58 // CamelIdentifier returns the CamelCased identifier without affecting the package name/path if any. 59 func CamelIdentifier(s string) string { 60 const dot = "." 61 if !strings.Contains(s, dot) { 62 return Camel(s) 63 } 64 identifier := filepath.Ext(s) 65 path := strings.TrimSuffix(s, identifier) 66 identifier = strings.TrimPrefix(identifier, dot) 67 return path + dot + Camel(identifier) 68 } 69 70 // JSONCamelCase converts a snake_case identifier to a camelCase identifier, 71 // according to the protobuf JSON specification. 72 func JSONCamelCase(s string) string { 73 var b []byte 74 var wasUnderscore bool 75 for i := 0; i < len(s); i++ { // proto identifiers are always ASCII 76 c := s[i] 77 if c != '_' { 78 if wasUnderscore && isASCIILower(c) { 79 c -= 'a' - 'A' // convert to uppercase 80 } 81 b = append(b, c) 82 } 83 wasUnderscore = c == '_' 84 } 85 return string(b) 86 } 87 88 // And now lots of helper functions. 89 90 // Is c an ASCII lower-case letter? 91 func isASCIILower(c byte) bool { 92 return 'a' <= c && c <= 'z' 93 } 94 95 // Is c an ASCII digit? 96 func isASCIIDigit(c byte) bool { 97 return '0' <= c && c <= '9' 98 } 99