Partial reloading of content on change

This commit is contained in:
Vincent Prouillet 2017-03-21 16:57:00 +09:00
parent b0cc1ac042
commit 8b5183d4ad
4 changed files with 69 additions and 35 deletions

View file

@ -6,6 +6,6 @@ use gutenberg::Site;
pub fn build() -> Result<()> { pub fn build() -> Result<()> {
let mut site = Site::new(env::current_dir().unwrap())?; let mut site = Site::new(env::current_dir().unwrap())?;
site.parse()?; site.load()?;
site.build() site.build()
} }

View file

@ -70,7 +70,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> {
format!("http://{}", address) format!("http://{}", address)
}; };
site.parse()?; site.load()?;
site.enable_live_reload(); site.enable_live_reload();
site.build()?; site.build()?;
report_elapsed_time(start); report_elapsed_time(start);
@ -129,7 +129,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> {
(ChangeKind::Content, _) => { (ChangeKind::Content, _) => {
println!("-> Content changed {}", path.display()); println!("-> Content changed {}", path.display());
// Force refresh // 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, _) => { (ChangeKind::Templates, _) => {
println!("-> Template changed {}", path.display()); println!("-> Template changed {}", path.display());

View file

@ -101,39 +101,60 @@ impl Site {
self.output_path = path.as_ref().to_path_buf(); 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 /// 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 path = self.base_path.to_string_lossy().replace("\\", "/");
let content_glob = format!("{}/{}", path, "content/**/*.md"); let content_glob = format!("{}/{}", path, "content/**/*.md");
// parent_dir -> Section // TODO: make that parallel, that's the main bottleneck
let mut sections = BTreeMap::new(); // `add_section` and `add_page` can't be used in the parallel version afaik
// Glob is giving us the result order so _index will show up first
// for each directory
for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) { for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) {
let path = entry.as_path(); let path = entry.as_path();
if path.file_name().unwrap() == "_index.md" { if path.file_name().unwrap() == "_index.md" {
let section = Section::from_file(&path, &self.config)?; self.add_section(&path)?;
sections.insert(section.parent_path.clone(), section);
} else { } else {
self.add_page(&path)?;
}
}
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)?; 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.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());
} }
} }
// Find out the direct subsections of each subsection if there are some
let mut grandparent_paths = HashMap::new(); 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(); let grand_parent = section.parent_path.parent().unwrap().to_path_buf();
grandparent_paths.entry(grand_parent).or_insert_with(|| vec![]).push(section.clone()); 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()); section.pages.sort_by(|a, b| a.partial_cmp(b).unwrap());
match grandparent_paths.get(parent_path) { match grandparent_paths.get(parent_path) {
@ -141,15 +162,10 @@ impl Site {
None => continue, None => continue,
}; };
} }
self.sections = sections;
self.parse_tags_and_categories();
Ok(())
} }
/// Separated from `parse` for easier testing /// 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() { for page in self.pages.values() {
if let Some(ref category) = page.meta.category { if let Some(ref category) = page.meta.category {
self.categories self.categories
@ -221,8 +237,26 @@ impl Site {
Ok(()) Ok(())
} }
pub fn rebuild_after_content_change(&mut self) -> Result<()> { pub fn rebuild_after_content_change(&mut self, path: &Path) -> Result<()> {
self.parse()?; 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() self.build()
} }

View file

@ -17,7 +17,7 @@ fn test_can_parse_site() {
let mut path = env::current_dir().unwrap().to_path_buf(); let mut path = env::current_dir().unwrap().to_path_buf();
path.push("test_site"); path.push("test_site");
let mut site = Site::new(&path).unwrap(); let mut site = Site::new(&path).unwrap();
site.parse().unwrap(); site.load().unwrap();
// Correct number of pages (sections are pages too) // Correct number of pages (sections are pages too)
assert_eq!(site.pages.len(), 10); 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(); let mut path = env::current_dir().unwrap().to_path_buf();
path.push("test_site"); path.push("test_site");
let mut site = Site::new(&path).unwrap(); let mut site = Site::new(&path).unwrap();
site.parse().unwrap(); site.load().unwrap();
let tmp_dir = TempDir::new("example").expect("create temp dir"); let tmp_dir = TempDir::new("example").expect("create temp dir");
let public = &tmp_dir.path().join("public"); let public = &tmp_dir.path().join("public");
site.set_output_path(&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(); let mut path = env::current_dir().unwrap().to_path_buf();
path.push("test_site"); path.push("test_site");
let mut site = Site::new(&path).unwrap(); let mut site = Site::new(&path).unwrap();
site.parse().unwrap(); site.load().unwrap();
let tmp_dir = TempDir::new("example").expect("create temp dir"); let tmp_dir = TempDir::new("example").expect("create temp dir");
let public = &tmp_dir.path().join("public"); let public = &tmp_dir.path().join("public");
site.set_output_path(&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(); let mut path = env::current_dir().unwrap().to_path_buf();
path.push("test_site"); path.push("test_site");
let mut site = Site::new(&path).unwrap(); let mut site = Site::new(&path).unwrap();
site.parse().unwrap(); site.load().unwrap();
for (i, page) in site.pages.values_mut().enumerate() { for (i, page) in site.pages.values_mut().enumerate() {
page.meta.category = if i % 2 == 0 { page.meta.category = if i % 2 == 0 {
@ -177,7 +177,7 @@ fn test_can_build_site_with_categories() {
Some("B".to_string()) 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 tmp_dir = TempDir::new("example").expect("create temp dir");
let public = &tmp_dir.path().join("public"); let public = &tmp_dir.path().join("public");
site.set_output_path(&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(); let mut path = env::current_dir().unwrap().to_path_buf();
path.push("test_site"); path.push("test_site");
let mut site = Site::new(&path).unwrap(); let mut site = Site::new(&path).unwrap();
site.parse().unwrap(); site.load().unwrap();
for (i, page) in site.pages.values_mut().enumerate() { for (i, page) in site.pages.values_mut().enumerate() {
page.meta.tags = if i % 2 == 0 { 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()]) 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 tmp_dir = TempDir::new("example").expect("create temp dir");
let public = &tmp_dir.path().join("public"); let public = &tmp_dir.path().join("public");