summaryrefslogtreecommitdiffstats
path: root/src/projects.rs
blob: 8819628f4d8371c482d1cf279251350f6181b415 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use crate::{Config, Result};
use figment::Provider;
use ignore::Walk;
use std::{path::PathBuf, vec::IntoIter};
use tracing::{error, info};

pub use entry::SearchPath;

mod entry;

pub struct Projects {
    search_path_iter: IntoIter<SearchPath>,
    walk: Option<Walk>,
}

impl std::fmt::Debug for Projects {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Projects")
            .field("paths_iter", &self.search_path_iter)
            .finish_non_exhaustive()
    }
}

impl Projects {
    pub fn new() -> Result<Self> {
        Self::from_provider(Config::figment())
    }

    /// Extract `Config` from `provider` to construct new `Paths`
    pub fn from_provider<T: Provider>(provider: T) -> Result<Self> {
        Config::extract(&provider)
            .map_err(Into::into)
            .map(Into::into)
    }
}

impl From<Vec<SearchPath>> for Projects {
    fn from(value: Vec<SearchPath>) -> Self {
        Self {
            search_path_iter: value.into_iter(),
            walk: None,
        }
    }
}

impl From<Config> for Projects {
    fn from(value: Config) -> Self {
        value.paths.into()
    }
}

impl Iterator for Projects {
    type Item = PathBuf;

    #[tracing::instrument]
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            match self.walk.as_mut().and_then(|iter| iter.next()) {
                Some(Ok(path)) if SearchPath::filter(&path) => return Some(path.into_path()),
                Some(Ok(path)) => info!(?path, "Ignoring filtered path"),
                Some(Err(err)) => error!(%err, "Ignoring errored path"),
                None => {
                    self.walk = Some(self.search_path_iter.next()?.into());
                }
            };
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;
    use std::fs;

    #[test]
    fn test_iteration() {
        let temp_dir = tempfile::Builder::new()
            .tempdir()
            .unwrap()
            .path()
            .to_owned();

        let project_dir = temp_dir.join("project_dir");
        let project1 = temp_dir.join("project_dir/project1");
        let project2 = temp_dir.join("project_dir/project2");
        let project3 = temp_dir.join("project3");
        let project4 = temp_dir.join("subdir/project4");

        let paths = Projects::from(Vec::from([
            SearchPath {
                path: project_dir.to_owned(),
                hidden: false,
                max_depth: Some(1),
            },
            SearchPath {
                path: project3.to_owned(),
                hidden: false,
                max_depth: Some(0),
            },
            SearchPath {
                path: project4.to_owned(),
                hidden: false,
                max_depth: Some(0),
            },
        ]));

        let mut path_bufs = Vec::from([project_dir, project1, project2, project3, project4]);
        path_bufs.iter().try_for_each(fs::create_dir_all).unwrap();
        path_bufs.sort();

        let mut results = paths.into_iter().collect::<Vec<PathBuf>>();
        results.sort();

        assert_eq!(path_bufs, results);
    }
}