Files

268 lines
6.4 KiB
Go
Raw Permalink Normal View History

2017-01-24 21:43:02 -05:00
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2017-01-24 21:43:02 -05:00
package util
2018-02-20 04:50:42 -08:00
import (
"bytes"
"crypto/rand"
"fmt"
"math/big"
"slices"
"strconv"
"strings"
2022-05-10 23:55:54 +02:00
"golang.org/x/text/cases"
"golang.org/x/text/language"
2018-02-20 04:50:42 -08:00
)
2019-01-21 12:45:32 +01:00
// IsEmptyString checks if the provided string is empty
func IsEmptyString(s string) bool {
return len(strings.TrimSpace(s)) == 0
}
// NormalizeEOL will convert Windows (CRLF) and Mac (CR) EOLs to UNIX (LF)
func NormalizeEOL(input []byte) []byte {
var right, left, pos int
if right = bytes.IndexByte(input, '\r'); right == -1 {
return input
}
length := len(input)
tmp := make([]byte, length)
// We know that left < length because otherwise right would be -1 from IndexByte.
copy(tmp[pos:pos+right], input[left:left+right])
pos += right
tmp[pos] = '\n'
left += right + 1
pos++
for left < length {
if input[left] == '\n' {
left++
}
right = bytes.IndexByte(input[left:], '\r')
if right == -1 {
copy(tmp[pos:], input[left:])
pos += length - left
break
}
copy(tmp[pos:pos+right], input[left:left+right])
pos += right
tmp[pos] = '\n'
left += right + 1
pos++
}
return tmp[:pos]
}
2020-11-25 12:20:40 +01:00
2022-01-26 12:10:10 +08:00
// CryptoRandomInt returns a crypto random integer between 0 and limit, inclusive
func CryptoRandomInt(limit int64) (int64, error) {
2022-01-04 15:13:52 +00:00
rInt, err := rand.Int(rand.Reader, big.NewInt(limit))
if err != nil {
return 0, err
}
2022-01-04 15:13:52 +00:00
return rInt.Int64(), nil
}
2022-01-26 12:10:10 +08:00
const alphanumericalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
2022-01-26 12:10:10 +08:00
// CryptoRandomString generates a crypto random alphanumerical string, each byte is generated by [0,61] range
func CryptoRandomString(length int64) (string, error) {
buf := make([]byte, length)
limit := int64(len(alphanumericalChars))
for i := range buf {
num, err := CryptoRandomInt(limit)
if err != nil {
return "", err
}
2022-01-26 12:10:10 +08:00
buf[i] = alphanumericalChars[num]
}
2022-01-26 12:10:10 +08:00
return string(buf), nil
}
2022-01-04 15:13:52 +00:00
2022-01-26 12:10:10 +08:00
// CryptoRandomBytes generates `length` crypto bytes
// This differs from CryptoRandomString, as each byte in CryptoRandomString is generated by [0,61] range
// This function generates totally random bytes, each byte is generated by [0,255] range
func CryptoRandomBytes(length int64) ([]byte, error) {
buf := make([]byte, length)
_, err := rand.Read(buf)
return buf, err
2022-01-04 15:13:52 +00:00
}
2022-02-01 13:59:25 +01:00
// ToLowerASCII returns s with all ASCII letters mapped to their lower case.
func ToLowerASCII(s string) string {
2022-02-01 13:59:25 +01:00
b := []byte(s)
for i, c := range b {
if 'A' <= c && c <= 'Z' {
b[i] += 'a' - 'A'
2022-02-01 13:59:25 +01:00
}
}
return string(b)
}
2022-05-10 23:55:54 +02:00
// ToTitleCase returns s with all english words capitalized
func ToTitleCase(s string) string {
// `cases.Title` is not thread-safe, do not use global shared variable for it
return cases.Title(language.English).String(s)
2022-05-10 23:55:54 +02:00
}
2022-06-10 15:45:28 +02:00
// ToTitleCaseNoLower returns s with all english words capitalized without lower-casing
func ToTitleCaseNoLower(s string) string {
// `cases.Title` is not thread-safe, do not use global shared variable for it
return cases.Title(language.English, cases.NoLower).String(s)
}
// ToInt64 transform a given int into int64.
2023-07-04 20:36:08 +02:00
func ToInt64(number any) (int64, error) {
2022-06-12 14:08:23 +02:00
var value int64
switch v := number.(type) {
case int:
value = int64(v)
case int8:
value = int64(v)
case int16:
value = int64(v)
case int32:
value = int64(v)
case int64:
value = v
case uint:
value = int64(v)
case uint8:
value = int64(v)
case uint16:
value = int64(v)
case uint32:
value = int64(v)
case uint64:
value = int64(v)
case float32:
value = int64(v)
case float64:
value = int64(v)
case string:
var err error
if value, err = strconv.ParseInt(v, 10, 64); err != nil {
return 0, err
}
default:
return 0, fmt.Errorf("unable to convert %v to int64", number)
}
return value, nil
}
// ToFloat64 transform a given int into float64.
2023-07-04 20:36:08 +02:00
func ToFloat64(number any) (float64, error) {
var value float64
switch v := number.(type) {
case int:
value = float64(v)
case int8:
value = float64(v)
case int16:
value = float64(v)
case int32:
value = float64(v)
case int64:
value = float64(v)
case uint:
value = float64(v)
case uint8:
value = float64(v)
case uint16:
value = float64(v)
case uint32:
value = float64(v)
case uint64:
value = float64(v)
case float32:
value = float64(v)
case float64:
value = v
case string:
var err error
if value, err = strconv.ParseFloat(v, 64); err != nil {
return 0, err
}
default:
return 0, fmt.Errorf("unable to convert %v to float64", number)
2022-06-12 14:08:23 +02:00
}
return value, nil
2022-06-12 14:08:23 +02:00
}
2023-07-07 07:31:56 +02:00
2024-04-03 10:16:46 +08:00
// Iif is an "inline-if", it returns "trueVal" if "condition" is true, otherwise "falseVal"
2024-04-07 19:17:06 +08:00
func Iif[T any](condition bool, trueVal, falseVal T) T {
2024-04-03 10:16:46 +08:00
if condition {
return trueVal
}
return falseVal
}
2024-03-14 09:10:51 +08:00
// IfZero returns "def" if "v" is a zero value, otherwise "v"
func IfZero[T comparable](v, def T) T {
var zero T
if v == zero {
return def
}
return v
}
2024-03-29 04:40:35 +08:00
2025-04-05 11:56:48 +08:00
func IfEmpty[T any](v, def []T) []T {
if len(v) == 0 {
return def
}
return v
}
// OptionalArg helps the "optional argument" in Golang:
//
// func foo(optArg ...int) { return OptionalArg(optArg) }
// calling `foo()` gets zero value 0, calling `foo(100)` gets 100
// func bar(optArg ...int) { return OptionalArg(optArg, 42) }
// calling `bar()` gets default value 42, calling `bar(100)` gets 100
//
// Passing more than 1 item to `optArg` or `defaultValue` is undefined behavior.
// At the moment only the first item is used.
func OptionalArg[T any](optArg []T, defaultValue ...T) (ret T) {
if len(optArg) >= 1 {
return optArg[0]
}
if len(defaultValue) >= 1 {
return defaultValue[0]
}
return ret
}
type EnumConst[T comparable] interface {
EnumValues() []T
}
// EnumValue returns the value if it's in the enum const's values,
// otherwise returns the first item of enums as default value.
func EnumValue[T comparable](val EnumConst[T]) (ret T, valid bool) {
enums := val.EnumValues()
if slices.Contains(enums, val.(T)) {
return val.(T), true
}
return enums[0], false
}
2026-04-24 07:33:25 +08:00
func NormalizeStringEOL(input string) string {
2024-03-29 04:40:35 +08:00
// Since the content is from a form which is a textarea, the line endings are \r\n.
// It's a standard behavior of HTML.
2026-04-24 07:33:25 +08:00
// But in most cases, we only want "\n" for EOL
// * Text files: use "\n" by default because "\r\n" sometimes doesn't work in POSIX
// * Actions values: store them as "\n" like what GitHub does.
// And users are unlikely to really need the "\r".
2024-03-29 04:40:35 +08:00
// Other than this, we should respect the original content, even leading or trailing spaces.
2026-04-24 07:33:25 +08:00
return UnsafeBytesToString(NormalizeEOL(UnsafeStringToBytes(input)))
2024-03-29 04:40:35 +08:00
}