code.gitea.io/gitea@v1.22.3/modules/util/util.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package util 5 6 import ( 7 "bytes" 8 "crypto/rand" 9 "fmt" 10 "math/big" 11 "strconv" 12 "strings" 13 14 "code.gitea.io/gitea/modules/optional" 15 16 "golang.org/x/text/cases" 17 "golang.org/x/text/language" 18 ) 19 20 // OptionalBoolParse get the corresponding optional.Option[bool] of a string using strconv.ParseBool 21 func OptionalBoolParse(s string) optional.Option[bool] { 22 v, e := strconv.ParseBool(s) 23 if e != nil { 24 return optional.None[bool]() 25 } 26 return optional.Some(v) 27 } 28 29 // IsEmptyString checks if the provided string is empty 30 func IsEmptyString(s string) bool { 31 return len(strings.TrimSpace(s)) == 0 32 } 33 34 // NormalizeEOL will convert Windows (CRLF) and Mac (CR) EOLs to UNIX (LF) 35 func NormalizeEOL(input []byte) []byte { 36 var right, left, pos int 37 if right = bytes.IndexByte(input, '\r'); right == -1 { 38 return input 39 } 40 length := len(input) 41 tmp := make([]byte, length) 42 43 // We know that left < length because otherwise right would be -1 from IndexByte. 44 copy(tmp[pos:pos+right], input[left:left+right]) 45 pos += right 46 tmp[pos] = '\n' 47 left += right + 1 48 pos++ 49 50 for left < length { 51 if input[left] == '\n' { 52 left++ 53 } 54 55 right = bytes.IndexByte(input[left:], '\r') 56 if right == -1 { 57 copy(tmp[pos:], input[left:]) 58 pos += length - left 59 break 60 } 61 copy(tmp[pos:pos+right], input[left:left+right]) 62 pos += right 63 tmp[pos] = '\n' 64 left += right + 1 65 pos++ 66 } 67 return tmp[:pos] 68 } 69 70 // CryptoRandomInt returns a crypto random integer between 0 and limit, inclusive 71 func CryptoRandomInt(limit int64) (int64, error) { 72 rInt, err := rand.Int(rand.Reader, big.NewInt(limit)) 73 if err != nil { 74 return 0, err 75 } 76 return rInt.Int64(), nil 77 } 78 79 const alphanumericalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 80 81 // CryptoRandomString generates a crypto random alphanumerical string, each byte is generated by [0,61] range 82 func CryptoRandomString(length int64) (string, error) { 83 buf := make([]byte, length) 84 limit := int64(len(alphanumericalChars)) 85 for i := range buf { 86 num, err := CryptoRandomInt(limit) 87 if err != nil { 88 return "", err 89 } 90 buf[i] = alphanumericalChars[num] 91 } 92 return string(buf), nil 93 } 94 95 // CryptoRandomBytes generates `length` crypto bytes 96 // This differs from CryptoRandomString, as each byte in CryptoRandomString is generated by [0,61] range 97 // This function generates totally random bytes, each byte is generated by [0,255] range 98 func CryptoRandomBytes(length int64) ([]byte, error) { 99 buf := make([]byte, length) 100 _, err := rand.Read(buf) 101 return buf, err 102 } 103 104 // ToUpperASCII returns s with all ASCII letters mapped to their upper case. 105 func ToUpperASCII(s string) string { 106 b := []byte(s) 107 for i, c := range b { 108 if 'a' <= c && c <= 'z' { 109 b[i] -= 'a' - 'A' 110 } 111 } 112 return string(b) 113 } 114 115 // ToTitleCase returns s with all english words capitalized 116 func ToTitleCase(s string) string { 117 // `cases.Title` is not thread-safe, do not use global shared variable for it 118 return cases.Title(language.English).String(s) 119 } 120 121 // ToTitleCaseNoLower returns s with all english words capitalized without lower-casing 122 func ToTitleCaseNoLower(s string) string { 123 // `cases.Title` is not thread-safe, do not use global shared variable for it 124 return cases.Title(language.English, cases.NoLower).String(s) 125 } 126 127 // ToInt64 transform a given int into int64. 128 func ToInt64(number any) (int64, error) { 129 var value int64 130 switch v := number.(type) { 131 case int: 132 value = int64(v) 133 case int8: 134 value = int64(v) 135 case int16: 136 value = int64(v) 137 case int32: 138 value = int64(v) 139 case int64: 140 value = v 141 142 case uint: 143 value = int64(v) 144 case uint8: 145 value = int64(v) 146 case uint16: 147 value = int64(v) 148 case uint32: 149 value = int64(v) 150 case uint64: 151 value = int64(v) 152 153 case float32: 154 value = int64(v) 155 case float64: 156 value = int64(v) 157 158 case string: 159 var err error 160 if value, err = strconv.ParseInt(v, 10, 64); err != nil { 161 return 0, err 162 } 163 default: 164 return 0, fmt.Errorf("unable to convert %v to int64", number) 165 } 166 return value, nil 167 } 168 169 // ToFloat64 transform a given int into float64. 170 func ToFloat64(number any) (float64, error) { 171 var value float64 172 switch v := number.(type) { 173 case int: 174 value = float64(v) 175 case int8: 176 value = float64(v) 177 case int16: 178 value = float64(v) 179 case int32: 180 value = float64(v) 181 case int64: 182 value = float64(v) 183 184 case uint: 185 value = float64(v) 186 case uint8: 187 value = float64(v) 188 case uint16: 189 value = float64(v) 190 case uint32: 191 value = float64(v) 192 case uint64: 193 value = float64(v) 194 195 case float32: 196 value = float64(v) 197 case float64: 198 value = v 199 200 case string: 201 var err error 202 if value, err = strconv.ParseFloat(v, 64); err != nil { 203 return 0, err 204 } 205 default: 206 return 0, fmt.Errorf("unable to convert %v to float64", number) 207 } 208 return value, nil 209 } 210 211 // ToPointer returns the pointer of a copy of any given value 212 func ToPointer[T any](val T) *T { 213 return &val 214 } 215 216 // Iif is an "inline-if", it returns "trueVal" if "condition" is true, otherwise "falseVal" 217 func Iif[T any](condition bool, trueVal, falseVal T) T { 218 if condition { 219 return trueVal 220 } 221 return falseVal 222 } 223 224 // IfZero returns "def" if "v" is a zero value, otherwise "v" 225 func IfZero[T comparable](v, def T) T { 226 var zero T 227 if v == zero { 228 return def 229 } 230 return v 231 } 232 233 func ReserveLineBreakForTextarea(input string) string { 234 // Since the content is from a form which is a textarea, the line endings are \r\n. 235 // It's a standard behavior of HTML. 236 // But we want to store them as \n like what GitHub does. 237 // And users are unlikely to really need to keep the \r. 238 // Other than this, we should respect the original content, even leading or trailing spaces. 239 return strings.ReplaceAll(input, "\r\n", "\n") 240 }