1package cache
2
3import (
4 "strings"
5
6 "github.com/MichaelMure/git-bug/bug"
7 "github.com/MichaelMure/git-bug/entity"
8 "github.com/MichaelMure/git-bug/query"
9)
10
11// resolver has the resolving functions needed by filters.
12// This exist mainly to go through the functions of the cache with proper locking.
13type resolver interface {
14 ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error)
15}
16
17// Filter is a predicate that match a subset of bugs
18type Filter func(excerpt *BugExcerpt, resolver resolver) bool
19
20// StatusFilter return a Filter that match a bug status
21func StatusFilter(status bug.Status) Filter {
22 return func(excerpt *BugExcerpt, resolver resolver) bool {
23 return excerpt.Status == status
24 }
25}
26
27// AuthorFilter return a Filter that match a bug author
28func AuthorFilter(query string) Filter {
29 return func(excerpt *BugExcerpt, resolver resolver) bool {
30 query = strings.ToLower(query)
31
32 author, err := resolver.ResolveIdentityExcerpt(excerpt.AuthorId)
33 if err != nil {
34 panic(err)
35 }
36
37 return author.Match(query)
38 }
39}
40
41// LabelFilter return a Filter that match a label
42func LabelFilter(label string) Filter {
43 return func(excerpt *BugExcerpt, resolver resolver) bool {
44 for _, l := range excerpt.Labels {
45 if string(l) == label {
46 return true
47 }
48 }
49 return false
50 }
51}
52
53// ActorFilter return a Filter that match a bug actor
54func ActorFilter(query string) Filter {
55 return func(excerpt *BugExcerpt, resolver resolver) bool {
56 query = strings.ToLower(query)
57
58 for _, id := range excerpt.Actors {
59 identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
60 if err != nil {
61 panic(err)
62 }
63
64 if identityExcerpt.Match(query) {
65 return true
66 }
67 }
68 return false
69 }
70}
71
72// ParticipantFilter return a Filter that match a bug participant
73func ParticipantFilter(query string) Filter {
74 return func(excerpt *BugExcerpt, resolver resolver) bool {
75 query = strings.ToLower(query)
76
77 for _, id := range excerpt.Participants {
78 identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
79 if err != nil {
80 panic(err)
81 }
82
83 if identityExcerpt.Match(query) {
84 return true
85 }
86 }
87 return false
88 }
89}
90
91// TitleFilter return a Filter that match if the title contains the given query
92func TitleFilter(query string) Filter {
93 return func(excerpt *BugExcerpt, resolver resolver) bool {
94 return strings.Contains(
95 strings.ToLower(excerpt.Title),
96 strings.ToLower(query),
97 )
98 }
99}
100
101// NoLabelFilter return a Filter that match the absence of labels
102func NoLabelFilter() Filter {
103 return func(excerpt *BugExcerpt, resolver resolver) bool {
104 return len(excerpt.Labels) == 0
105 }
106}
107
108// Matcher is a collection of Filter that implement a complex filter
109type Matcher struct {
110 Status []Filter
111 Author []Filter
112 Actor []Filter
113 Participant []Filter
114 Label []Filter
115 Title []Filter
116 NoFilters []Filter
117}
118
119// compileMatcher transform a query.Filters into a specialized matcher
120// for the cache.
121func compileMatcher(filters query.Filters) *Matcher {
122 result := &Matcher{}
123
124 for _, value := range filters.Status {
125 result.Status = append(result.Status, StatusFilter(value))
126 }
127 for _, value := range filters.Author {
128 result.Author = append(result.Author, AuthorFilter(value))
129 }
130 for _, value := range filters.Actor {
131 result.Actor = append(result.Actor, ActorFilter(value))
132 }
133 for _, value := range filters.Participant {
134 result.Participant = append(result.Participant, ParticipantFilter(value))
135 }
136 for _, value := range filters.Label {
137 result.Label = append(result.Label, LabelFilter(value))
138 }
139 for _, value := range filters.Title {
140 result.Title = append(result.Title, TitleFilter(value))
141 }
142
143 return result
144}
145
146// Match check if a bug match the set of filters
147func (f *Matcher) Match(excerpt *BugExcerpt, resolver resolver) bool {
148 if match := f.orMatch(f.Status, excerpt, resolver); !match {
149 return false
150 }
151
152 if match := f.orMatch(f.Author, excerpt, resolver); !match {
153 return false
154 }
155
156 if match := f.orMatch(f.Participant, excerpt, resolver); !match {
157 return false
158 }
159
160 if match := f.orMatch(f.Actor, excerpt, resolver); !match {
161 return false
162 }
163
164 if match := f.andMatch(f.Label, excerpt, resolver); !match {
165 return false
166 }
167
168 if match := f.andMatch(f.NoFilters, excerpt, resolver); !match {
169 return false
170 }
171
172 if match := f.andMatch(f.Title, excerpt, resolver); !match {
173 return false
174 }
175
176 return true
177}
178
179// Check if any of the filters provided match the bug
180func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
181 if len(filters) == 0 {
182 return true
183 }
184
185 match := false
186 for _, f := range filters {
187 match = match || f(excerpt, resolver)
188 }
189
190 return match
191}
192
193// Check if all of the filters provided match the bug
194func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
195 if len(filters) == 0 {
196 return true
197 }
198
199 match := true
200 for _, f := range filters {
201 match = match && f(excerpt, resolver)
202 }
203
204 return match
205}