stringutil.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. /*
  2. * Public Domain Software
  3. *
  4. * I (Matthias Ladkau) am the author of the source code in this file.
  5. * I have placed the source code in this file in the public domain.
  6. *
  7. * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
  8. */
  9. /*
  10. Package stringutil contains common function for string operations.
  11. */
  12. package stringutil
  13. import (
  14. "bytes"
  15. "crypto/md5"
  16. "encoding/json"
  17. "fmt"
  18. "regexp"
  19. "sort"
  20. "strconv"
  21. "strings"
  22. "unicode/utf8"
  23. )
  24. /*
  25. LongestCommonPrefix determines the longest common prefix of a given list of strings.
  26. */
  27. func LongestCommonPrefix(s []string) string {
  28. var res string
  29. commonPrefix := func(str1, str2 string) string {
  30. var buf bytes.Buffer
  31. rs2 := StringToRuneSlice(str2)
  32. rs2len := len(rs2)
  33. for i, c := range str1 {
  34. if i >= rs2len {
  35. break
  36. } else if c == rs2[i] {
  37. buf.WriteRune(c)
  38. }
  39. }
  40. return buf.String()
  41. }
  42. lens := len(s)
  43. if lens > 0 {
  44. res = s[0]
  45. for i := 1; i < lens; i++ {
  46. res = commonPrefix(res, s[i])
  47. }
  48. }
  49. return res
  50. }
  51. /*
  52. PrintStringTable prints a given list of strings as table with c columns.
  53. */
  54. func PrintStringTable(ss []string, c int) string {
  55. var ret bytes.Buffer
  56. if c < 1 {
  57. return ""
  58. }
  59. // Determine max widths of columns
  60. maxWidths := make(map[int]int)
  61. for i, s := range ss {
  62. col := i % c
  63. if l := utf8.RuneCountInString(s); l > maxWidths[col] {
  64. maxWidths[col] = l
  65. }
  66. }
  67. for i, s := range ss {
  68. col := i % c
  69. if i < len(ss)-1 {
  70. var formatString string
  71. if col != c-1 {
  72. formatString = fmt.Sprintf("%%-%vv ", maxWidths[col])
  73. } else {
  74. formatString = "%v"
  75. }
  76. ret.WriteString(fmt.Sprintf(formatString, s))
  77. } else {
  78. ret.WriteString(fmt.Sprintln(s))
  79. break
  80. }
  81. if col == c-1 {
  82. ret.WriteString(fmt.Sprintln())
  83. }
  84. }
  85. return ret.String()
  86. }
  87. /*
  88. GraphicStringTableSymbols defines how to draw a graphic table.
  89. */
  90. type GraphicStringTableSymbols struct {
  91. BoxHorizontal string
  92. BoxVertical string
  93. BoxMiddle string
  94. BoxCornerTopLeft string
  95. BoxCornerTopRight string
  96. BoxCornerBottomLeft string
  97. BoxCornerBottomRight string
  98. BoxTopMiddle string
  99. BoxLeftMiddle string
  100. BoxRightMiddle string
  101. BoxBottomMiddle string
  102. }
  103. /*
  104. Standard graphic table drawing definitions.
  105. */
  106. var (
  107. SingleLineTable = &GraphicStringTableSymbols{"─", "│", "┼", "┌", "┐", "└", "┘", "┬", "├", "┤", "┴"}
  108. DoubleLineTable = &GraphicStringTableSymbols{"═", "║", "╬", "╔", "╗", "╚", "╝", "╦", "╠", "╣", "╩"}
  109. SingleDoubleLineTable = &GraphicStringTableSymbols{"═", "│", "╪", "╒", "╕", "╘", "╛", "╤", "╞", "╡", "╧"}
  110. DoubleSingleLineTable = &GraphicStringTableSymbols{"─", "║", "╫", "╓", "╖", "╙", "╜", "╥", "╟", "╢", "╨"}
  111. MonoTable = &GraphicStringTableSymbols{"#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"}
  112. )
  113. /*
  114. PrintGraphicStringTable prints a given list of strings in a graphic table
  115. with c columns - creates a header after n rows using syms as drawing symbols.
  116. */
  117. func PrintGraphicStringTable(ss []string, c int, n int, syms *GraphicStringTableSymbols) string {
  118. var topline, bottomline, middleline, ret bytes.Buffer
  119. if c < 1 {
  120. return ""
  121. }
  122. if syms == nil {
  123. syms = MonoTable
  124. }
  125. // Determine max widths of columns
  126. maxWidths := make(map[int]int)
  127. for i, s := range ss {
  128. col := i % c
  129. l := utf8.RuneCountInString(s)
  130. if l > maxWidths[col] {
  131. maxWidths[col] = l
  132. }
  133. }
  134. // Determine total width and create top, middle and bottom line
  135. totalWidth := 1
  136. topline.WriteString(syms.BoxCornerTopLeft)
  137. bottomline.WriteString(syms.BoxCornerBottomLeft)
  138. middleline.WriteString(syms.BoxLeftMiddle)
  139. for i := 0; i < len(maxWidths); i++ {
  140. totalWidth += maxWidths[i] + 2
  141. topline.WriteString(GenerateRollingString(syms.BoxHorizontal, maxWidths[i]+1))
  142. bottomline.WriteString(GenerateRollingString(syms.BoxHorizontal, maxWidths[i]+1))
  143. middleline.WriteString(GenerateRollingString(syms.BoxHorizontal, maxWidths[i]+1))
  144. if i < len(maxWidths)-1 {
  145. topline.WriteString(syms.BoxTopMiddle)
  146. bottomline.WriteString(syms.BoxBottomMiddle)
  147. middleline.WriteString(syms.BoxMiddle)
  148. }
  149. }
  150. topline.WriteString(syms.BoxCornerTopRight)
  151. bottomline.WriteString(syms.BoxCornerBottomRight)
  152. middleline.WriteString(syms.BoxRightMiddle)
  153. // Draw the table
  154. ret.WriteString(topline.String())
  155. ret.WriteString(fmt.Sprintln())
  156. row := 0
  157. for i, s := range ss {
  158. col := i % c
  159. ret.WriteString(syms.BoxVertical)
  160. if i < len(ss)-1 {
  161. formatString := fmt.Sprintf("%%-%vv ", maxWidths[col])
  162. ret.WriteString(fmt.Sprintf(formatString, s))
  163. } else {
  164. formatString := fmt.Sprintf("%%-%vv ", maxWidths[col])
  165. ret.WriteString(fmt.Sprintf(formatString, s))
  166. for col < c-1 && col < len(ss)-1 {
  167. col++
  168. ret.WriteString(syms.BoxVertical)
  169. ret.WriteString(GenerateRollingString(" ", maxWidths[col]))
  170. ret.WriteString(" ")
  171. }
  172. ret.WriteString(syms.BoxVertical)
  173. ret.WriteString(fmt.Sprintln())
  174. break
  175. }
  176. if col == c-1 {
  177. ret.WriteString(syms.BoxVertical)
  178. ret.WriteString(fmt.Sprintln())
  179. row++
  180. if row == n {
  181. ret.WriteString(middleline.String())
  182. ret.WriteString(fmt.Sprintln())
  183. }
  184. }
  185. }
  186. ret.WriteString(bottomline.String())
  187. ret.WriteString(fmt.Sprintln())
  188. return ret.String()
  189. }
  190. /*
  191. PrintCSVTable prints a given list of strings in a CSV table with c
  192. columns.
  193. */
  194. func PrintCSVTable(ss []string, c int) string {
  195. var ret bytes.Buffer
  196. var col int
  197. if c < 1 || len(ss) == 0 {
  198. return ""
  199. }
  200. // Write the table
  201. for i, s := range ss {
  202. col = i % c
  203. ret.WriteString(strings.TrimSpace(fmt.Sprint(s)))
  204. if col == c-1 {
  205. ret.WriteString(fmt.Sprintln())
  206. } else if i < len(ss)-1 {
  207. ret.WriteString(", ")
  208. }
  209. }
  210. if col != c-1 {
  211. ret.WriteString(fmt.Sprintln())
  212. }
  213. return ret.String()
  214. }
  215. /*
  216. RuneSliceToString converts a slice of runes into a string.
  217. */
  218. func RuneSliceToString(buf []rune) string {
  219. var sbuf bytes.Buffer
  220. for _, r := range buf {
  221. fmt.Fprintf(&sbuf, "%c", r)
  222. }
  223. return sbuf.String()
  224. }
  225. /*
  226. StringToRuneSlice converts a string into a slice of runes.
  227. */
  228. func StringToRuneSlice(s string) []rune {
  229. var buf []rune
  230. for _, r := range s {
  231. buf = append(buf, r)
  232. }
  233. return buf
  234. }
  235. var cSyleCommentsRegexp = regexp.MustCompile("(?s)//.*?\n|/\\*.*?\\*/")
  236. /*
  237. StripCStyleComments strips out C-Style comments from a given string.
  238. */
  239. func StripCStyleComments(text []byte) []byte {
  240. return cSyleCommentsRegexp.ReplaceAll(text, nil)
  241. }
  242. /*
  243. Plural returns the string 's' if the parameter is greater than one or
  244. if the parameter is 0.
  245. */
  246. func Plural(l int) string {
  247. if l > 1 || l == 0 {
  248. return "s"
  249. }
  250. return ""
  251. }
  252. /*
  253. GlobParseError describes a failure to parse a glob expression
  254. and gives the offending expression.
  255. */
  256. type GlobParseError struct {
  257. Msg string
  258. Pos int
  259. Glob string
  260. }
  261. /*
  262. Error Returns a string representation of the error.
  263. */
  264. func (e *GlobParseError) Error() string {
  265. return fmt.Sprintf("%s at %d of %s", e.Msg, e.Pos, e.Glob)
  266. }
  267. /*
  268. GlobToRegex converts a given glob expression into a regular expression.
  269. */
  270. func GlobToRegex(glob string) (string, error) {
  271. buf := new(bytes.Buffer)
  272. brackets, braces := 0, 0
  273. n := len(glob)
  274. for i := 0; i < n; i++ {
  275. char := glob[i]
  276. switch char {
  277. case '\\':
  278. // Escapes
  279. i++
  280. if i >= n {
  281. return "", &GlobParseError{"Missing escaped character", i, glob}
  282. }
  283. buf.WriteByte(char)
  284. buf.WriteByte(glob[i])
  285. continue
  286. case '*':
  287. // Wildcard match multiple characters
  288. buf.WriteByte('.')
  289. case '?':
  290. // Wildcard match any single character
  291. buf.WriteByte('.')
  292. continue
  293. case '{':
  294. // Group (always non-capturing)
  295. buf.WriteString("(?:")
  296. braces++
  297. continue
  298. case '}':
  299. // End of group
  300. if braces > 0 {
  301. braces--
  302. buf.WriteByte(')')
  303. continue
  304. }
  305. case '[':
  306. // Character class
  307. if brackets > 0 {
  308. return "", &GlobParseError{"Unclosed character class", i, glob}
  309. }
  310. brackets++
  311. case ']':
  312. // End of character class
  313. brackets = 0
  314. case ',':
  315. // OR in groups
  316. if braces > 0 {
  317. buf.WriteByte('|')
  318. } else {
  319. buf.WriteByte(char)
  320. }
  321. continue
  322. case '^':
  323. // Beginning of line in character classes otherwise normal
  324. // escaped character
  325. if brackets == 0 {
  326. buf.WriteByte('\\')
  327. }
  328. case '!':
  329. // [! is the equivalent of [^ in glob
  330. if brackets > 0 && glob[i-1] == '[' {
  331. buf.WriteByte('^')
  332. } else {
  333. buf.WriteByte('!')
  334. }
  335. continue
  336. case '.', '$', '(', ')', '|', '+':
  337. // Escape all regex characters which are not glob characters
  338. buf.WriteByte('\\')
  339. }
  340. buf.WriteByte(char)
  341. }
  342. if brackets > 0 {
  343. return "", &GlobParseError{"Unclosed character class", n, glob}
  344. } else if braces > 0 {
  345. return "", &GlobParseError{"Unclosed group", n, glob}
  346. }
  347. return buf.String(), nil
  348. }
  349. /*
  350. GlobStartingLiterals gets the first literals of a glob string.
  351. */
  352. func GlobStartingLiterals(glob string) string {
  353. buf := new(bytes.Buffer)
  354. n := len(glob)
  355. for i := 0; i < n; i++ {
  356. char := glob[i]
  357. if char == '\\' || char == '*' || char == '?' ||
  358. char == '{' || char == '[' {
  359. break
  360. }
  361. buf.WriteByte(char)
  362. }
  363. return buf.String()
  364. }
  365. /*
  366. LevenshteinDistance computes the Levenshtein distance between two strings.
  367. */
  368. func LevenshteinDistance(str1, str2 string) int {
  369. if str1 == str2 {
  370. return 0
  371. }
  372. rslice1 := StringToRuneSlice(str1)
  373. rslice2 := StringToRuneSlice(str2)
  374. n, m := len(rslice1), len(rslice2)
  375. if n == 0 {
  376. return m
  377. } else if m == 0 {
  378. return n
  379. }
  380. v0 := make([]int, m+1, m+1)
  381. v1 := make([]int, m+1, m+1)
  382. for i := 0; i <= m; i++ {
  383. v0[i] = i
  384. }
  385. var cost int
  386. for i := 0; i < n; i++ {
  387. v1[0] = i + 1
  388. for j := 0; j < m; j++ {
  389. if rslice1[i] == rslice2[j] {
  390. cost = 0
  391. } else {
  392. cost = 1
  393. }
  394. v1[j+1] = min3(v1[j]+1, v0[j+1]+1, v0[j]+cost)
  395. }
  396. v0, v1 = v1, v0
  397. }
  398. return v0[m]
  399. }
  400. /*
  401. 3 way min for computing the Levenshtein distance.
  402. */
  403. func min3(a, b, c int) int {
  404. ret := a
  405. if b < ret {
  406. ret = b
  407. }
  408. if c < ret {
  409. ret = c
  410. }
  411. return ret
  412. }
  413. /*
  414. VersionStringCompare compares two version strings. Returns: 0 if the strings are
  415. equal; -1 if the first string is smaller; 1 if the first string is greater.
  416. */
  417. func VersionStringCompare(str1, str2 string) int {
  418. val1 := strings.Split(str1, ".")
  419. val2 := strings.Split(str2, ".")
  420. idx := 0
  421. for idx < len(val1) && idx < len(val2) && val1[idx] == val2[idx] {
  422. idx++
  423. }
  424. switch {
  425. case idx < len(val1) && idx < len(val2):
  426. return versionStringPartCompare(val1[idx], val2[idx])
  427. case len(val1) > len(val2):
  428. return 1
  429. case len(val1) < len(val2):
  430. return -1
  431. }
  432. return 0
  433. }
  434. /*
  435. versionStringPartCompare compares two version string parts. Returns: 0 if the
  436. strings are equal; -1 if the first string is smaller; 1 if the first string is
  437. greater.
  438. */
  439. func versionStringPartCompare(str1, str2 string) int {
  440. pat := regexp.MustCompile("^([0-9]+)([\\D].*)?")
  441. res1 := pat.FindStringSubmatch(str1)
  442. res2 := pat.FindStringSubmatch(str2)
  443. switch {
  444. case res1 == nil && res2 == nil:
  445. return strings.Compare(str1, str2)
  446. case res1 == nil && res2 != nil:
  447. return -1
  448. case res1 != nil && res2 == nil:
  449. return 1
  450. }
  451. v1, _ := strconv.Atoi(res1[1])
  452. v2, _ := strconv.Atoi(res2[1])
  453. res := 0
  454. switch {
  455. case v1 > v2:
  456. res = 1
  457. case v1 < v2:
  458. res = -1
  459. }
  460. if res == 0 {
  461. switch {
  462. case res1[2] != "" && res2[2] == "":
  463. return 1
  464. case res1[2] == "" && res2[2] != "":
  465. return -1
  466. case res1[2] != "" && res2[2] != "":
  467. return strings.Compare(res1[2], res2[2])
  468. }
  469. }
  470. return res
  471. }
  472. /*
  473. IsAlphaNumeric checks if a string contains only alpha numerical characters or "_".
  474. */
  475. func IsAlphaNumeric(str string) bool {
  476. ret, _ := regexp.MatchString("^[a-zA-Z0-9_]*$", str)
  477. return ret
  478. }
  479. /*
  480. IsTrueValue checks if a given string is a true value.
  481. */
  482. func IsTrueValue(str string) bool {
  483. str = strings.ToLower(str)
  484. return str == "true" || str == "yes" || str == "on" || str == "ok" ||
  485. str == "1" || str == "active" || str == "enabled"
  486. }
  487. /*
  488. IndexOf returns the index of str in slice or -1 if it does not exist.
  489. */
  490. func IndexOf(str string, slice []string) int {
  491. for i, s := range slice {
  492. if str == s {
  493. return i
  494. }
  495. }
  496. return -1
  497. }
  498. /*
  499. MapKeys returns the keys of a map as a sorted list.
  500. */
  501. func MapKeys(m map[string]interface{}) []string {
  502. ret := make([]string, 0, len(m))
  503. for k := range m {
  504. ret = append(ret, k)
  505. }
  506. sort.Strings(ret)
  507. return ret
  508. }
  509. /*
  510. CreateDisplayString changes all "_" characters into spaces and properly capitalizes
  511. the resulting string.
  512. */
  513. func CreateDisplayString(str string) string {
  514. if len(str) == 0 {
  515. return ""
  516. }
  517. return ProperTitle(strings.Replace(str, "_", " ", -1))
  518. }
  519. // The following words should not be capitalized
  520. //
  521. var notCapitalize = map[string]string{
  522. "a": "",
  523. "an": "",
  524. "and": "",
  525. "at": "",
  526. "but": "",
  527. "by": "",
  528. "for": "",
  529. "from": "",
  530. "in": "",
  531. "nor": "",
  532. "on": "",
  533. "of": "",
  534. "or": "",
  535. "the": "",
  536. "to": "",
  537. "with": "",
  538. }
  539. /*
  540. ProperTitle will properly capitalize a title string by capitalizing the first, last
  541. and any important words. Not capitalized are articles: a, an, the; coordinating
  542. conjunctions: and, but, or, for, nor; prepositions (fewer than five
  543. letters): on, at, to, from, by.
  544. */
  545. func ProperTitle(input string) string {
  546. words := strings.Fields(strings.ToLower(input))
  547. size := len(words)
  548. for index, word := range words {
  549. if _, ok := notCapitalize[word]; !ok || index == 0 || index == size-1 {
  550. words[index] = strings.Title(word)
  551. }
  552. }
  553. return strings.Join(words, " ")
  554. }
  555. /*
  556. GenerateRollingString creates a string by repeating a given string pattern.
  557. */
  558. func GenerateRollingString(seq string, size int) string {
  559. var buf bytes.Buffer
  560. rs := StringToRuneSlice(seq)
  561. l := len(rs)
  562. if l == 0 {
  563. return ""
  564. }
  565. for i := 0; i < size; i++ {
  566. buf.WriteRune(rs[i%l])
  567. }
  568. return buf.String()
  569. }
  570. /*
  571. ConvertToString tries to convert a given object into a stable string. This
  572. function can be used to display nested maps.
  573. */
  574. func ConvertToString(v interface{}) string {
  575. if vStringer, ok := v.(fmt.Stringer); ok {
  576. return vStringer.String()
  577. }
  578. if _, err := json.Marshal(v); err != nil {
  579. v = containerStringConvert(v)
  580. }
  581. if vString, ok := v.(string); ok {
  582. return vString
  583. } else if res, err := json.Marshal(v); err == nil {
  584. return string(res)
  585. }
  586. return fmt.Sprint(v)
  587. }
  588. /*
  589. containerStringConvert converts container contents into strings.
  590. */
  591. func containerStringConvert(v interface{}) interface{} {
  592. res := v
  593. if mapContainer, ok := v.(map[interface{}]interface{}); ok {
  594. newRes := make(map[string]interface{})
  595. for mk, mv := range mapContainer {
  596. newRes[ConvertToString(mk)] = containerStringConvert(mv)
  597. }
  598. res = newRes
  599. } else if mapList, ok := v.([]interface{}); ok {
  600. newRes := make([]interface{}, len(mapList))
  601. for i, lv := range mapList {
  602. newRes[i] = containerStringConvert(lv)
  603. }
  604. res = newRes
  605. }
  606. return res
  607. }
  608. /*
  609. MD5HexString calculates the MD5 sum of a string and returns it as hex string.
  610. */
  611. func MD5HexString(str string) string {
  612. return fmt.Sprintf("%x", md5.Sum([]byte(str)))
  613. }
  614. /*
  615. LengthConstantEquals compares two strings in length-constant time. This
  616. function is deliberately inefficient in that it does not stop at the earliest
  617. possible time. This is to prevent timing attacks when comparing password
  618. hashes.
  619. */
  620. func LengthConstantEquals(str1 []byte, str2 []byte) bool {
  621. diff := len(str1) ^ len(str2)
  622. for i := 0; i < len(str1) && i < len(str2); i++ {
  623. diff |= int(str1[i] ^ str2[i])
  624. }
  625. return diff == 0
  626. }