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