aboutsummaryrefslogtreecommitdiffstats
path: root/cache
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2020-03-14 16:47:38 +0100
committerMichael Muré <batolettre@gmail.com>2020-03-28 17:13:27 +0100
commit5e4dc87ffec7f87bbf3ebfcf256777ad773e8450 (patch)
tree04553cfb7ab8ea279c7415586ce1d0fe5c819996 /cache
parent58abc6b0a35b679ac0c34579ff1cb53c8fa71af4 (diff)
downloadgit-bug-5e4dc87ffec7f87bbf3ebfcf256777ad773e8450.tar.gz
cache: replace the all-in-one query parser by a complete one with AST/lexer/parser
Diffstat (limited to 'cache')
-rw-r--r--cache/filter.go47
-rw-r--r--cache/query.go172
-rw-r--r--cache/query_test.go41
-rw-r--r--cache/repo_cache.go20
-rw-r--r--cache/repo_cache_test.go5
-rw-r--r--cache/sorting.go18
6 files changed, 52 insertions, 251 deletions
diff --git a/cache/filter.go b/cache/filter.go
index ebe774ac..021962a7 100644
--- a/cache/filter.go
+++ b/cache/filter.go
@@ -5,6 +5,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/query/ast"
)
// resolver has the resolving functions needed by filters.
@@ -17,15 +18,10 @@ type resolver interface {
type Filter func(excerpt *BugExcerpt, resolver resolver) bool
// StatusFilter return a Filter that match a bug status
-func StatusFilter(query string) (Filter, error) {
- status, err := bug.StatusFromString(query)
- if err != nil {
- return nil, err
- }
-
+func StatusFilter(status bug.Status) Filter {
return func(excerpt *BugExcerpt, resolver resolver) bool {
return excerpt.Status == status
- }, nil
+ }
}
// AuthorFilter return a Filter that match a bug author
@@ -116,8 +112,8 @@ func NoLabelFilter() Filter {
}
}
-// Filters is a collection of Filter that implement a complex filter
-type Filters struct {
+// Matcher is a collection of Filter that implement a complex filter
+type Matcher struct {
Status []Filter
Author []Filter
Actor []Filter
@@ -127,8 +123,35 @@ type Filters struct {
NoFilters []Filter
}
+// compileMatcher transform an ast.Filters into a specialized matcher
+// for the cache.
+func compileMatcher(filters ast.Filters) *Matcher {
+ result := &Matcher{}
+
+ for _, value := range filters.Status {
+ result.Status = append(result.Status, StatusFilter(value))
+ }
+ for _, value := range filters.Author {
+ result.Author = append(result.Author, AuthorFilter(value))
+ }
+ for _, value := range filters.Actor {
+ result.Actor = append(result.Actor, ActorFilter(value))
+ }
+ for _, value := range filters.Participant {
+ result.Participant = append(result.Participant, ParticipantFilter(value))
+ }
+ for _, value := range filters.Label {
+ result.Label = append(result.Label, LabelFilter(value))
+ }
+ for _, value := range filters.Title {
+ result.Title = append(result.Title, TitleFilter(value))
+ }
+
+ return result
+}
+
// Match check if a bug match the set of filters
-func (f *Filters) Match(excerpt *BugExcerpt, resolver resolver) bool {
+func (f *Matcher) Match(excerpt *BugExcerpt, resolver resolver) bool {
if match := f.orMatch(f.Status, excerpt, resolver); !match {
return false
}
@@ -161,7 +184,7 @@ func (f *Filters) Match(excerpt *BugExcerpt, resolver resolver) bool {
}
// Check if any of the filters provided match the bug
-func (*Filters) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
+func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
if len(filters) == 0 {
return true
}
@@ -175,7 +198,7 @@ func (*Filters) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver
}
// Check if all of the filters provided match the bug
-func (*Filters) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
+func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
if len(filters) == 0 {
return true
}
diff --git a/cache/query.go b/cache/query.go
deleted file mode 100644
index 967c18d6..00000000
--- a/cache/query.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package cache
-
-import (
- "fmt"
- "strings"
- "unicode"
-)
-
-type Query struct {
- Filters
- OrderBy
- OrderDirection
-}
-
-// Return an identity query with default sorting (creation-desc)
-func NewQuery() *Query {
- return &Query{
- OrderBy: OrderByCreation,
- OrderDirection: OrderDescending,
- }
-}
-
-// ParseQuery parse a query DSL
-//
-// Ex: "status:open author:descartes sort:edit-asc"
-//
-// Supported filter qualifiers and syntax are described in docs/queries.md
-func ParseQuery(query string) (*Query, error) {
- fields := splitQuery(query)
-
- result := &Query{
- OrderBy: OrderByCreation,
- OrderDirection: OrderDescending,
- }
-
- sortingDone := false
-
- for _, field := range fields {
- split := strings.Split(field, ":")
- if len(split) != 2 {
- return nil, fmt.Errorf("can't parse \"%s\"", field)
- }
-
- qualifierName := split[0]
- qualifierQuery := removeQuote(split[1])
-
- switch qualifierName {
- case "status", "state":
- f, err := StatusFilter(qualifierQuery)
- if err != nil {
- return nil, err
- }
- result.Status = append(result.Status, f)
-
- case "author":
- f := AuthorFilter(qualifierQuery)
- result.Author = append(result.Author, f)
-
- case "actor":
- f := ActorFilter(qualifierQuery)
- result.Actor = append(result.Actor, f)
-
- case "participant":
- f := ParticipantFilter(qualifierQuery)
- result.Participant = append(result.Participant, f)
-
- case "label":
- f := LabelFilter(qualifierQuery)
- result.Label = append(result.Label, f)
-
- case "title":
- f := TitleFilter(qualifierQuery)
- result.Title = append(result.Title, f)
-
- case "no":
- err := result.parseNoFilter(qualifierQuery)
- if err != nil {
- return nil, err
- }
-
- case "sort":
- if sortingDone {
- return nil, fmt.Errorf("multiple sorting")
- }
-
- err := result.parseSorting(qualifierQuery)
- if err != nil {
- return nil, err
- }
-
- sortingDone = true
-
- default:
- return nil, fmt.Errorf("unknown qualifier name %s", qualifierName)
- }
- }
-
- return result, nil
-}
-
-func splitQuery(query string) []string {
- lastQuote := rune(0)
- f := func(c rune) bool {
- switch {
- case c == lastQuote:
- lastQuote = rune(0)
- return false
- case lastQuote != rune(0):
- return false
- case unicode.In(c, unicode.Quotation_Mark):
- lastQuote = c
- return false
- default:
- return unicode.IsSpace(c)
- }
- }
-
- return strings.FieldsFunc(query, f)
-}
-
-func removeQuote(field string) string {
- if len(field) >= 2 {
- if field[0] == '"' && field[len(field)-1] == '"' {
- return field[1 : len(field)-1]
- }
- }
- return field
-}
-
-func (q *Query) parseNoFilter(query string) error {
- switch query {
- case "label":
- q.NoFilters = append(q.NoFilters, NoLabelFilter())
- default:
- return fmt.Errorf("unknown \"no\" filter %s", query)
- }
-
- return nil
-}
-
-func (q *Query) parseSorting(query string) error {
- switch query {
- // default ASC
- case "id-desc":
- q.OrderBy = OrderById
- q.OrderDirection = OrderDescending
- case "id", "id-asc":
- q.OrderBy = OrderById
- q.OrderDirection = OrderAscending
-
- // default DESC
- case "creation", "creation-desc":
- q.OrderBy = OrderByCreation
- q.OrderDirection = OrderDescending
- case "creation-asc":
- q.OrderBy = OrderByCreation
- q.OrderDirection = OrderAscending
-
- // default DESC
- case "edit", "edit-desc":
- q.OrderBy = OrderByEdit
- q.OrderDirection = OrderDescending
- case "edit-asc":
- q.OrderBy = OrderByEdit
- q.OrderDirection = OrderAscending
-
- default:
- return fmt.Errorf("unknown sorting %s", query)
- }
-
- return nil
-}
diff --git a/cache/query_test.go b/cache/query_test.go
deleted file mode 100644
index 9ae62ac4..00000000
--- a/cache/query_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package cache
-
-import "testing"
-
-func TestQueryParse(t *testing.T) {
-
- var tests = []struct {
- input string
- ok bool
- }{
- {"gibberish", false},
-
- {"status:", false},
-
- {"status:open", true},
- {"status:closed", true},
- {"status:unknown", false},
-
- {"author:rene", true},
- {`author:"René Descartes"`, true},
-
- {"actor:bernhard", true},
- {"participant:leonhard", true},
-
- {"label:hello", true},
- {`label:"Good first issue"`, true},
-
- {"title:titleOne", true},
- {`title:"Bug titleTwo"`, true},
-
- {"sort:edit", true},
- {"sort:unknown", false},
- }
-
- for _, test := range tests {
- _, err := ParseQuery(test.input)
- if (err == nil) != test.ok {
- t.Fatalf("Unexpected parse result, expected: %v, err: %v", test.ok, err)
- }
- }
-}
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index d859a966..6546b15e 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -18,6 +18,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/query/ast"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/process"
@@ -525,7 +526,7 @@ func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, erro
}
// QueryBugs return the id of all Bug matching the given Query
-func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
+func (c *RepoCache) QueryBugs(query *ast.Query) []entity.Id {
c.muBug.RLock()
defer c.muBug.RUnlock()
@@ -533,10 +534,12 @@ func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
return c.AllBugsIds()
}
+ matcher := compileMatcher(query.Filters)
+
var filtered []*BugExcerpt
for _, excerpt := range c.bugExcerpts {
- if query.Match(excerpt, c) {
+ if matcher.Match(excerpt, c) {
filtered = append(filtered, excerpt)
}
}
@@ -544,18 +547,23 @@ func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
var sorter sort.Interface
switch query.OrderBy {
- case OrderById:
+ case ast.OrderById:
sorter = BugsById(filtered)
- case OrderByCreation:
+ case ast.OrderByCreation:
sorter = BugsByCreationTime(filtered)
- case OrderByEdit:
+ case ast.OrderByEdit:
sorter = BugsByEditTime(filtered)
default:
panic("missing sort type")
}
- if query.OrderDirection == OrderDescending {
+ switch query.OrderDirection {
+ case ast.OrderAscending:
+ // Nothing to do
+ case ast.OrderDescending:
sorter = sort.Reverse(sorter)
+ default:
+ panic("missing sort direction")
}
sort.Sort(sorter)
diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go
index c3bd3cc4..51393dfd 100644
--- a/cache/repo_cache_test.go
+++ b/cache/repo_cache_test.go
@@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/repository"
)
@@ -68,9 +69,9 @@ func TestCache(t *testing.T) {
require.NoError(t, err)
// Querying
- query, err := ParseQuery("status:open author:descartes sort:edit-asc")
+ q, err := query.Parse("status:open author:descartes sort:edit-asc")
require.NoError(t, err)
- require.Len(t, cache.QueryBugs(query), 2)
+ require.Len(t, cache.QueryBugs(q), 2)
// Close
require.NoError(t, cache.Close())
diff --git a/cache/sorting.go b/cache/sorting.go
deleted file mode 100644
index 19034a9d..00000000
--- a/cache/sorting.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package cache
-
-type OrderBy int
-
-const (
- _ OrderBy = iota
- OrderById
- OrderByCreation
- OrderByEdit
-)
-
-type OrderDirection int
-
-const (
- _ OrderDirection = iota
- OrderAscending
- OrderDescending
-)