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  }