aboutsummaryrefslogtreecommitdiff
path: root/bin/src/dirs.rs
blob: 31c97f84146c0facd60e8454acfe59446fe2f22d (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
use std::{
    fs,
    io::{self, Error, ErrorKind},
    path::{Path, PathBuf},
};

use crate::dirs;

use ignore::{
    gitignore::{Gitignore, GitignoreBuilder},
    Error as IgnoreError, Match,
};

#[derive(Debug)]
pub struct Walker {
    dirs: Vec<PathBuf>,
    files: Vec<PathBuf>,
    ignore: Gitignore,
}

impl Walker {
    pub fn new<P: AsRef<Path>>(target: P, ignore: Gitignore) -> io::Result<Self> {
        let target = target.as_ref().to_path_buf();
        if !target.exists() {
            Err(Error::new(
                ErrorKind::NotFound,
                format!("file not found: {}", target.display()),
            ))
        } else if target.is_dir() {
            Ok(Self {
                dirs: vec![target],
                files: vec![],
                ignore,
            })
        } else {
            Ok(Self {
                dirs: vec![],
                files: vec![target],
                ignore,
            })
        }
    }
}

impl Iterator for Walker {
    type Item = PathBuf;
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(dir) = self.dirs.pop() {
            if dir.is_dir() {
                if let Match::None | Match::Whitelist(_) = self.ignore.matched(&dir, true) {
                    for entry in fs::read_dir(&dir).ok()? {
                        let entry = entry.ok()?;
                        let path = entry.path();
                        if path.is_dir() {
                            self.dirs.push(path);
                        } else if path.is_file() {
                            if let Match::None | Match::Whitelist(_) =
                                self.ignore.matched(&path, false)
                            {
                                self.files.push(path);
                            }
                        }
                    }
                }
            }
        }
        self.files.pop()
    }
}

pub fn build_ignore_set<P: AsRef<Path>>(
    ignore: &[String],
    target: P,
    unrestricted: bool,
) -> Result<Gitignore, IgnoreError> {
    let gitignore_path = target.as_ref().join(".gitignore");

    // Looks like GitignoreBuilder::new does not source globs
    // within gitignore_path by default, we have to enforce that
    // using GitignoreBuilder::add. Probably a bug in the ignore
    // crate?
    let mut gitignore = GitignoreBuilder::new(&gitignore_path);

    // if we are to "restrict" aka "respect" .gitignore, then
    // add globs from gitignore path as well
    if !unrestricted {
        gitignore.add(&gitignore_path);

        // ignore .git by default, nobody cares about .git, i'm sure
        gitignore.add_line(None, ".git")?;
    }

    for i in ignore {
        gitignore.add_line(None, i.as_str())?;
    }

    gitignore.build()
}

pub fn walk_nix_files<P: AsRef<Path>>(
    ignore: Gitignore,
    target: P,
) -> Result<impl Iterator<Item = PathBuf>, io::Error> {
    let walker = dirs::Walker::new(target, ignore)?;
    Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix")))
}