From 8b5183d4adb977c7e0443d8712d2b34b8c313fdb Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 21 Mar 2017 16:57:00 +0900 Subject: [PATCH] Partial reloading of content on change --- src/cmd/build.rs | 2 +- src/cmd/serve.rs | 4 +-- src/site.rs | 84 ++++++++++++++++++++++++++++++++++-------------- tests/site.rs | 14 ++++---- 4 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 521ecd06..9893a18d 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -6,6 +6,6 @@ use gutenberg::Site; pub fn build() -> Result<()> { let mut site = Site::new(env::current_dir().unwrap())?; - site.parse()?; + site.load()?; site.build() } diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 49f1578d..83701755 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -70,7 +70,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { format!("http://{}", address) }; - site.parse()?; + site.load()?; site.enable_live_reload(); site.build()?; report_elapsed_time(start); @@ -129,7 +129,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { (ChangeKind::Content, _) => { println!("-> Content changed {}", path.display()); // Force refresh - rebuild_done_handling(&broadcaster, site.rebuild_after_content_change(), "/x.js"); + rebuild_done_handling(&broadcaster, site.rebuild_after_content_change(&path), "/x.js"); }, (ChangeKind::Templates, _) => { println!("-> Template changed {}", path.display()); diff --git a/src/site.rs b/src/site.rs index b29174f0..c2914708 100644 --- a/src/site.rs +++ b/src/site.rs @@ -101,39 +101,60 @@ impl Site { self.output_path = path.as_ref().to_path_buf(); } - /// Reads all .md files in the `content` directory and create pages + /// Reads all .md files in the `content` directory and create pages/sections /// out of them - pub fn parse(&mut self) -> Result<()> { + pub fn load(&mut self) -> Result<()> { let path = self.base_path.to_string_lossy().replace("\\", "/"); let content_glob = format!("{}/{}", path, "content/**/*.md"); - // parent_dir -> Section - let mut sections = BTreeMap::new(); - - // Glob is giving us the result order so _index will show up first - // for each directory + // TODO: make that parallel, that's the main bottleneck + // `add_section` and `add_page` can't be used in the parallel version afaik for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) { let path = entry.as_path(); if path.file_name().unwrap() == "_index.md" { - let section = Section::from_file(&path, &self.config)?; - sections.insert(section.parent_path.clone(), section); + self.add_section(&path)?; } else { - let page = Page::from_file(&path, &self.config)?; - if sections.contains_key(&page.parent_path) { - sections.get_mut(&page.parent_path).unwrap().pages.push(page.clone()); - } - self.pages.insert(page.file_path.clone(), page); + self.add_page(&path)?; } } - // Find out the direct subsections of each subsection if there are some + + self.populate_sections(); + self.populate_tags_and_categories(); + + Ok(()) + } + + /// Simple wrapper fn to avoid repeating that code in several places + fn add_page(&mut self, path: &Path) -> Result<()> { + let page = Page::from_file(&path, &self.config)?; + self.pages.insert(page.file_path.clone(), page); + Ok(()) + } + + /// Simple wrapper fn to avoid repeating that code in several places + fn add_section(&mut self, path: &Path) -> Result<()> { + let section = Section::from_file(path, &self.config)?; + self.sections.insert(section.parent_path.clone(), section); + Ok(()) + } + + /// Find out the direct subsections of each subsection if there are some + /// as well as the pages for each section + fn populate_sections(&mut self) { + for page in self.pages.values() { + if self.sections.contains_key(&page.parent_path) { + self.sections.get_mut(&page.parent_path).unwrap().pages.push(page.clone()); + } + } + let mut grandparent_paths = HashMap::new(); - for section in sections.values() { + for section in self.sections.values() { let grand_parent = section.parent_path.parent().unwrap().to_path_buf(); grandparent_paths.entry(grand_parent).or_insert_with(|| vec![]).push(section.clone()); } - for (parent_path, section) in &mut sections { + for (parent_path, section) in &mut self.sections { section.pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); match grandparent_paths.get(parent_path) { @@ -141,15 +162,10 @@ impl Site { None => continue, }; } - - self.sections = sections; - self.parse_tags_and_categories(); - - Ok(()) } /// Separated from `parse` for easier testing - pub fn parse_tags_and_categories(&mut self) { + pub fn populate_tags_and_categories(&mut self) { for page in self.pages.values() { if let Some(ref category) = page.meta.category { self.categories @@ -221,8 +237,26 @@ impl Site { Ok(()) } - pub fn rebuild_after_content_change(&mut self) -> Result<()> { - self.parse()?; + pub fn rebuild_after_content_change(&mut self, path: &Path) -> Result<()> { + if path.exists() { + // file exists, either a new one or updating content + if self.pages.contains_key(path) { + if path.ends_with("_index.md") { + self.add_section(path)?; + } else { + // probably just an update so just re-parse that page + self.add_page(path)?; + } + } else { + // new file? + self.add_page(path)?; + } + } else { + // File doesn't exist -> a deletion so we remove it from + self.pages.remove(path); + } + self.populate_sections(); + self.populate_tags_and_categories(); self.build() } diff --git a/tests/site.rs b/tests/site.rs index 3bb84128..3c550ae7 100644 --- a/tests/site.rs +++ b/tests/site.rs @@ -17,7 +17,7 @@ fn test_can_parse_site() { let mut path = env::current_dir().unwrap().to_path_buf(); path.push("test_site"); let mut site = Site::new(&path).unwrap(); - site.parse().unwrap(); + site.load().unwrap(); // Correct number of pages (sections are pages too) assert_eq!(site.pages.len(), 10); @@ -89,7 +89,7 @@ fn test_can_build_site_without_live_reload() { let mut path = env::current_dir().unwrap().to_path_buf(); path.push("test_site"); let mut site = Site::new(&path).unwrap(); - site.parse().unwrap(); + site.load().unwrap(); let tmp_dir = TempDir::new("example").expect("create temp dir"); let public = &tmp_dir.path().join("public"); site.set_output_path(&public); @@ -130,7 +130,7 @@ fn test_can_build_site_with_live_reload() { let mut path = env::current_dir().unwrap().to_path_buf(); path.push("test_site"); let mut site = Site::new(&path).unwrap(); - site.parse().unwrap(); + site.load().unwrap(); let tmp_dir = TempDir::new("example").expect("create temp dir"); let public = &tmp_dir.path().join("public"); site.set_output_path(&public); @@ -168,7 +168,7 @@ fn test_can_build_site_with_categories() { let mut path = env::current_dir().unwrap().to_path_buf(); path.push("test_site"); let mut site = Site::new(&path).unwrap(); - site.parse().unwrap(); + site.load().unwrap(); for (i, page) in site.pages.values_mut().enumerate() { page.meta.category = if i % 2 == 0 { @@ -177,7 +177,7 @@ fn test_can_build_site_with_categories() { Some("B".to_string()) }; } - site.parse_tags_and_categories(); + site.populate_tags_and_categories(); let tmp_dir = TempDir::new("example").expect("create temp dir"); let public = &tmp_dir.path().join("public"); site.set_output_path(&public); @@ -219,7 +219,7 @@ fn test_can_build_site_with_tags() { let mut path = env::current_dir().unwrap().to_path_buf(); path.push("test_site"); let mut site = Site::new(&path).unwrap(); - site.parse().unwrap(); + site.load().unwrap(); for (i, page) in site.pages.values_mut().enumerate() { page.meta.tags = if i % 2 == 0 { @@ -228,7 +228,7 @@ fn test_can_build_site_with_tags() { Some(vec!["tag with space".to_string()]) }; } - site.parse_tags_and_categories(); + site.populate_tags_and_categories(); let tmp_dir = TempDir::new("example").expect("create temp dir"); let public = &tmp_dir.path().join("public");