github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/util/util.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package util 7 8 import ( 9 "bytes" 10 "crypto/rand" 11 "errors" 12 "math/big" 13 "regexp" 14 "strconv" 15 "strings" 16 17 "golang.org/x/text/cases" 18 "golang.org/x/text/language" 19 ) 20 21 // OptionalBool a boolean that can be "null" 22 type OptionalBool byte 23 24 const ( 25 // OptionalBoolNone a "null" boolean value 26 OptionalBoolNone OptionalBool = iota 27 // OptionalBoolTrue a "true" boolean value 28 OptionalBoolTrue 29 // OptionalBoolFalse a "false" boolean value 30 OptionalBoolFalse 31 ) 32 33 // IsTrue return true if equal to OptionalBoolTrue 34 func (o OptionalBool) IsTrue() bool { 35 return o == OptionalBoolTrue 36 } 37 38 // IsFalse return true if equal to OptionalBoolFalse 39 func (o OptionalBool) IsFalse() bool { 40 return o == OptionalBoolFalse 41 } 42 43 // IsNone return true if equal to OptionalBoolNone 44 func (o OptionalBool) IsNone() bool { 45 return o == OptionalBoolNone 46 } 47 48 // OptionalBoolOf get the corresponding OptionalBool of a bool 49 func OptionalBoolOf(b bool) OptionalBool { 50 if b { 51 return OptionalBoolTrue 52 } 53 return OptionalBoolFalse 54 } 55 56 // OptionalBoolParse get the corresponding OptionalBool of a string using strconv.ParseBool 57 func OptionalBoolParse(s string) OptionalBool { 58 b, e := strconv.ParseBool(s) 59 if e != nil { 60 return OptionalBoolNone 61 } 62 return OptionalBoolOf(b) 63 } 64 65 // Max max of two ints 66 func Max(a, b int) int { 67 if a < b { 68 return b 69 } 70 return a 71 } 72 73 // Min min of two ints 74 func Min(a, b int) int { 75 if a > b { 76 return b 77 } 78 return a 79 } 80 81 // IsEmptyString checks if the provided string is empty 82 func IsEmptyString(s string) bool { 83 return len(strings.TrimSpace(s)) == 0 84 } 85 86 // NormalizeEOL will convert Windows (CRLF) and Mac (CR) EOLs to UNIX (LF) 87 func NormalizeEOL(input []byte) []byte { 88 var right, left, pos int 89 if right = bytes.IndexByte(input, '\r'); right == -1 { 90 return input 91 } 92 length := len(input) 93 tmp := make([]byte, length) 94 95 // We know that left < length because otherwise right would be -1 from IndexByte. 96 copy(tmp[pos:pos+right], input[left:left+right]) 97 pos += right 98 tmp[pos] = '\n' 99 left += right + 1 100 pos++ 101 102 for left < length { 103 if input[left] == '\n' { 104 left++ 105 } 106 107 right = bytes.IndexByte(input[left:], '\r') 108 if right == -1 { 109 copy(tmp[pos:], input[left:]) 110 pos += length - left 111 break 112 } 113 copy(tmp[pos:pos+right], input[left:left+right]) 114 pos += right 115 tmp[pos] = '\n' 116 left += right + 1 117 pos++ 118 } 119 return tmp[:pos] 120 } 121 122 // MergeInto merges pairs of values into a "dict" 123 func MergeInto(dict map[string]interface{}, values ...interface{}) (map[string]interface{}, error) { 124 for i := 0; i < len(values); i++ { 125 switch key := values[i].(type) { 126 case string: 127 i++ 128 if i == len(values) { 129 return nil, errors.New("specify the key for non array values") 130 } 131 dict[key] = values[i] 132 case map[string]interface{}: 133 m := values[i].(map[string]interface{}) 134 for i, v := range m { 135 dict[i] = v 136 } 137 default: 138 return nil, errors.New("dict values must be maps") 139 } 140 } 141 142 return dict, nil 143 } 144 145 // CryptoRandomInt returns a crypto random integer between 0 and limit, inclusive 146 func CryptoRandomInt(limit int64) (int64, error) { 147 rInt, err := rand.Int(rand.Reader, big.NewInt(limit)) 148 if err != nil { 149 return 0, err 150 } 151 return rInt.Int64(), nil 152 } 153 154 const alphanumericalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 155 156 // CryptoRandomString generates a crypto random alphanumerical string, each byte is generated by [0,61] range 157 func CryptoRandomString(length int64) (string, error) { 158 buf := make([]byte, length) 159 limit := int64(len(alphanumericalChars)) 160 for i := range buf { 161 num, err := CryptoRandomInt(limit) 162 if err != nil { 163 return "", err 164 } 165 buf[i] = alphanumericalChars[num] 166 } 167 return string(buf), nil 168 } 169 170 // CryptoRandomBytes generates `length` crypto bytes 171 // This differs from CryptoRandomString, as each byte in CryptoRandomString is generated by [0,61] range 172 // This function generates totally random bytes, each byte is generated by [0,255] range 173 func CryptoRandomBytes(length int64) ([]byte, error) { 174 buf := make([]byte, length) 175 _, err := rand.Read(buf) 176 return buf, err 177 } 178 179 // ToUpperASCII returns s with all ASCII letters mapped to their upper case. 180 func ToUpperASCII(s string) string { 181 b := []byte(s) 182 for i, c := range b { 183 if 'a' <= c && c <= 'z' { 184 b[i] -= 'a' - 'A' 185 } 186 } 187 return string(b) 188 } 189 190 var titleCaser = cases.Title(language.English) 191 192 // ToTitleCase returns s with all english words capitalized 193 func ToTitleCase(s string) string { 194 return titleCaser.String(s) 195 } 196 197 var ( 198 whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$") 199 leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])") 200 ) 201 202 // Dedent removes common indentation of a multi-line string along with whitespace around it 203 // Based on https://github.com/lithammer/dedent 204 func Dedent(s string) string { 205 var margin string 206 207 s = whitespaceOnly.ReplaceAllString(s, "") 208 indents := leadingWhitespace.FindAllStringSubmatch(s, -1) 209 210 for i, indent := range indents { 211 if i == 0 { 212 margin = indent[1] 213 } else if strings.HasPrefix(indent[1], margin) { 214 continue 215 } else if strings.HasPrefix(margin, indent[1]) { 216 margin = indent[1] 217 } else { 218 margin = "" 219 break 220 } 221 } 222 223 if margin != "" { 224 s = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(s, "") 225 } 226 return strings.TrimSpace(s) 227 } 228 229 // NumberIntoInt64 transform a given int into int64. 230 func NumberIntoInt64(number interface{}) int64 { 231 var value int64 232 switch v := number.(type) { 233 case int: 234 value = int64(v) 235 case int8: 236 value = int64(v) 237 case int16: 238 value = int64(v) 239 case int32: 240 value = int64(v) 241 case int64: 242 value = v 243 } 244 return value 245 }