#![warn(missing_docs)]
pub mod error;
pub mod iter;
pub mod util;
use std::{
collections::HashMap,
env, fs, mem,
path::{Path, PathBuf},
};
use file_type_enum::FileType as FileTypeEnum;
pub use self::{
error::*,
iter::{FilesIter, PathsIter},
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FileTree {
pub path: PathBuf,
pub file_type: FileTreeType,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FileTreeType {
Regular,
Directory(Vec<FileTree>),
Symlink(PathBuf),
}
impl FileTreeType {
pub fn is_same_type_as(&self, other: &Self) -> bool {
mem::discriminant(self) == mem::discriminant(other)
}
pub fn is_regular(&self) -> bool {
matches!(self, Self::Regular)
}
pub fn is_dir(&self) -> bool {
matches!(self, Self::Directory(_))
}
pub fn is_symlink(&self) -> bool {
matches!(self, Self::Symlink(_))
}
}
impl FileTree {
pub fn new_regular(path: impl AsRef<Path>) -> Self {
Self {
path: path.as_ref().to_owned(),
file_type: FileTreeType::Regular,
}
}
pub fn new_directory(path: impl AsRef<Path>, children: Vec<Self>) -> Self {
Self {
path: path.as_ref().to_path_buf(),
file_type: FileTreeType::Directory(children),
}
}
pub fn new_symlink(path: impl AsRef<Path>, target_path: impl AsRef<Path>) -> Self {
let path = path.as_ref().to_path_buf();
let target_path = target_path.as_ref().to_path_buf();
Self {
path,
file_type: FileTreeType::Symlink(target_path),
}
}
fn __collect_from_directory(path: &Path, follow_symlinks: bool) -> Result<Vec<Self>> {
if !path.exists() {
return Err(Error::NotFoundError(path.to_path_buf()));
} else if !FileTypeEnum::from_path(path)?.is_directory() {
return Err(Error::NotADirectoryError(path.to_path_buf()));
}
let dirs = fs::read_dir(path)?;
let mut children = vec![];
for entry in dirs {
let entry = entry?;
let file = Self::__from_path(&entry.path(), follow_symlinks)?;
children.push(file);
}
Ok(children)
}
pub fn collect_from_directory(path: impl AsRef<Path>) -> Result<Vec<Self>> {
Self::__collect_from_directory(path.as_ref(), true)
}
pub fn collect_from_directory_symlink(path: impl AsRef<Path>) -> Result<Vec<Self>> {
Self::__collect_from_directory(path.as_ref(), false)
}
fn __collect_from_directory_cd(path: &Path, follow_symlinks: bool) -> Result<Vec<Self>> {
let previous_path = env::current_dir()?;
debug_assert!(path.is_absolute());
env::set_current_dir(path)?;
let result = Self::__collect_from_directory(Path::new("."), follow_symlinks);
env::set_current_dir(previous_path)?;
result
}
pub fn collect_from_directory_cd(path: impl AsRef<Path>) -> Result<Vec<Self>> {
Self::__collect_from_directory_cd(path.as_ref(), false)
}
pub fn collect_from_directory_symlink_cd(path: impl AsRef<Path>) -> Result<Vec<Self>> {
Self::__collect_from_directory_cd(path.as_ref(), false)
}
fn __from_path(path: &Path, follow_symlinks: bool) -> Result<Self> {
let get_file_type = if follow_symlinks {
FileTypeEnum::from_path
} else {
FileTypeEnum::from_symlink_path
};
match get_file_type(path)? {
FileTypeEnum::Regular => Ok(Self::new_regular(path)),
FileTypeEnum::Directory => {
let children = Self::__collect_from_directory(path, follow_symlinks)?;
Ok(Self::new_directory(path, children))
},
FileTypeEnum::Symlink => {
let target_path = util::symlink_follow(path)?;
Ok(Self::new_symlink(path, target_path))
},
other_type => Err(Error::UnexpectedFileTypeError(
other_type,
path.to_path_buf(),
)),
}
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
Self::__from_path(path.as_ref(), true)
}
pub fn from_path_symlink(path: impl AsRef<Path>) -> Result<Self> {
Self::__from_path(path.as_ref(), false)
}
fn ___from_path_cd(path: &Path, follow_symlinks: bool) -> Result<Self> {
let previous_path = env::current_dir()?;
debug_assert!(path.is_absolute());
env::set_current_dir(path)?;
let result = Self::__from_path(Path::new("."), follow_symlinks);
env::set_current_dir(previous_path)?;
result
}
pub fn from_path_cd(path: impl AsRef<Path>) -> Result<Self> {
Self::___from_path_cd(path.as_ref(), true)
}
pub fn from_cd_symlink_path(path: impl AsRef<Path>) -> Result<Self> {
Self::___from_path_cd(path.as_ref(), false)
}
pub fn from_path_text(path: impl AsRef<Path>) -> Option<Self> {
Self::from_path_pieces(path.as_ref().iter())
}
pub fn from_path_pieces<I, P>(path_iter: I) -> Option<Self>
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
let mut path_iter = path_iter.into_iter();
let first_piece = path_iter.next()?;
let mut tree = Self::from_path_text_recursive_impl(first_piece, path_iter);
tree.make_paths_relative();
Some(tree)
}
fn from_path_text_recursive_impl<I, P>(piece: P, mut path_iter: I) -> Self
where
I: Iterator<Item = P>,
P: AsRef<Path>,
{
match path_iter.next() {
Some(next) => FileTree::new_directory(
piece.as_ref(),
vec![Self::from_path_text_recursive_impl(next, path_iter)],
),
None => FileTree::new_regular(piece),
}
}
pub fn files(&self) -> FilesIter {
FilesIter::new(self)
}
pub fn paths(&self) -> PathsIter {
self.files().paths()
}
pub fn make_paths_relative(&mut self) {
if let FileTreeType::Directory(children) = &mut self.file_type {
for child in children.iter_mut() {
*child.path_mut() = self.path.join(child.path());
if let Some(target) = child.target_mut() {
*target = self.path.join(&target);
}
child.make_paths_relative();
}
}
}
pub fn make_paths_absolute(&mut self) -> Result<()> {
*self.path_mut() = self.path().canonicalize()?;
if let Some(children) = self.children_mut() {
for child in children.iter_mut() {
Self::make_paths_absolute(child)?;
}
}
Ok(())
}
pub fn merge(self, other: Self) -> Option<Self> {
if self.path() != other.path() {
return None;
}
let path = self.path;
match (self.file_type, other.file_type) {
(FileTreeType::Directory(left_children), FileTreeType::Directory(right_children)) => {
let mut left_map: HashMap<PathBuf, FileTree> = left_children
.into_iter()
.map(|child| (child.path.clone(), child))
.collect();
let mut result_vec = vec![];
for child in right_children {
match left_map.remove(child.path()) {
None => result_vec.push(child),
Some(left_equivalent) => {
if !child.has_same_type_as(&left_equivalent) {
return None;
} else if child.is_dir() && left_equivalent.is_dir() {
result_vec.push(left_equivalent.merge(child).unwrap());
} else {
result_vec.push(left_equivalent);
result_vec.push(child);
}
},
}
}
result_vec.extend(left_map.into_values());
Some(Self::new_directory(path, result_vec))
},
_ => None,
}
}
pub fn children(&self) -> Option<&Vec<Self>> {
match &self.file_type {
FileTreeType::Directory(children) => Some(children),
_ => None,
}
}
pub fn children_mut(&mut self) -> Option<&mut Vec<Self>> {
match &mut self.file_type {
FileTreeType::Directory(children) => Some(children),
_ => None,
}
}
pub fn target(&self) -> Option<&PathBuf> {
match &self.file_type {
FileTreeType::Symlink(target_path) => Some(target_path),
_ => None,
}
}
pub fn target_mut(&mut self) -> Option<&mut PathBuf> {
match &mut self.file_type {
FileTreeType::Symlink(target_path) => Some(target_path),
_ => None,
}
}
pub fn apply_to_children0(&mut self, f: impl FnMut(&mut Self)) {
if let Some(children) = self.children_mut() {
children.iter_mut().for_each(f);
}
}
pub fn apply_to_all_children1(&mut self, f: impl FnMut(&mut Self) + Copy) {
if let Some(children) = self.children_mut() {
children
.iter_mut()
.for_each(|x| x.apply_to_all_children1(f));
children.iter_mut().for_each(f);
}
}
pub fn apply_to_all(&mut self, mut f: impl FnMut(&mut Self) + Copy) {
f(self);
if let Some(children) = self.children_mut() {
for child in children.iter_mut() {
child.apply_to_all(f);
}
}
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub fn path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
pub fn is_regular(&self) -> bool {
self.file_type.is_regular()
}
pub fn is_dir(&self) -> bool {
self.file_type.is_dir()
}
pub fn is_symlink(&self) -> bool {
self.file_type.is_symlink()
}
pub fn to_regular(&mut self) {
self.file_type = FileTreeType::Regular;
}
pub fn to_directory(&mut self, children: Vec<Self>) {
self.file_type = FileTreeType::Directory(children);
}
pub fn to_symlink(&mut self, target_path: impl AsRef<Path>) {
self.file_type = FileTreeType::Symlink(target_path.as_ref().to_owned());
}
pub fn has_same_type_as(&self, other: &FileTree) -> bool {
self.file_type.is_same_type_as(&other.file_type)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge() {
let left = FileTree::from_path_text(".config/i3/file").unwrap();
let right = FileTree::from_path_text(".config/i3/folder/file").unwrap();
let result = left.merge(right);
let expected = {
FileTree::new_directory(
".config",
vec![FileTree::new_directory(
".config/i3",
vec![
FileTree::new_directory(
".config/i3/folder",
vec![FileTree::new_regular(".config/i3/folder/file")],
),
FileTree::new_regular(".config/i3/file"),
],
)],
)
};
assert_eq!(result, Some(expected));
}
#[test]
fn test_partial_eq_fails() {
let left = FileTree::from_path_text(".config/i3/a").unwrap();
let right = FileTree::from_path_text(".config/i3/b").unwrap();
assert_ne!(left, right);
}
}