// Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the License is // located at // // http://www.apache.org/licenses/LICENSE-2.0 // // or in the "license" file accompanying this file. This file is distributed on // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // // This file contains some code from https://github.com/golang/protobuf: // Copyright 2010 The Go Authors. All rights reserved. // https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package stringutils import ( "bytes" "fmt" "strings" "unicode" ) // 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' } // CamelCase converts a string from snake_case to CamelCased. // // 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 "_" and then camelcased. func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) } // DotJoin joins a slice of strings with '.' func DotJoin(elem []string) string { return strings.Join(elem, ".") } // AlphaDigitize replaces non-letter, non-digit, non-underscore characters with // underscore. func AlphaDigitize(r rune) rune { if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { return r } return '_' } // CleanIdentifier makes sure s is a valid 'identifier' string: it contains only // letters, numbers, and underscore. func CleanIdentifier(s string) string { return strings.Map(AlphaDigitize, s) } // BaseName the last path element of a slash-delimited name, with the last // dotted suffix removed. func BaseName(name string) string { // First, find the last element if i := strings.LastIndex(name, "/"); i >= 0 { name = name[i+1:] } // Now drop the suffix if i := strings.LastIndex(name, "."); i >= 0 { name = name[0:i] } return name } // SnakeCase converts a string from CamelCase to snake_case. func SnakeCase(s string) string { var buf bytes.Buffer for i, r := range s { if unicode.IsUpper(r) && i > 0 { fmt.Fprintf(&buf, "_") } r = unicode.ToLower(r) fmt.Fprintf(&buf, "%c", r) } return buf.String() }