You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
5.3 KiB
158 lines
5.3 KiB
// 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() |
|
}
|
|
|