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