filter.go

  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}