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/entity"
|
||||||
"git.loyso.art/frx/eway/internal/export"
|
"git.loyso.art/frx/eway/internal/export"
|
||||||
"git.loyso.art/frx/eway/internal/interconnect/eway"
|
"git.loyso.art/frx/eway/internal/interconnect/eway"
|
||||||
|
"git.loyso.art/frx/eway/internal/matcher"
|
||||||
|
|
||||||
"github.com/rodaine/table"
|
"github.com/rodaine/table"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
@ -387,8 +388,18 @@ func newViewItemsUniqueParams() *cli.Command {
|
|||||||
|
|
||||||
func newViewItemsParamsKnownValues() *cli.Command {
|
func newViewItemsParamsKnownValues() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "params-values",
|
Name: "params-values",
|
||||||
Usage: "Show all values of requested parameters",
|
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),
|
Action: decorateAction(viewItemsParamsKnownValuesAction),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -558,19 +569,45 @@ func viewItemsParamsKnownValuesAction(ctx context.Context, c *cli.Command) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
params := c.Args().Slice()
|
params := c.Args().Slice()
|
||||||
requestedValues := make(map[string]map[string]struct{}, len(params))
|
opts := make([]matcher.RadixOpt, 0, 1)
|
||||||
for _, param := range params {
|
if c.Bool("case-insensitive") {
|
||||||
log.Debug().Str("param", param).Msg("registering param")
|
opts = append(opts, matcher.RadixCaseInsensitive())
|
||||||
requestedValues[param] = make(map[string]struct{}, 16)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
iter := getItemsIter(ctx, repository.GoodsItem())
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
item := iter.Get()
|
item := iter.Get()
|
||||||
for k, v := range item.Parameters {
|
for k, v := range item.Parameters {
|
||||||
if _, ok := requestedValues[k]; ok {
|
matchedPattern := m.MatchByPattern(k)
|
||||||
requestedValues[k][v] = struct{}{}
|
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