git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/validate/utils.go (about)

     1  package validate
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"html"
     7  	"math"
     8  	"path"
     9  	"regexp"
    10  	"strings"
    11  	"unicode"
    12  	"unicode/utf8"
    13  )
    14  
    15  // Contains checks if the string contains the substring.
    16  func Contains(str, substring string) bool {
    17  	return strings.Contains(str, substring)
    18  }
    19  
    20  // Matches checks if string matches the pattern (pattern is regular expression)
    21  // In case of error return false
    22  func Matches(str, pattern string) bool {
    23  	match, _ := regexp.MatchString(pattern, str)
    24  	return match
    25  }
    26  
    27  // LeftTrim trims characters from the left side of the input.
    28  // If second argument is empty, it will remove leading spaces.
    29  func LeftTrim(str, chars string) string {
    30  	if chars == "" {
    31  		return strings.TrimLeftFunc(str, unicode.IsSpace)
    32  	}
    33  	r, _ := regexp.Compile("^[" + chars + "]+")
    34  	return r.ReplaceAllString(str, "")
    35  }
    36  
    37  // RightTrim trims characters from the right side of the input.
    38  // If second argument is empty, it will remove trailing spaces.
    39  func RightTrim(str, chars string) string {
    40  	if chars == "" {
    41  		return strings.TrimRightFunc(str, unicode.IsSpace)
    42  	}
    43  	r, _ := regexp.Compile("[" + chars + "]+$")
    44  	return r.ReplaceAllString(str, "")
    45  }
    46  
    47  // Trim trims characters from both sides of the input.
    48  // If second argument is empty, it will remove spaces.
    49  func Trim(str, chars string) string {
    50  	return LeftTrim(RightTrim(str, chars), chars)
    51  }
    52  
    53  // WhiteList removes characters that do not appear in the whitelist.
    54  func WhiteList(str, chars string) string {
    55  	pattern := "[^" + chars + "]+"
    56  	r, _ := regexp.Compile(pattern)
    57  	return r.ReplaceAllString(str, "")
    58  }
    59  
    60  // BlackList removes characters that appear in the blacklist.
    61  func BlackList(str, chars string) string {
    62  	pattern := "[" + chars + "]+"
    63  	r, _ := regexp.Compile(pattern)
    64  	return r.ReplaceAllString(str, "")
    65  }
    66  
    67  // StripLow removes characters with a numerical value < 32 and 127, mostly control characters.
    68  // If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
    69  func StripLow(str string, keepNewLines bool) string {
    70  	chars := ""
    71  	if keepNewLines {
    72  		chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
    73  	} else {
    74  		chars = "\x00-\x1F\x7F"
    75  	}
    76  	return BlackList(str, chars)
    77  }
    78  
    79  // ReplacePattern replaces regular expression pattern in string
    80  func ReplacePattern(str, pattern, replace string) string {
    81  	r, _ := regexp.Compile(pattern)
    82  	return r.ReplaceAllString(str, replace)
    83  }
    84  
    85  // Escape replaces <, >, & and " with HTML entities.
    86  var Escape = html.EscapeString
    87  
    88  func addSegment(inrune, segment []rune) []rune {
    89  	if len(segment) == 0 {
    90  		return inrune
    91  	}
    92  	if len(inrune) != 0 {
    93  		inrune = append(inrune, '_')
    94  	}
    95  	inrune = append(inrune, segment...)
    96  	return inrune
    97  }
    98  
    99  // UnderscoreToCamelCase converts from underscore separated form to camel case form.
   100  // Ex.: my_func => MyFunc
   101  func UnderscoreToCamelCase(s string) string {
   102  	return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
   103  }
   104  
   105  // CamelCaseToUnderscore converts from camel case form to underscore separated form.
   106  // Ex.: MyFunc => my_func
   107  func CamelCaseToUnderscore(str string) string {
   108  	var output []rune
   109  	var segment []rune
   110  	for _, r := range str {
   111  
   112  		// not treat number as separate segment
   113  		if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) {
   114  			output = addSegment(output, segment)
   115  			segment = nil
   116  		}
   117  		segment = append(segment, unicode.ToLower(r))
   118  	}
   119  	output = addSegment(output, segment)
   120  	return string(output)
   121  }
   122  
   123  // Reverse returns reversed string
   124  func Reverse(s string) string {
   125  	r := []rune(s)
   126  	for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
   127  		r[i], r[j] = r[j], r[i]
   128  	}
   129  	return string(r)
   130  }
   131  
   132  // GetLines splits string by "\n" and return array of lines
   133  func GetLines(s string) []string {
   134  	return strings.Split(s, "\n")
   135  }
   136  
   137  // GetLine returns specified line of multiline string
   138  func GetLine(s string, index int) (string, error) {
   139  	lines := GetLines(s)
   140  	if index < 0 || index >= len(lines) {
   141  		return "", errors.New("line index out of bounds")
   142  	}
   143  	return lines[index], nil
   144  }
   145  
   146  // RemoveTags removes all tags from HTML string
   147  func RemoveTags(s string) string {
   148  	return ReplacePattern(s, "<[^>]*>", "")
   149  }
   150  
   151  // SafeFileName returns safe string that can be used in file names
   152  func SafeFileName(str string) string {
   153  	name := strings.ToLower(str)
   154  	name = path.Clean(path.Base(name))
   155  	name = strings.Trim(name, " ")
   156  	separators, err := regexp.Compile(`[ &_=+:]`)
   157  	if err == nil {
   158  		name = separators.ReplaceAllString(name, "-")
   159  	}
   160  	legal, err := regexp.Compile(`[^[:alnum:]-.]`)
   161  	if err == nil {
   162  		name = legal.ReplaceAllString(name, "")
   163  	}
   164  	for strings.Contains(name, "--") {
   165  		name = strings.Replace(name, "--", "-", -1)
   166  	}
   167  	return name
   168  }
   169  
   170  // NormalizeEmail canonicalize an email address.
   171  // The local part of the email address is lowercased for all domains; the hostname is always lowercased and
   172  // the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
   173  // Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
   174  // are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
   175  // normalized to @gmail.com.
   176  func NormalizeEmail(str string) (string, error) {
   177  	if !IsEmail(str) {
   178  		return "", fmt.Errorf("%s is not an email", str)
   179  	}
   180  	parts := strings.Split(str, "@")
   181  	parts[0] = strings.ToLower(parts[0])
   182  	parts[1] = strings.ToLower(parts[1])
   183  	if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
   184  		parts[1] = "gmail.com"
   185  		parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
   186  	}
   187  	return strings.Join(parts, "@"), nil
   188  }
   189  
   190  // Truncate a string to the closest length without breaking words.
   191  func Truncate(str string, length int, ending string) string {
   192  	var aftstr, befstr string
   193  	if len(str) > length {
   194  		words := strings.Fields(str)
   195  		before, present := 0, 0
   196  		for i := range words {
   197  			befstr = aftstr
   198  			before = present
   199  			aftstr = aftstr + words[i] + " "
   200  			present = len(aftstr)
   201  			if present > length && i != 0 {
   202  				if (length - before) < (present - length) {
   203  					return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
   204  				}
   205  				return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
   206  			}
   207  		}
   208  	}
   209  
   210  	return str
   211  }
   212  
   213  // PadLeft pads left side of a string if size of string is less then indicated pad length
   214  func PadLeft(str string, padStr string, padLen int) string {
   215  	return buildPadStr(str, padStr, padLen, true, false)
   216  }
   217  
   218  // PadRight pads right side of a string if size of string is less then indicated pad length
   219  func PadRight(str string, padStr string, padLen int) string {
   220  	return buildPadStr(str, padStr, padLen, false, true)
   221  }
   222  
   223  // PadBoth pads both sides of a string if size of string is less then indicated pad length
   224  func PadBoth(str string, padStr string, padLen int) string {
   225  	return buildPadStr(str, padStr, padLen, true, true)
   226  }
   227  
   228  // PadString either left, right or both sides.
   229  // Note that padding string can be unicode and more then one character
   230  func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
   231  
   232  	// When padded length is less then the current string size
   233  	if padLen < utf8.RuneCountInString(str) {
   234  		return str
   235  	}
   236  
   237  	padLen -= utf8.RuneCountInString(str)
   238  
   239  	targetLen := padLen
   240  
   241  	targetLenLeft := targetLen
   242  	targetLenRight := targetLen
   243  	if padLeft && padRight {
   244  		targetLenLeft = padLen / 2
   245  		targetLenRight = padLen - targetLenLeft
   246  	}
   247  
   248  	strToRepeatLen := utf8.RuneCountInString(padStr)
   249  
   250  	repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen)))
   251  	repeatedString := strings.Repeat(padStr, repeatTimes)
   252  
   253  	leftSide := ""
   254  	if padLeft {
   255  		leftSide = repeatedString[0:targetLenLeft]
   256  	}
   257  
   258  	rightSide := ""
   259  	if padRight {
   260  		rightSide = repeatedString[0:targetLenRight]
   261  	}
   262  
   263  	return leftSide + str + rightSide
   264  }
   265  
   266  // TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object
   267  func TruncatingErrorf(str string, args ...interface{}) error {
   268  	n := strings.Count(str, "%s")
   269  	return fmt.Errorf(str, args[:n]...)
   270  }