snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
52
server/pkg/str/levenshtein.go
Normal file
52
server/pkg/str/levenshtein.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package str
|
||||
|
||||
// write Levenshtein Distance search algorithm for strings
|
||||
// https://en.wikipedia.org/wiki/Levenshtein_distance
|
||||
func ToLevenshteinDistance(a, b string) int {
|
||||
var (
|
||||
// length of a
|
||||
la = len(a)
|
||||
// length of b
|
||||
lb = len(b)
|
||||
// distance matrix
|
||||
d = make([][]int, la+1)
|
||||
)
|
||||
|
||||
// initialize distance matrix
|
||||
for i := 0; i <= la; i++ {
|
||||
d[i] = make([]int, lb+1)
|
||||
d[i][0] = i
|
||||
}
|
||||
|
||||
for j := 0; j <= lb; j++ {
|
||||
d[0][j] = j
|
||||
}
|
||||
|
||||
// calculate distance matrix
|
||||
for i := 1; i <= la; i++ {
|
||||
for j := 1; j <= lb; j++ {
|
||||
if a[i-1] == b[j-1] {
|
||||
d[i][j] = d[i-1][j-1]
|
||||
} else {
|
||||
// fix this min function
|
||||
d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return d[la][lb]
|
||||
}
|
||||
|
||||
func min(a, b, c int) int {
|
||||
if a < b {
|
||||
if a < c {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
if b < c {
|
||||
return b
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
68
server/pkg/str/levenshtein_test.go
Normal file
68
server/pkg/str/levenshtein_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package str
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLevenshteinDistance(t *testing.T) {
|
||||
tests := []struct {
|
||||
a string
|
||||
b string
|
||||
want int
|
||||
}{
|
||||
{"", "hello", 5},
|
||||
{"hello", "", 5},
|
||||
{"hello", "hello", 0},
|
||||
{"ab", "aa", 1},
|
||||
{"ab", "ba", 2},
|
||||
{"ab", "aaa", 2},
|
||||
{"bbb", "a", 3},
|
||||
{"kitten", "sitting", 3},
|
||||
{"distance", "difference", 5},
|
||||
{"levenshtein", "frankenstein", 6},
|
||||
{"resume and cafe", "resumes and cafes", 2},
|
||||
{"a very long string that is meant to exceed", "another very long string that is meant to exceed", 6},
|
||||
// Testing acutes and umlauts
|
||||
{"resumé and café", "resumés and cafés", 2},
|
||||
{"resume and cafe", "resumé and café", 4},
|
||||
{"Hafþór Júlíus Björnsson", "Hafþor Julius Bjornsson", 8},
|
||||
// Only 2 characters are less in the 2nd string
|
||||
{"།་གམ་འས་པ་་མ།", "།་གམའས་པ་་མ", 6},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.a, func(t *testing.T) {
|
||||
if got := ToLevenshteinDistance(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("LevenshteinDistance() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/cortezaproject/corteza/server/pkg/str
|
||||
// BenchmarkLeven_100_100-12 39949 25767 ns/op 93184 B/op 102 allocs/op
|
||||
// BenchmarkLeven_1000_1000-12 390 3081967 ns/op 8298552 B/op 1011 allocs/op
|
||||
// BenchmarkLeven_10000_10000-12 4 299103531 ns/op 829957216 B/op 10131 allocs/op
|
||||
// PASS
|
||||
|
||||
func benchmarkLeven(b *testing.B, w1l, w2l int) {
|
||||
w1 := randStringRunes(w1l)
|
||||
w2 := randStringRunes(w2l)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ToLevenshteinDistance(w1, w2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLeven_100_100(b *testing.B) {
|
||||
benchmarkLeven(b, 100, 100)
|
||||
}
|
||||
|
||||
func BenchmarkLeven_1000_1000(b *testing.B) {
|
||||
benchmarkLeven(b, 1000, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkLeven_10000_10000(b *testing.B) {
|
||||
benchmarkLeven(b, 10000, 10000)
|
||||
}
|
||||
13
server/pkg/str/main_test.go
Normal file
13
server/pkg/str/main_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package str
|
||||
|
||||
import "math/rand"
|
||||
|
||||
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890?*_Ž!$%&'()=?*")
|
||||
|
||||
func randStringRunes(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
104
server/pkg/str/soundex.go
Normal file
104
server/pkg/str/soundex.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package str
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ToSoundex takes a word and returns the soundex code for it.
|
||||
// https://en.wikipedia.org/wiki/Soundex
|
||||
//
|
||||
// 1. Retain the first letter of the name and drop all other occurrences of a, e, i, o, u, y, h, w.
|
||||
// 2. Replace consonants with digits as follows (after the first letter):
|
||||
// b, f, p, v → 1
|
||||
// c, g, j, k, q, s, x, z → 2
|
||||
// d, t → 3
|
||||
// l → 4
|
||||
// m, n → 5
|
||||
// r → 6
|
||||
// 3. If two or more letters with the same number are adjacent in the original name (before step 1),
|
||||
// only retain the first letter; also two letters with the same number separated
|
||||
// by 'h' or 'w' are coded as a single number, whereas such letters separated by a vowel are coded twice.
|
||||
// This rule also applies to the first letter.
|
||||
// 4. Iterate the previous step until you have one letter and three numbers.
|
||||
// If you have too few letters in your word that you can't assign three numbers, append with zeros
|
||||
// until there are three numbers. If you have more than 3 letters, just retain the first 3 numbers.
|
||||
func ToSoundex(s string) string {
|
||||
var (
|
||||
// soundex code
|
||||
code string
|
||||
// last code
|
||||
lastCode string
|
||||
// last rune
|
||||
lastRune rune
|
||||
// last rune is vowel
|
||||
lastRuneIsVowel bool
|
||||
)
|
||||
|
||||
// retain the first letter of the name and drop all other occurrences of a, e, i, o, u, y, h, w
|
||||
for _, r := range s {
|
||||
if r == 'a' || r == 'e' || r == 'i' || r == 'o' || r == 'u' || r == 'y' || r == 'h' || r == 'w' {
|
||||
continue
|
||||
}
|
||||
|
||||
code = string(r)
|
||||
break
|
||||
}
|
||||
|
||||
// replace consonants with digits as follows (after the first letter)
|
||||
for _, r := range s {
|
||||
if r == 'a' || r == 'e' || r == 'i' || r == 'o' || r == 'u' || r == 'y' || r == 'h' || r == 'w' {
|
||||
lastRuneIsVowel = true
|
||||
continue
|
||||
}
|
||||
|
||||
if lastRuneIsVowel {
|
||||
lastRuneIsVowel = false
|
||||
lastCode = ""
|
||||
}
|
||||
|
||||
switch r {
|
||||
case 'b', 'f', 'p', 'v':
|
||||
lastCode = "1"
|
||||
case 'c', 'g', 'j', 'k', 'q', 's', 'x', 'z':
|
||||
lastCode = "2"
|
||||
case 'd', 't':
|
||||
lastCode = "3"
|
||||
case 'l':
|
||||
lastCode = "4"
|
||||
case 'm', 'n':
|
||||
lastCode = "5"
|
||||
case 'r':
|
||||
lastCode = "6"
|
||||
}
|
||||
|
||||
if lastCode != "" && lastCode != string(lastRune) {
|
||||
code += lastCode
|
||||
}
|
||||
|
||||
lastRune = r
|
||||
}
|
||||
|
||||
// if two or more letters with the same number are adjacent in the original name (before step 1),
|
||||
// only retain the first letter
|
||||
// also two letters with the same number separated by 'h' or 'w' are coded as a single number,
|
||||
// whereas such letters separated by a vowel are coded twice
|
||||
// this rule also applies to the first letter
|
||||
code = strings.ReplaceAll(code, "11", "1")
|
||||
code = strings.ReplaceAll(code, "22", "2")
|
||||
code = strings.ReplaceAll(code, "33", "3")
|
||||
code = strings.ReplaceAll(code, "44", "4")
|
||||
code = strings.ReplaceAll(code, "55", "5")
|
||||
code = strings.ReplaceAll(code, "66", "6")
|
||||
|
||||
// iterate the previous step until you have one letter and three numbers
|
||||
// if you have too few letters in your word that you can't assign three numbers,
|
||||
// append with zeros until there are three numbers
|
||||
// if you have more than 3 letters, just retain the first 3 numbers
|
||||
if len(code) < 4 {
|
||||
code += strings.Repeat("0", 4-len(code))
|
||||
} else {
|
||||
code = code[:4]
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
95
server/pkg/str/soundex_test.go
Normal file
95
server/pkg/str/soundex_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package str
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_soundex(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
"Robert",
|
||||
"R163",
|
||||
},
|
||||
{
|
||||
"Rupert",
|
||||
"R163",
|
||||
},
|
||||
{
|
||||
"Rubin",
|
||||
"R150",
|
||||
},
|
||||
{
|
||||
"Ashcraft",
|
||||
"A261",
|
||||
},
|
||||
{
|
||||
"Ashcroft",
|
||||
"A261",
|
||||
},
|
||||
{
|
||||
"Tymczak",
|
||||
"T522",
|
||||
},
|
||||
{
|
||||
"Pfister",
|
||||
"P123",
|
||||
},
|
||||
{
|
||||
"AH KEY",
|
||||
"A000",
|
||||
},
|
||||
{
|
||||
"The quick brown fox",
|
||||
"T221",
|
||||
},
|
||||
{
|
||||
"h3110 w021d",
|
||||
"3000",
|
||||
},
|
||||
{
|
||||
"1337",
|
||||
"1000",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ToSoundex(tt.name); got != tt.want {
|
||||
t.Errorf("soundex() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/cortezaproject/corteza/server/pkg/str
|
||||
// BenchmarkSoundex_100_100-12 475930 2835 ns/op 3568 B/op 80 allocs/op
|
||||
// BenchmarkSoundex_1000_1000-12 30688 37494 ns/op 225458 B/op 657 allocs/op
|
||||
// BenchmarkSoundex_10000_10000-12 568 2023842 ns/op 25114459 B/op 6892 allocs/op
|
||||
// BenchmarkSoundex_100000_100000-12 7 160967804 ns/op 2544780057 B/op 69015 allocs/op
|
||||
func benchmarkSoundex(b *testing.B, w1l int) {
|
||||
w1 := randStringRunes(w1l)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ToSoundex(w1)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSoundex_100_100(b *testing.B) {
|
||||
benchmarkSoundex(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkSoundex_1000_1000(b *testing.B) {
|
||||
benchmarkSoundex(b, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkSoundex_10000_10000(b *testing.B) {
|
||||
benchmarkSoundex(b, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkSoundex_100000_100000(b *testing.B) {
|
||||
benchmarkSoundex(b, 100000)
|
||||
}
|
||||
75
server/pkg/str/str.go
Normal file
75
server/pkg/str/str.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package str
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/handle"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultLevenshteinDistance is the default levenshtein distance
|
||||
DefaultLevenshteinDistance = 3
|
||||
|
||||
CaseInSensitiveMatch = iota
|
||||
CaseSensitiveMatch
|
||||
LevenshteinDistance
|
||||
Soundex
|
||||
)
|
||||
|
||||
// Match will match string as per given algorithm
|
||||
func Match(str1, str2 string, algorithm int) bool {
|
||||
switch algorithm {
|
||||
case LevenshteinDistance:
|
||||
return ToLevenshteinDistance(str1, str2) <= DefaultLevenshteinDistance
|
||||
case Soundex:
|
||||
return ToSoundex(str1) == ToSoundex(str2)
|
||||
case CaseSensitiveMatch:
|
||||
return strings.Compare(str1, str2) == 0
|
||||
case CaseInSensitiveMatch:
|
||||
return strings.EqualFold(str1, str2)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ParseStrings(ss []string) (m map[string]string, err error) {
|
||||
if len(ss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
m = make(map[string]string)
|
||||
|
||||
for _, s := range ss {
|
||||
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") {
|
||||
// assume json
|
||||
if err = json.Unmarshal([]byte(s), &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("invalid label format")
|
||||
}
|
||||
|
||||
if !handle.IsValid(kv[0]) {
|
||||
return nil, fmt.Errorf("invalid label key format")
|
||||
}
|
||||
|
||||
m[kv[0]] = kv[1]
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func HashStringSHA256(str string) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(str))
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
Reference in New Issue
Block a user