1package deb
2
3import (
4	"fmt"
5	"path/filepath"
6	"regexp"
7	"strings"
8)
9
10// PackageLike is something like Package :) To be refined later
11type PackageLike interface {
12	GetField(string) string
13	MatchesDependency(Dependency) bool
14	MatchesArchitecture(string) bool
15	GetName() string
16	GetVersion() string
17	GetArchitecture() string
18}
19
20// PackageCatalog is abstraction on top of PackageCollection and PackageList
21type PackageCatalog interface {
22	Scan(q PackageQuery) (result *PackageList)
23	Search(dep Dependency, allMatches bool) (searchResults []*Package)
24	SearchSupported() bool
25	SearchByKey(arch, name, version string) (result *PackageList)
26}
27
28// PackageQuery is interface of predicate on Package
29type PackageQuery interface {
30	// Matches calculates match of condition against package
31	Matches(pkg PackageLike) bool
32	// Fast returns if search strategy is possible for this query
33	Fast(list PackageCatalog) bool
34	// Query performs search on package list
35	Query(list PackageCatalog) *PackageList
36	// String interface
37	String() string
38}
39
40// OrQuery is L | R
41type OrQuery struct {
42	L, R PackageQuery
43}
44
45// AndQuery is L , R
46type AndQuery struct {
47	L, R PackageQuery
48}
49
50// NotQuery is ! Q
51type NotQuery struct {
52	Q PackageQuery
53}
54
55// FieldQuery is generic request against field
56type FieldQuery struct {
57	Field    string
58	Relation int
59	Value    string
60	Regexp   *regexp.Regexp `codec:"-"`
61}
62
63// PkgQuery is search request against specific package
64type PkgQuery struct {
65	Pkg     string
66	Version string
67	Arch    string
68}
69
70// DependencyQuery is generic Debian-dependency like query
71type DependencyQuery struct {
72	Dep Dependency
73}
74
75// MatchAllQuery is query that matches all the packages
76type MatchAllQuery struct{}
77
78// Matches if any of L, R matches
79func (q *OrQuery) Matches(pkg PackageLike) bool {
80	return q.L.Matches(pkg) || q.R.Matches(pkg)
81}
82
83// Fast is true only if both parts are fast
84func (q *OrQuery) Fast(list PackageCatalog) bool {
85	return q.L.Fast(list) && q.R.Fast(list)
86}
87
88// Query strategy depends on nodes
89func (q *OrQuery) Query(list PackageCatalog) (result *PackageList) {
90	if q.Fast(list) {
91		result = q.L.Query(list)
92		result.Append(q.R.Query(list))
93	} else {
94		result = list.Scan(q)
95	}
96	return
97}
98
99// String interface
100func (q *OrQuery) String() string {
101	return fmt.Sprintf("(%s) | (%s)", q.L, q.R)
102}
103
104// Matches if both of L, R matches
105func (q *AndQuery) Matches(pkg PackageLike) bool {
106	return q.L.Matches(pkg) && q.R.Matches(pkg)
107}
108
109// Fast is true if any of the parts are fast
110func (q *AndQuery) Fast(list PackageCatalog) bool {
111	return q.L.Fast(list) || q.R.Fast(list)
112}
113
114// Query strategy depends on nodes
115func (q *AndQuery) Query(list PackageCatalog) (result *PackageList) {
116	if !q.Fast(list) {
117		result = list.Scan(q)
118	} else {
119		if q.L.Fast(list) {
120			result = q.L.Query(list)
121			result = result.Scan(q.R)
122		} else {
123			result = q.R.Query(list)
124			result = result.Scan(q.L)
125		}
126	}
127	return
128}
129
130// String interface
131func (q *AndQuery) String() string {
132	return fmt.Sprintf("(%s), (%s)", q.L, q.R)
133}
134
135// Matches if not matches
136func (q *NotQuery) Matches(pkg PackageLike) bool {
137	return !q.Q.Matches(pkg)
138}
139
140// Fast is false
141func (q *NotQuery) Fast(list PackageCatalog) bool {
142	return false
143}
144
145// Query strategy is scan always
146func (q *NotQuery) Query(list PackageCatalog) (result *PackageList) {
147	result = list.Scan(q)
148	return
149}
150
151// String interface
152func (q *NotQuery) String() string {
153	return fmt.Sprintf("!(%s)", q.Q)
154}
155
156// Matches on generic field
157func (q *FieldQuery) Matches(pkg PackageLike) bool {
158	if q.Field == "$Version" {
159		return pkg.MatchesDependency(Dependency{Pkg: pkg.GetName(), Relation: q.Relation, Version: q.Value, Regexp: q.Regexp})
160	}
161	if q.Field == "$Architecture" && q.Relation == VersionEqual {
162		return pkg.MatchesArchitecture(q.Value)
163	}
164
165	field := pkg.GetField(q.Field)
166
167	switch q.Relation {
168	case VersionDontCare:
169		return field != ""
170	case VersionEqual:
171		return field == q.Value
172	case VersionGreater:
173		return field > q.Value
174	case VersionGreaterOrEqual:
175		return field >= q.Value
176	case VersionLess:
177		return field < q.Value
178	case VersionLessOrEqual:
179		return field <= q.Value
180	case VersionPatternMatch:
181		matched, err := filepath.Match(q.Value, field)
182		return err == nil && matched
183	case VersionRegexp:
184		if q.Regexp == nil {
185			q.Regexp = regexp.MustCompile(q.Value)
186		}
187		return q.Regexp.FindStringIndex(field) != nil
188
189	}
190	panic("unknown relation")
191}
192
193// Query runs iteration through list
194func (q *FieldQuery) Query(list PackageCatalog) (result *PackageList) {
195	result = list.Scan(q)
196	return
197}
198
199// Fast depends on the query
200func (q *FieldQuery) Fast(list PackageCatalog) bool {
201	return false
202}
203
204// String interface
205func (q *FieldQuery) String() string {
206	escape := func(val string) string {
207		if strings.ContainsAny(val, "()|,!{} \t\n") {
208			return "'" + strings.Replace(strings.Replace(val, "\\", "\\\\", -1), "'", "\\'", -1) + "'"
209		}
210		return val
211	}
212
213	var op string
214	switch q.Relation {
215	case VersionEqual:
216		op = "="
217	case VersionGreater:
218		op = ">>"
219	case VersionLess:
220		op = "<<"
221	case VersionRegexp:
222		op = "~"
223	case VersionPatternMatch:
224		op = "%"
225	case VersionGreaterOrEqual:
226		op = ">="
227	case VersionLessOrEqual:
228		op = "<="
229	}
230	return fmt.Sprintf("%s (%s %s)", escape(q.Field), op, escape(q.Value))
231}
232
233// Matches on dependency condition
234func (q *DependencyQuery) Matches(pkg PackageLike) bool {
235	return pkg.MatchesDependency(q.Dep)
236}
237
238// Fast is always true for dependency query
239func (q *DependencyQuery) Fast(list PackageCatalog) bool {
240	return list.SearchSupported()
241}
242
243// Query runs PackageList.Search
244func (q *DependencyQuery) Query(list PackageCatalog) (result *PackageList) {
245	if q.Fast(list) {
246		result = NewPackageList()
247		for _, pkg := range list.Search(q.Dep, true) {
248			result.Add(pkg)
249		}
250	} else {
251		result = list.Scan(q)
252	}
253
254	return
255}
256
257// String interface
258func (q *DependencyQuery) String() string {
259	return q.Dep.String()
260}
261
262// Matches on specific properties
263func (q *PkgQuery) Matches(pkg PackageLike) bool {
264	return pkg.GetName() == q.Pkg && pkg.GetVersion() == q.Version && pkg.GetArchitecture() == q.Arch
265}
266
267// Fast is always true for package query
268func (q *PkgQuery) Fast(list PackageCatalog) bool {
269	return true
270}
271
272// Query looks up specific package
273func (q *PkgQuery) Query(list PackageCatalog) (result *PackageList) {
274	return list.SearchByKey(q.Arch, q.Pkg, q.Version)
275}
276
277// String interface
278func (q *PkgQuery) String() string {
279	return fmt.Sprintf("%s_%s_%s", q.Pkg, q.Version, q.Arch)
280}
281
282// Matches on specific properties
283func (q *MatchAllQuery) Matches(pkg PackageLike) bool {
284	return true
285}
286
287// Fast is always true for match all query
288func (q *MatchAllQuery) Fast(list PackageCatalog) bool {
289	return true
290}
291
292// Query looks up specific package
293func (q *MatchAllQuery) Query(list PackageCatalog) (result *PackageList) {
294	return list.Scan(q)
295}
296
297// String interface
298func (q *MatchAllQuery) String() string {
299	return ""
300}
301