add pattern matching
This commit is contained in:
@ -26,6 +26,7 @@ import (
|
||||
"git.loyso.art/frx/eway/internal/entity"
|
||||
"git.loyso.art/frx/eway/internal/export"
|
||||
"git.loyso.art/frx/eway/internal/interconnect/eway"
|
||||
"git.loyso.art/frx/eway/internal/matcher"
|
||||
|
||||
"github.com/rodaine/table"
|
||||
"github.com/rs/zerolog"
|
||||
@ -387,8 +388,18 @@ func newViewItemsUniqueParams() *cli.Command {
|
||||
|
||||
func newViewItemsParamsKnownValues() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "params-values",
|
||||
Usage: "Show all values of requested parameters",
|
||||
Name: "params-values",
|
||||
Usage: "Show all values of requested parameters",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "case-insensitive",
|
||||
Usage: "Ignores cases of keys",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "regex",
|
||||
Usage: "Registers regex to match",
|
||||
},
|
||||
},
|
||||
Action: decorateAction(viewItemsParamsKnownValuesAction),
|
||||
}
|
||||
}
|
||||
@ -558,19 +569,45 @@ func viewItemsParamsKnownValuesAction(ctx context.Context, c *cli.Command) error
|
||||
}
|
||||
|
||||
params := c.Args().Slice()
|
||||
requestedValues := make(map[string]map[string]struct{}, len(params))
|
||||
for _, param := range params {
|
||||
log.Debug().Str("param", param).Msg("registering param")
|
||||
requestedValues[param] = make(map[string]struct{}, 16)
|
||||
opts := make([]matcher.RadixOpt, 0, 1)
|
||||
if c.Bool("case-insensitive") {
|
||||
opts = append(opts, matcher.RadixCaseInsensitive())
|
||||
}
|
||||
|
||||
m := matcher.NewRadix(opts...)
|
||||
for _, param := range params {
|
||||
log.Debug().Str("param", param).Msg("registering param")
|
||||
m.Register(param)
|
||||
}
|
||||
for _, regexp := range c.StringSlice("regex") {
|
||||
log.Debug().Str("regexp", regexp).Msg("registering regexp")
|
||||
m.RegisterRegexp(regexp)
|
||||
}
|
||||
|
||||
requestedValues := make(map[string]map[string]struct{}, len(params))
|
||||
requestedValuesByPattern := make(map[string]map[string]struct{}, len(params))
|
||||
iter := getItemsIter(ctx, repository.GoodsItem())
|
||||
for iter.Next() {
|
||||
item := iter.Get()
|
||||
for k, v := range item.Parameters {
|
||||
if _, ok := requestedValues[k]; ok {
|
||||
requestedValues[k][v] = struct{}{}
|
||||
matchedPattern := m.MatchByPattern(k)
|
||||
if matchedPattern == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
values, ok := requestedValues[k]
|
||||
if !ok {
|
||||
values = make(map[string]struct{})
|
||||
}
|
||||
values[v] = struct{}{}
|
||||
requestedValues[k] = values
|
||||
|
||||
values, ok = requestedValuesByPattern[matchedPattern]
|
||||
if !ok {
|
||||
values = map[string]struct{}{}
|
||||
}
|
||||
values[v] = struct{}{}
|
||||
requestedValuesByPattern[matchedPattern] = values
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
118
internal/matcher/radix_test.go
Normal file
118
internal/matcher/radix_test.go
Normal file
@ -0,0 +1,118 @@
|
||||
package matcher_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.loyso.art/frx/eway/internal/matcher"
|
||||
)
|
||||
|
||||
func TestRadixMatcherWithPattern(t *testing.T) {
|
||||
m := matcher.NewRadix()
|
||||
m.Register("aloha")
|
||||
m.Register("hawaii")
|
||||
m.Register("te*")
|
||||
|
||||
var tt = []struct {
|
||||
name string
|
||||
in string
|
||||
pattern string
|
||||
}{{
|
||||
name: "should match exact",
|
||||
in: "aloha",
|
||||
pattern: "aloha",
|
||||
}, {
|
||||
name: "should match exact 2",
|
||||
in: "hawaii",
|
||||
pattern: "hawaii",
|
||||
}, {
|
||||
name: "should match pattern",
|
||||
in: "test",
|
||||
pattern: "te*",
|
||||
}, {
|
||||
name: "should not match",
|
||||
in: "alohe",
|
||||
}, {
|
||||
name: "should not match 2",
|
||||
in: "whoa",
|
||||
}}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
pattern := m.MatchByPattern(tc.in)
|
||||
if pattern != tc.pattern {
|
||||
t.Errorf("expected %s got %s", tc.pattern, pattern)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRadixMatcher(t *testing.T) {
|
||||
m := matcher.NewRadix()
|
||||
m.Register("aloha")
|
||||
m.Register("hawaii")
|
||||
m.Register("te*")
|
||||
|
||||
var tt = []struct {
|
||||
name string
|
||||
in string
|
||||
match bool
|
||||
}{{
|
||||
name: "should match exact",
|
||||
in: "aloha",
|
||||
match: true,
|
||||
}, {
|
||||
name: "should match exact 2",
|
||||
in: "hawaii",
|
||||
match: true,
|
||||
}, {
|
||||
name: "should match pattern",
|
||||
in: "test",
|
||||
match: true,
|
||||
}, {
|
||||
name: "should not match",
|
||||
in: "alohe",
|
||||
}, {
|
||||
name: "should not match 2",
|
||||
in: "whoa",
|
||||
}}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
match := m.Match(tc.in)
|
||||
if match != tc.match {
|
||||
t.Errorf("expected %t got %t", tc.match, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRadixMatcherWildcard(t *testing.T) {
|
||||
m := matcher.NewRadix()
|
||||
m.Register("*")
|
||||
|
||||
var tt = []struct {
|
||||
name string
|
||||
in string
|
||||
match bool
|
||||
}{{
|
||||
name: "should match exact",
|
||||
in: "aloha",
|
||||
match: true,
|
||||
}, {
|
||||
name: "should match exact 2",
|
||||
in: "hawaii",
|
||||
match: true,
|
||||
}}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
match := m.Match(tc.in)
|
||||
if match != tc.match {
|
||||
t.Errorf("expected %t got %t", tc.match, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user