1package cache
  2
  3import (
  4	"strings"
  5
  6	"github.com/MichaelMure/git-bug/bug"
  7	"github.com/MichaelMure/git-bug/entity"
  8)
  9
 10// resolver has the resolving functions needed by filters.
 11// This exist mainly to go through the functions of the cache with proper locking.
 12type resolver interface {
 13	ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error)
 14}
 15
 16// Filter is a predicate that match a subset of bugs
 17type Filter func(excerpt *BugExcerpt, resolver resolver) bool
 18
 19// StatusFilter return a Filter that match a bug status
 20func StatusFilter(query string) (Filter, error) {
 21	status, err := bug.StatusFromString(query)
 22	if err != nil {
 23		return nil, err
 24	}
 25
 26	return func(excerpt *BugExcerpt, resolver resolver) bool {
 27		return excerpt.Status == status
 28	}, nil
 29}
 30
 31// AuthorFilter return a Filter that match a bug author
 32func AuthorFilter(query string) Filter {
 33	return func(excerpt *BugExcerpt, resolver resolver) bool {
 34		query = strings.ToLower(query)
 35
 36		// Normal identity
 37		if excerpt.AuthorId != "" {
 38			author, err := resolver.ResolveIdentityExcerpt(excerpt.AuthorId)
 39			if err != nil {
 40				panic(err)
 41			}
 42
 43			return author.Match(query)
 44		}
 45
 46		// Legacy identity support
 47		return strings.Contains(strings.ToLower(excerpt.LegacyAuthor.Name), query)
 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// Filters is a collection of Filter that implement a complex filter
119type Filters struct {
120	Status      []Filter
121	Author      []Filter
122	Actor       []Filter
123	Participant []Filter
124	Label       []Filter
125	Title       []Filter
126	NoFilters   []Filter
127}
128
129// Match check if a bug match the set of filters
130func (f *Filters) Match(excerpt *BugExcerpt, resolver resolver) bool {
131	if match := f.orMatch(f.Status, excerpt, resolver); !match {
132		return false
133	}
134
135	if match := f.orMatch(f.Author, excerpt, resolver); !match {
136		return false
137	}
138
139	if match := f.orMatch(f.Participant, excerpt, resolver); !match {
140		return false
141	}
142
143	if match := f.orMatch(f.Actor, excerpt, resolver); !match {
144		return false
145	}
146
147	if match := f.andMatch(f.Label, excerpt, resolver); !match {
148		return false
149	}
150
151	if match := f.andMatch(f.NoFilters, excerpt, resolver); !match {
152		return false
153	}
154
155	if match := f.andMatch(f.Title, excerpt, resolver); !match {
156		return false
157	}
158
159	return true
160}
161
162// Check if any of the filters provided match the bug
163func (*Filters) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
164	if len(filters) == 0 {
165		return true
166	}
167
168	match := false
169	for _, f := range filters {
170		match = match || f(excerpt, resolver)
171	}
172
173	return match
174}
175
176// Check if all of the filters provided match the bug
177func (*Filters) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
178	if len(filters) == 0 {
179		return true
180	}
181
182	match := true
183	for _, f := range filters {
184		match = match && f(excerpt, resolver)
185	}
186
187	return match
188}