#![allow(dead_code)]
#![deny(bare_trait_objects)]
use crossbeam_channel::{Receiver, Sender};
use rayon::prelude::*;
use std::{fs, path::PathBuf, sync::Arc};
macro_rules! trait_aliases {(
$(
pub
trait
alias
$Trait:ident
$(
( $($ty_params:tt)* )
)?
= {
$($traits:tt)*
}
$(
where {
$($wc:tt)*
}
)?
;
)*
) => (
$(
pub
trait $Trait $(<$($ty_params)*>)? :
$($traits)*
$(
where
$($wc)*
)?
{}
impl<Slf : ?Sized, $($($ty_params)*)?> $Trait $(<$($ty_params)*>)?
for Slf
where
Slf : $($traits)*,
$($($wc)*)?
{}
)*
)}
pub struct FileTaskResponse {
pub content: String,
pub path: PathBuf,
}
pub trait FileTask: FileTaskClone + Send {
fn execute(&mut self, response: &FileTaskResponse);
}
pub trait FileTaskClone {
fn clone_box(&self) -> Box<dyn FileTask>;
}
impl<T> FileTaskClone for T
where
T: FileTask + Clone + 'static,
{
fn clone_box(&self) -> Box<dyn FileTask> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn FileTask> {
fn clone(&self) -> Box<dyn FileTask> {
self.clone_box()
}
}
trait_aliases! {
pub trait alias Contextual = {
Send + 'static +
Clone
};
pub trait alias Function(Context, Output) = {
Send + Sync + 'static +
Fn(&mut Context, &FileTaskResponse) -> Output
} where {
Context : Contextual,
Output : Send + 'static,
};
}
pub struct FunctionFileTask<Context, Output>
where
Context: Contextual,
Output: Send + 'static,
{
context: Context,
function: Arc<dyn Function<Context, Output>>,
results: Sender<Output>,
completed: bool,
}
impl<Context, Output> Clone for FunctionFileTask<Context, Output>
where
Context: Contextual,
Output: Send + 'static,
{
fn clone(&self) -> Self {
let Self {
context,
function,
results,
completed,
} = self;
Self {
context: context.clone(),
function: function.clone(),
results: results.clone(),
completed: *completed,
}
}
}
impl<Context, Output> FileTask for FunctionFileTask<Context, Output>
where
Context: Contextual,
Output: Send + 'static,
{
fn execute(&mut self, response: &FileTaskResponse) {
if self.completed {
return;
}
let result = (*self.function)(&mut self.context, response);
let completed = self.results.send(result).is_err();
self.completed = completed;
}
}
impl<Context, Output> FunctionFileTask<Context, Output>
where
Context: Contextual,
Output: Send + 'static,
{
pub fn new<F>(sender: Sender<Output>, context: Context, function: F) -> Self
where
F: Function<Context, Output>,
{
Self {
context,
function: Arc::new(function),
results: sender,
completed: false,
}
}
}
pub struct WorkTree {
tasks: Vec<Box<dyn FileTask>>,
}
impl WorkTree {
pub fn add_task<Context, Output, F>(
&mut self,
context: Context,
function: F,
) -> Receiver<Output>
where
Context: Contextual,
Output: Send + 'static,
F: Function<Context, Output>,
{
let (sender, receiver) = crossbeam_channel::bounded(100);
let task = FunctionFileTask::new(sender, context, function);
self.tasks.push(Box::new(task));
receiver
}
pub fn run(&self, tree_paths: Vec<PathBuf>) {
let initial_tasks = self.tasks.clone();
let read_file = |path: PathBuf| {
let content = fs::read_to_string(&path).ok();
content.map(move |c| FileTaskResponse { content: c, path })
};
tree_paths
.into_par_iter()
.filter_map(read_file)
.for_each_with(initial_tasks, |tasks, ref file_contents| {
tasks
.iter_mut()
.for_each(|task| task.execute(file_contents))
});
}
pub fn new() -> Self {
Self { tasks: vec![] }
}
}
#[cfg(test)]
mod tests {
use crate::utils::testing::create_temp_file;
use super::*;
#[derive(Clone)]
struct MockContext;
fn mock_function(_context: &mut MockContext, _file_contents: &FileTaskResponse) -> i32 {
42
}
#[test]
fn test_file_processor() {
#[derive(Clone)]
struct MockFileTask;
impl FileTask for MockFileTask {
fn execute(&mut self, _file_contents: &FileTaskResponse) {
}
}
let processor = WorkTree {
tasks: vec![Box::new(MockFileTask)],
};
processor.run(vec![]);
}
#[test]
fn test_function_file_processor() {
let (sender, receiver) = crossbeam_channel::unbounded();
let function_processor = FunctionFileTask {
context: MockContext,
function: Arc::new(mock_function),
results: sender,
completed: false,
};
let mut cloned_processor = function_processor.clone();
let response = &FileTaskResponse {
content: "example test content".into(),
path: PathBuf::new(),
};
cloned_processor.execute(response);
assert!(!cloned_processor.completed);
assert_eq!(receiver.try_recv(), Ok(42));
}
#[test]
fn test_work_tree_processor() {
let mut work_tree_processor = WorkTree { tasks: vec![] };
let receiver = work_tree_processor.add_task(MockContext, mock_function);
let (tmp_dir, tmp_file) = create_temp_file("work_tree_processor.txt");
work_tree_processor.run(vec![tmp_file]);
assert_eq!(
receiver.try_recv(),
Ok(42),
"Expected result of 42 from the receiver"
);
let _ = tmp_dir.close();
}
}