1use std::hash::{Hash, Hasher};
2use std::sync::Arc;
3use std::{fmt, fs};
4
5use camino::{Utf8Path, Utf8PathBuf};
6use indexmap::{IndexMap, IndexSet};
7use tracing::error;
8
9use crate::Error;
10use crate::config::RepoConfig;
11use crate::dep::{Cpn, Cpv, Dep, Version};
12use crate::pkg::fake::Pkg;
13use crate::restrict::{Restrict, Restriction};
14use crate::traits::Contains;
15use crate::types::OrderedSet;
16
17use super::{PkgRepository, RepoFormat, Repository, make_repo_traits};
18
19type VersionMap = IndexMap<String, IndexSet<Version>>;
20type PkgMap = IndexMap<String, VersionMap>;
21
22#[derive(Clone)]
23struct InternalFakeRepo {
24 id: String,
25 config: RepoConfig,
26 pkgmap: PkgMap,
27 cpvs: OrderedSet<Cpv>,
28}
29
30#[derive(Clone)]
31pub struct FakeRepo(Arc<InternalFakeRepo>);
32
33impl fmt::Debug for FakeRepo {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 f.debug_struct("FakeRepo").field("id", &self.id()).finish()
36 }
37}
38
39impl PartialEq for FakeRepo {
40 fn eq(&self, other: &Self) -> bool {
41 self.0.id == other.0.id && self.0.cpvs == other.0.cpvs
42 }
43}
44
45impl Eq for FakeRepo {}
46
47impl Hash for FakeRepo {
48 fn hash<H: Hasher>(&self, state: &mut H) {
49 self.0.id.hash(state);
50 self.0.cpvs.hash(state);
51 }
52}
53
54make_repo_traits!(FakeRepo);
55
56impl FakeRepo {
57 pub fn new(id: &str, priority: i32) -> Self {
58 Self(Arc::new(InternalFakeRepo {
59 id: id.to_string(),
60 config: RepoConfig {
61 priority: Some(priority),
62 ..RepoFormat::Fake.into()
63 },
64 pkgmap: Default::default(),
65 cpvs: Default::default(),
66 }))
67 }
68
69 pub fn pkgs<I>(mut self, iter: I) -> crate::Result<Self>
70 where
71 I: IntoIterator,
72 I::Item: TryInto<Cpv>,
73 <I::Item as TryInto<Cpv>>::Error: fmt::Display,
74 {
75 self.extend(iter)?;
76 Ok(self)
77 }
78
79 pub(crate) fn from_config<S: AsRef<str>>(
80 id: S,
81 config: &RepoConfig,
82 ) -> crate::Result<Self> {
83 let id = id.as_ref();
84 let data = fs::read_to_string(&config.location).map_err(|e| Error::NotARepo {
85 kind: RepoFormat::Fake,
86 id: id.to_string(),
87 err: e.to_string(),
88 })?;
89 let mut repo = Self(Arc::new(InternalFakeRepo {
90 id: id.to_string(),
91 config: config.clone(),
92 pkgmap: Default::default(),
93 cpvs: Default::default(),
94 }));
95 repo.extend(data.lines())?;
96 Ok(repo)
97 }
98
99 pub fn from_path<P: AsRef<Utf8Path>, S: AsRef<str>>(
100 id: S,
101 priority: i32,
102 path: P,
103 ) -> crate::Result<Self> {
104 let id = id.as_ref();
105 let path = path.as_ref();
106 let data = fs::read_to_string(path).map_err(|e| Error::NotARepo {
107 kind: RepoFormat::Fake,
108 id: id.to_string(),
109 err: e.to_string(),
110 })?;
111 let config = RepoConfig {
112 location: Utf8PathBuf::from(path),
113 priority: Some(priority),
114 ..RepoFormat::Fake.into()
115 };
116 let mut repo = Self(Arc::new(InternalFakeRepo {
117 id: id.to_string(),
118 config,
119 pkgmap: Default::default(),
120 cpvs: Default::default(),
121 }));
122 repo.extend(data.lines())?;
123 Ok(repo)
124 }
125
126 pub fn extend<I>(&mut self, iter: I) -> crate::Result<()>
127 where
128 I: IntoIterator,
129 I::Item: TryInto<Cpv>,
130 <I::Item as TryInto<Cpv>>::Error: fmt::Display,
131 {
132 let mut repo = (*self.0).clone();
133 let orig_len = repo.cpvs.len();
134 for s in iter {
135 match s.try_into() {
136 Ok(cpv) => {
137 repo.cpvs.insert(cpv);
138 }
139 Err(e) => error!("{e}"),
140 }
141 }
142
143 if orig_len != repo.cpvs.len() {
144 repo.cpvs.sort_unstable();
145
146 let mut pkgmap = PkgMap::new();
148 for cpv in &repo.cpvs {
149 pkgmap
150 .entry(cpv.category().into())
151 .or_default()
152 .entry(cpv.package().into())
153 .or_default()
154 .insert(cpv.version().clone());
155 }
156 repo.pkgmap = pkgmap;
157 }
158
159 self.0 = Arc::new(repo);
160 Ok(())
161 }
162
163 pub fn get_pkg<T>(&self, value: T) -> crate::Result<Pkg>
165 where
166 T: TryInto<Cpv>,
167 Error: From<T::Error>,
168 {
169 let cpv = value.try_into()?;
170 if self.contains(&cpv) {
171 Ok(Pkg::new(cpv, self.clone()))
172 } else {
173 Err(Error::InvalidValue(format!("not in repo: {cpv}")))
174 }
175 }
176}
177
178impl fmt::Display for FakeRepo {
179 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180 write!(f, "{}", self.0.id)
181 }
182}
183
184impl PkgRepository for FakeRepo {
185 type Pkg = Pkg;
186 type IterCpn = IterCpn;
187 type IterCpnRestrict = IterCpnRestrict;
188 type IterCpv = IterCpv;
189 type IterCpvRestrict = IterCpvRestrict;
190 type Iter = Iter;
191 type IterRestrict = IterRestrict;
192
193 fn categories(&self) -> IndexSet<String> {
195 self.0.pkgmap.keys().cloned().collect()
196 }
197
198 fn packages(&self, cat: &str) -> IndexSet<String> {
199 self.0
200 .pkgmap
201 .get(cat)
202 .map(|pkgs| pkgs.keys().cloned().collect())
203 .unwrap_or_default()
204 }
205
206 fn versions(&self, cat: &str, pkg: &str) -> IndexSet<Version> {
207 self.0
208 .pkgmap
209 .get(cat)
210 .and_then(|pkgs| pkgs.get(pkg))
211 .cloned()
212 .unwrap_or_default()
213 }
214
215 fn len(&self) -> usize {
216 self.0.cpvs.len()
217 }
218
219 fn is_empty(&self) -> bool {
220 self.0.cpvs.is_empty()
221 }
222
223 fn iter_cpn(&self) -> Self::IterCpn {
224 IterCpn {
225 iter: self
226 .0
227 .cpvs
228 .iter()
229 .map(|x| x.cpn())
230 .cloned()
231 .collect::<IndexSet<_>>()
232 .into_iter(),
233 }
234 }
235
236 fn iter_cpn_restrict<R: Into<Restrict>>(&self, value: R) -> Self::IterCpnRestrict {
237 IterCpnRestrict {
238 iter: self.iter_cpn(),
239 restrict: value.into(),
240 }
241 }
242
243 fn iter_cpv(&self) -> Self::IterCpv {
244 IterCpv {
245 iter: self.0.cpvs.clone().into_iter(),
246 }
247 }
248
249 fn iter_cpv_restrict<R: Into<Restrict>>(&self, value: R) -> Self::IterCpvRestrict {
250 IterCpvRestrict {
251 iter: self.iter_cpv(),
252 restrict: value.into(),
253 }
254 }
255
256 fn iter(&self) -> Self::Iter {
257 self.into_iter()
258 }
259
260 fn iter_restrict<R: Into<Restrict>>(&self, val: R) -> Self::IterRestrict {
261 IterRestrict {
262 iter: self.into_iter(),
263 restrict: val.into(),
264 }
265 }
266}
267
268impl Contains<&Cpn> for FakeRepo {
269 fn contains(&self, cpn: &Cpn) -> bool {
270 self.iter_restrict(cpn).next().is_some()
271 }
272}
273
274impl Contains<&Cpv> for FakeRepo {
275 fn contains(&self, cpv: &Cpv) -> bool {
276 self.0.cpvs.contains(cpv)
277 }
278}
279
280impl Contains<&Dep> for FakeRepo {
281 fn contains(&self, dep: &Dep) -> bool {
282 self.iter_restrict(dep).next().is_some()
283 }
284}
285
286impl Repository for FakeRepo {
287 fn config(&self) -> &RepoConfig {
288 &self.0.config
289 }
290
291 fn id(&self) -> &str {
292 &self.0.id
293 }
294}
295
296impl IntoIterator for &FakeRepo {
297 type Item = crate::Result<Pkg>;
298 type IntoIter = Iter;
299
300 fn into_iter(self) -> Self::IntoIter {
301 Iter {
302 iter: self.0.cpvs.clone().into_iter(),
303 repo: self.clone(),
304 }
305 }
306}
307
308#[derive(Debug)]
309pub struct IterCpn {
310 iter: indexmap::set::IntoIter<Cpn>,
311}
312
313impl Iterator for IterCpn {
314 type Item = Cpn;
315
316 fn next(&mut self) -> Option<Self::Item> {
317 self.iter.next()
318 }
319}
320
321#[derive(Debug)]
322pub struct IterCpnRestrict {
323 iter: IterCpn,
324 restrict: Restrict,
325}
326
327impl Iterator for IterCpnRestrict {
328 type Item = Cpn;
329
330 fn next(&mut self) -> Option<Self::Item> {
331 self.iter.find(|cpn| self.restrict.matches(cpn))
332 }
333}
334
335#[derive(Debug)]
336pub struct IterCpv {
337 iter: indexmap::set::IntoIter<Cpv>,
338}
339
340impl Iterator for IterCpv {
341 type Item = Cpv;
342
343 fn next(&mut self) -> Option<Self::Item> {
344 self.iter.next()
345 }
346}
347
348#[derive(Debug)]
349pub struct IterCpvRestrict {
350 iter: IterCpv,
351 restrict: Restrict,
352}
353
354impl Iterator for IterCpvRestrict {
355 type Item = Cpv;
356
357 fn next(&mut self) -> Option<Self::Item> {
358 self.iter.find(|cpv| self.restrict.matches(cpv))
359 }
360}
361
362#[derive(Debug)]
363pub struct Iter {
364 iter: indexmap::set::IntoIter<Cpv>,
365 repo: FakeRepo,
366}
367
368impl Iterator for Iter {
369 type Item = crate::Result<Pkg>;
370
371 fn next(&mut self) -> Option<Self::Item> {
372 self.iter
373 .next()
374 .map(|cpv| Ok(Pkg::new(cpv, self.repo.clone())))
375 }
376}
377
378#[derive(Debug)]
379pub struct IterRestrict {
380 iter: Iter,
381 restrict: Restrict,
382}
383
384impl Iterator for IterRestrict {
385 type Item = crate::Result<Pkg>;
386
387 fn next(&mut self) -> Option<Self::Item> {
388 self.iter.find_map(|r| match r {
389 Ok(pkg) if self.restrict.matches(&pkg) => Some(Ok(pkg)),
390 Ok(_) => None,
391 Err(e) => unreachable!("invalid fake pkg: {e}"),
392 })
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use std::fs::File;
399 use std::io::Write;
400
401 use itertools::Itertools;
402 use tempfile::tempdir;
403 use tracing_test::traced_test;
404
405 use crate::pkg::Package;
406 use crate::test::*;
407
408 use super::*;
409
410 #[test]
411 fn from_path() {
412 let dir = tempdir().unwrap();
413 let path = Utf8Path::from_path(dir.path()).unwrap();
414
415 assert!(FakeRepo::from_path("test", 0, path).is_err());
417
418 let repo_path = path.join("fake");
420 let mut f = File::create(&repo_path).unwrap();
421 let repo = FakeRepo::from_path("test", 0, &repo_path).unwrap();
422 assert!(repo.is_empty());
423
424 writeln!(&mut f, "cat/pkg-1").unwrap();
426 let repo = FakeRepo::from_path("test", 0, &repo_path).unwrap();
427 assert_ordered_eq!(repo.iter_cpv().map(|x| x.to_string()), ["cat/pkg-1"]);
428 }
429
430 #[test]
431 fn repository_trait() {
432 let repo = FakeRepo::new("fake", 0);
433 assert_eq!(repo.format(), RepoFormat::Fake);
434 assert_eq!(repo.id(), "fake");
435 assert_eq!(repo.priority(), 0);
436 assert_eq!(repo.path(), "");
437 assert!(repo.sync().is_ok());
438 }
439
440 #[test]
441 fn categories() {
442 let mut repo = FakeRepo::new("fake", 0);
443 assert!(repo.categories().is_empty());
445 repo.extend(["cat1/pkg-a-1", "cat1/pkg-a-2", "cat2/pkg-b-3"])
447 .unwrap();
448 assert_ordered_eq!(repo.categories(), ["cat1", "cat2"])
449 }
450
451 #[test]
452 fn packages() {
453 let mut repo: FakeRepo;
454 repo = FakeRepo::new("fake", 0);
456 assert!(repo.packages("cat").is_empty());
457 repo.extend(["cat1/pkg-a-1", "cat1/pkg-a-2", "cat2/pkg-b-3"])
459 .unwrap();
460 assert!(repo.packages("cat").is_empty());
461 assert_ordered_eq!(repo.packages("cat1"), ["pkg-a"]);
462 assert_ordered_eq!(repo.packages("cat2"), ["pkg-b"]);
463 }
464
465 #[test]
466 fn versions() {
467 let ver = |s: &str| Version::try_new(s).unwrap();
468 let mut repo: FakeRepo;
469 repo = FakeRepo::new("fake", 0);
471 assert!(repo.versions("cat", "pkg").is_empty());
472 repo.extend(["cat1/pkg-a-1", "cat1/pkg-a-2", "cat2/pkg-b-3"])
474 .unwrap();
475 assert!(repo.versions("cat", "pkg").is_empty());
476 assert_ordered_eq!(repo.versions("cat1", "pkg-a"), [ver("1"), ver("2")]);
477 assert_ordered_eq!(repo.versions("cat2", "pkg-b"), [ver("3")]);
478 }
479
480 #[test]
481 fn len() {
482 let mut repo = FakeRepo::new("fake", 0);
483 assert_eq!(repo.len(), 0);
484 repo.extend(["cat/pkg-0"]).unwrap();
485 assert_eq!(repo.len(), 1);
486 repo.extend(["cat/pkg-0", "cat1/pkg1-1", "cat2/pkg2-2"])
487 .unwrap();
488 assert_eq!(repo.len(), 3);
489 }
490
491 #[traced_test]
492 #[test]
493 fn extend() {
494 let mut repo = FakeRepo::new("fake", 0).pkgs(["cat/pkg-2"]).unwrap();
495 let pkgs: Vec<_> = repo.iter().try_collect().unwrap();
496 assert_ordered_eq!(pkgs.iter().map(|x| x.cpv().to_string()), ["cat/pkg-2"]);
497
498 repo.extend(["cat/pkg-0"]).unwrap();
500 let pkgs: Vec<_> = repo.iter().try_collect().unwrap();
501 assert_ordered_eq!(
502 pkgs.iter().map(|x| x.cpv().to_string()),
503 ["cat/pkg-0", "cat/pkg-2"]
504 );
505
506 repo.extend(["cat/pkg-3", "cat/pkg", "cat/pkg-1", "a/b-0"])
508 .unwrap();
509 let pkgs: Vec<_> = repo.iter().try_collect().unwrap();
510 assert_ordered_eq!(
511 pkgs.iter().map(|x| x.cpv().to_string()),
512 ["a/b-0", "cat/pkg-0", "cat/pkg-1", "cat/pkg-2", "cat/pkg-3"]
513 );
514 assert_logs_re!("invalid cpv: cat/pkg");
515
516 repo.extend(["cat/pkg-3", "cat/pkg-1", "a/b-0"]).unwrap();
518 let pkgs: Vec<_> = repo.iter().try_collect().unwrap();
519 assert_ordered_eq!(
520 pkgs.iter().map(|x| x.cpv().to_string()),
521 ["a/b-0", "cat/pkg-0", "cat/pkg-1", "cat/pkg-2", "cat/pkg-3"]
522 );
523 }
524
525 #[test]
526 fn contains() {
527 let repo = FakeRepo::new("fake", 0).pkgs(["cat/pkg-1"]).unwrap();
528
529 assert!(!repo.contains("cat/pkg"));
531
532 let cpn = Cpn::try_new("cat/pkg").unwrap();
534 assert!(repo.contains(&cpn));
535 let cpn = Cpn::try_new("a/pkg").unwrap();
536 assert!(!repo.contains(&cpn));
537
538 let cpv = Cpv::try_new("cat/pkg-1").unwrap();
540 assert!(repo.contains(&cpv));
541 let cpv = Cpv::try_new("cat/pkg-2").unwrap();
542 assert!(!repo.contains(&cpv));
543
544 let dep = Dep::try_new("cat/pkg::fake").unwrap();
546 assert!(repo.contains(&dep));
547 let dep = Dep::try_new("cat/pkg::repo").unwrap();
548 assert!(!repo.contains(&dep));
549 let dep = Dep::try_new("=cat/pkg-1").unwrap();
550 assert!(repo.contains(&dep));
551 let dep = Dep::try_new(">cat/pkg-1").unwrap();
552 assert!(!repo.contains(&dep));
553
554 assert!(repo.contains(&Restrict::True));
556 assert!(!repo.contains(&Restrict::False));
557 let restrict = Restrict::from(Cpn::try_new("cat/pkg").unwrap());
558 assert!(repo.contains(&restrict));
559 let restrict = Restrict::from(Cpv::try_new("cat/pkg-1").unwrap());
560 assert!(repo.contains(&restrict));
561 }
562
563 #[test]
564 fn iter() {
565 let repo = FakeRepo::new("fake", 0)
566 .pkgs(["cat/pkg-0", "acat/bpkg-1"])
567 .unwrap();
568 let pkgs: Vec<_> = repo.iter().try_collect().unwrap();
569 assert_ordered_eq!(
570 pkgs.iter().map(|x| x.cpv().to_string()),
571 ["acat/bpkg-1", "cat/pkg-0"]
572 );
573 }
574
575 #[test]
576 fn iter_cpn() {
577 let cpvs: Vec<_> = (0..100)
578 .map(|x| Cpv::try_new(format!("cat/pkg-{x}")).unwrap())
579 .collect();
580 let repo = FakeRepo::new("fake", 0).pkgs(&cpvs).unwrap();
581 let cpn = Cpn::try_new("cat/pkg").unwrap();
582 assert_ordered_eq!(repo.iter_cpn(), [cpn]);
583 }
584
585 #[test]
586 fn iter_cpn_restrict() {
587 let cpvs: Vec<_> = (0..100)
588 .map(|x| Cpv::try_new(format!("cat/pkg-{x}")).unwrap())
589 .collect();
590 let repo = FakeRepo::new("fake", 0).pkgs(&cpvs).unwrap();
591 let cpn = Cpn::try_new("a/b").unwrap();
592 assert!(repo.iter_cpn_restrict(&cpn).next().is_none());
593 let cpv = Cpv::try_new("cat/pkg-1").unwrap();
594 assert!(repo.iter_cpn_restrict(&cpv).next().is_none());
595 let cpn = Cpn::try_new("cat/pkg").unwrap();
596 assert_ordered_eq!(repo.iter_cpn_restrict(&cpn), [cpn]);
597 }
598
599 #[test]
600 fn iter_cpv() {
601 let cpvs: Vec<_> = (0..100)
602 .map(|x| Cpv::try_new(format!("cat/pkg-{x}")).unwrap())
603 .collect();
604 let repo = FakeRepo::new("fake", 0).pkgs(&cpvs).unwrap();
605 assert_ordered_eq!(repo.iter_cpv(), cpvs);
606 }
607
608 #[test]
609 fn iter_cpv_restrict() {
610 let cpvs: Vec<_> = (0..100)
611 .map(|x| Cpv::try_new(format!("cat/pkg-{x}")).unwrap())
612 .collect();
613 let repo = FakeRepo::new("fake", 0).pkgs(&cpvs).unwrap();
614 let cpv = Cpv::try_new("cat/pkg-1").unwrap();
615 assert_ordered_eq!(repo.iter_cpv_restrict(&cpv), [cpv]);
616 let cpn = Cpn::try_new("a/b").unwrap();
617 assert!(repo.iter_cpv_restrict(&cpn).next().is_none());
618 let cpn = Cpn::try_new("cat/pkg").unwrap();
619 assert_ordered_eq!(repo.iter_cpv_restrict(&cpn), cpvs);
620 }
621
622 #[test]
623 fn get_pkg() {
624 let repo = FakeRepo::new("fake", 0).pkgs(["cat/pkg-1"]).unwrap();
625 assert!(repo.get_pkg("cat/pkg-1").is_ok());
627 assert!(repo.get_pkg("cat/pkg-2").is_err());
629 assert!(repo.get_pkg("cat/pkg").is_err());
631 }
632}