Skip to main content

chaste_types/
chastefile.rs

1// SPDX-FileCopyrightText: 2024 The Chaste Authors
2// SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause
3
4use std::collections::{HashMap, HashSet, VecDeque};
5
6use crate::dependency::Dependency;
7use crate::error::{Error, Result};
8use crate::installation::Installation;
9use crate::package::{Package, PackageID};
10
11#[derive(Debug, Clone)]
12pub struct Chastefile {
13    packages: HashMap<PackageID, Package>,
14    installations: Vec<Installation>,
15    dependencies: Vec<Dependency>,
16    root_package_id: PackageID,
17    workspace_members: Vec<PackageID>,
18}
19
20impl<'a> Chastefile {
21    pub fn package(&'a self, package_id: PackageID) -> &'a Package {
22        self.packages.get(&package_id).unwrap()
23    }
24
25    pub fn packages(&'a self) -> Vec<&'a Package> {
26        self.packages.values().collect()
27    }
28
29    pub fn packages_with_ids(&'a self) -> Vec<(PackageID, &'a Package)> {
30        self.packages.iter().map(|(pid, pkg)| (*pid, pkg)).collect()
31    }
32
33    fn package_dependencies_iter(
34        &'a self,
35        package_id: PackageID,
36    ) -> impl Iterator<Item = &'a Dependency> {
37        self.dependencies
38            .iter()
39            .filter(move |d| d.from == package_id)
40    }
41
42    fn package_prod_dependencies_iter(
43        &'a self,
44        package_id: PackageID,
45    ) -> impl Iterator<Item = &'a Dependency> {
46        self.dependencies
47            .iter()
48            .filter(move |d| d.kind.is_prod() && d.from == package_id)
49    }
50
51    /// Direct dependencies of any kind from specified package
52    pub fn package_dependencies(&'a self, package_id: PackageID) -> Vec<&'a Dependency> {
53        self.package_dependencies_iter(package_id).collect()
54    }
55
56    /// Direct dependencies of any kind other than [`crate::DependencyKind::DevDependency`] from specified package
57    pub fn package_prod_dependencies(&'a self, package_id: PackageID) -> Vec<&'a Dependency> {
58        self.package_prod_dependencies_iter(package_id).collect()
59    }
60
61    /// Dependencies, direct and transitive, of any kind from specified package
62    pub fn recursive_package_dependencies(&'a self, package_id: PackageID) -> Vec<&'a Dependency> {
63        let mut result = self.package_dependencies(package_id);
64        let mut seen = HashSet::with_capacity(result.len());
65        let mut q = VecDeque::with_capacity(result.len());
66        result.iter().for_each(|d| {
67            seen.insert(d.on);
68            q.push_back(d.on);
69        });
70        while let Some(pid) = q.pop_front() {
71            for dep in self.package_dependencies_iter(pid) {
72                if seen.insert(dep.on) {
73                    q.push_back(dep.on);
74                    result.push(dep);
75                }
76            }
77        }
78        result
79    }
80
81    /// Dependencies, direct and transitive, of any kind other than [`crate::DependencyKind::DevDependency`]
82    /// from specified package
83    pub fn recursive_prod_package_dependencies(
84        &'a self,
85        package_id: PackageID,
86    ) -> Vec<&'a Dependency> {
87        let mut result = self.package_prod_dependencies(package_id);
88        let mut seen = HashSet::with_capacity(result.len());
89        let mut q = VecDeque::with_capacity(result.len());
90        result.iter().for_each(|d| {
91            seen.insert(d.on);
92            q.push_back(d.on);
93        });
94        while let Some(pid) = q.pop_front() {
95            for dep in self.package_prod_dependencies_iter(pid) {
96                if seen.insert(dep.on) {
97                    q.push_back(dep.on);
98                    result.push(dep);
99                }
100            }
101        }
102        result
103    }
104
105    fn package_dependents_iter(
106        &'a self,
107        package_id: PackageID,
108    ) -> impl Iterator<Item = &'a Dependency> {
109        self.dependencies.iter().filter(move |d| d.on == package_id)
110    }
111
112    /// Direct dependencies of any kind *on* the specified package (reverse dependencies)
113    pub fn package_dependents(&'a self, package_id: PackageID) -> Vec<&'a Dependency> {
114        self.package_dependents_iter(package_id).collect()
115    }
116
117    pub fn root_package_id(&'a self) -> PackageID {
118        self.root_package_id
119    }
120
121    pub fn root_package(&'a self) -> &'a Package {
122        self.packages.get(&self.root_package_id).unwrap()
123    }
124
125    pub fn root_package_dependencies(&'a self) -> Vec<&'a Dependency> {
126        self.package_dependencies(self.root_package_id)
127    }
128
129    pub fn root_package_prod_dependencies(&'a self) -> Vec<&'a Dependency> {
130        self.package_prod_dependencies(self.root_package_id)
131    }
132
133    pub fn workspace_member_ids(&'a self) -> &'a [PackageID] {
134        &self.workspace_members
135    }
136
137    pub fn workspace_members(&'a self) -> Vec<&'a Package> {
138        self.workspace_members
139            .iter()
140            .map(|pid| self.package(*pid))
141            .collect()
142    }
143
144    pub fn package_installations(&'a self, package_id: PackageID) -> Vec<&'a Installation> {
145        self.installations
146            .iter()
147            .filter(|i| i.package_id() == package_id)
148            .collect()
149    }
150}
151
152#[derive(Debug)]
153pub struct ChastefileBuilder {
154    packages: HashMap<PackageID, Package>,
155    dependencies: Vec<Dependency>,
156    installations: Vec<Installation>,
157    next_pid: u64,
158    root_package_id: Option<PackageID>,
159    workspace_members: Vec<PackageID>,
160}
161
162impl ChastefileBuilder {
163    #[allow(clippy::new_without_default)]
164    pub fn new() -> Self {
165        Self {
166            packages: HashMap::new(),
167            dependencies: Vec::new(),
168            installations: Vec::new(),
169            next_pid: 0,
170            root_package_id: None,
171            workspace_members: Vec::new(),
172        }
173    }
174
175    fn new_pid(&mut self) -> PackageID {
176        let pid = PackageID(self.next_pid);
177        self.next_pid += 1;
178        pid
179    }
180
181    pub fn add_package(&mut self, package: Package) -> Result<PackageID> {
182        if let Some((original_pid, _)) = self.packages.iter().find(|(_, p)| *p == &package) {
183            return Err(Error::DuplicatePackage(*original_pid));
184        }
185        let pid = self.new_pid();
186        self.packages.insert(pid, package);
187        Ok(pid)
188    }
189
190    pub fn add_package_installation(&mut self, installation: Installation) {
191        self.installations.push(installation);
192    }
193
194    pub fn add_dependency(&mut self, dependency: Dependency) {
195        self.dependencies.push(dependency);
196    }
197
198    pub fn add_dependencies(&mut self, dependencies: impl Iterator<Item = Dependency>) {
199        self.dependencies.extend(dependencies);
200    }
201
202    pub fn set_root_package_id(&mut self, root_pid: PackageID) -> Result<()> {
203        self.root_package_id = Some(root_pid);
204        Ok(())
205    }
206
207    pub fn set_as_workspace_member(&mut self, member_pid: PackageID) -> Result<()> {
208        self.workspace_members.push(member_pid);
209        Ok(())
210    }
211
212    pub fn build(self) -> Result<Chastefile> {
213        Ok(Chastefile {
214            packages: self.packages,
215            dependencies: self.dependencies,
216            installations: self.installations,
217            root_package_id: self.root_package_id.ok_or(Error::MissingRootPackageID)?,
218            workspace_members: self.workspace_members,
219        })
220    }
221}