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