Skip to main content

ploys/project/
packages.rs

1use std::borrow::Cow;
2use std::iter::FusedIterator;
3use std::path::Path;
4
5use relative_path::RelativePath;
6use strum::IntoEnumIterator;
7
8use crate::package::manifest::Members;
9use crate::package::{Manifest, Package, PackageKind};
10use crate::repository::Repository;
11
12use super::Project;
13
14/// An iterator over packages in a project.
15pub struct Packages<'a, T> {
16    kinds: <PackageKind as IntoEnumIterator>::Iterator,
17    state: State<'a, T>,
18}
19
20impl<'a, T> Packages<'a, T> {
21    /// Constructs a new packages iterator.
22    pub(super) fn new(project: &'a Project<T>) -> Self {
23        Self {
24            kinds: PackageKind::iter(),
25            state: State::Initial { project },
26        }
27    }
28}
29
30impl<'a, T> Iterator for Packages<'a, T>
31where
32    T: Repository,
33{
34    type Item = Package<&'a T>;
35
36    fn next(&mut self) -> Option<Self::Item> {
37        loop {
38            match &mut self.state {
39                State::Initial { project } => {
40                    let kind = self.kinds.next()?;
41                    let manifest = project
42                        .repository
43                        .get_file(kind.file_name())
44                        .ok()
45                        .flatten()
46                        .and_then(|bytes| Manifest::from_bytes(kind, &bytes).ok());
47
48                    if let Some(manifest) = manifest
49                        && let Ok(members) = manifest.members()
50                        && let Ok(files) = project.repository.get_index()
51                    {
52                        self.state = State::Manifest {
53                            packages: ManifestPackages {
54                                project,
55                                manifest,
56                                members,
57                                files: Box::new(files),
58                            },
59                        };
60                    }
61                }
62                State::Manifest { packages } => match packages.next() {
63                    Some(package) => break Some(package),
64                    None => {
65                        let kind = self.kinds.next()?;
66                        let manifest = packages
67                            .project
68                            .repository
69                            .get_file(kind.file_name())
70                            .ok()
71                            .flatten()
72                            .and_then(|bytes| Manifest::from_bytes(kind, &bytes).ok());
73
74                        if let Some(manifest) = manifest
75                            && let Ok(members) = manifest.members()
76                            && let Ok(files) = packages.project.repository.get_index()
77                        {
78                            packages.manifest = manifest;
79                            packages.members = members;
80                            packages.files = Box::new(files);
81                        }
82                    }
83                },
84            }
85        }
86    }
87}
88
89impl<T> FusedIterator for Packages<'_, T> where T: Repository {}
90
91#[allow(clippy::large_enum_variant)]
92enum State<'a, T> {
93    Initial { project: &'a Project<T> },
94    Manifest { packages: ManifestPackages<'a, T> },
95}
96
97/// An iterator over packages in a package manifest.
98struct ManifestPackages<'a, T> {
99    project: &'a Project<T>,
100    manifest: Manifest,
101    members: Members,
102    files: Box<dyn Iterator<Item = Cow<'a, RelativePath>> + 'a>,
103}
104
105impl<'a, T> Iterator for ManifestPackages<'a, T>
106where
107    T: Repository,
108{
109    type Item = Package<&'a T>;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        loop {
113            let path = self.files.next()?;
114            let manifest_path = self.manifest.package_kind().file_name();
115
116            if path.file_name() != Some(manifest_path) {
117                continue;
118            }
119
120            let Some(parent) = path.parent() else {
121                continue;
122            };
123
124            if path.as_ref() != manifest_path && !self.members.includes(Path::new(parent.as_str()))
125            {
126                continue;
127            }
128
129            let manifest = self
130                .project
131                .repository
132                .get_file(&path)
133                .ok()
134                .flatten()
135                .and_then(|bytes| Manifest::from_bytes(self.manifest.package_kind(), &bytes).ok());
136
137            let Some(manifest) = manifest else {
138                continue;
139            };
140
141            let Some(package) = Package::from_manifest(self.project, parent, manifest) else {
142                continue;
143            };
144
145            break Some(package);
146        }
147    }
148}
149
150impl<T> FusedIterator for ManifestPackages<'_, T> where T: Repository {}