github.com/openconfig/goyang@v1.4.5/pkg/yang/camelcase.go (about)

     1  // Copyright 2015 Google Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package yang
    16  
    17  var knownWords = map[string]string{
    18  	"Ietf": "IETF",
    19  }
    20  
    21  // Is c an ASCII lower-case letter?
    22  func isASCIILower(c byte) bool {
    23  	return 'a' <= c && c <= 'z'
    24  }
    25  
    26  // Is c an ASCII digit?
    27  func isASCIIDigit(c byte) bool {
    28  	return '0' <= c && c <= '9'
    29  }
    30  
    31  // CamelCase returns a CamelCased name for a YANG identifier.
    32  // Currently this supports the output being used for a Go or proto identifier.
    33  // Dash and dot are first converted to underscore, and then any underscores
    34  // before a lower-case letter are removed, and the letter converted to
    35  // upper-case. Any input characters not part of the YANG identifier
    36  // specification (https://tools.ietf.org/html/rfc7950#section-6.2) are treated
    37  // as lower-case characters.
    38  // The first letter is always upper-case in order to be an exported name in Go.
    39  // There is a remote possibility of this rewrite causing a name collision, but
    40  // it's so remote we're prepared to pretend it's nonexistent - since the C++
    41  // generator lowercases names, it's extremely unlikely to have two fields with
    42  // different capitalizations. In short, _my_field-name_2 becomes XMyFieldName_2.
    43  func CamelCase(s string) string {
    44  	if s == "" {
    45  		return ""
    46  	}
    47  
    48  	fix := func(c byte) byte {
    49  		if c == '-' || c == '.' {
    50  			return '_'
    51  		}
    52  		return c
    53  	}
    54  
    55  	t := make([]byte, 0, 32)
    56  	i := 0
    57  	if fix(s[0]) == '_' {
    58  		// Need a capital letter; drop the '_'.
    59  		t = append(t, 'X')
    60  		i++
    61  	}
    62  
    63  	// Invariant: if the next letter is lower case, it must be converted
    64  	// to upper case.
    65  	// That is, we process a word at a time, where words are marked by _ or
    66  	// upper case letter. Digits are treated as words.
    67  	for ; i < len(s); i++ {
    68  		c := fix(s[i])
    69  		if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
    70  			continue // Skip the underscore in s.
    71  		}
    72  		if isASCIIDigit(c) {
    73  			t = append(t, c)
    74  			continue
    75  		}
    76  		// Assume we have a letter now - if not, it's a bogus identifier.
    77  		// The next word is a sequence of characters that must start upper case.
    78  		if isASCIILower(c) {
    79  			c ^= ' ' // Make it a capital letter.
    80  		}
    81  		start := len(t)
    82  		t = append(t, c) // Guaranteed not lower case.
    83  		// Accept lower case sequence that follows.
    84  		for i+1 < len(s) && isASCIILower(s[i+1]) {
    85  			i++
    86  			t = append(t, s[i])
    87  		}
    88  		// If the word turns out to be a special word, then use that instead.
    89  		if kn := knownWords[string(t[start:])]; kn != "" {
    90  			t = append(t[:start], []byte(kn)...)
    91  		}
    92  	}
    93  	return string(t)
    94  }