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.

270 lines
7.8 KiB

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