add pattern matching
This commit is contained in:
164
internal/matcher/radix.go
Normal file
164
internal/matcher/radix.go
Normal file
@ -0,0 +1,164 @@
|
||||
package matcher
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const radixWildcard = '*'
|
||||
|
||||
type radixNode struct {
|
||||
exact bool
|
||||
next map[rune]*radixNode
|
||||
}
|
||||
|
||||
type radixMatcher struct {
|
||||
root *radixNode
|
||||
|
||||
caseInsensitive bool
|
||||
saved []string
|
||||
regexps []*regexp.Regexp
|
||||
}
|
||||
|
||||
type RadixOpt func(m *radixMatcher)
|
||||
|
||||
func RadixCaseInsensitive() RadixOpt {
|
||||
return func(m *radixMatcher) {
|
||||
m.caseInsensitive = true
|
||||
}
|
||||
}
|
||||
|
||||
func NewRadix(opts ...RadixOpt) *radixMatcher {
|
||||
m := &radixMatcher{
|
||||
root: &radixNode{
|
||||
next: make(map[rune]*radixNode),
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(m)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *radixMatcher) MatchByPattern(value string) (pattern string) {
|
||||
originValue := value
|
||||
if r.caseInsensitive {
|
||||
value = strings.ToLower(value)
|
||||
}
|
||||
|
||||
node := r.root
|
||||
lastIdx := len([]rune(value)) - 1
|
||||
|
||||
var sb strings.Builder
|
||||
for i, v := range value {
|
||||
var ok bool
|
||||
if _, ok := node.next[radixWildcard]; ok {
|
||||
_, _ = sb.WriteRune(radixWildcard)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
node, ok = node.next[v]
|
||||
if !ok {
|
||||
return r.findByRegexp(originValue)
|
||||
}
|
||||
|
||||
sb.WriteRune(v)
|
||||
|
||||
if i != lastIdx {
|
||||
continue
|
||||
}
|
||||
|
||||
if !node.exact {
|
||||
return r.findByRegexp(value)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
return r.findByRegexp(originValue)
|
||||
}
|
||||
|
||||
func (r *radixMatcher) Match(value string) bool {
|
||||
originValue := value
|
||||
if r.caseInsensitive {
|
||||
value = strings.ToLower(value)
|
||||
}
|
||||
|
||||
node := r.root
|
||||
lastIdx := len([]rune(value)) - 1
|
||||
for i, v := range value {
|
||||
var ok bool
|
||||
if _, ok = node.next[radixWildcard]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
node, ok = node.next[v]
|
||||
if !ok {
|
||||
return r.findByRegexp(originValue) != ""
|
||||
}
|
||||
|
||||
if i == lastIdx {
|
||||
return node.exact
|
||||
}
|
||||
}
|
||||
|
||||
return r.findByRegexp(originValue) != ""
|
||||
}
|
||||
|
||||
func (r *radixMatcher) findByRegexp(value string) (regexpPattern string) {
|
||||
for _, rx := range r.regexps {
|
||||
if rx.MatchString(value) {
|
||||
return rx.String()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *radixMatcher) RegisterRegexp(regexpPattern string) {
|
||||
pattern := regexp.MustCompile(regexpPattern)
|
||||
r.regexps = append(r.regexps, pattern)
|
||||
}
|
||||
|
||||
func (r *radixMatcher) Register(pattern string) {
|
||||
if len(pattern) == 0 {
|
||||
panic("unable to handle empty pattern")
|
||||
}
|
||||
|
||||
if r.caseInsensitive {
|
||||
pattern = strings.ToLower(pattern)
|
||||
}
|
||||
|
||||
r.saved = append(r.saved, pattern)
|
||||
|
||||
node := r.root
|
||||
lastIdx := len([]rune(pattern)) - 1
|
||||
for i, v := range pattern {
|
||||
nextNode, ok := node.next[v]
|
||||
if !ok {
|
||||
nextNode = &radixNode{
|
||||
next: make(map[rune]*radixNode),
|
||||
}
|
||||
node.next[v] = nextNode
|
||||
}
|
||||
node = nextNode
|
||||
|
||||
if v == '*' {
|
||||
return
|
||||
}
|
||||
|
||||
if i != lastIdx {
|
||||
continue
|
||||
}
|
||||
|
||||
node.exact = true
|
||||
}
|
||||
}
|
||||
|
||||
func (r *radixMatcher) Patterns() []string {
|
||||
out := make([]string, len(r.saved))
|
||||
copy(out, r.saved)
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user