1package cache
2
3import (
4 "strings"
5
6 "github.com/MichaelMure/git-bug/entities/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 if filters.NoLabel {
157 result.NoFilters = append(result.NoFilters, NoLabelFilter())
158 }
159
160 return result
161}
162
163// Match check if a bug match the set of filters
164func (f *Matcher) Match(excerpt *BugExcerpt, resolver resolver) bool {
165 if match := f.orMatch(f.Status, excerpt, resolver); !match {
166 return false
167 }
168
169 if match := f.orMatch(f.Author, excerpt, resolver); !match {
170 return false
171 }
172
173 if match := f.orMatch(f.Metadata, excerpt, resolver); !match {
174 return false
175 }
176
177 if match := f.orMatch(f.Participant, excerpt, resolver); !match {
178 return false
179 }
180
181 if match := f.orMatch(f.Actor, excerpt, resolver); !match {
182 return false
183 }
184
185 if match := f.andMatch(f.Label, excerpt, resolver); !match {
186 return false
187 }
188
189 if match := f.andMatch(f.NoFilters, excerpt, resolver); !match {
190 return false
191 }
192
193 if match := f.andMatch(f.Title, excerpt, resolver); !match {
194 return false
195 }
196
197 return true
198}
199
200// Check if any of the filters provided match the bug
201func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
202 if len(filters) == 0 {
203 return true
204 }
205
206 match := false
207 for _, f := range filters {
208 match = match || f(excerpt, resolver)
209 }
210
211 return match
212}
213
214// Check if all of the filters provided match the bug
215func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
216 if len(filters) == 0 {
217 return true
218 }
219
220 match := true
221 for _, f := range filters {
222 match = match && f(excerpt, resolver)
223 }
224
225 return match
226}