stringutils.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"). You may not
  4. // use this file except in compliance with the License. A copy of the License is
  5. // located at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // or in the "license" file accompanying this file. This file is distributed on
  10. // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  11. // express or implied. See the License for the specific language governing
  12. // permissions and limitations under the License.
  13. //
  14. // This file contains some code from https://github.com/golang/protobuf:
  15. // Copyright 2010 The Go Authors. All rights reserved.
  16. // https://github.com/golang/protobuf
  17. //
  18. // Redistribution and use in source and binary forms, with or without
  19. // modification, are permitted provided that the following conditions are
  20. // met:
  21. //
  22. // * Redistributions of source code must retain the above copyright
  23. // notice, this list of conditions and the following disclaimer.
  24. // * Redistributions in binary form must reproduce the above
  25. // copyright notice, this list of conditions and the following disclaimer
  26. // in the documentation and/or other materials provided with the
  27. // distribution.
  28. // * Neither the name of Google Inc. nor the names of its
  29. // contributors may be used to endorse or promote products derived from
  30. // this software without specific prior written permission.
  31. //
  32. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  33. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  34. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  35. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  36. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  37. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  38. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  39. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  40. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  41. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  42. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. package stringutils
  44. import (
  45. "bytes"
  46. "fmt"
  47. "strings"
  48. "unicode"
  49. )
  50. // Is c an ASCII lower-case letter?
  51. func isASCIILower(c byte) bool {
  52. return 'a' <= c && c <= 'z'
  53. }
  54. // Is c an ASCII digit?
  55. func isASCIIDigit(c byte) bool {
  56. return '0' <= c && c <= '9'
  57. }
  58. // CamelCase converts a string from snake_case to CamelCased.
  59. //
  60. // If there is an interior underscore followed by a lower case letter, drop the
  61. // underscore and convert the letter to upper case. There is a remote
  62. // possibility of this rewrite causing a name collision, but it's so remote
  63. // we're prepared to pretend it's nonexistent - since the C++ generator
  64. // lowercases names, it's extremely unlikely to have two fields with different
  65. // capitalizations. In short, _my_field_name_2 becomes XMyFieldName_2.
  66. func CamelCase(s string) string {
  67. if s == "" {
  68. return ""
  69. }
  70. t := make([]byte, 0, 32)
  71. i := 0
  72. if s[0] == '_' {
  73. // Need a capital letter; drop the '_'.
  74. t = append(t, 'X')
  75. i++
  76. }
  77. // Invariant: if the next letter is lower case, it must be converted
  78. // to upper case.
  79. //
  80. // That is, we process a word at a time, where words are marked by _ or upper
  81. // case letter. Digits are treated as words.
  82. for ; i < len(s); i++ {
  83. c := s[i]
  84. if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
  85. continue // Skip the underscore in s.
  86. }
  87. if isASCIIDigit(c) {
  88. t = append(t, c)
  89. continue
  90. }
  91. // Assume we have a letter now - if not, it's a bogus identifier. The next
  92. // word is a sequence of characters that must start upper case.
  93. if isASCIILower(c) {
  94. c ^= ' ' // Make it a capital letter.
  95. }
  96. t = append(t, c) // Guaranteed not lower case.
  97. // Accept lower case sequence that follows.
  98. for i+1 < len(s) && isASCIILower(s[i+1]) {
  99. i++
  100. t = append(t, s[i])
  101. }
  102. }
  103. return string(t)
  104. }
  105. // CamelCaseSlice is like CamelCase, but the argument is a slice of strings to
  106. // be joined with "_" and then camelcased.
  107. func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) }
  108. // DotJoin joins a slice of strings with '.'
  109. func DotJoin(elem []string) string { return strings.Join(elem, ".") }
  110. // AlphaDigitize replaces non-letter, non-digit, non-underscore characters with
  111. // underscore.
  112. func AlphaDigitize(r rune) rune {
  113. if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
  114. return r
  115. }
  116. return '_'
  117. }
  118. // CleanIdentifier makes sure s is a valid 'identifier' string: it contains only
  119. // letters, numbers, and underscore.
  120. func CleanIdentifier(s string) string {
  121. return strings.Map(AlphaDigitize, s)
  122. }
  123. // BaseName the last path element of a slash-delimited name, with the last
  124. // dotted suffix removed.
  125. func BaseName(name string) string {
  126. // First, find the last element
  127. if i := strings.LastIndex(name, "/"); i >= 0 {
  128. name = name[i+1:]
  129. }
  130. // Now drop the suffix
  131. if i := strings.LastIndex(name, "."); i >= 0 {
  132. name = name[0:i]
  133. }
  134. return name
  135. }
  136. // SnakeCase converts a string from CamelCase to snake_case.
  137. func SnakeCase(s string) string {
  138. var buf bytes.Buffer
  139. for i, r := range s {
  140. if unicode.IsUpper(r) && i > 0 {
  141. fmt.Fprintf(&buf, "_")
  142. }
  143. r = unicode.ToLower(r)
  144. fmt.Fprintf(&buf, "%c", r)
  145. }
  146. return buf.String()
  147. }