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