filter.go

  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}