1use std::fmt;
2
3use enum_as_inner::EnumAsInner;
4use scallop::ExecStatus;
5
6use crate::dep::{Cpn, Cpv, Dep, Version};
7use crate::eapi::{Eapi, Restrict as EapiRestrict};
8use crate::repo::{Repo, Repository};
9use crate::restrict::str::Restrict as StrRestrict;
10use crate::restrict::{Restrict as BaseRestrict, Restriction};
11use crate::traits::Intersects;
12
13pub mod ebuild;
14pub mod fake;
15
16#[allow(clippy::large_enum_variant)]
17#[derive(EnumAsInner, Debug, Clone)]
18pub enum Pkg {
19 Configured(ebuild::EbuildConfiguredPkg),
20 Ebuild(ebuild::EbuildPkg),
21 Fake(fake::Pkg),
22}
23
24make_pkg_traits!(Pkg);
25
26pub trait Package:
27 fmt::Debug + fmt::Display + Intersects<Dep> + Intersects<Cpv> + Intersects<Cpn>
28{
29 fn eapi(&self) -> &'static Eapi;
31
32 fn cpv(&self) -> &Cpv;
34
35 fn cpn(&self) -> &Cpn {
37 self.cpv().cpn()
38 }
39
40 fn category(&self) -> &str {
42 self.cpv().category()
43 }
44
45 fn package(&self) -> &str {
47 self.cpv().package()
48 }
49
50 fn version(&self) -> &Version {
52 self.cpv().version()
53 }
54
55 fn p(&self) -> String {
57 self.cpv().p()
58 }
59
60 fn pf(&self) -> String {
62 self.cpv().pf()
63 }
64
65 fn pr(&self) -> String {
67 self.cpv().pr()
68 }
69
70 fn pv(&self) -> String {
72 self.cpv().pv()
73 }
74
75 fn pvr(&self) -> String {
77 self.cpv().pvr()
78 }
79}
80
81pub trait RepoPackage: Package + Ord {
82 type Repo: Repository;
83
84 fn repo(&self) -> Self::Repo;
86}
87
88#[allow(dead_code)]
89pub(crate) trait Build: Package {
90 fn build(&self) -> scallop::Result<()>;
92}
93
94pub(crate) trait PkgPretend: Package {
95 fn pkg_pretend(&self) -> scallop::Result<Option<String>>;
97}
98
99pub trait Source: Package {
100 fn source(&self) -> scallop::Result<ExecStatus>;
102}
103
104macro_rules! make_pkg_traits {
105 ($($x:ty),+) => {$(
106 impl PartialEq for $x {
107 fn eq(&self, other: &Self) -> bool {
108 self.repo() == other.repo() && self.cpv() == other.cpv()
109 }
110 }
111
112 impl Eq for $x {}
113
114 impl std::hash::Hash for $x {
115 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
116 self.repo().hash(state);
117 self.cpv().hash(state);
118 }
119 }
120
121 impl PartialOrd for $x {
122 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
123 Some(self.cmp(other))
124 }
125 }
126
127 impl Ord for $x {
128 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
129 self.cpv().cmp(other.cpv()).then_with(|| self.repo().cmp(&other.repo()))
130 }
131 }
132
133 impl std::fmt::Display for $x {
134 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
135 write!(f, "{}::{}", self.cpv(), self.repo())
136 }
137 }
138
139 impl From<&$x> for $crate::restrict::Restrict {
140 fn from(pkg: &$x) -> Self {
141 let r1 = pkg.cpv().into();
142 let r2 = $crate::restrict::Restrict::Dep(pkg.repo().into());
143 $crate::restrict::Restrict::and([r1, r2])
144 }
145 }
146
147 impl $crate::traits::Intersects<$x> for $crate::dep::Dep {
148 fn intersects(&self, other: &$x) -> bool {
149 other.intersects(self)
150 }
151 }
152
153 impl $crate::traits::Intersects<$crate::dep::Cpv> for $x {
154 fn intersects(&self, cpv: &$crate::dep::Cpv) -> bool {
155 self.cpv() == cpv
156 }
157 }
158
159 impl $crate::traits::Intersects<$x> for $crate::dep::Cpv {
160 fn intersects(&self, other: &$x) -> bool {
161 other.intersects(self)
162 }
163 }
164
165 impl $crate::traits::Intersects<$crate::dep::Cpn> for $x {
166 fn intersects(&self, cpn: &$crate::dep::Cpn) -> bool {
167 self.cpn() == cpn
168 }
169 }
170
171 impl $crate::traits::Intersects<$x> for $crate::dep::Cpn {
172 fn intersects(&self, other: &$x) -> bool {
173 other.intersects(self)
174 }
175 }
176
177 impl From<&$x> for $crate::dep::Cpv {
178 fn from(value: &$x) -> Self {
179 value.cpv().clone()
180 }
181 }
182 )+};
183}
184use make_pkg_traits;
185
186impl Package for Pkg {
187 fn eapi(&self) -> &'static Eapi {
188 match self {
189 Self::Configured(pkg) => pkg.eapi(),
190 Self::Ebuild(pkg) => pkg.eapi(),
191 Self::Fake(pkg) => pkg.eapi(),
192 }
193 }
194
195 fn cpv(&self) -> &Cpv {
196 match self {
197 Self::Configured(pkg) => pkg.cpv(),
198 Self::Ebuild(pkg) => pkg.cpv(),
199 Self::Fake(pkg) => pkg.cpv(),
200 }
201 }
202}
203
204impl RepoPackage for Pkg {
205 type Repo = Repo;
206
207 fn repo(&self) -> Self::Repo {
208 match self {
209 Self::Configured(pkg) => pkg.repo().into(),
210 Self::Ebuild(pkg) => pkg.repo().into(),
211 Self::Fake(pkg) => pkg.repo().into(),
212 }
213 }
214}
215
216impl Intersects<Dep> for Pkg {
217 fn intersects(&self, dep: &Dep) -> bool {
218 match self {
219 Self::Configured(pkg) => pkg.intersects(dep),
220 Self::Ebuild(pkg) => pkg.intersects(dep),
221 Self::Fake(pkg) => pkg.intersects(dep),
222 }
223 }
224}
225
226impl<T> Package for &T
227where
228 T: Package,
229{
230 fn eapi(&self) -> &'static Eapi {
231 (*self).eapi()
232 }
233 fn cpv(&self) -> &Cpv {
234 (*self).cpv()
235 }
236}
237
238impl<T> RepoPackage for &T
239where
240 T: RepoPackage,
241{
242 type Repo = T::Repo;
243
244 fn repo(&self) -> Self::Repo {
245 (*self).repo()
246 }
247}
248
249impl<T> Intersects<Dep> for &T
250where
251 T: Package,
252{
253 fn intersects(&self, dep: &Dep) -> bool {
254 (*self).intersects(dep)
255 }
256}
257
258impl<T> Intersects<Cpv> for &T
259where
260 T: Package,
261{
262 fn intersects(&self, cpv: &Cpv) -> bool {
263 (*self).intersects(cpv)
264 }
265}
266
267impl<T> Intersects<Cpn> for &T
268where
269 T: Package,
270{
271 fn intersects(&self, cpn: &Cpn) -> bool {
272 (*self).intersects(cpn)
273 }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Hash)]
277pub enum Restrict {
278 Eapi(EapiRestrict),
279 Ebuild(ebuild::Restrict),
280 Repo(StrRestrict),
281}
282
283impl Restrict {
284 pub fn eapi(s: &str) -> Self {
285 Self::Eapi(EapiRestrict::Id(StrRestrict::equal(s)))
286 }
287
288 pub fn repo(s: &str) -> Self {
289 Self::Repo(StrRestrict::equal(s))
290 }
291}
292
293impl From<Restrict> for BaseRestrict {
294 fn from(r: Restrict) -> Self {
295 Self::Pkg(r)
296 }
297}
298
299impl Restriction<&Pkg> for Restrict {
300 fn matches(&self, pkg: &Pkg) -> bool {
301 match self {
302 Self::Eapi(r) => r.matches(pkg.eapi()),
303 Self::Repo(r) => r.matches(pkg.repo().id()),
304 Self::Ebuild(r) => match pkg {
305 Pkg::Ebuild(p) => r.matches(p),
306 _ => false,
307 },
308 }
309 }
310}
311
312impl Restriction<&Pkg> for BaseRestrict {
313 fn matches(&self, pkg: &Pkg) -> bool {
314 crate::restrict::restrict_match! {self, pkg,
315 Self::Dep(r) => r.matches(pkg),
316 Self::Pkg(r) => r.matches(pkg),
317 }
318 }
319}
320
321impl Restriction<&Repo> for Restrict {
322 fn matches(&self, repo: &Repo) -> bool {
323 match self {
324 Self::Repo(r) => r.matches(repo.id()),
325 _ => false,
326 }
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use itertools::Itertools;
333
334 use crate::eapi::EAPI_LATEST_OFFICIAL;
335 use crate::repo::{PkgRepository, fake};
336 use crate::test::assert_ordered_eq;
337
338 use super::*;
339
340 #[test]
341 fn ordering() {
342 let r1: Repo = fake::FakeRepo::new("b", 0)
344 .pkgs(["cat/pkg-1"])
345 .unwrap()
346 .into();
347 let r2: Repo = fake::FakeRepo::new("a", 0)
348 .pkgs(["cat/pkg-0"])
349 .unwrap()
350 .into();
351 let pkgs: Vec<_> = r1.iter().chain(r2.iter()).try_collect().unwrap();
352 let sorted_pkgs: Vec<_> = pkgs.iter().sorted().collect();
353 assert_ordered_eq!(pkgs.iter().rev(), sorted_pkgs);
354
355 let r1: Repo = fake::FakeRepo::new("a", -1)
357 .pkgs(["cat/pkg-0"])
358 .unwrap()
359 .into();
360 let r2: Repo = fake::FakeRepo::new("b", 0)
361 .pkgs(["cat/pkg-0"])
362 .unwrap()
363 .into();
364 let pkgs: Vec<_> = r1.iter().chain(r2.iter()).try_collect().unwrap();
365 let sorted_pkgs: Vec<_> = pkgs.iter().sorted().collect();
366 assert_ordered_eq!(pkgs.iter().rev(), sorted_pkgs);
367
368 let r1: Repo = fake::FakeRepo::new("2", 0)
370 .pkgs(["cat/pkg-0"])
371 .unwrap()
372 .into();
373 let r2: Repo = fake::FakeRepo::new("1", 0)
374 .pkgs(["cat/pkg-0"])
375 .unwrap()
376 .into();
377 let pkgs: Vec<_> = r1.iter().chain(r2.iter()).try_collect().unwrap();
378 let sorted_pkgs: Vec<_> = pkgs.iter().sorted().collect();
379 assert_ordered_eq!(pkgs.iter().rev(), sorted_pkgs);
380 }
381
382 #[test]
383 fn package_trait() {
384 let cpv = Cpv::try_new("cat/pkg-1-r2").unwrap();
385 let cpn = Cpn::try_new("cat/pkg").unwrap();
386 let dep = Dep::try_new(">=cat/pkg-1").unwrap();
387 let r: Repo = fake::FakeRepo::new("test", 0).pkgs([&cpv]).unwrap().into();
388 let pkg = r.iter_restrict(&cpv).next().unwrap().unwrap();
389 assert_eq!(pkg.eapi(), *EAPI_LATEST_OFFICIAL);
390 assert_eq!(pkg.cpv(), &cpv);
391 assert_eq!(pkg.cpn(), &cpn);
392 assert_eq!(pkg.category(), "cat");
393 assert_eq!(pkg.package(), "pkg");
394 assert_eq!(pkg.version().to_string(), "1-r2");
395 assert_eq!(pkg.p(), "pkg-1");
396 assert_eq!(pkg.pf(), "pkg-1-r2");
397 assert_eq!(pkg.pr(), "r2");
398 assert_eq!(pkg.pv(), "1");
399 assert_eq!(pkg.pvr(), "1-r2");
400
401 assert!(pkg.intersects(&dep));
403 assert!(dep.intersects(&pkg));
404 assert!(pkg.intersects(&cpv));
405 assert!(cpv.intersects(&pkg));
406 assert!(pkg.intersects(&cpn));
407 assert!(cpn.intersects(&pkg));
408
409 let pkg = &&pkg;
410 assert!(pkg.intersects(&dep));
411 assert!(pkg.intersects(&cpv));
412 assert!(pkg.intersects(&cpn));
413 }
414
415 #[test]
416 fn intersects_dep() {
417 let cpv = Cpv::try_new("cat/pkg-1-r2").unwrap();
418 let r: Repo = fake::FakeRepo::new("test", 0).pkgs([&cpv]).unwrap().into();
419 let pkg = r.iter_restrict(&cpv).next().unwrap().unwrap();
420
421 for (s, expected) in [
422 ("cat/pkg", true),
423 ("a/b", false),
424 ("=cat/pkg-1-r2", true),
425 (">cat/pkg-1-r2", false),
426 ("~cat/pkg-1", true),
427 ] {
428 let dep: Dep = s.parse().unwrap();
429 assert_eq!(pkg.intersects(&dep), expected, "failed for {s}");
430 }
431 }
432}