123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792 |
- /*
- * Public Domain Software
- *
- * I (Matthias Ladkau) am the author of the source code in this file.
- * I have placed the source code in this file in the public domain.
- *
- * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
- */
- /*
- Package stringutil contains common function for string operations.
- */
- package stringutil
- import (
- "bytes"
- "crypto/md5"
- "encoding/json"
- "fmt"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "unicode"
- "unicode/utf8"
- )
- /*
- LongestCommonPrefix determines the longest common prefix of a given list of strings.
- */
- func LongestCommonPrefix(s []string) string {
- var res string
- commonPrefix := func(str1, str2 string) string {
- var buf bytes.Buffer
- rs2 := StringToRuneSlice(str2)
- rs2len := len(rs2)
- for i, c := range str1 {
- if i >= rs2len {
- break
- } else if c == rs2[i] {
- buf.WriteRune(c)
- }
- }
- return buf.String()
- }
- lens := len(s)
- if lens > 0 {
- res = s[0]
- for i := 1; i < lens; i++ {
- res = commonPrefix(res, s[i])
- }
- }
- return res
- }
- /*
- PrintStringTable prints a given list of strings as table with c columns.
- */
- func PrintStringTable(ss []string, c int) string {
- var ret bytes.Buffer
- if c < 1 {
- return ""
- }
- // Determine max widths of columns
- maxWidths := make(map[int]int)
- for i, s := range ss {
- col := i % c
- if l := utf8.RuneCountInString(s); l > maxWidths[col] {
- maxWidths[col] = l
- }
- }
- for i, s := range ss {
- col := i % c
- if i < len(ss)-1 {
- var formatString string
- if col != c-1 {
- formatString = fmt.Sprintf("%%-%vv ", maxWidths[col])
- } else {
- formatString = "%v"
- }
- ret.WriteString(fmt.Sprintf(formatString, s))
- } else {
- ret.WriteString(fmt.Sprintln(s))
- break
- }
- if col == c-1 {
- ret.WriteString(fmt.Sprintln())
- }
- }
- return ret.String()
- }
- /*
- GraphicStringTableSymbols defines how to draw a graphic table.
- */
- type GraphicStringTableSymbols struct {
- BoxHorizontal string
- BoxVertical string
- BoxMiddle string
- BoxCornerTopLeft string
- BoxCornerTopRight string
- BoxCornerBottomLeft string
- BoxCornerBottomRight string
- BoxTopMiddle string
- BoxLeftMiddle string
- BoxRightMiddle string
- BoxBottomMiddle string
- }
- /*
- Standard graphic table drawing definitions.
- */
- var (
- SingleLineTable = &GraphicStringTableSymbols{"─", "│", "┼", "┌", "┐", "└", "┘", "┬", "├", "┤", "┴"}
- DoubleLineTable = &GraphicStringTableSymbols{"═", "║", "╬", "╔", "╗", "╚", "╝", "╦", "╠", "╣", "╩"}
- SingleDoubleLineTable = &GraphicStringTableSymbols{"═", "│", "╪", "╒", "╕", "╘", "╛", "╤", "╞", "╡", "╧"}
- DoubleSingleLineTable = &GraphicStringTableSymbols{"─", "║", "╫", "╓", "╖", "╙", "╜", "╥", "╟", "╢", "╨"}
- MonoTable = &GraphicStringTableSymbols{"#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"}
- )
- /*
- PrintGraphicStringTable prints a given list of strings in a graphic table
- with c columns - creates a header after n rows using syms as drawing symbols.
- */
- func PrintGraphicStringTable(ss []string, c int, n int, syms *GraphicStringTableSymbols) string {
- var topline, bottomline, middleline, ret bytes.Buffer
- if c < 1 {
- return ""
- }
- if syms == nil {
- syms = MonoTable
- }
- // Determine max widths of columns
- maxWidths := make(map[int]int)
- for i, s := range ss {
- col := i % c
- l := utf8.RuneCountInString(s)
- if l > maxWidths[col] {
- maxWidths[col] = l
- }
- }
- // Determine total width and create top, middle and bottom line
- totalWidth := 1
- topline.WriteString(syms.BoxCornerTopLeft)
- bottomline.WriteString(syms.BoxCornerBottomLeft)
- middleline.WriteString(syms.BoxLeftMiddle)
- for i := 0; i < len(maxWidths); i++ {
- totalWidth += maxWidths[i] + 2
- topline.WriteString(GenerateRollingString(syms.BoxHorizontal, maxWidths[i]+1))
- bottomline.WriteString(GenerateRollingString(syms.BoxHorizontal, maxWidths[i]+1))
- middleline.WriteString(GenerateRollingString(syms.BoxHorizontal, maxWidths[i]+1))
- if i < len(maxWidths)-1 {
- topline.WriteString(syms.BoxTopMiddle)
- bottomline.WriteString(syms.BoxBottomMiddle)
- middleline.WriteString(syms.BoxMiddle)
- }
- }
- topline.WriteString(syms.BoxCornerTopRight)
- bottomline.WriteString(syms.BoxCornerBottomRight)
- middleline.WriteString(syms.BoxRightMiddle)
- // Draw the table
- ret.WriteString(topline.String())
- ret.WriteString(fmt.Sprintln())
- row := 0
- for i, s := range ss {
- col := i % c
- ret.WriteString(syms.BoxVertical)
- if i < len(ss)-1 {
- formatString := fmt.Sprintf("%%-%vv ", maxWidths[col])
- ret.WriteString(fmt.Sprintf(formatString, s))
- } else {
- formatString := fmt.Sprintf("%%-%vv ", maxWidths[col])
- ret.WriteString(fmt.Sprintf(formatString, s))
- for col < c-1 && col < len(ss)-1 {
- col++
- ret.WriteString(syms.BoxVertical)
- ret.WriteString(GenerateRollingString(" ", maxWidths[col]))
- ret.WriteString(" ")
- }
- ret.WriteString(syms.BoxVertical)
- ret.WriteString(fmt.Sprintln())
- break
- }
- if col == c-1 {
- ret.WriteString(syms.BoxVertical)
- ret.WriteString(fmt.Sprintln())
- row++
- if row == n {
- ret.WriteString(middleline.String())
- ret.WriteString(fmt.Sprintln())
- }
- }
- }
- ret.WriteString(bottomline.String())
- ret.WriteString(fmt.Sprintln())
- return ret.String()
- }
- /*
- PrintCSVTable prints a given list of strings in a CSV table with c
- columns.
- */
- func PrintCSVTable(ss []string, c int) string {
- var ret bytes.Buffer
- var col int
- if c < 1 || len(ss) == 0 {
- return ""
- }
- // Write the table
- for i, s := range ss {
- col = i % c
- ret.WriteString(strings.TrimSpace(fmt.Sprint(s)))
- if col == c-1 {
- ret.WriteString(fmt.Sprintln())
- } else if i < len(ss)-1 {
- ret.WriteString(", ")
- }
- }
- if col != c-1 {
- ret.WriteString(fmt.Sprintln())
- }
- return ret.String()
- }
- /*
- RuneSliceToString converts a slice of runes into a string.
- */
- func RuneSliceToString(buf []rune) string {
- var sbuf bytes.Buffer
- for _, r := range buf {
- fmt.Fprintf(&sbuf, "%c", r)
- }
- return sbuf.String()
- }
- /*
- StringToRuneSlice converts a string into a slice of runes.
- */
- func StringToRuneSlice(s string) []rune {
- var buf []rune
- for _, r := range s {
- buf = append(buf, r)
- }
- return buf
- }
- /*
- Plural returns the string 's' if the parameter is greater than one or
- if the parameter is 0.
- */
- func Plural(l int) string {
- if l > 1 || l == 0 {
- return "s"
- }
- return ""
- }
- /*
- GlobParseError describes a failure to parse a glob expression
- and gives the offending expression.
- */
- type GlobParseError struct {
- Msg string
- Pos int
- Glob string
- }
- /*
- Error Returns a string representation of the error.
- */
- func (e *GlobParseError) Error() string {
- return fmt.Sprintf("%s at %d of %s", e.Msg, e.Pos, e.Glob)
- }
- /*
- GlobToRegex converts a given glob expression into a regular expression.
- */
- func GlobToRegex(glob string) (string, error) {
- buf := new(bytes.Buffer)
- brackets, braces := 0, 0
- n := len(glob)
- for i := 0; i < n; i++ {
- char := glob[i]
- switch char {
- case '\\':
- // Escapes
- i++
- if i >= n {
- return "", &GlobParseError{"Missing escaped character", i, glob}
- }
- buf.WriteByte(char)
- buf.WriteByte(glob[i])
- continue
- case '*':
- // Wildcard match multiple characters
- buf.WriteByte('.')
- case '?':
- // Wildcard match any single character
- buf.WriteByte('.')
- continue
- case '{':
- // Group (always non-capturing)
- buf.WriteString("(?:")
- braces++
- continue
- case '}':
- // End of group
- if braces > 0 {
- braces--
- buf.WriteByte(')')
- continue
- }
- case '[':
- // Character class
- if brackets > 0 {
- return "", &GlobParseError{"Unclosed character class", i, glob}
- }
- brackets++
- case ']':
- // End of character class
- brackets = 0
- case ',':
- // OR in groups
- if braces > 0 {
- buf.WriteByte('|')
- } else {
- buf.WriteByte(char)
- }
- continue
- case '^':
- // Beginning of line in character classes otherwise normal
- // escaped character
- if brackets == 0 {
- buf.WriteByte('\\')
- }
- case '!':
- // [! is the equivalent of [^ in glob
- if brackets > 0 && glob[i-1] == '[' {
- buf.WriteByte('^')
- } else {
- buf.WriteByte('!')
- }
- continue
- case '.', '$', '(', ')', '|', '+':
- // Escape all regex characters which are not glob characters
- buf.WriteByte('\\')
- }
- buf.WriteByte(char)
- }
- if brackets > 0 {
- return "", &GlobParseError{"Unclosed character class", n, glob}
- } else if braces > 0 {
- return "", &GlobParseError{"Unclosed group", n, glob}
- }
- return buf.String(), nil
- }
- /*
- GlobStartingLiterals gets the first literals of a glob string.
- */
- func GlobStartingLiterals(glob string) string {
- buf := new(bytes.Buffer)
- n := len(glob)
- for i := 0; i < n; i++ {
- char := glob[i]
- if char == '\\' || char == '*' || char == '?' ||
- char == '{' || char == '[' {
- break
- }
- buf.WriteByte(char)
- }
- return buf.String()
- }
- /*
- LevenshteinDistance computes the Levenshtein distance between two strings.
- */
- func LevenshteinDistance(str1, str2 string) int {
- if str1 == str2 {
- return 0
- }
- rslice1 := StringToRuneSlice(str1)
- rslice2 := StringToRuneSlice(str2)
- n, m := len(rslice1), len(rslice2)
- if n == 0 {
- return m
- } else if m == 0 {
- return n
- }
- v0 := make([]int, m+1, m+1)
- v1 := make([]int, m+1, m+1)
- for i := 0; i <= m; i++ {
- v0[i] = i
- }
- var cost int
- for i := 0; i < n; i++ {
- v1[0] = i + 1
- for j := 0; j < m; j++ {
- if rslice1[i] == rslice2[j] {
- cost = 0
- } else {
- cost = 1
- }
- v1[j+1] = min3(v1[j]+1, v0[j+1]+1, v0[j]+cost)
- }
- v0, v1 = v1, v0
- }
- return v0[m]
- }
- /*
- 3 way min for computing the Levenshtein distance.
- */
- func min3(a, b, c int) int {
- ret := a
- if b < ret {
- ret = b
- }
- if c < ret {
- ret = c
- }
- return ret
- }
- /*
- VersionStringCompare compares two version strings. Returns: 0 if the strings are
- equal; -1 if the first string is smaller; 1 if the first string is greater.
- */
- func VersionStringCompare(str1, str2 string) int {
- val1 := strings.Split(str1, ".")
- val2 := strings.Split(str2, ".")
- idx := 0
- for idx < len(val1) && idx < len(val2) && val1[idx] == val2[idx] {
- idx++
- }
- switch {
- case idx < len(val1) && idx < len(val2):
- return versionStringPartCompare(val1[idx], val2[idx])
- case len(val1) > len(val2):
- return 1
- case len(val1) < len(val2):
- return -1
- }
- return 0
- }
- /*
- versionStringPartCompare compares two version string parts. Returns: 0 if the
- strings are equal; -1 if the first string is smaller; 1 if the first string is
- greater.
- */
- func versionStringPartCompare(str1, str2 string) int {
- pat := regexp.MustCompile("^([0-9]+)([\\D].*)?")
- res1 := pat.FindStringSubmatch(str1)
- res2 := pat.FindStringSubmatch(str2)
- switch {
- case res1 == nil && res2 == nil:
- return strings.Compare(str1, str2)
- case res1 == nil && res2 != nil:
- return -1
- case res1 != nil && res2 == nil:
- return 1
- }
- v1, _ := strconv.Atoi(res1[1])
- v2, _ := strconv.Atoi(res2[1])
- res := 0
- switch {
- case v1 > v2:
- res = 1
- case v1 < v2:
- res = -1
- }
- if res == 0 {
- switch {
- case res1[2] != "" && res2[2] == "":
- return 1
- case res1[2] == "" && res2[2] != "":
- return -1
- case res1[2] != "" && res2[2] != "":
- return strings.Compare(res1[2], res2[2])
- }
- }
- return res
- }
- /*
- IsAlphaNumeric checks if a string contains only alpha numerical characters or "_".
- */
- func IsAlphaNumeric(str string) bool {
- ret, _ := regexp.MatchString("^[a-zA-Z0-9_]*$", str)
- return ret
- }
- /*
- IsTrueValue checks if a given string is a true value.
- */
- func IsTrueValue(str string) bool {
- str = strings.ToLower(str)
- return str == "true" || str == "yes" || str == "on" || str == "ok" ||
- str == "1" || str == "active" || str == "enabled"
- }
- /*
- IndexOf returns the index of str in slice or -1 if it does not exist.
- */
- func IndexOf(str string, slice []string) int {
- for i, s := range slice {
- if str == s {
- return i
- }
- }
- return -1
- }
- /*
- MapKeys returns the keys of a map as a sorted list.
- */
- func MapKeys(m map[string]interface{}) []string {
- ret := make([]string, 0, len(m))
- for k := range m {
- ret = append(ret, k)
- }
- sort.Strings(ret)
- return ret
- }
- /*
- GenerateRollingString creates a string by repeating a given string pattern.
- */
- func GenerateRollingString(seq string, size int) string {
- var buf bytes.Buffer
- rs := StringToRuneSlice(seq)
- l := len(rs)
- if l == 0 {
- return ""
- }
- for i := 0; i < size; i++ {
- buf.WriteRune(rs[i%l])
- }
- return buf.String()
- }
- /*
- ConvertToString tries to convert a given object into a stable string. This
- function can be used to display nested maps.
- */
- func ConvertToString(v interface{}) string {
- if vStringer, ok := v.(fmt.Stringer); ok {
- return vStringer.String()
- }
- if _, err := json.Marshal(v); err != nil {
- v = containerStringConvert(v)
- }
- if vString, ok := v.(string); ok {
- return vString
- } else if res, err := json.Marshal(v); err == nil {
- return string(res)
- }
- return fmt.Sprint(v)
- }
- /*
- ConvertToPrettyString tries to convert a given object into a stable human-readable
- string.
- */
- func ConvertToPrettyString(v interface{}) string {
- var res []byte
- var err error
- if res, err = json.MarshalIndent(v, "", " "); err != nil {
- if res, err = json.MarshalIndent(containerStringConvert(v), "", " "); err != nil {
- res = []byte(fmt.Sprint(v))
- }
- }
- return string(res)
- }
- /*
- containerStringConvert converts container contents into strings.
- */
- func containerStringConvert(v interface{}) interface{} {
- res := v
- if mapContainer, ok := v.(map[interface{}]interface{}); ok {
- newRes := make(map[string]interface{})
- for mk, mv := range mapContainer {
- newRes[ConvertToString(mk)] = containerStringConvert(mv)
- }
- res = newRes
- } else if mapList, ok := v.([]interface{}); ok {
- newRes := make([]interface{}, len(mapList))
- for i, lv := range mapList {
- newRes[i] = containerStringConvert(lv)
- }
- res = newRes
- }
- return res
- }
- /*
- MD5HexString calculates the MD5 sum of a string and returns it as hex string.
- */
- func MD5HexString(str string) string {
- return fmt.Sprintf("%x", md5.Sum([]byte(str)))
- }
- /*
- LengthConstantEquals compares two strings in length-constant time. This
- function is deliberately inefficient in that it does not stop at the earliest
- possible time. This is to prevent timing attacks when comparing password
- hashes.
- */
- func LengthConstantEquals(str1 []byte, str2 []byte) bool {
- diff := len(str1) ^ len(str2)
- for i := 0; i < len(str1) && i < len(str2); i++ {
- diff |= int(str1[i] ^ str2[i])
- }
- return diff == 0
- }
- /*
- CamelCaseSplit splits a camel case string into a slice.
- */
- func CamelCaseSplit(src string) []string {
- var result []string
- if !utf8.ValidString(src) {
- result = []string{src}
- } else {
- type rType int
- const (
- undefined rType = iota
- lower
- upper
- digit
- other
- )
- var current, previous rType
- var runes [][]rune
- for _, r := range src {
- if unicode.IsLower(r) {
- current = lower
- } else if unicode.IsUpper(r) {
- current = upper
- } else if unicode.IsDigit(r) {
- current = digit
- } else {
- current = other
- }
- if current == previous {
- runes[len(runes)-1] = append(runes[len(runes)-1], r)
- } else {
- runes = append(runes, []rune{r})
- previous = current
- }
- }
- for i := 0; i < len(runes)-1; i++ {
- // Detect cases like "ROCKH" "ard" and correct them to
- // "ROCK" "Hard"
- if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
- runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
- runes[i] = runes[i][:len(runes[i])-1]
- }
- }
- for _, s := range runes {
- if len(s) > 0 {
- result = append(result, string(s))
- }
- }
- }
- return result
- }
|