1package cache
  2
  3import (
  4	"strings"
  5
  6	"github.com/git-bug/git-bug/entities/common"
  7	"github.com/git-bug/git-bug/entity"
  8	"github.com/git-bug/git-bug/query"
  9)
 10
 11// Filter is a predicate that match a subset of bugs
 12type Filter func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool
 13
 14// StatusFilter return a Filter that match a bug status
 15func StatusFilter(status common.Status) Filter {
 16	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
 17		return excerpt.Status == status
 18	}
 19}
 20
 21// AuthorFilter return a Filter that match a bug author
 22func AuthorFilter(query string) Filter {
 23	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
 24		query = strings.ToLower(query)
 25
 26		author, err := entity.Resolve[*IdentityExcerpt](resolvers, excerpt.AuthorId)
 27		if err != nil {
 28			panic(err)
 29		}
 30
 31		return author.Match(query)
 32	}
 33}
 34
 35// MetadataFilter return a Filter that match a bug metadata at creation time
 36func MetadataFilter(pair query.StringPair) Filter {
 37	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
 38		if value, ok := excerpt.CreateMetadata[pair.Key]; ok {
 39			return value == pair.Value
 40		}
 41		return false
 42	}
 43}
 44
 45// LabelFilter return a Filter that match a label
 46func LabelFilter(label string) Filter {
 47	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
 48		for _, l := range excerpt.Labels {
 49			if string(l) == label {
 50				return true
 51			}
 52		}
 53		return false
 54	}
 55}
 56
 57// ActorFilter return a Filter that match a bug actor
 58func ActorFilter(query string) Filter {
 59	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
 60		query = strings.ToLower(query)
 61
 62		for _, id := range excerpt.Actors {
 63			identityExcerpt, err := entity.Resolve[*IdentityExcerpt](resolvers, id)
 64			if err != nil {
 65				panic(err)
 66			}
 67
 68			if identityExcerpt.Match(query) {
 69				return true
 70			}
 71		}
 72		return false
 73	}
 74}
 75
 76// ParticipantFilter return a Filter that match a bug participant
 77func ParticipantFilter(query string) Filter {
 78	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
 79		query = strings.ToLower(query)
 80
 81		for _, id := range excerpt.Participants {
 82			identityExcerpt, err := entity.Resolve[*IdentityExcerpt](resolvers, id)
 83			if err != nil {
 84				panic(err)
 85			}
 86
 87			if identityExcerpt.Match(query) {
 88				return true
 89			}
 90		}
 91		return false
 92	}
 93}
 94
 95// TitleFilter return a Filter that match if the title contains the given query
 96func TitleFilter(query string) Filter {
 97	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
 98		return strings.Contains(
 99			strings.ToLower(excerpt.Title),
100			strings.ToLower(query),
101		)
102	}
103}
104
105// NoLabelFilter return a Filter that match the absence of labels
106func NoLabelFilter() Filter {
107	return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
108		return len(excerpt.Labels) == 0
109	}
110}
111
112// Matcher is a collection of Filter that implement a complex filter
113type Matcher struct {
114	Status      []Filter
115	Author      []Filter
116	Metadata    []Filter
117	Actor       []Filter
118	Participant []Filter
119	Label       []Filter
120	Title       []Filter
121	NoFilters   []Filter
122}
123
124// compileMatcher transform a query.Filters into a specialized matcher
125// for the cache.
126func compileMatcher(filters query.Filters) *Matcher {
127	result := &Matcher{}
128
129	for _, value := range filters.Status {
130		result.Status = append(result.Status, StatusFilter(value))
131	}
132	for _, value := range filters.Author {
133		result.Author = append(result.Author, AuthorFilter(value))
134	}
135	for _, value := range filters.Metadata {
136		result.Metadata = append(result.Metadata, MetadataFilter(value))
137	}
138	for _, value := range filters.Actor {
139		result.Actor = append(result.Actor, ActorFilter(value))
140	}
141	for _, value := range filters.Participant {
142		result.Participant = append(result.Participant, ParticipantFilter(value))
143	}
144	for _, value := range filters.Label {
145		result.Label = append(result.Label, LabelFilter(value))
146	}
147	for _, value := range filters.Title {
148		result.Title = append(result.Title, TitleFilter(value))
149	}
150	if filters.NoLabel {
151		result.NoFilters = append(result.NoFilters, NoLabelFilter())
152	}
153
154	return result
155}
156
157// Match check if a bug match the set of filters
158func (f *Matcher) Match(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
159	if match := f.orMatch(f.Status, excerpt, resolvers); !match {
160		return false
161	}
162
163	if match := f.orMatch(f.Author, excerpt, resolvers); !match {
164		return false
165	}
166
167	if match := f.orMatch(f.Metadata, excerpt, resolvers); !match {
168		return false
169	}
170
171	if match := f.orMatch(f.Participant, excerpt, resolvers); !match {
172		return false
173	}
174
175	if match := f.orMatch(f.Actor, excerpt, resolvers); !match {
176		return false
177	}
178
179	if match := f.andMatch(f.Label, excerpt, resolvers); !match {
180		return false
181	}
182
183	if match := f.andMatch(f.NoFilters, excerpt, resolvers); !match {
184		return false
185	}
186
187	if match := f.andMatch(f.Title, excerpt, resolvers); !match {
188		return false
189	}
190
191	return true
192}
193
194// Check if any of the filters provided match the bug
195func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
196	if len(filters) == 0 {
197		return true
198	}
199
200	match := false
201	for _, f := range filters {
202		match = match || f(excerpt, resolvers)
203	}
204
205	return match
206}
207
208// Check if all the filters provided match the bug
209func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
210	if len(filters) == 0 {
211		return true
212	}
213
214	match := true
215	for _, f := range filters {
216		match = match && f(excerpt, resolvers)
217	}
218
219	return match
220}