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