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