use async_trait::async_trait;
use futures::StreamExt;
use std::iter::FromIterator;
use tokio::fs;
use tokio::stream;
#[async_trait]
pub trait TreeBuilder {
async fn build_tree(
dir_path: String,
excluding: Option<Vec<String>>,
recursive_excluding: bool,
) -> Vec<Tree>;
fn tree_diff(dir_tree: Vec<Tree>, dir_tree_comp: Vec<Tree>) -> bool;
async fn get_content_files(dir_tree: Vec<Tree>) -> Vec<String>;
fn compare_dir_content(dir_content: Vec<String>, dir_content_comp: Vec<String>) -> bool;
}
#[derive(Debug, PartialEq, Clone)]
pub struct Tree {
pub name: String,
pub path: String,
pub subdir: Option<Vec<Tree>>,
}
#[derive(Debug, PartialEq)]
struct TreeComp {
pub name: String,
pub subdir: Option<Vec<TreeComp>>,
}
struct ExtratedFile {
pub name: String,
pub path: String,
}
struct TreeFlatted(Vec<ExtratedFile>);
impl TreeFlatted {
fn new() -> Self {
TreeFlatted(Vec::new())
}
fn add(&mut self, elem: ExtratedFile) {
self.0.push(elem);
}
}
impl FromIterator<Tree> for TreeFlatted {
fn from_iter<I: IntoIterator<Item = Tree>>(iter: I) -> Self {
let mut tree_flatted = TreeFlatted::new();
for i in iter {
if let Some(entry) = i.subdir {
for sub_iter in TreeFlatted::from_iter(entry).0 {
tree_flatted.add(sub_iter);
}
} else {
let extrated_file = ExtratedFile {
name: i.name,
path: i.path,
};
tree_flatted.add(extrated_file);
}
}
tree_flatted
}
}
impl From<Tree> for TreeComp {
fn from(tree: Tree) -> Self {
TreeComp {
name: tree.name,
subdir: if let Some(entry) = tree.subdir {
Some(entry.into_iter().map(TreeComp::from).collect())
} else {
None
},
}
}
}
#[async_trait]
impl TreeBuilder for Tree {
async fn build_tree(
dir_path: String,
excluding: Option<Vec<String>>,
recursive_excluding: bool,
) -> Vec<Tree> {
let mut entries = fs::read_dir(dir_path).await.unwrap();
let mut tree: Vec<Tree> = vec![];
let mut exclude: Vec<String> = vec![];
if let Some(mut item) = excluding {
exclude.append(&mut item);
}
while let Some(entry) = entries.next_entry().await.unwrap() {
let file_name = entry.file_name().into_string().unwrap();
if !exclude.clone().into_iter().any(|item| item == file_name) {
let path: String = entry.path().into_os_string().into_string().unwrap();
tree.push(Tree {
name: entry.file_name().into_string().unwrap(),
path: path.clone(),
subdir: if entry.path().is_dir() {
Some(
Tree::build_tree(
path,
if recursive_excluding {
Some(exclude.clone())
} else {
None
},
recursive_excluding,
)
.await,
)
} else {
None
},
});
}
}
tree
}
fn tree_diff(dir_tree: Vec<Tree>, dir_tree_comp: Vec<Tree>) -> bool {
let dir_tree_from: Vec<TreeComp> = dir_tree.into_iter().map(TreeComp::from).collect();
let dir_tree_comp_from: Vec<TreeComp> =
dir_tree_comp.into_iter().map(TreeComp::from).collect();
dir_tree_from != dir_tree_comp_from
}
async fn get_content_files(dir_tree: Vec<Tree>) -> Vec<String> {
let file_list: TreeFlatted = TreeFlatted::from_iter(dir_tree);
let files = stream::iter(file_list.0);
let file_content: Vec<String> = files
.then(|file| async { fs::read_to_string(file.path).await.unwrap() })
.collect::<Vec<String>>()
.await;
file_content
}
fn compare_dir_content(dir_content: Vec<String>, dir_content_comp: Vec<String>) -> bool {
dir_content.into_iter().all(move |content| {
dir_content_comp
.clone()
.into_iter()
.any(move |content_comp| content_comp == content)
})
}
}
#[tokio::test]
async fn should_return_false_equal_dir_tree() {
let dir_one = Tree::build_tree("./mocks/dir_one".to_string(), None, false).await;
let dir_two = Tree::build_tree("./mocks/dir_two".to_string(), None, false).await;
let diff = Tree::tree_diff(dir_one, dir_two);
assert_eq!(diff, false);
}
#[tokio::test]
async fn should_return_true_different_dir_tree() {
let dir_one = Tree::build_tree("./mocks/dir_one".to_string(), None, false).await;
let dir_three = Tree::build_tree("./mocks/dir_three".to_string(), None, false).await;
let diff = Tree::tree_diff(dir_one, dir_three);
assert_eq!(diff, true);
}
#[tokio::test]
async fn should_return_all_file_content() {
let dir_one = Tree::build_tree("./mocks/dir_one".to_string(), None, false).await;
let content = Tree::get_content_files(dir_one).await;
assert_eq!(
content,
vec!(
"Hello world",
"print(\"This line will be printed.\")",
"new language",
"fn main() {\n println(\"hello world\")\n}\n",
)
)
}
#[tokio::test]
async fn should_return_true_if_both_dir_content_are_equal() {
let dir_one = Tree::build_tree("./mocks/dir_one".to_string(), None, false).await;
let content_one = Tree::get_content_files(dir_one).await;
let dir_two = Tree::build_tree("./mocks/dir_two".to_string(), None, false).await;
let content_two = Tree::get_content_files(dir_two).await;
assert_eq!(Tree::compare_dir_content(content_one, content_two), true);
}
#[tokio::test]
async fn should_return_false_if_both_dir_content_are_differents() {
let dir_one = Tree::build_tree("./mocks/dir_one".to_string(), None, false).await;
let content_one = Tree::get_content_files(dir_one).await;
let dir_four = Tree::build_tree("./mocks/dir_four".to_string(), None, false).await;
let content_four = Tree::get_content_files(dir_four).await;
assert_eq!(Tree::compare_dir_content(content_one, content_four), false);
}
#[tokio::test]
async fn should_return_true_if_both_dir_tree_have_different_subdir_excluded_recursively() {
let dir_one = Tree::build_tree(
"./mocks/dir_one".to_string(),
Some(vec!["purpose".to_string()]),
true,
)
.await;
let content_one = Tree::get_content_files(dir_one).await;
let dir_five = Tree::build_tree(
"./mocks/dir_five".to_string(),
Some(vec!["purpose".to_string()]),
true,
)
.await;
let content_five = Tree::get_content_files(dir_five).await;
assert_eq!(Tree::compare_dir_content(content_one, content_five), true);
}
#[tokio::test]
async fn should_return_false_if_both_dir_tree_have_different_subdir_excluded_not_recursively() {
let dir_one = Tree::build_tree(
"./mocks/dir_one".to_string(),
Some(vec!["purpose".to_string()]),
false,
)
.await;
let content_one = Tree::get_content_files(dir_one).await;
let dir_five = Tree::build_tree(
"./mocks/dir_five".to_string(),
Some(vec!["purpose".to_string()]),
false,
)
.await;
let content_five = Tree::get_content_files(dir_five).await;
assert_eq!(Tree::compare_dir_content(content_one, content_five), false);
}