From b2cbe13aaa1ee25b4f7710230a2a6c350e545381 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 27 Feb 2018 19:33:17 -0700 Subject: [PATCH] perf(layouts): Switch from lazy to eager No idea what impact this will have on performance actually. BREAKING CHANGE: The paths used for layouts now must be clean (no `//./file.liquid`). --- src/cobalt.rs | 83 ++++++++++++++++++++++++++++--------------------- src/document.rs | 24 +++++--------- tests/mod.rs | 2 +- 3 files changed, 56 insertions(+), 53 deletions(-) diff --git a/src/cobalt.rs b/src/cobalt.rs index 1f2e1156..5e184fcc 100644 --- a/src/cobalt.rs +++ b/src/cobalt.rs @@ -22,8 +22,8 @@ pub fn build(config: &Config) -> Result<()> { let dest = config.destination.as_path(); let parser = &config.liquid; - let layouts = &config.layouts_dir; - let mut layouts_cache = HashMap::new(); + let layouts = find_layouts(&config.layouts_dir)?; + let layouts = parse_layouts(layouts); debug!("Layouts directory: {:?}", layouts); @@ -35,12 +35,7 @@ pub fn build(config: &Config) -> Result<()> { let documents = parse_pages(&page_files, &config.pages, source)?; sort_pages(&mut posts, &config.posts)?; - generate_posts(&mut posts, - config, - &parser, - dest, - &layouts, - &mut layouts_cache)?; + generate_posts(&mut posts, config, &parser, dest, &layouts)?; // check if we should create an RSS file and create it! if let Some(ref path) = config.posts.rss { @@ -51,13 +46,7 @@ pub fn build(config: &Config) -> Result<()> { create_jsonfeed(path, dest, &config.posts, &posts)?; } - generate_pages(posts, - documents, - config, - &parser, - dest, - &layouts, - &mut layouts_cache)?; + generate_pages(posts, documents, config, &parser, dest, &layouts)?; // copy all remaining files in the source to the destination // compile SASS along the way @@ -70,8 +59,7 @@ pub fn build(config: &Config) -> Result<()> { } fn generate_doc(dest: &Path, - layouts: &Path, - mut layouts_cache: &mut HashMap, + layouts: &HashMap, parser: &cobalt_model::Liquid, posts_data: &[liquid::Value], doc: &mut Document, @@ -102,7 +90,7 @@ fn generate_doc(dest: &Path, // Refresh `page` with the `excerpt` / `content` attribute globals.insert("page".to_owned(), liquid::Value::Object(doc.attributes.clone())); - let doc_html = doc.render(&globals, parser, layouts, &mut layouts_cache) + let doc_html = doc.render(&globals, parser, &layouts) .chain_err(|| format!("Failed to render for {:?}", doc.file_path))?; files::write_document_file(doc_html, dest.join(&doc.file_path))?; Ok(()) @@ -113,8 +101,7 @@ fn generate_pages(posts: Vec, config: &Config, parser: &cobalt_model::Liquid, dest: &Path, - layouts: &Path, - mut layouts_cache: &mut HashMap) + layouts: &HashMap) -> Result<()> { // during post rendering additional attributes such as content were // added to posts. collect them so that non-post documents can access them @@ -126,13 +113,7 @@ fn generate_pages(posts: Vec, trace!("Generating other documents"); for mut doc in documents { trace!("Generating {}", doc.url_path); - generate_doc(dest, - layouts, - &mut layouts_cache, - parser, - &posts_data, - &mut doc, - config)?; + generate_doc(dest, &layouts, parser, &posts_data, &mut doc, config)?; } Ok(()) @@ -142,8 +123,7 @@ fn generate_posts(posts: &mut Vec, config: &Config, parser: &cobalt_model::Liquid, dest: &Path, - layouts: &Path, - mut layouts_cache: &mut HashMap) + layouts: &HashMap) -> Result<()> { // collect all posts attributes to pass them to other posts for rendering let simple_posts_data: Vec = posts @@ -170,13 +150,7 @@ fn generate_posts(posts: &mut Vec, .unwrap_or(liquid::Value::Nil); post.attributes.insert("next".to_owned(), next); - generate_doc(dest, - layouts, - &mut layouts_cache, - parser, - &simple_posts_data, - post, - config)?; + generate_doc(dest, &layouts, parser, &simple_posts_data, post, config)?; } Ok(()) @@ -280,6 +254,43 @@ fn find_page_files(source: &Path, collection: &Collection) -> Result Result { + let mut files = files::FilesBuilder::new(layouts)?; + files.ignore_hidden(false)?; + files.build() +} + +fn parse_layouts(files: files::Files) -> HashMap { + let (entries, errors): (Vec<_>, Vec<_>) = files + .files() + .map(|file_path| { + let rel_src = file_path + .strip_prefix(files.root()) + .expect("file was found under the root"); + + let layout_data = + files::read_file(&file_path) + .map_err(|e| format!("Failed to load layout {:?}: {}", rel_src, e))?; + + let path = rel_src + .to_str() + .ok_or_else(|| format!("File name not valid liquid path: {:?}", rel_src))? + .to_owned(); + + Ok((path, layout_data)) + }) + .partition(Result::is_ok); + + for error in errors { + warn!("{}", error.expect_err("partition to filter out oks")); + } + + entries + .into_iter() + .map(|entry| entry.expect("partition to filter out errors")) + .collect() +} + fn parse_pages(page_files: &files::Files, collection: &Collection, source: &Path) diff --git a/src/document.rs b/src/document.rs index 47cef29d..9c9010f8 100644 --- a/src/document.rs +++ b/src/document.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::collections::hash_map::Entry; use std::default::Default; use std::path::{Path, PathBuf}; use std::clone::Clone; @@ -326,23 +325,16 @@ impl Document { pub fn render(&mut self, globals: &liquid::Object, parser: &cobalt_model::Liquid, - layouts_dir: &Path, - layouts_cache: &mut HashMap) + layouts: &HashMap) -> Result { if let Some(ref layout) = self.front.layout { - let layout_data_ref = match layouts_cache.entry(layout.to_owned()) { - Entry::Vacant(vacant) => { - let layout_data = files::read_file(layouts_dir.join(layout)) - .map_err(|e| { - format!("Layout {} can not be read (defined in {:?}): {}", - layout, - self.file_path, - e) - })?; - vacant.insert(layout_data) - } - Entry::Occupied(occupied) => occupied.into_mut(), - }; + let layout_data_ref = layouts + .get(layout) + .ok_or_else(|| { + format!("Layout {} does not exist (referenced in {:?}).", + layout, + self.file_path) + })?; let template = parser .parse(layout_data_ref) diff --git a/tests/mod.rs b/tests/mod.rs index f9b61ff4..817addfe 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -185,7 +185,7 @@ pub fn no_extends_error() { let err = run_test("no_extends_error"); assert!(err.is_err()); assert_contains!(format!("{}", err.unwrap_err().display_chain()), - "Layout default_nonexistent.liquid can not be read (defined in \ + "Layout default_nonexistent.liquid does not exist (referenced in \ \"index.html\")"); }