You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

util.go 5.4KB

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