1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::time::SystemTime;
4
5use crate::builder::{load_templates, Builder};
6use crate::error::DocError;
7use crate::walk;
8
9pub struct Watcher<'b> {
17 builder: &'b Builder,
18 md_mtimes: HashMap<PathBuf, SystemTime>,
19 template_mtimes: HashMap<PathBuf, SystemTime>,
20}
21
22impl<'b> Watcher<'b> {
23 pub(crate) fn new(builder: &'b Builder) -> Result<Self, DocError> {
24 builder.build()?;
25 Ok(Self {
26 builder,
27 md_mtimes: snapshot_mtimes(builder.input_dir(), "md")?,
28 template_mtimes: snapshot_mtimes(builder.templates_dir(), "html")?,
29 })
30 }
31
32 pub fn tick(&mut self) -> Result<Vec<PathBuf>, DocError> {
47 let current_templates = snapshot_mtimes(self.builder.templates_dir(), "html")?;
48 let templates_changed = current_templates != self.template_mtimes;
49 self.template_mtimes = current_templates;
50
51 let current_md = snapshot_mtimes(self.builder.input_dir(), "md")?;
52 let md_set_changed = current_md != self.md_mtimes;
53
54 let changed: Vec<PathBuf> = if templates_changed {
55 current_md.keys().cloned().collect()
56 } else {
57 current_md
58 .iter()
59 .filter(|(path, mtime)| self.md_mtimes.get(path.as_path()) != Some(*mtime))
60 .map(|(path, _)| path.clone())
61 .collect()
62 };
63
64 if !changed.is_empty() {
65 let tera = load_templates(self.builder.templates_dir())?;
66 for md_path in &changed {
67 self.builder.build_one(&tera, md_path)?;
68 }
69 }
70
71 if md_set_changed {
72 self.builder.rebuild_data_json()?;
73 }
74
75 self.md_mtimes = current_md;
76 Ok(changed)
77 }
78}
79
80fn snapshot_mtimes(dir: &Path, extension: &str) -> Result<HashMap<PathBuf, SystemTime>, DocError> {
81 let mut mtimes = HashMap::new();
82 for path in walk::walk_files_with_extension(dir, extension)? {
83 let mtime = std::fs::metadata(&path)?.modified()?;
84 mtimes.insert(path, mtime);
85 }
86 Ok(mtimes)
87}