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 // Normal identity
33 if excerpt.AuthorId != "" {
34 author, err := resolver.ResolveIdentityExcerpt(excerpt.AuthorId)
35 if err != nil {
36 panic(err)
37 }
38
39 return author.Match(query)
40 }
41
42 // Legacy identity support
43 return strings.Contains(strings.ToLower(excerpt.LegacyAuthor.Name), query) ||
44 strings.Contains(strings.ToLower(excerpt.LegacyAuthor.Login), query)
45 }
46}
47
48// LabelFilter return a Filter that match a label
49func LabelFilter(label string) Filter {
50 return func(excerpt *BugExcerpt, resolver resolver) bool {
51 for _, l := range excerpt.Labels {
52 if string(l) == label {
53 return true
54 }
55 }
56 return false
57 }
58}
59
60// ActorFilter return a Filter that match a bug actor
61func ActorFilter(query string) Filter {
62 return func(excerpt *BugExcerpt, resolver resolver) bool {
63 query = strings.ToLower(query)
64
65 for _, id := range excerpt.Actors {
66 identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
67 if err != nil {
68 panic(err)
69 }
70
71 if identityExcerpt.Match(query) {
72 return true
73 }
74 }
75 return false
76 }
77}
78
79// ParticipantFilter return a Filter that match a bug participant
80func ParticipantFilter(query string) Filter {
81 return func(excerpt *BugExcerpt, resolver resolver) bool {
82 query = strings.ToLower(query)
83
84 for _, id := range excerpt.Participants {
85 identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
86 if err != nil {
87 panic(err)
88 }
89
90 if identityExcerpt.Match(query) {
91 return true
92 }
93 }
94 return false
95 }
96}
97
98// TitleFilter return a Filter that match if the title contains the given query
99func TitleFilter(query string) Filter {
100 return func(excerpt *BugExcerpt, resolver resolver) bool {
101 return strings.Contains(
102 strings.ToLower(excerpt.Title),
103 strings.ToLower(query),
104 )
105 }
106}
107
108// NoLabelFilter return a Filter that match the absence of labels
109func NoLabelFilter() Filter {
110 return func(excerpt *BugExcerpt, resolver resolver) bool {
111 return len(excerpt.Labels) == 0
112 }
113}
114
115// Matcher is a collection of Filter that implement a complex filter
116type Matcher struct {
117 Status []Filter
118 Author []Filter
119 Actor []Filter
120 Participant []Filter
121 Label []Filter
122 Title []Filter
123 NoFilters []Filter
124}
125
126// compileMatcher transform a query.Filters into a specialized matcher
127// for the cache.
128func compileMatcher(filters query.Filters) *Matcher {
129 result := &Matcher{}
130
131 for _, value := range filters.Status {
132 result.Status = append(result.Status, StatusFilter(value))
133 }
134 for _, value := range filters.Author {
135 result.Author = append(result.Author, AuthorFilter(value))
136 }
137 for _, value := range filters.Actor {
138 result.Actor = append(result.Actor, ActorFilter(value))
139 }
140 for _, value := range filters.Participant {
141 result.Participant = append(result.Participant, ParticipantFilter(value))
142 }
143 for _, value := range filters.Label {
144 result.Label = append(result.Label, LabelFilter(value))
145 }
146 for _, value := range filters.Title {
147 result.Title = append(result.Title, TitleFilter(value))
148 }
149
150 return result
151}
152
153// Match check if a bug match the set of filters
154func (f *Matcher) Match(excerpt *BugExcerpt, resolver resolver) bool {
155 if match := f.orMatch(f.Status, excerpt, resolver); !match {
156 return false
157 }
158
159 if match := f.orMatch(f.Author, excerpt, resolver); !match {
160 return false
161 }
162
163 if match := f.orMatch(f.Participant, excerpt, resolver); !match {
164 return false
165 }
166
167 if match := f.orMatch(f.Actor, excerpt, resolver); !match {
168 return false
169 }
170
171 if match := f.andMatch(f.Label, excerpt, resolver); !match {
172 return false
173 }
174
175 if match := f.andMatch(f.NoFilters, excerpt, resolver); !match {
176 return false
177 }
178
179 if match := f.andMatch(f.Title, excerpt, resolver); !match {
180 return false
181 }
182
183 return true
184}
185
186// Check if any of the filters provided match the bug
187func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
188 if len(filters) == 0 {
189 return true
190 }
191
192 match := false
193 for _, f := range filters {
194 match = match || f(excerpt, resolver)
195 }
196
197 return match
198}
199
200// Check if all of the filters provided match the bug
201func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
202 if len(filters) == 0 {
203 return true
204 }
205
206 match := true
207 for _, f := range filters {
208 match = match && f(excerpt, resolver)
209 }
210
211 return match
212}