assemble_core/
file_collection.rs

1/// Defines types of file collections and the FileCollection trait
2use 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
29/// A file set is a collection of files. File collections are intended to be live.
30pub trait FileCollection {
31    /// Gets the files contained by this collection.
32    fn files(&self) -> HashSet<PathBuf>;
33    /// Gets the files contained by this collection. Is fallible.
34    fn try_files(&self) -> BuildResult<HashSet<PathBuf>> {
35        Ok(self.files())
36    }
37    /// Gets whether this file collection is empty or not
38    fn is_empty(&self) -> bool {
39        self.files().is_empty()
40    }
41    /// Get this file collection as a path
42    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);
60// impl<P : AsRef<Path>> FileCollection for HashSet<P> {
61//     fn files(&self) -> HashSet<PathBuf> {
62//         self.iter()
63//             .map(|p| p.as_ref().to_path_buf())
64//             .collect()
65//     }
66// }
67//
68// impl<P : AsRef<Path>> FileCollection for Vec<P> {
69//     fn files(&self) -> HashSet<PathBuf> {
70//         self.iter()
71//             .map(|p| p.as_ref().to_path_buf())
72//             .collect()
73//     }
74// }
75
76impl 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    /// Adds a filter to a fileset
172    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
358// impl Debug for dyn Provider<Component> {
359//     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
360//         write!(f, "Component Provider")
361//     }
362// }
363
364impl<'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
373/// An iterator over file components.
374pub 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}