assemble_core/
file_collection.rs1use std::collections::{HashSet, LinkedList, VecDeque};
3
4use std::env::JoinPathsError;
5use std::ffi::OsString;
6use std::fmt::{Debug, Formatter};
7
8use std::iter::FusedIterator;
9use std::ops::{Add, AddAssign};
10use std::path::{Path, PathBuf};
11use std::str::FromStr;
12use std::sync::Arc;
13
14use itertools::Itertools;
15use walkdir::WalkDir;
16
17use crate::exception::BuildException;
18
19use crate::identifier::TaskId;
20use crate::lazy_evaluation::ProviderExt;
21use crate::lazy_evaluation::{IntoProvider, Prop, Provider};
22use crate::project::buildable::{Buildable, BuiltByContainer, IntoBuildable};
23
24use crate::project::ProjectResult;
25use crate::utilities::{AndSpec, Spec, True};
26use crate::{BuildResult, Project};
27use crate::error::PayloadError;
28
29pub trait FileCollection {
31 fn files(&self) -> HashSet<PathBuf>;
33 fn try_files(&self) -> BuildResult<HashSet<PathBuf>> {
35 Ok(self.files())
36 }
37 fn is_empty(&self) -> bool {
39 self.files().is_empty()
40 }
41 fn path(&self) -> Result<OsString, JoinPathsError> {
43 std::env::join_paths(self.files())
44 }
45}
46
47macro_rules! implement_file_collection {
48 ($ty:tt) => {
49 impl<P: AsRef<Path>> FileCollection for $ty<P> {
50 fn files(&self) -> HashSet<PathBuf> {
51 self.iter().map(|p| p.as_ref().to_path_buf()).collect()
52 }
53 }
54 };
55}
56implement_file_collection!(HashSet);
57implement_file_collection!(Vec);
58implement_file_collection!(VecDeque);
59implement_file_collection!(LinkedList);
60impl FileCollection for PathBuf {
77 fn files(&self) -> HashSet<PathBuf> {
78 HashSet::from_iter([self.clone()])
79 }
80}
81
82#[derive(Clone)]
83pub struct FileSet {
84 filter: Arc<dyn FileFilter>,
85 built_by: BuiltByContainer,
86 components: Vec<Component>,
87}
88
89impl Debug for FileSet {
90 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91 if f.alternate() {
92 let files = self.files();
93 f.debug_set().entries(files).finish()
94 } else {
95 f.debug_struct("FileSet")
96 .field("components", &self.components)
97 .finish_non_exhaustive()
98 }
99 }
100}
101
102impl FileSet {
103 pub fn new() -> Self {
104 Self {
105 filter: Arc::new(True::new()),
106 built_by: BuiltByContainer::default(),
107 components: vec![],
108 }
109 }
110
111 pub fn with_path(path: impl AsRef<Path>) -> Self {
112 Self {
113 filter: Arc::new(True::new()),
114 built_by: BuiltByContainer::default(),
115 components: vec![Component::Path(path.as_ref().to_path_buf())],
116 }
117 }
118
119 pub fn with_path_providers<I, P>(providers: I) -> Self
120 where
121 I: IntoIterator<Item = P>,
122 P: IntoProvider<PathBuf>,
123 <P as IntoProvider<PathBuf>>::Provider: 'static,
124 {
125 let mut output = Self::new();
126 for provider in providers {
127 output += FileSet::with_provider(provider);
128 }
129 output
130 }
131
132 pub fn with_provider<F: FileCollection, P: IntoProvider<F>>(fc_provider: P) -> Self
133 where
134 F: Send + Sync + Clone + 'static,
135 P::Provider: 'static,
136 {
137 let mut prop: Prop<FileSet> = Prop::default();
138 let provider = fc_provider.into_provider();
139 prop.set_with(provider.map(|f: F| FileSet::from_iter(f.files())))
140 .unwrap();
141 let component = Component::Provider(prop);
142 Self {
143 filter: Arc::new(True::new()),
144 built_by: BuiltByContainer::default(),
145 components: vec![component],
146 }
147 }
148
149 pub fn built_by<B: IntoBuildable>(&mut self, b: B)
150 where
151 <B as IntoBuildable>::Buildable: 'static,
152 {
153 self.built_by.add(b);
154 }
155
156 pub fn join(self, other: Self) -> Self {
157 Self {
158 components: vec![Component::Collection(self), Component::Collection(other)],
159 ..Default::default()
160 }
161 }
162
163 pub fn insert<T: Into<FileSet>>(&mut self, fileset: T) {
164 *self += fileset;
165 }
166
167 pub fn iter(&self) -> FileIterator {
168 self.into_iter()
169 }
170
171 pub fn filter<F: FileFilter + 'static>(self, filter: F) -> Self {
173 let mut files = self;
174 let prev = std::mem::replace(&mut files.filter, Arc::new(True::new()));
175 let and = AndSpec::new(prev, filter);
176 files.filter = Arc::new(and);
177 files
178 }
179}
180
181impl Default for FileSet {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187impl<'f> IntoIterator for &'f FileSet {
188 type Item = PathBuf;
189 type IntoIter = FileIterator<'f>;
190
191 fn into_iter(self) -> Self::IntoIter {
192 FileIterator {
193 components: &self.components[..],
194 filters: &*self.filter,
195 index: 0,
196 current_iterator: None,
197 }
198 }
199}
200
201impl<'f> IntoIterator for FileSet {
202 type Item = PathBuf;
203 type IntoIter = std::vec::IntoIter<PathBuf>;
204
205 fn into_iter(self) -> Self::IntoIter {
206 self.iter().collect::<Vec<_>>().into_iter()
207 }
208}
209
210impl FileCollection for FileSet {
211 fn files(&self) -> HashSet<PathBuf> {
212 self.iter().collect()
213 }
214
215 fn try_files(&self) -> BuildResult<HashSet<PathBuf>> {
216 Ok(self
217 .components
218 .iter()
219 .map(|c| c.try_files())
220 .collect::<Result<Vec<HashSet<_>>, _>>()?
221 .into_iter()
222 .flatten()
223 .filter(|p| self.filter.accept(p))
224 .collect())
225 }
226}
227
228impl<F: Into<FileSet>> Add<F> for FileSet {
229 type Output = Self;
230
231 fn add(self, rhs: F) -> Self::Output {
232 self.join(rhs.into())
233 }
234}
235
236impl<F: Into<FileSet>> AddAssign<F> for FileSet {
237 fn add_assign(&mut self, rhs: F) {
238 let old = std::mem::take(self);
239 *self = old.join(rhs.into())
240 }
241}
242
243impl<P: AsRef<Path>> From<P> for FileSet {
244 fn from(path: P) -> Self {
245 Self::with_path(path)
246 }
247}
248
249impl Buildable for FileSet {
250 fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
251 self.built_by.get_dependencies(project)
252 }
253}
254
255impl<P: AsRef<Path>> FromIterator<P> for FileSet {
256 fn from_iter<T: IntoIterator<Item = P>>(iter: T) -> Self {
257 iter.into_iter()
258 .map(|p: P| FileSet::with_path(p))
259 .reduce(|accum, next| accum + next)
260 .unwrap_or_default()
261 }
262}
263
264impl Provider<FileSet> for FileSet {
265 fn try_get(&self) -> Option<FileSet> {
266 Some(self.clone())
267 }
268}
269
270#[derive(Clone)]
271pub enum Component {
272 Path(PathBuf),
273 Collection(FileSet),
274 Provider(Prop<FileSet>),
275}
276
277impl Debug for Component {
278 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
279 if f.alternate() {
280 match self {
281 Component::Path(p) => {
282 write!(f, "{:?}", p)
283 }
284 Component::Collection(c) => {
285 write!(f, "{:#?}", c)
286 }
287 Component::Provider(p) => {
288 write!(f, "{:#?}", p)
289 }
290 }
291 } else {
292 match self {
293 Component::Path(p) => f.debug_tuple("Path").field(p).finish(),
294 Component::Collection(c) => f.debug_tuple("Collection").field(c).finish(),
295 Component::Provider(p) => f.debug_tuple("Provider").field(p).finish(),
296 }
297 }
298 }
299}
300
301impl Component {
302 pub fn iter(&self) -> Box<dyn Iterator<Item = PathBuf> + '_> {
303 match self {
304 Component::Path(p) => {
305 if p.is_file() || !p.exists() {
306 Box::new(Some(p.clone()).into_iter())
307 } else {
308 Box::new(
309 WalkDir::new(p)
310 .into_iter()
311 .map_ok(|entry| entry.into_path())
312 .map(|res| res.unwrap()),
313 )
314 }
315 }
316 Component::Collection(c) => Box::new(c.iter()),
317 Component::Provider(pro) => {
318 let component = pro.get();
319 Box::new(component.into_iter())
320 }
321 }
322 }
323}
324
325impl FileCollection for Component {
326 fn files(&self) -> HashSet<PathBuf> {
327 self.iter().collect()
328 }
329
330 fn try_files(&self) -> BuildResult<HashSet<PathBuf>> {
331 Ok(match self {
332 Component::Path(p) => {
333 if p.is_file() || !p.exists() {
334 Box::new(Some(p.clone()).into_iter()) as Box<dyn Iterator<Item = PathBuf> + '_>
335 } else {
336 Box::new(
337 WalkDir::new(p)
338 .into_iter()
339 .map_ok(|entry| entry.into_path())
340 .map(|r| r.map_err(BuildException::new))
341 .collect::<Result<HashSet<PathBuf>, _>>()?
342 .into_iter(),
343 ) as Box<dyn Iterator<Item = PathBuf> + '_>
344 }
345 }
346 Component::Collection(c) => {
347 Box::new(c.iter()) as Box<dyn Iterator<Item = PathBuf> + '_>
348 }
349 Component::Provider(pro) => {
350 let component = pro.fallible_get().map_err(PayloadError::<BuildException>::new)?;
351 Box::new(component.into_iter()) as Box<dyn Iterator<Item = PathBuf> + '_>
352 }
353 }
354 .collect())
355 }
356}
357
358impl<'f> IntoIterator for &'f Component {
365 type Item = PathBuf;
366 type IntoIter = Box<dyn Iterator<Item = PathBuf> + 'f>;
367
368 fn into_iter(self) -> Self::IntoIter {
369 self.iter()
370 }
371}
372
373pub struct FileIterator<'files> {
375 components: &'files [Component],
376 filters: &'files dyn FileFilter,
377 index: usize,
378 current_iterator: Option<Box<dyn Iterator<Item = PathBuf> + 'files>>,
379}
380
381impl<'files> Debug for FileIterator<'files> {
382 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
383 f.debug_struct("FileIterator")
384 .field("components", &self.components)
385 .field("index", &self.index)
386 .finish_non_exhaustive()
387 }
388}
389
390impl<'files> FileIterator<'files> {
391 fn next_iterator(&mut self) -> Option<Box<dyn Iterator<Item = PathBuf> + 'files>> {
392 if self.index == self.components.len() {
393 return None;
394 }
395
396 let output = Some(self.components[self.index].iter());
397 self.index += 1;
398 output
399 }
400
401 fn get_next_path(&mut self) -> Option<PathBuf> {
402 if self.index == self.components.len() {
403 return None;
404 }
405 loop {
406 if self.current_iterator.is_none() {
407 self.current_iterator = self.next_iterator();
408 }
409
410 if let Some(iterator) = &mut self.current_iterator {
411 for path in iterator.by_ref() {
412 if self.filters.accept(&path) {
413 return Some(path);
414 }
415 }
416 self.current_iterator = None;
417 } else {
418 return None;
419 }
420 }
421 }
422}
423
424impl<'files> Iterator for FileIterator<'files> {
425 type Item = PathBuf;
426
427 fn next(&mut self) -> Option<Self::Item> {
428 self.get_next_path()
429 }
430}
431
432impl<'files> FusedIterator for FileIterator<'files> {}
433
434pub trait FileFilter: Spec<Path> + Send + Sync {}
435
436assert_obj_safe!(FileFilter);
437
438impl<F> FileFilter for F where F: Spec<Path> + Send + Sync + ?Sized {}
439
440impl Spec<Path> for glob::Pattern {
441 fn accept(&self, value: &Path) -> bool {
442 self.matches_path(value)
443 }
444}
445
446impl Spec<Path> for &str {
447 fn accept(&self, value: &Path) -> bool {
448 glob::Pattern::from_str(self).unwrap().accept(value)
449 }
450}