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
118
119
120
121
122
use std::path::Path;

use anyhow::{Context, Error};
use globset::{Glob, GlobSet, GlobSetBuilder};
use walkdir::{DirEntry, WalkDir};

#[derive(Debug, Clone)]
pub struct BulkCopy {
    include: GlobSet,
    blacklist: GlobSet,
    max_depth: Option<usize>,
}

impl BulkCopy {
    pub fn new<I, S>(include_globs: I) -> Result<Self, Error>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<str>,
    {
        Ok(BulkCopy {
            include: compile_globs(include_globs)?,
            blacklist: GlobSet::empty(),
            max_depth: None,
        })
    }

    pub fn with_max_depth(self, depth: impl Into<Option<usize>>) -> Self {
        BulkCopy {
            max_depth: depth.into(),
            ..self
        }
    }

    pub fn with_blacklist<I, S>(self, globs: I) -> Result<Self, Error>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<str>,
    {
        Ok(BulkCopy {
            blacklist: compile_globs(globs)?,
            ..self
        })
    }

    pub fn copy<P, Q>(&self, from: P, to: Q) -> Result<(), Error>
    where
        P: AsRef<Path>,
        Q: AsRef<Path>,
    {
        let from = from.as_ref();
        let to = to.as_ref();

        let mut wd = WalkDir::new(from);

        if let Some(max_depth) = self.max_depth {
            wd = wd.max_depth(max_depth);
        }

        for entry in wd.into_iter().filter_map(|e| e.ok()) {
            let path = entry.path();

            if !self.include.is_match(path) || self.blacklist.is_match(path) {
                continue;
            }

            self.copy_entry(from, to, &entry)?;
        }

        Ok(())
    }

    fn copy_entry(
        &self,
        from: &Path,
        to: &Path,
        entry: &DirEntry,
    ) -> Result<(), Error> {
        let path = entry.path();
        let stripped = path.strip_prefix(from)?;
        let new_name = to.join(stripped);

        if let Some(parent) = new_name.parent() {
            std::fs::create_dir_all(parent).with_context(|| {
                format!(
                    "Unable to create the \"{}\" directory",
                    parent.display()
                )
            })?;
        }

        log::debug!(
            "Copying \"{}\" to \"{}\"",
            path.display(),
            new_name.display()
        );

        std::fs::copy(path, &new_name).with_context(|| {
            format!(
                "Unable to copy \"{}\" to \"{}\"",
                path.display(),
                new_name.display()
            )
        })?;

        Ok(())
    }
}

fn compile_globs<I, S>(globs: I) -> Result<GlobSet, Error>
where
    I: IntoIterator<Item = S>,
    S: AsRef<str>,
{
    let mut builder = GlobSetBuilder::new();

    for glob in globs {
        let glob = Glob::new(glob.as_ref())?;
        builder.add(glob);
    }

    builder.build().map_err(Error::from)
}