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  }