github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/util/string.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package util
     7  
     8  import "github.com/yuin/goldmark/util"
     9  
    10  func isSnakeCaseUpper(c byte) bool {
    11  	return 'A' <= c && c <= 'Z'
    12  }
    13  
    14  func isSnakeCaseLowerOrNumber(c byte) bool {
    15  	return 'a' <= c && c <= 'z' || '0' <= c && c <= '9'
    16  }
    17  
    18  // ToSnakeCase convert the input string to snake_case format.
    19  //
    20  // Some samples.
    21  //
    22  //	"FirstName"  => "first_name"
    23  //	"HTTPServer" => "http_server"
    24  //	"NoHTTPS"    => "no_https"
    25  //	"GO_PATH"    => "go_path"
    26  //	"GO PATH"    => "go_path"      // space is converted to underscore.
    27  //	"GO-PATH"    => "go_path"      // hyphen is converted to underscore.
    28  func ToSnakeCase(input string) string {
    29  	if len(input) == 0 {
    30  		return ""
    31  	}
    32  
    33  	var res []byte
    34  	if len(input) == 1 {
    35  		c := input[0]
    36  		if isSnakeCaseUpper(c) {
    37  			res = []byte{c + 'a' - 'A'}
    38  		} else if isSnakeCaseLowerOrNumber(c) {
    39  			res = []byte{c}
    40  		} else {
    41  			res = []byte{'_'}
    42  		}
    43  	} else {
    44  		res = make([]byte, 0, len(input)*4/3)
    45  		pos := 0
    46  		needSep := false
    47  		for pos < len(input) {
    48  			c := input[pos]
    49  			if c >= 0x80 {
    50  				res = append(res, c)
    51  				pos++
    52  				continue
    53  			}
    54  			isUpper := isSnakeCaseUpper(c)
    55  			if isUpper || isSnakeCaseLowerOrNumber(c) {
    56  				end := pos + 1
    57  				if isUpper {
    58  					// skip the following upper letters
    59  					for end < len(input) && isSnakeCaseUpper(input[end]) {
    60  						end++
    61  					}
    62  					if end-pos > 1 && end < len(input) && isSnakeCaseLowerOrNumber(input[end]) {
    63  						end--
    64  					}
    65  				}
    66  				// skip the following lower or number letters
    67  				for end < len(input) && (isSnakeCaseLowerOrNumber(input[end]) || input[end] >= 0x80) {
    68  					end++
    69  				}
    70  				if needSep {
    71  					res = append(res, '_')
    72  				}
    73  				res = append(res, input[pos:end]...)
    74  				pos = end
    75  				needSep = true
    76  			} else {
    77  				res = append(res, '_')
    78  				pos++
    79  				needSep = false
    80  			}
    81  		}
    82  		for i := 0; i < len(res); i++ {
    83  			if isSnakeCaseUpper(res[i]) {
    84  				res[i] += 'a' - 'A'
    85  			}
    86  		}
    87  	}
    88  	return util.BytesToReadOnlyString(res)
    89  }