use crate::{SUMMARY_FILE, CONFIG_FILE};
use mdbook::MDBook;
use notify::Watcher;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
let paths: Vec<&PathBuf> = paths
.iter()
.filter(|x| x.file_name() != Some(OsStr::new(SUMMARY_FILE)))
.collect();
match find_gitignore(book_root) {
Some(gitignore_path) => {
match gitignore::File::new(gitignore_path.as_path()) {
Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths),
Err(_) => {
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
None => {
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
book_root
.ancestors()
.map(|p| p.join(".gitignore"))
.find(|p| p.exists())
}
fn filter_ignored_files(exclusion_checker: gitignore::File, paths: Vec<&PathBuf>) -> Vec<PathBuf> {
paths
.iter()
.filter(|path| match exclusion_checker.is_excluded(path) {
Ok(exclude) => !exclude,
Err(error) => {
error!(
"Unable to determine if {:?} is excluded: {:?}. Including it.",
&path, error
);
true
}
})
.map(|path| path.to_path_buf())
.collect()
}
pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where
F: Fn(Vec<PathBuf>, &Path),
{
use notify::DebouncedEvent::*;
use notify::RecursiveMode::*;
let (tx, rx) = channel();
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
Ok(w) => w,
Err(e) => {
error!("Error while trying to watch the files:\n\n\t{:?}", e);
std::process::exit(1)
}
};
if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
std::process::exit(1);
};
let _ = watcher.watch(book.theme_dir(), Recursive);
let _ = watcher.watch(book.root.join(CONFIG_FILE), NonRecursive);
info!("Listening for changes...");
loop {
let first_event = rx.recv().unwrap();
sleep(Duration::from_millis(50));
let other_events = rx.try_iter();
let all_events = std::iter::once(first_event).chain(other_events);
let paths = all_events
.filter_map(|event| {
debug!("Received filesystem event: {:?}", event);
match event {
Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path),
_ => None,
}
})
.collect::<Vec<_>>();
let paths = remove_ignored_files(&book.root, &paths[..]);
if !paths.is_empty() {
closure(paths, &book.root);
}
}
}