From 9278bd11c6e9de39df72877e53ba4dc9102b4c3f Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 6 Dec 2024 06:43:30 +0100 Subject: [PATCH 01/11] Compute colocated path for sections Colocated path is required for computing serialized assets and assets permalinks. Test have been added to check colocated path format as expected by those computations. --- components/content/src/file_info.rs | 36 ++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/components/content/src/file_info.rs b/components/content/src/file_info.rs index a94cb4310a..ab02cd303f 100644 --- a/components/content/src/file_info.rs +++ b/components/content/src/file_info.rs @@ -109,6 +109,18 @@ impl FileInfo { format!("{}.md", name) }; let grand_parent = parent.parent().map(|p| p.to_path_buf()); + let mut colocated_path = None; + + // If we have a folder with an asset, don't consider it as a component + // Splitting on `.` as we might have a language so it isn't *only* index but also index.fr + // etc + if !components.is_empty() && name.split('.').collect::>()[0] == "_index" { + colocated_path = Some({ + let mut val = components.join("/"); + val.push('/'); + val + }); + } FileInfo { filename: file_path.file_name().unwrap().to_string_lossy().to_string(), @@ -119,7 +131,7 @@ impl FileInfo { name, components, relative, - colocated_path: None, + colocated_path: colocated_path, } } @@ -289,4 +301,26 @@ mod tests { Path::new("/home/vincent/code/site/content/posts/tutorials/python/index") ); } + + #[test] + fn correct_colocated_path() { + let files = vec![ + FileInfo::new_page( + Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.md"), + &PathBuf::new(), + ), + FileInfo::new_section( + Path::new("/home/vincent/code/site/content/posts/tutorials/_index.fr.md"), + &PathBuf::new(), + ), + ]; + + for file in files { + assert!(file.colocated_path.is_some()); + if let Some(colocated_path) = file.colocated_path { + assert!(!colocated_path.starts_with('/')); + assert!(colocated_path.ends_with('/')); + } + } + } } From 8ff616d1736bfb749cb6b4525c8b7b67065d7e94 Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 6 Dec 2024 06:55:14 +0100 Subject: [PATCH 02/11] Harmonize serialize assets for pages and sections Fix assets serialization behavior which was relying on the output path instead of the source path like for pages. This behavior was making assets permalinks computation fail. --- components/content/src/page.rs | 35 +++++---------- components/content/src/section.rs | 28 ++++++------ components/content/src/utils.rs | 73 +++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 39 deletions(-) diff --git a/components/content/src/page.rs b/components/content/src/page.rs index 041ab981da..6e91179832 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -19,7 +19,7 @@ use crate::front_matter::{split_page_content, PageFrontMatter}; use crate::library::Library; use crate::ser::SerializingPage; use crate::utils::get_reading_analytics; -use crate::utils::{find_related_assets, has_anchor}; +use crate::utils::{find_related_assets, has_anchor, serialize_assets}; use utils::anchors::has_anchor_id; use utils::fs::read_file; @@ -195,7 +195,11 @@ impl Page { if page.file.name == "index" { let parent_dir = path.parent().unwrap(); page.assets = find_related_assets(parent_dir, config, true); - page.serialized_assets = page.serialize_assets(base_path); + page.serialized_assets = serialize_assets( + &page.assets, + page.file.path.parent(), + page.file.colocated_path.as_ref(), + ); } else { page.assets = vec![]; } @@ -255,28 +259,6 @@ impl Page { .with_context(|| format!("Failed to render page '{}'", self.file.path.display())) } - /// Creates a vectors of asset URLs. - fn serialize_assets(&self, base_path: &Path) -> Vec { - self.assets - .iter() - .filter_map(|asset| asset.strip_prefix(self.file.path.parent().unwrap()).ok()) - .filter_map(|filename| filename.to_str()) - .map(|filename| { - let mut path = self.file.path.clone(); - // Popping the index.md from the path since file.parent would be one level too high - // for our need here - path.pop(); - path.push(filename); - path = path - .strip_prefix(&base_path.join("content")) - .expect("Should be able to stripe prefix") - .to_path_buf(); - path - }) - .map(|path| format!("/{}", path.display())) - .collect() - } - pub fn has_anchor(&self, anchor: &str) -> bool { has_anchor(&self.toc, anchor) } @@ -589,6 +571,7 @@ And here's another. [^3] assert_eq!(page.file.parent, path.join("content").join("posts")); assert_eq!(page.slug, "with-assets"); assert_eq!(page.assets.len(), 3); + assert_eq!(page.serialized_assets.len(), 3); assert!(page.serialized_assets[0].starts_with('/')); assert_eq!(page.permalink, "http://a-website.com/posts/with-assets/"); } @@ -664,6 +647,9 @@ And here's another. [^3] assert_eq!(page.slug, "with-assets"); assert_eq!(page.meta.date, Some("2013-06-02".to_string())); assert_eq!(page.assets.len(), 3); + assert_eq!(page.serialized_assets.len(), 3); + // We should not get with-assets since that's the slugified version + assert!(page.serialized_assets[0].contains("2013-06-02")); assert_eq!(page.permalink, "http://a-website.com/posts/with-assets/"); } @@ -692,6 +678,7 @@ And here's another. [^3] let page = res.unwrap(); assert_eq!(page.assets.len(), 1); assert_eq!(page.assets[0].file_name().unwrap().to_str(), Some("graph.jpg")); + assert_eq!(page.serialized_assets.len(), 1); } // https://github.com/getzola/zola/issues/1566 diff --git a/components/content/src/section.rs b/components/content/src/section.rs index d4c84a9bd3..2077736b7b 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -15,7 +15,9 @@ use crate::file_info::FileInfo; use crate::front_matter::{split_section_content, SectionFrontMatter}; use crate::library::Library; use crate::ser::{SectionSerMode, SerializingSection}; -use crate::utils::{find_related_assets, get_reading_analytics, has_anchor}; +use crate::utils::{ + find_related_assets, get_reading_analytics, has_anchor, serialize_assets, +}; // Default is used to create a default index section if there is no _index.md in the root content directory #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -125,7 +127,11 @@ impl Section { let parent_dir = path.parent().unwrap(); section.assets = find_related_assets(parent_dir, config, false); - section.serialized_assets = section.serialize_assets(); + section.serialized_assets = serialize_assets( + §ion.assets, + section.file.path.parent(), + section.file.colocated_path.as_ref(), + ); Ok(section) } @@ -202,16 +208,6 @@ impl Section { self.file.components.is_empty() } - /// Creates a vectors of asset URLs. - fn serialize_assets(&self) -> Vec { - self.assets - .iter() - .filter_map(|asset| asset.strip_prefix(self.file.path.parent().unwrap()).ok()) - .filter_map(|filename| filename.to_str()) - .map(|filename| format!("{}{}", self.path, filename)) - .collect() - } - pub fn has_anchor(&self, anchor: &str) -> bool { has_anchor(&self.toc, anchor) } @@ -269,6 +265,7 @@ mod tests { assert!(res.is_ok()); let section = res.unwrap(); assert_eq!(section.assets.len(), 3); + assert_eq!(section.serialized_assets.len(), 3); assert!(section.serialized_assets[0].starts_with('/')); assert_eq!(section.permalink, "http://a-website.com/posts/with-assets/"); } @@ -300,9 +297,10 @@ mod tests { Section::from_file(article_path.join("_index.md").as_path(), &config, &PathBuf::new()); 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")); + let section = res.unwrap(); + assert_eq!(section.assets.len(), 1); + assert_eq!(section.assets[0].file_name().unwrap().to_str(), Some("graph.jpg")); + assert_eq!(section.serialized_assets.len(), 1); } #[test] diff --git a/components/content/src/utils.rs b/components/content/src/utils.rs index e269988624..5a9cbfe80d 100644 --- a/components/content/src/utils.rs +++ b/components/content/src/utils.rs @@ -58,6 +58,32 @@ pub fn find_related_assets(path: &Path, config: &Config, recursive: bool) -> Vec assets } +/// Serializes assets source path for assets colocated with a section or a page +pub fn serialize_assets( + assets: &Vec, + parent_path: Option<&Path>, + colocated_path: Option<&String>, +) -> Vec { + assets + .iter() + .filter_map(|asset| asset.strip_prefix(parent_path.unwrap()).ok()) + .map(|asset_relative_path| { + asset_relative_path + .components() + .map(|component| component.as_os_str().to_string_lossy().to_string()) + .collect::>() + .join("/") + }) + .map(|asset_relative_path_as_string| { + format!( + "/{}{}", + colocated_path.expect("Should have colocated path for assets"), + asset_relative_path_as_string + ) + }) + .collect() +} + /// Get word count and estimated reading time pub fn get_reading_analytics(content: &str) -> (usize, usize) { // code fences "toggle" the state from non-code to code and back, so anything inbetween the @@ -149,6 +175,53 @@ mod tests { ); } } + + #[test] + fn can_serialize_assets() { + let parent_path = Path::new("/tmp/test"); + let page_folder_path = parent_path.join("content").join("posts").join("my-article"); + let assets = vec![ + page_folder_path.join("example.js"), + page_folder_path.join("graph.jpg"), + page_folder_path.join("fail.png"), + page_folder_path.join("extensionless"), + page_folder_path.join("subdir").join("example.js"), + page_folder_path.join("FFF.txt"), + page_folder_path.join("GRAPH.txt"), + page_folder_path.join("subdir").join("GGG.txt"), + ]; + let colocated_path = "posts/my-article/".to_string(); + let expected_serialized_assets = vec![ + "/posts/my-article/example.js", + "/posts/my-article/graph.jpg", + "/posts/my-article/fail.png", + "/posts/my-article/extensionless", + "/posts/my-article/subdir/example.js", + "/posts/my-article/FFF.txt", + "/posts/my-article/GRAPH.txt", + "/posts/my-article/subdir/GGG.txt", + ]; + + let serialized_assets = + serialize_assets(&assets, Some(&page_folder_path), Some(&colocated_path)); + + assert_eq!( + serialized_assets, expected_serialized_assets, + "Serialized assets (left) are different from expected (right)", + ); + } + + #[test] + fn can_serialize_empty_assets() { + let parent_path = Path::new("/tmp/test"); + let page_folder_path = parent_path.join("content").join("posts").join("my-article"); + let assets: Vec = vec![]; + + let serialized_assets = serialize_assets(&assets, Some(&page_folder_path), None); + + assert!(serialized_assets.is_empty()); + } + #[test] fn can_find_anchor_at_root() { let input = vec![ From faed94a7ada8205ef99ab34995e4621b67e06537 Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 6 Dec 2024 07:02:57 +0100 Subject: [PATCH 03/11] Add assets permalinks for pages and sections Compute assets permalinks for serialized assets based on associated page or section permalink. --- components/content/src/page.rs | 49 ++++++++++++++++- components/content/src/section.rs | 23 +++++++- components/content/src/utils.rs | 89 +++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 2 deletions(-) diff --git a/components/content/src/page.rs b/components/content/src/page.rs index 6e91179832..6d3e77d29e 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -19,7 +19,7 @@ use crate::front_matter::{split_page_content, PageFrontMatter}; use crate::library::Library; use crate::ser::SerializingPage; use crate::utils::get_reading_analytics; -use crate::utils::{find_related_assets, has_anchor, serialize_assets}; +use crate::utils::{find_related_assets, get_assets_permalinks, has_anchor, serialize_assets}; use utils::anchors::has_anchor_id; use utils::fs::read_file; @@ -45,6 +45,8 @@ pub struct Page { pub assets: Vec, /// All the non-md files we found next to the .md file pub serialized_assets: Vec, + /// The permalinks of all the non-md files we found next to the .md file + pub assets_permalinks: HashMap, /// The HTML rendered of the page pub content: String, /// The slug of that page. @@ -200,6 +202,11 @@ impl Page { page.file.path.parent(), page.file.colocated_path.as_ref(), ); + page.assets_permalinks = get_assets_permalinks( + &page.serialized_assets, + &page.permalink, + page.file.colocated_path.as_ref(), + ); } else { page.assets = vec![]; } @@ -574,6 +581,16 @@ And here's another. [^3] assert_eq!(page.serialized_assets.len(), 3); assert!(page.serialized_assets[0].starts_with('/')); assert_eq!(page.permalink, "http://a-website.com/posts/with-assets/"); + assert_eq!(page.assets_permalinks.len(), 3); + let random_assets_permalinks_key = + page.assets_permalinks.keys().next().expect("assets permalinks key should be present"); + assert!(!random_assets_permalinks_key.starts_with('/')); + let random_assets_permalinks_value = page + .assets_permalinks + .values() + .next() + .expect("assets permalinks value should be present"); + assert!(random_assets_permalinks_value.starts_with(&page.permalink)); } #[test] @@ -597,6 +614,13 @@ And here's another. [^3] assert_eq!(page.slug, "hey"); assert_eq!(page.assets.len(), 3); assert_eq!(page.permalink, "http://a-website.com/posts/hey/"); + assert_eq!(page.assets_permalinks.len(), 3); + let random_assets_permalinks_value = page + .assets_permalinks + .values() + .next() + .expect("assets permalinks value should be present"); + assert!(random_assets_permalinks_value.starts_with(&page.permalink)); } // https://github.com/getzola/zola/issues/674 @@ -623,6 +647,17 @@ And here's another. [^3] // We should not get with-assets since that's the slugified version assert!(page.serialized_assets[0].contains("with_assets")); assert_eq!(page.permalink, "http://a-website.com/posts/with-assets/"); + assert_eq!(page.assets_permalinks.len(), 3); + let random_assets_permalinks_key = + page.assets_permalinks.keys().next().expect("assets permalinks key should be present"); + // We should not get with-assets since that's the slugified version + assert!(random_assets_permalinks_key.contains("with_assets")); + let random_assets_permalinks_value = page + .assets_permalinks + .values() + .next() + .expect("assets permalinks value should be present"); + assert!(random_assets_permalinks_value.starts_with(&page.permalink)); } // https://github.com/getzola/zola/issues/607 @@ -651,6 +686,17 @@ And here's another. [^3] // We should not get with-assets since that's the slugified version assert!(page.serialized_assets[0].contains("2013-06-02")); assert_eq!(page.permalink, "http://a-website.com/posts/with-assets/"); + assert_eq!(page.assets_permalinks.len(), 3); + let random_assets_permalinks_key = + page.assets_permalinks.keys().next().expect("assets permalinks key should be present"); + // We should not get with-assets since that's the slugified version + assert!(random_assets_permalinks_key.contains("2013-06-02")); + let random_assets_permalinks_value = page + .assets_permalinks + .values() + .next() + .expect("assets permalinks value should be present"); + assert!(random_assets_permalinks_value.starts_with(&page.permalink)); } #[test] @@ -679,6 +725,7 @@ And here's another. [^3] assert_eq!(page.assets.len(), 1); assert_eq!(page.assets[0].file_name().unwrap().to_str(), Some("graph.jpg")); assert_eq!(page.serialized_assets.len(), 1); + assert_eq!(page.assets_permalinks.len(), 1); } // https://github.com/getzola/zola/issues/1566 diff --git a/components/content/src/section.rs b/components/content/src/section.rs index 2077736b7b..fbbb2161e0 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -16,7 +16,7 @@ use crate::front_matter::{split_section_content, SectionFrontMatter}; use crate::library::Library; use crate::ser::{SectionSerMode, SerializingSection}; use crate::utils::{ - find_related_assets, get_reading_analytics, has_anchor, serialize_assets, + find_related_assets, get_assets_permalinks, get_reading_analytics, has_anchor, serialize_assets, }; // Default is used to create a default index section if there is no _index.md in the root content directory @@ -40,6 +40,8 @@ pub struct Section { pub assets: Vec, /// All the non-md files we found next to the .md file as string pub serialized_assets: Vec, + /// The permalinks of all the non-md files we found next to the .md file + pub assets_permalinks: HashMap, /// All direct pages of that section pub pages: Vec, /// All pages that cannot be sorted in this section @@ -132,6 +134,11 @@ impl Section { section.file.path.parent(), section.file.colocated_path.as_ref(), ); + section.assets_permalinks = get_assets_permalinks( + §ion.serialized_assets, + §ion.permalink, + section.file.colocated_path.as_ref(), + ); Ok(section) } @@ -268,6 +275,19 @@ mod tests { assert_eq!(section.serialized_assets.len(), 3); assert!(section.serialized_assets[0].starts_with('/')); assert_eq!(section.permalink, "http://a-website.com/posts/with-assets/"); + assert_eq!(section.assets_permalinks.len(), 3); + let random_assets_permalinks_key = section + .assets_permalinks + .keys() + .next() + .expect("assets permalinks key should be present"); + assert!(!random_assets_permalinks_key.starts_with('/')); + let random_assets_permalinks_value = section + .assets_permalinks + .values() + .next() + .expect("assets permalinks value should be present"); + assert!(random_assets_permalinks_value.starts_with(§ion.permalink)); } #[test] @@ -301,6 +321,7 @@ mod tests { assert_eq!(section.assets.len(), 1); assert_eq!(section.assets[0].file_name().unwrap().to_str(), Some("graph.jpg")); assert_eq!(section.serialized_assets.len(), 1); + assert_eq!(section.assets_permalinks.len(), 1); } #[test] diff --git a/components/content/src/utils.rs b/components/content/src/utils.rs index 5a9cbfe80d..f60212b971 100644 --- a/components/content/src/utils.rs +++ b/components/content/src/utils.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::path::{Path, PathBuf}; use libs::unicode_segmentation::UnicodeSegmentation; @@ -84,6 +85,27 @@ pub fn serialize_assets( .collect() } +/// Create assets permalinks based on the permalin of the section or the page they are colocated with +pub fn get_assets_permalinks( + serialized_assets: &Vec, + parent_permalink: &str, + colocated_path: Option<&String>, +) -> HashMap { + let mut permalinks = HashMap::new(); + if !serialized_assets.is_empty() { + let colocated_path = colocated_path.expect("Should have a colocated path for assets"); + for asset in serialized_assets { + let asset_file_path = asset.strip_prefix("/").unwrap_or(asset); + let page_relative_asset_path = asset_file_path + .strip_prefix(colocated_path) + .expect("Should be able to stripe colocated path from asset path"); + let asset_permalink = format!("{}{}", parent_permalink, page_relative_asset_path); + permalinks.insert(asset_file_path.to_string(), asset_permalink.to_string()); + } + } + permalinks +} + /// Get word count and estimated reading time pub fn get_reading_analytics(content: &str) -> (usize, usize) { // code fences "toggle" the state from non-code to code and back, so anything inbetween the @@ -222,6 +244,73 @@ mod tests { assert!(serialized_assets.is_empty()); } + #[test] + fn can_get_assets_permalinks() { + let serialized_assets = vec![ + "/posts/my-article/example.js".to_string(), + "/posts/my-article/graph.jpg".to_string(), + "/posts/my-article/fail.png".to_string(), + "/posts/my-article/extensionless".to_string(), + "/posts/my-article/subdir/example.js".to_string(), + "/posts/my-article/FFF.txt".to_string(), + "/posts/my-article/GRAPH.txt".to_string(), + "/posts/my-article/subdir/GGG.txt".to_string(), + ]; + let parent_permalink = "https://remplace-par-ton-url.fr/posts/my-super-article/"; + let colocated_path = "posts/my-article/".to_string(); + let mut expected_assets_permalinks = HashMap::::new(); + expected_assets_permalinks.insert( + "posts/my-article/example.js".to_string(), + format!("{}{}", parent_permalink, "example.js"), + ); + expected_assets_permalinks.insert( + "posts/my-article/graph.jpg".to_string(), + format!("{}{}", parent_permalink, "graph.jpg"), + ); + expected_assets_permalinks.insert( + "posts/my-article/fail.png".to_string(), + format!("{}{}", parent_permalink, "fail.png"), + ); + expected_assets_permalinks.insert( + "posts/my-article/extensionless".to_string(), + format!("{}{}", parent_permalink, "extensionless"), + ); + expected_assets_permalinks.insert( + "posts/my-article/subdir/example.js".to_string(), + format!("{}{}", parent_permalink, "subdir/example.js"), + ); + expected_assets_permalinks.insert( + "posts/my-article/FFF.txt".to_string(), + format!("{}{}", parent_permalink, "FFF.txt"), + ); + expected_assets_permalinks.insert( + "posts/my-article/GRAPH.txt".to_string(), + format!("{}{}", parent_permalink, "GRAPH.txt"), + ); + expected_assets_permalinks.insert( + "posts/my-article/subdir/GGG.txt".to_string(), + format!("{}{}", parent_permalink, "subdir/GGG.txt"), + ); + + let assets_permalinks = + get_assets_permalinks(&serialized_assets, &parent_permalink, Some(&colocated_path)); + + assert_eq!( + assets_permalinks, expected_assets_permalinks, + "Assets permalinks (left) are different from expected (right)", + ); + } + + #[test] + fn can_get_empty_assets_permalinks() { + let serialized_assets: Vec = vec![]; + let parent_permalink = "https://remplace-par-ton-url.fr/posts/my-super-article/"; + + let assets_permalinks = get_assets_permalinks(&serialized_assets, &parent_permalink, None); + + assert!(assets_permalinks.is_empty()); + } + #[test] fn can_find_anchor_at_root() { let input = vec![ From 106c547ecb4f0601ec36fa90844e57adafe39ba9 Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:38:13 +0100 Subject: [PATCH 04/11] Add assets permalinks per language to site Assets are duplicated for every language so permalinks should be releated to it in order to be language-aware. --- components/site/src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs index cd48c219e4..bab47a0d29 100644 --- a/components/site/src/lib.rs +++ b/components/site/src/lib.rs @@ -56,9 +56,12 @@ pub struct Site { pub static_path: PathBuf, pub templates_path: PathBuf, pub taxonomies: Vec, - /// A map of all .md files (section and pages) and their permalink + /// A map of all .md files (sections and pages) and their permalink /// We need that if there are relative links in the content that need to be resolved pub permalinks: HashMap, + /// A map of all assets (non .md files colocated to sections and pages) and their permalink, distributed per lang + /// We need that if there are relative links in the content that need to be resolved + pub assets_permalinks: HashMap>, /// Contains all pages and sections of the site pub library: Arc>, /// Whether to load draft pages @@ -103,6 +106,7 @@ impl Site { templates_path, taxonomies: Vec::new(), permalinks: HashMap::new(), + assets_permalinks: HashMap::new(), include_drafts: false, // We will allocate it properly later on library: Arc::new(RwLock::new(Library::default())), @@ -477,6 +481,10 @@ impl Site { } self.permalinks.insert(page.file.relative.clone(), page.permalink.clone()); + let assets_permalinks = + self.assets_permalinks.entry(page.lang.to_string()).or_insert(HashMap::new()); + assets_permalinks.extend(page.assets_permalinks.clone().into_iter()); + if render_md { let insert_anchor = self.find_parent_section_insert_anchor(&page.file.parent, &page.lang); @@ -512,6 +520,10 @@ impl Site { /// The `render` parameter is used in the serve command with --fast, when rebuilding a page. pub fn add_section(&mut self, mut section: Section, render_md: bool) -> Result<()> { self.permalinks.insert(section.file.relative.clone(), section.permalink.clone()); + let assets_permalinks = + self.assets_permalinks.entry(section.lang.to_string()).or_insert(HashMap::new()); + assets_permalinks.extend(section.assets_permalinks.clone().into_iter()); + if render_md { section.render_markdown( &self.permalinks, From 9620795dd08e16d01c0c5650806e9685f0e71045 Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:51:25 +0100 Subject: [PATCH 05/11] Add assets permalinks to `get_url` Assets permalinks allows to resolve internally colocated assets as well as pages and sections. --- components/site/src/tpls.rs | 1 + components/templates/src/global_fns/files.rs | 223 +++++++++++++++++-- 2 files changed, 205 insertions(+), 19 deletions(-) diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index c51d0de175..09595056ca 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -16,6 +16,7 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> { site.base_path.clone(), site.config.clone(), site.permalinks.clone(), + site.assets_permalinks.clone(), site.output_path.clone(), ), ); diff --git a/components/templates/src/global_fns/files.rs b/components/templates/src/global_fns/files.rs index eaad1231d5..ab5d595d90 100644 --- a/components/templates/src/global_fns/files.rs +++ b/components/templates/src/global_fns/files.rs @@ -30,6 +30,7 @@ pub struct GetUrl { base_path: PathBuf, config: Config, permalinks: HashMap, + assets_permalinks: HashMap>, output_path: PathBuf, } @@ -38,15 +39,21 @@ impl GetUrl { base_path: PathBuf, config: Config, permalinks: HashMap, + assets_permalinks: HashMap>, output_path: PathBuf, ) -> Self { - Self { base_path, config, permalinks, output_path } + Self { base_path, config, permalinks, assets_permalinks, output_path } } } -fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result { +fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result<(String, bool)> { + let mut split_path: Vec = path.split('.').map(String::from).collect(); + let ilast = split_path.len() - 1; + + let is_markdown = split_path[ilast].starts_with("md"); + if lang == config.default_language { - return Ok(path); + return Ok((path, is_markdown)); } if !config.other_languages().contains_key(lang) { @@ -55,10 +62,12 @@ fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result = path.split('.').map(String::from).collect(); - let ilast = split_path.len() - 1; - split_path[ilast] = format!("{}.{}", lang, split_path[ilast]); - Ok(split_path.join(".")) + if is_markdown { + split_path[ilast] = format!("{}.{}", lang, split_path[ilast]); + Ok((split_path.join("."), is_markdown)) + } else { + Ok((path, is_markdown)) + } } impl TeraFn for GetUrl { @@ -85,12 +94,22 @@ impl TeraFn for GetUrl { // if it starts with @/, resolve it as an internal link if path.starts_with("@/") { - let path_with_lang = match make_path_with_lang(path, &lang, &self.config) { + let (path_with_lang, is_markdown) = match make_path_with_lang(path, &lang, &self.config) + { Ok(x) => x, Err(e) => return Err(e), }; - match resolve_internal_link(&path_with_lang, &self.permalinks) { + let permalinks = if is_markdown { + &self.permalinks + } else { + match self.assets_permalinks.get(&lang) { + Some(permalink) => permalink, + None => &HashMap::new(), + } + }; + + match resolve_internal_link(&path_with_lang, permalinks) { Ok(resolved) => Ok(to_value(resolved.permalink).unwrap()), Err(_) => Err(format!( "`get_url`: could not resolve URL for link `{}` not found.", @@ -282,6 +301,7 @@ title = "A title" dir.path().to_path_buf(), Config::default(), HashMap::new(), + HashMap::new(), PathBuf::new(), ); let mut args = HashMap::new(); @@ -310,6 +330,7 @@ title = "A title" dir.path().to_path_buf(), Config::default(), HashMap::new(), + HashMap::new(), PathBuf::new(), ); let mut args = HashMap::new(); @@ -325,6 +346,7 @@ title = "A title" dir.path().to_path_buf(), Config::default(), HashMap::new(), + HashMap::new(), PathBuf::new(), ); let mut args = HashMap::new(); @@ -344,6 +366,7 @@ title = "A title" dir.path().to_path_buf(), Config::default(), HashMap::new(), + HashMap::new(), PathBuf::new(), ); let mut args = HashMap::new(); @@ -363,8 +386,13 @@ title = "A title" create_file(&public.join("style.css"), "// Hello world") .expect("Failed to create file in output directory"); - let static_fn = - GetUrl::new(dir.path().to_path_buf(), Config::default(), HashMap::new(), public); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + Config::default(), + HashMap::new(), + HashMap::new(), + public, + ); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("style.css").unwrap()); assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/style.css"); @@ -374,8 +402,13 @@ title = "A title" fn error_when_language_not_available() { let config = Config::parse(CONFIG_DATA).unwrap(); let dir = create_temp_dir(); - let static_fn = - GetUrl::new(dir.path().to_path_buf(), config, HashMap::new(), PathBuf::new()); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config, + HashMap::new(), + HashMap::new(), + PathBuf::new(), + ); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap()); args.insert("lang".to_string(), to_value("it").unwrap()); @@ -403,6 +436,7 @@ title = "A title" dir.path().to_path_buf(), config.clone(), permalinks.clone(), + HashMap::new(), PathBuf::new(), ); let mut args = HashMap::new(); @@ -427,7 +461,13 @@ title = "A title" "https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(), ); let dir = create_temp_dir(); - let static_fn = GetUrl::new(dir.path().to_path_buf(), config, permalinks, PathBuf::new()); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config, + permalinks, + HashMap::new(), + PathBuf::new(), + ); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap()); args.insert("lang".to_string(), to_value("en").unwrap()); @@ -437,6 +477,135 @@ title = "A title" ); } + #[test] + fn can_get_asset_url_with_default_language() { + let config = Config::parse(CONFIG_DATA).unwrap(); + let asset_path = "a_section/an_asset.jpg"; + let mut assets_permalinks = HashMap::new(); + let mut fr_assets_permalinks = HashMap::new(); + fr_assets_permalinks.insert( + asset_path.to_string(), + "https://remplace-par-ton-url.fr/a_section/an_asset.jpg".to_string(), + ); + assets_permalinks.insert("fr".to_string(), fr_assets_permalinks); + let mut en_assets_permalinks = HashMap::new(); + en_assets_permalinks.insert( + asset_path.to_string(), + "https://remplace-par-ton-url.fr/en/a_section/an_asset.jpg".to_string(), + ); + assets_permalinks.insert("en".to_string(), en_assets_permalinks); + let dir = create_temp_dir(); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config.clone(), + HashMap::new(), + assets_permalinks.clone(), + PathBuf::new(), + ); + let mut args = HashMap::new(); + args.insert("path".to_string(), to_value("@/a_section/an_asset.jpg").unwrap()); + args.insert("lang".to_string(), to_value("fr").unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "https://remplace-par-ton-url.fr/a_section/an_asset.jpg" + ); + } + + #[test] + fn can_get_asset_url_with_other_language() { + let config = Config::parse(CONFIG_DATA).unwrap(); + let asset_path = "a_section/an_asset.jpg"; + let mut assets_permalinks = HashMap::new(); + let mut fr_assets_permalinks = HashMap::new(); + fr_assets_permalinks.insert( + asset_path.to_string(), + "https://remplace-par-ton-url.fr/a_section/an_asset.jpg".to_string(), + ); + assets_permalinks.insert("fr".to_string(), fr_assets_permalinks); + let mut en_assets_permalinks = HashMap::new(); + en_assets_permalinks.insert( + asset_path.to_string(), + "https://remplace-par-ton-url.fr/en/a_section/an_asset.jpg".to_string(), + ); + assets_permalinks.insert("en".to_string(), en_assets_permalinks); + let dir = create_temp_dir(); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config.clone(), + HashMap::new(), + assets_permalinks.clone(), + PathBuf::new(), + ); + let mut args = HashMap::new(); + args.insert("path".to_string(), to_value("@/a_section/an_asset.jpg").unwrap()); + args.insert("lang".to_string(), to_value("en").unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "https://remplace-par-ton-url.fr/en/a_section/an_asset.jpg" + ); + } + + #[test] + fn look_for_markdown_in_permalinks() { + let config = Config::parse(CONFIG_DATA).unwrap(); + let non_asset_path = "a_section/a_page.md"; + let mut permalinks = HashMap::new(); + permalinks.insert( + non_asset_path.to_string(), + "https://remplace-par-ton-url.fr/a_section/a_page".to_string(), + ); + let mut assets_permalinks = HashMap::new(); + let mut fr_assets_permalinks = HashMap::new(); + fr_assets_permalinks + .insert(non_asset_path.to_string(), "only assets should be there".to_string()); + assets_permalinks.insert("fr".to_string(), fr_assets_permalinks); + let dir = create_temp_dir(); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config.clone(), + permalinks.clone(), + assets_permalinks.clone(), + PathBuf::new(), + ); + let mut args = HashMap::new(); + args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap()); + args.insert("lang".to_string(), to_value("fr").unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "https://remplace-par-ton-url.fr/a_section/a_page" + ); + } + + #[test] + fn look_for_asset_in_assets_permalinks() { + let config = Config::parse(CONFIG_DATA).unwrap(); + let asset_path = "a_section/an_asset.jpg"; + let mut permalinks = HashMap::new(); + permalinks.insert(asset_path.to_string(), "only markdown should be there".to_string()); + let mut assets_permalinks = HashMap::new(); + let mut fr_assets_permalinks = HashMap::new(); + fr_assets_permalinks.insert( + asset_path.to_string(), + "https://remplace-par-ton-url.fr/a_section/an_asset.jpg".to_string(), + ); + assets_permalinks.insert("fr".to_string(), fr_assets_permalinks); + let dir = create_temp_dir(); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config.clone(), + permalinks.clone(), + assets_permalinks.clone(), + PathBuf::new(), + ); + let mut args = HashMap::new(); + args.insert("path".to_string(), to_value("@/a_section/an_asset.jpg").unwrap()); + args.insert("lang".to_string(), to_value("fr").unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "https://remplace-par-ton-url.fr/a_section/an_asset.jpg" + ); + } + #[test] fn does_not_duplicate_lang() { let config = Config::parse(CONFIG_DATA).unwrap(); @@ -450,7 +619,13 @@ title = "A title" "https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(), ); let dir = create_temp_dir(); - let static_fn = GetUrl::new(dir.path().to_path_buf(), config, permalinks, PathBuf::new()); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config, + permalinks, + HashMap::new(), + PathBuf::new(), + ); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("/en/a_section/a_page/").unwrap()); args.insert("lang".to_string(), to_value("en").unwrap()); @@ -464,8 +639,13 @@ title = "A title" fn can_get_feed_urls_with_default_language() { let config = Config::parse(CONFIG_DATA).unwrap(); let dir = create_temp_dir(); - let static_fn = - GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new(), PathBuf::new()); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config.clone(), + HashMap::new(), + HashMap::new(), + PathBuf::new(), + ); for feed_filename in &config.feed_filenames { let mut args = HashMap::new(); args.insert("path".to_string(), to_value(feed_filename).unwrap()); @@ -478,8 +658,13 @@ title = "A title" fn can_get_feed_urls_with_other_language() { let config = Config::parse(CONFIG_DATA).unwrap(); let dir = create_temp_dir(); - let static_fn = - GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new(), PathBuf::new()); + let static_fn = GetUrl::new( + dir.path().to_path_buf(), + config.clone(), + HashMap::new(), + HashMap::new(), + PathBuf::new(), + ); for feed_filename in &config.feed_filenames { let mut args = HashMap::new(); args.insert("path".to_string(), to_value(feed_filename).unwrap()); From f74f551adde28d071f1de67fdbd670aeff555d8a Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:17:29 +0100 Subject: [PATCH 06/11] Fix documentation about galery Galery can be fixed by using internal resolution. Adding the lang as well allows to make the target URL lang-aware even if the image is just duplicated. Example file has been fixed as well even if it might not be used. --- docs/content/documentation/content/image-processing/index.md | 2 +- docs/templates/shortcodes/gallery.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/documentation/content/image-processing/index.md b/docs/content/documentation/content/image-processing/index.md index c04e46d986..6534eb2d0c 100644 --- a/docs/content/documentation/content/image-processing/index.md +++ b/docs/content/documentation/content/image-processing/index.md @@ -153,7 +153,7 @@ picture gallery with the following shortcode named `gallery.html`: {% for asset in page.assets -%} {%- if asset is matching("[.](jpg|png)$") -%} {% set image = resize_image(path=asset, width=240, height=180) %} - + {%- endif %} diff --git a/docs/templates/shortcodes/gallery.html b/docs/templates/shortcodes/gallery.html index d171d84974..af325d14a4 100644 --- a/docs/templates/shortcodes/gallery.html +++ b/docs/templates/shortcodes/gallery.html @@ -2,7 +2,7 @@ {% for asset in page.assets -%} {%- if asset is matching("[.](jpg|png)$") -%} {% set image = resize_image(path=asset, width=240, height=180) %} - + {%- endif %} From 49f66b266fb7b6bd2c3349153ba5c66f073fc13a Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Thu, 19 Dec 2024 06:38:18 +0100 Subject: [PATCH 07/11] Fix hashmap init for assets permalink test --- components/content/src/utils.rs | 64 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/components/content/src/utils.rs b/components/content/src/utils.rs index f60212b971..f0ea202564 100644 --- a/components/content/src/utils.rs +++ b/components/content/src/utils.rs @@ -258,39 +258,37 @@ mod tests { ]; let parent_permalink = "https://remplace-par-ton-url.fr/posts/my-super-article/"; let colocated_path = "posts/my-article/".to_string(); - let mut expected_assets_permalinks = HashMap::::new(); - expected_assets_permalinks.insert( - "posts/my-article/example.js".to_string(), - format!("{}{}", parent_permalink, "example.js"), - ); - expected_assets_permalinks.insert( - "posts/my-article/graph.jpg".to_string(), - format!("{}{}", parent_permalink, "graph.jpg"), - ); - expected_assets_permalinks.insert( - "posts/my-article/fail.png".to_string(), - format!("{}{}", parent_permalink, "fail.png"), - ); - expected_assets_permalinks.insert( - "posts/my-article/extensionless".to_string(), - format!("{}{}", parent_permalink, "extensionless"), - ); - expected_assets_permalinks.insert( - "posts/my-article/subdir/example.js".to_string(), - format!("{}{}", parent_permalink, "subdir/example.js"), - ); - expected_assets_permalinks.insert( - "posts/my-article/FFF.txt".to_string(), - format!("{}{}", parent_permalink, "FFF.txt"), - ); - expected_assets_permalinks.insert( - "posts/my-article/GRAPH.txt".to_string(), - format!("{}{}", parent_permalink, "GRAPH.txt"), - ); - expected_assets_permalinks.insert( - "posts/my-article/subdir/GGG.txt".to_string(), - format!("{}{}", parent_permalink, "subdir/GGG.txt"), - ); + let expected_assets_permalinks: HashMap<_, _> = HashMap::from_iter([ + ( + "posts/my-article/example.js".to_string(), + format!("{}{}", parent_permalink, "example.js"), + ), + ( + "posts/my-article/graph.jpg".to_string(), + format!("{}{}", parent_permalink, "graph.jpg"), + ), + ( + "posts/my-article/fail.png".to_string(), + format!("{}{}", parent_permalink, "fail.png"), + ), + ( + "posts/my-article/extensionless".to_string(), + format!("{}{}", parent_permalink, "extensionless"), + ), + ( + "posts/my-article/subdir/example.js".to_string(), + format!("{}{}", parent_permalink, "subdir/example.js"), + ), + ("posts/my-article/FFF.txt".to_string(), format!("{}{}", parent_permalink, "FFF.txt")), + ( + "posts/my-article/GRAPH.txt".to_string(), + format!("{}{}", parent_permalink, "GRAPH.txt"), + ), + ( + "posts/my-article/subdir/GGG.txt".to_string(), + format!("{}{}", parent_permalink, "subdir/GGG.txt"), + ), + ]); let assets_permalinks = get_assets_permalinks(&serialized_assets, &parent_permalink, Some(&colocated_path)); From 334f687385a96f9f632abecb89dcd19d12355fef Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 3 Jan 2025 08:59:14 +0100 Subject: [PATCH 08/11] Check page and section entire colocated path As discussed during PR review, it is better to check for the entire path than just the required assumptions. Assumptions have been written as a comment. --- components/content/src/file_info.rs | 45 +++++++++++++++++++---------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/components/content/src/file_info.rs b/components/content/src/file_info.rs index ab02cd303f..fd58f21d70 100644 --- a/components/content/src/file_info.rs +++ b/components/content/src/file_info.rs @@ -304,23 +304,38 @@ mod tests { #[test] fn correct_colocated_path() { - let files = vec![ - FileInfo::new_page( - Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.md"), - &PathBuf::new(), - ), - FileInfo::new_section( - Path::new("/home/vincent/code/site/content/posts/tutorials/_index.fr.md"), - &PathBuf::new(), - ), + struct Test<'a> { + file_info: FileInfo, + expected_colocated_path: &'a str, + } + + // A colocated path: + // - MUST NOT start with a '/' + // - MUST end with a '/' + // Breaking those assumptions may have uncontrolled side effects in some other code, including but not limited to assets permalinks generation. + let tests = vec![ + Test { + file_info: FileInfo::new_page( + Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.md"), + &PathBuf::new(), + ), + expected_colocated_path: "posts/tutorials/python/", + }, + Test { + file_info: FileInfo::new_section( + Path::new("/home/vincent/code/site/content/posts/tutorials/_index.fr.md"), + &PathBuf::new(), + ), + expected_colocated_path: "posts/tutorials/", + }, ]; - for file in files { - assert!(file.colocated_path.is_some()); - if let Some(colocated_path) = file.colocated_path { - assert!(!colocated_path.starts_with('/')); - assert!(colocated_path.ends_with('/')); - } + for test in tests { + assert!(test.file_info.colocated_path.is_some()); + assert_eq!( + test.file_info.colocated_path.as_ref().unwrap(), + test.expected_colocated_path + ) } } } From 23051b8f2a0077ccc64e524745a83ea5f10227f4 Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:16:57 +0100 Subject: [PATCH 09/11] Rework serialize_assets and get_assets_permalinks As discussed during PR review, move option unwrapping logic outside of each function --- components/content/src/page.rs | 30 ++++++++++++++-------- components/content/src/section.rs | 32 ++++++++++++++++-------- components/content/src/utils.rs | 41 ++++++------------------------- 3 files changed, 49 insertions(+), 54 deletions(-) diff --git a/components/content/src/page.rs b/components/content/src/page.rs index 6d3e77d29e..9fff5ed4ef 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -197,16 +197,26 @@ impl Page { if page.file.name == "index" { let parent_dir = path.parent().unwrap(); page.assets = find_related_assets(parent_dir, config, true); - page.serialized_assets = serialize_assets( - &page.assets, - page.file.path.parent(), - page.file.colocated_path.as_ref(), - ); - page.assets_permalinks = get_assets_permalinks( - &page.serialized_assets, - &page.permalink, - page.file.colocated_path.as_ref(), - ); + if !page.assets.is_empty() { + page.serialized_assets = serialize_assets( + &page.assets, + page.file.path.parent().unwrap(), + page.file + .colocated_path + .as_ref() + .expect("Should have colocated path for assets"), + ); + } + if !page.serialized_assets.is_empty() { + page.assets_permalinks = get_assets_permalinks( + &page.serialized_assets, + &page.permalink, + page.file + .colocated_path + .as_ref() + .expect("Should have colocated path for assets"), + ); + } } else { page.assets = vec![]; } diff --git a/components/content/src/section.rs b/components/content/src/section.rs index fbbb2161e0..2cf36644f8 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -129,16 +129,28 @@ impl Section { let parent_dir = path.parent().unwrap(); section.assets = find_related_assets(parent_dir, config, false); - section.serialized_assets = serialize_assets( - §ion.assets, - section.file.path.parent(), - section.file.colocated_path.as_ref(), - ); - section.assets_permalinks = get_assets_permalinks( - §ion.serialized_assets, - §ion.permalink, - section.file.colocated_path.as_ref(), - ); + if !section.assets.is_empty() { + section.serialized_assets = serialize_assets( + §ion.assets, + section.file.path.parent().unwrap(), + section + .file + .colocated_path + .as_ref() + .expect("Should have colocated path for assets"), + ); + } + if !section.serialized_assets.is_empty() { + section.assets_permalinks = get_assets_permalinks( + §ion.serialized_assets, + §ion.permalink, + section + .file + .colocated_path + .as_ref() + .expect("Should have colocated path for assets"), + ); + } Ok(section) } diff --git a/components/content/src/utils.rs b/components/content/src/utils.rs index f0ea202564..bd9d3ae06e 100644 --- a/components/content/src/utils.rs +++ b/components/content/src/utils.rs @@ -62,12 +62,12 @@ pub fn find_related_assets(path: &Path, config: &Config, recursive: bool) -> Vec /// Serializes assets source path for assets colocated with a section or a page pub fn serialize_assets( assets: &Vec, - parent_path: Option<&Path>, - colocated_path: Option<&String>, + parent_path: &Path, + colocated_path: &str, ) -> Vec { assets .iter() - .filter_map(|asset| asset.strip_prefix(parent_path.unwrap()).ok()) + .filter_map(|asset| asset.strip_prefix(parent_path).ok()) .map(|asset_relative_path| { asset_relative_path .components() @@ -76,11 +76,7 @@ pub fn serialize_assets( .join("/") }) .map(|asset_relative_path_as_string| { - format!( - "/{}{}", - colocated_path.expect("Should have colocated path for assets"), - asset_relative_path_as_string - ) + format!("/{}{}", colocated_path, asset_relative_path_as_string) }) .collect() } @@ -89,11 +85,10 @@ pub fn serialize_assets( pub fn get_assets_permalinks( serialized_assets: &Vec, parent_permalink: &str, - colocated_path: Option<&String>, + colocated_path: &str, ) -> HashMap { let mut permalinks = HashMap::new(); if !serialized_assets.is_empty() { - let colocated_path = colocated_path.expect("Should have a colocated path for assets"); for asset in serialized_assets { let asset_file_path = asset.strip_prefix("/").unwrap_or(asset); let page_relative_asset_path = asset_file_path @@ -224,8 +219,7 @@ mod tests { "/posts/my-article/subdir/GGG.txt", ]; - let serialized_assets = - serialize_assets(&assets, Some(&page_folder_path), Some(&colocated_path)); + let serialized_assets = serialize_assets(&assets, &page_folder_path, &colocated_path); assert_eq!( serialized_assets, expected_serialized_assets, @@ -233,17 +227,6 @@ mod tests { ); } - #[test] - fn can_serialize_empty_assets() { - let parent_path = Path::new("/tmp/test"); - let page_folder_path = parent_path.join("content").join("posts").join("my-article"); - let assets: Vec = vec![]; - - let serialized_assets = serialize_assets(&assets, Some(&page_folder_path), None); - - assert!(serialized_assets.is_empty()); - } - #[test] fn can_get_assets_permalinks() { let serialized_assets = vec![ @@ -291,7 +274,7 @@ mod tests { ]); let assets_permalinks = - get_assets_permalinks(&serialized_assets, &parent_permalink, Some(&colocated_path)); + get_assets_permalinks(&serialized_assets, &parent_permalink, &colocated_path); assert_eq!( assets_permalinks, expected_assets_permalinks, @@ -299,16 +282,6 @@ mod tests { ); } - #[test] - fn can_get_empty_assets_permalinks() { - let serialized_assets: Vec = vec![]; - let parent_permalink = "https://remplace-par-ton-url.fr/posts/my-super-article/"; - - let assets_permalinks = get_assets_permalinks(&serialized_assets, &parent_permalink, None); - - assert!(assets_permalinks.is_empty()); - } - #[test] fn can_find_anchor_at_root() { let input = vec![ From d213e39dbf39ee3668ecd65b0873d30260f11966 Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Sun, 5 Jan 2025 07:43:32 +0100 Subject: [PATCH 10/11] Use iterator for get_asset_permalinks As per PR discussion, align get_asset_permalinks with serialize_assets. --- components/content/src/utils.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/components/content/src/utils.rs b/components/content/src/utils.rs index bd9d3ae06e..9952f259b3 100644 --- a/components/content/src/utils.rs +++ b/components/content/src/utils.rs @@ -87,18 +87,17 @@ pub fn get_assets_permalinks( parent_permalink: &str, colocated_path: &str, ) -> HashMap { - let mut permalinks = HashMap::new(); - if !serialized_assets.is_empty() { - for asset in serialized_assets { - let asset_file_path = asset.strip_prefix("/").unwrap_or(asset); + serialized_assets + .iter() + .map(|asset| asset.strip_prefix("/").unwrap_or(asset)) + .map(|asset_file_path| { let page_relative_asset_path = asset_file_path .strip_prefix(colocated_path) .expect("Should be able to stripe colocated path from asset path"); let asset_permalink = format!("{}{}", parent_permalink, page_relative_asset_path); - permalinks.insert(asset_file_path.to_string(), asset_permalink.to_string()); - } - } - permalinks + (asset_file_path.to_string(), asset_permalink.to_string()) + }) + .collect() } /// Get word count and estimated reading time From 2f69aa57f928ec7599fdeea0fd17b0070361619d Mon Sep 17 00:00:00 2001 From: ZzMzaw <89450172+ZzMzaw@users.noreply.github.com> Date: Wed, 8 Jan 2025 08:57:07 +0100 Subject: [PATCH 11/11] Regroup assets serialization and permalinks calls As per PR discussion, it makes more sense to have both asset serialization and permalinks generation in the same logical unit. Change has been made for both page and section. --- components/content/src/page.rs | 22 ++++----- components/content/src/section.rs | 19 +++----- q | 74 +++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 q diff --git a/components/content/src/page.rs b/components/content/src/page.rs index 9fff5ed4ef..71fed7153a 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -198,24 +198,18 @@ impl Page { let parent_dir = path.parent().unwrap(); page.assets = find_related_assets(parent_dir, config, true); if !page.assets.is_empty() { + let colocated_path = page + .file + .colocated_path + .as_ref() + .expect("Should have colocated path for assets"); page.serialized_assets = serialize_assets( &page.assets, page.file.path.parent().unwrap(), - page.file - .colocated_path - .as_ref() - .expect("Should have colocated path for assets"), - ); - } - if !page.serialized_assets.is_empty() { - page.assets_permalinks = get_assets_permalinks( - &page.serialized_assets, - &page.permalink, - page.file - .colocated_path - .as_ref() - .expect("Should have colocated path for assets"), + colocated_path, ); + page.assets_permalinks = + get_assets_permalinks(&page.serialized_assets, &page.permalink, colocated_path); } } else { page.assets = vec![]; diff --git a/components/content/src/section.rs b/components/content/src/section.rs index 2cf36644f8..b4ac035e03 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -130,25 +130,20 @@ impl Section { let parent_dir = path.parent().unwrap(); section.assets = find_related_assets(parent_dir, config, false); if !section.assets.is_empty() { + let colocated_path = section + .file + .colocated_path + .as_ref() + .expect("Should have colocated path for assets"); section.serialized_assets = serialize_assets( §ion.assets, section.file.path.parent().unwrap(), - section - .file - .colocated_path - .as_ref() - .expect("Should have colocated path for assets"), + colocated_path, ); - } - if !section.serialized_assets.is_empty() { section.assets_permalinks = get_assets_permalinks( §ion.serialized_assets, §ion.permalink, - section - .file - .colocated_path - .as_ref() - .expect("Should have colocated path for assets"), + colocated_path, ); } diff --git a/q b/q new file mode 100644 index 0000000000..1d3c7540e3 --- /dev/null +++ b/q @@ -0,0 +1,74 @@ +diff --git a/components/content/src/page.rs b/components/content/src/page.rs +index 9fff5ed4..71fed715 100644 +--- a/components/content/src/page.rs ++++ b/components/content/src/page.rs +@@ -198,24 +198,18 @@ impl Page { + let parent_dir = path.parent().unwrap(); + page.assets = find_related_assets(parent_dir, config, true); + if !page.assets.is_empty() { ++ let colocated_path = page ++ .file ++ .colocated_path ++ .as_ref() ++ .expect("Should have colocated path for assets"); + page.serialized_assets = serialize_assets( + &page.assets, + page.file.path.parent().unwrap(), +- page.file +- .colocated_path +- .as_ref() +- .expect("Should have colocated path for assets"), +- ); +- } +- if !page.serialized_assets.is_empty() { +- page.assets_permalinks = get_assets_permalinks( +- &page.serialized_assets, +- &page.permalink, +- page.file +- .colocated_path +- .as_ref() +- .expect("Should have colocated path for assets"), ++ colocated_path, + ); ++ page.assets_permalinks = ++ get_assets_permalinks(&page.serialized_assets, &page.permalink, colocated_path); + } + } else { + page.assets = vec![]; +diff --git a/components/content/src/section.rs b/components/content/src/section.rs +index 2cf36644..b4ac035e 100644 +--- a/components/content/src/section.rs ++++ b/components/content/src/section.rs +@@ -130,25 +130,20 @@ impl Section { + let parent_dir = path.parent().unwrap(); + section.assets = find_related_assets(parent_dir, config, false); + if !section.assets.is_empty() { ++ let colocated_path = section ++ .file ++ .colocated_path ++ .as_ref() ++ .expect("Should have colocated path for assets"); + section.serialized_assets = serialize_assets( + §ion.assets, + section.file.path.parent().unwrap(), +- section +- .file +- .colocated_path +- .as_ref() +- .expect("Should have colocated path for assets"), ++ colocated_path, + ); +- } +- if !section.serialized_assets.is_empty() { + section.assets_permalinks = get_assets_permalinks( + §ion.serialized_assets, + §ion.permalink, +- section +- .file +- .colocated_path +- .as_ref() +- .expect("Should have colocated path for assets"), ++ colocated_path, + ); + } +