|
|
@@ -8,7 +8,7 @@ use serde::ser::{SerializeStruct, self}; |
|
|
|
use config::Config; |
|
|
|
use front_matter::{SectionFrontMatter, split_section_content}; |
|
|
|
use errors::{Result, ResultExt}; |
|
|
|
use utils::fs::read_file; |
|
|
|
use utils::fs::{read_file, find_related_assets}; |
|
|
|
use utils::templates::render_template; |
|
|
|
use utils::site::get_reading_analytics; |
|
|
|
use rendering::{RenderContext, Header, render_content}; |
|
|
@@ -33,6 +33,8 @@ pub struct Section { |
|
|
|
pub raw_content: String, |
|
|
|
/// The HTML rendered of the page |
|
|
|
pub content: String, |
|
|
|
/// All the non-md files we found next to the .md file |
|
|
|
pub assets: Vec<PathBuf>, |
|
|
|
/// All direct pages of that section |
|
|
|
pub pages: Vec<Page>, |
|
|
|
/// All pages that cannot be sorted in this section |
|
|
@@ -54,6 +56,7 @@ impl Section { |
|
|
|
components: vec![], |
|
|
|
permalink: "".to_string(), |
|
|
|
raw_content: "".to_string(), |
|
|
|
assets: vec![], |
|
|
|
content: "".to_string(), |
|
|
|
pages: vec![], |
|
|
|
ignored_pages: vec![], |
|
|
@@ -79,8 +82,35 @@ impl Section { |
|
|
|
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Section> { |
|
|
|
let path = path.as_ref(); |
|
|
|
let content = read_file(path)?; |
|
|
|
let mut section = Section::parse(path, &content, config)?; |
|
|
|
|
|
|
|
Section::parse(path, &content, config) |
|
|
|
if section.file.name == "_index" { |
|
|
|
let parent_dir = path.parent().unwrap(); |
|
|
|
let assets = find_related_assets(parent_dir); |
|
|
|
|
|
|
|
if let Some(ref globset) = config.ignored_content_globset { |
|
|
|
// `find_related_assets` only scans the immediate directory (it is not recursive) so our |
|
|
|
// filtering only needs to work against the file_name component, not the full suffix. If |
|
|
|
// `find_related_assets` was changed to also return files in subdirectories, we could |
|
|
|
// use `PathBuf.strip_prefix` to remove the parent directory and then glob-filter |
|
|
|
// against the remaining path. Note that the current behaviour effectively means that |
|
|
|
// the `ignored_content` setting in the config file is limited to single-file glob |
|
|
|
// patterns (no "**" patterns). |
|
|
|
section.assets = assets.into_iter() |
|
|
|
.filter(|path| |
|
|
|
match path.file_name() { |
|
|
|
None => true, |
|
|
|
Some(file) => !globset.is_match(file) |
|
|
|
} |
|
|
|
).collect(); |
|
|
|
} else { |
|
|
|
section.assets = assets; |
|
|
|
} |
|
|
|
} else { |
|
|
|
section.assets = vec![]; |
|
|
|
} |
|
|
|
|
|
|
|
Ok(section) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn get_template_name(&self) -> String { |
|
|
@@ -146,6 +176,15 @@ impl Section { |
|
|
|
pub fn is_child_page(&self, path: &PathBuf) -> bool { |
|
|
|
self.all_pages_path().contains(path) |
|
|
|
} |
|
|
|
|
|
|
|
/// Creates a vectors of asset URLs. |
|
|
|
fn serialize_assets(&self) -> Vec<String> { |
|
|
|
self.assets.iter() |
|
|
|
.filter_map(|asset| asset.file_name()) |
|
|
|
.filter_map(|filename| filename.to_str()) |
|
|
|
.map(|filename| self.path.clone() + filename) |
|
|
|
.collect() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
impl ser::Serialize for Section { |
|
|
@@ -165,6 +204,8 @@ impl ser::Serialize for Section { |
|
|
|
state.serialize_field("word_count", &word_count)?; |
|
|
|
state.serialize_field("reading_time", &reading_time)?; |
|
|
|
state.serialize_field("toc", &self.toc)?; |
|
|
|
let assets = self.serialize_assets(); |
|
|
|
state.serialize_field("assets", &assets)?; |
|
|
|
state.end() |
|
|
|
} |
|
|
|
} |
|
|
@@ -179,6 +220,7 @@ impl Default for Section { |
|
|
|
components: vec![], |
|
|
|
permalink: "".to_string(), |
|
|
|
raw_content: "".to_string(), |
|
|
|
assets: vec![], |
|
|
|
content: "".to_string(), |
|
|
|
pages: vec![], |
|
|
|
ignored_pages: vec![], |
|
|
@@ -187,3 +229,69 @@ impl Default for Section { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
mod tests { |
|
|
|
use std::io::Write; |
|
|
|
use std::fs::{File, create_dir}; |
|
|
|
|
|
|
|
use tempfile::tempdir; |
|
|
|
use globset::{Glob, GlobSetBuilder}; |
|
|
|
|
|
|
|
use config::Config; |
|
|
|
use super::Section; |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn section_with_assets_gets_right_info() { |
|
|
|
let tmp_dir = tempdir().expect("create temp dir"); |
|
|
|
let path = tmp_dir.path(); |
|
|
|
create_dir(&path.join("content")).expect("create content temp dir"); |
|
|
|
create_dir(&path.join("content").join("posts")).expect("create posts temp dir"); |
|
|
|
let nested_path = path.join("content").join("posts").join("with-assets"); |
|
|
|
create_dir(&nested_path).expect("create nested temp dir"); |
|
|
|
let mut f = File::create(nested_path.join("_index.md")).unwrap(); |
|
|
|
f.write_all(b"+++\n+++\n").unwrap(); |
|
|
|
File::create(nested_path.join("example.js")).unwrap(); |
|
|
|
File::create(nested_path.join("graph.jpg")).unwrap(); |
|
|
|
File::create(nested_path.join("fail.png")).unwrap(); |
|
|
|
|
|
|
|
let res = Section::from_file( |
|
|
|
nested_path.join("_index.md").as_path(), |
|
|
|
&Config::default(), |
|
|
|
); |
|
|
|
assert!(res.is_ok()); |
|
|
|
let section = res.unwrap(); |
|
|
|
assert_eq!(section.assets.len(), 3); |
|
|
|
assert_eq!(section.permalink, "http://a-website.com/posts/with-assets/"); |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn section_with_ignored_assets_filters_out_correct_files() { |
|
|
|
let tmp_dir = tempdir().expect("create temp dir"); |
|
|
|
let path = tmp_dir.path(); |
|
|
|
create_dir(&path.join("content")).expect("create content temp dir"); |
|
|
|
create_dir(&path.join("content").join("posts")).expect("create posts temp dir"); |
|
|
|
let nested_path = path.join("content").join("posts").join("with-assets"); |
|
|
|
create_dir(&nested_path).expect("create nested temp dir"); |
|
|
|
let mut f = File::create(nested_path.join("_index.md")).unwrap(); |
|
|
|
f.write_all(b"+++\nslug=\"hey\"\n+++\n").unwrap(); |
|
|
|
File::create(nested_path.join("example.js")).unwrap(); |
|
|
|
File::create(nested_path.join("graph.jpg")).unwrap(); |
|
|
|
File::create(nested_path.join("fail.png")).unwrap(); |
|
|
|
|
|
|
|
let mut gsb = GlobSetBuilder::new(); |
|
|
|
gsb.add(Glob::new("*.{js,png}").unwrap()); |
|
|
|
let mut config = Config::default(); |
|
|
|
config.ignored_content_globset = Some(gsb.build().unwrap()); |
|
|
|
|
|
|
|
let res = Section::from_file( |
|
|
|
nested_path.join("_index.md").as_path(), |
|
|
|
&config, |
|
|
|
); |
|
|
|
|
|
|
|
assert!(res.is_ok()); |
|
|
|
let page = res.unwrap(); |
|
|
|
assert_eq!(page.assets.len(), 1); |
|
|
|
assert_eq!(page.assets[0].file_name().unwrap().to_str(), Some("graph.jpg")); |
|
|
|
} |
|
|
|
} |