use std::{
collections::BTreeMap,
ffi::OsString,
path::{Path, PathBuf},
};
use crate::PathSegment;
pub(crate) fn from_path_with_ignore<'a, TDir, TDirEntry>(
directory: impl AsRef<Path>,
make_dir: impl Fn(&Path) -> Result<TDir, DirectoryFromPathError>,
make_entry_dir: impl Fn(TDir) -> TDirEntry,
make_entry_file: impl Fn(&Path) -> Result<TDirEntry, DirectoryFromPathError>,
get_children: impl Fn(&mut TDir) -> &mut BTreeMap<PathSegment, TDirEntry>,
get_dir_from_entry: impl Fn(&mut TDirEntry) -> Option<&mut TDir>,
) -> Result<TDir, DirectoryFromPathError>
where
TDir: 'a,
TDirEntry: 'a,
{
let mut result = make_dir(directory.as_ref())?;
let walker = ignore::WalkBuilder::new(directory.as_ref())
.require_git(true)
.add_custom_ignore_filename(".wasmerignore")
.follow_links(false)
.build();
for entry in walker {
let entry = entry?;
let is_file = !entry.path().is_dir();
let path = entry.path().strip_prefix(directory.as_ref()).map_err(|_| {
DirectoryFromPathError::StripPrefix {
path: entry.path().to_owned(),
root: directory.as_ref().to_owned(),
}
})?;
let mut segments = path.iter().peekable();
let mut cursor = &mut result;
let mut full_path = directory.as_ref().to_owned();
while let Some(segment_str) = segments.next() {
let segment = segment_str
.to_str()
.ok_or_else(|| DirectoryFromPathError::NonUtf8Path(segment_str.to_owned()))?
.parse::<PathSegment>()?;
let is_last = segments.peek().is_none();
if is_last && is_file {
get_children(cursor).insert(segment, make_entry_file(entry.path())?);
} else {
full_path.push(segment_str);
let entry = match get_children(cursor).entry(segment) {
std::collections::btree_map::Entry::Occupied(o) => o.into_mut(),
std::collections::btree_map::Entry::Vacant(v) => {
let dir = make_dir(&full_path)?;
v.insert(make_entry_dir(dir))
}
};
cursor = match get_dir_from_entry(entry) {
Some(d) => d,
None => unreachable!("Path was both a file and a directory"),
}
}
}
}
Ok(result)
}
#[derive(thiserror::Error, Debug)]
pub enum DirectoryFromPathError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to strip root path {root} from entry {path}")]
StripPrefix {
path: PathBuf,
root: PathBuf,
},
#[error("Non-UTF8 path {0:?}")]
NonUtf8Path(OsString),
#[error("Path segment error: {0}")]
PathSegment(#[from] crate::PathSegmentError),
#[error("Ignore error: {0}")]
Ignore(ignore::Error),
}
impl From<ignore::Error> for DirectoryFromPathError {
fn from(e: ignore::Error) -> Self {
match e {
ignore::Error::Io(io) => Self::Io(io),
_ => DirectoryFromPathError::Ignore(e),
}
}
}