From 523f367d754de61d0de642d07ffab1fefad59e2c Mon Sep 17 00:00:00 2001
From: Tim Schumacher <tim@tschumacher.net>
Date: Sun, 8 Aug 2021 16:15:35 +0200
Subject: [PATCH 1/2] Support colocating subfolders

---
 Cargo.lock                                |  1 +
 components/library/Cargo.toml             |  1 +
 components/library/src/content/mod.rs     | 47 +++++++++++------------
 components/library/src/content/page.rs    |  2 +-
 components/library/src/content/section.rs |  2 +-
 components/rendering/src/markdown.rs      | 17 --------
 components/site/src/lib.rs                |  4 +-
 7 files changed, 29 insertions(+), 45 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 5148e9d6a2..7615a68f3b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1261,6 +1261,7 @@ dependencies = [
  "tera",
  "toml",
  "utils",
+ "walkdir",
 ]
 
 [[package]]
diff --git a/components/library/Cargo.toml b/components/library/Cargo.toml
index bfaac903d0..d7122e0e2d 100644
--- a/components/library/Cargo.toml
+++ b/components/library/Cargo.toml
@@ -14,6 +14,7 @@ serde_derive = "1"
 regex = "1"
 lazy_static = "1"
 lexical-sort = "0.3"
+walkdir = "2"
 
 front_matter = { path = "../front_matter" }
 config = { path = "../config" }
diff --git a/components/library/src/content/mod.rs b/components/library/src/content/mod.rs
index 161a1ab900..377a86016d 100644
--- a/components/library/src/content/mod.rs
+++ b/components/library/src/content/mod.rs
@@ -3,9 +3,10 @@ mod page;
 mod section;
 mod ser;
 
-use std::fs::read_dir;
 use std::path::{Path, PathBuf};
 
+use walkdir::WalkDir;
+
 pub use self::file_info::FileInfo;
 pub use self::page::Page;
 pub use self::section::Section;
@@ -32,7 +33,7 @@ pub fn has_anchor(headings: &[Heading], anchor: &str) -> bool {
 pub fn find_related_assets(path: &Path, config: &Config) -> Vec<PathBuf> {
     let mut assets = vec![];
 
-    for entry in read_dir(path).unwrap().filter_map(std::result::Result::ok) {
+    for entry in WalkDir::new(path).into_iter().filter_map(std::result::Result::ok) {
         let entry_path = entry.path();
         if entry_path.is_file() {
             match entry_path.extension() {
@@ -46,18 +47,11 @@ pub fn find_related_assets(path: &Path, config: &Config) -> Vec<PathBuf> {
     }
 
     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).
         assets = assets
             .into_iter()
-            .filter(|path| match path.file_name() {
-                None => false,
-                Some(file) => !globset.is_match(file),
+            .filter(|p| match p.strip_prefix(path) {
+                Err(_) => false,
+                Ok(file) => !globset.is_match(file),
             })
             .collect();
     }
@@ -68,7 +62,7 @@ pub fn find_related_assets(path: &Path, config: &Config) -> Vec<PathBuf> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use std::fs::File;
+    use std::fs::{File, create_dir};
 
     use config::Config;
     use tempfile::tempdir;
@@ -76,17 +70,22 @@ mod tests {
     #[test]
     fn can_find_related_assets() {
         let tmp_dir = tempdir().expect("create temp dir");
-        File::create(tmp_dir.path().join("index.md")).unwrap();
-        File::create(tmp_dir.path().join("example.js")).unwrap();
-        File::create(tmp_dir.path().join("graph.jpg")).unwrap();
-        File::create(tmp_dir.path().join("fail.png")).unwrap();
-
-        let assets = find_related_assets(tmp_dir.path(), &Config::default());
-        assert_eq!(assets.len(), 3);
-        assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3);
-        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1);
-        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1);
-        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1);
+        let path = tmp_dir.path();
+        File::create(path.join("index.md")).unwrap();
+        File::create(path.join("example.js")).unwrap();
+        File::create(path.join("graph.jpg")).unwrap();
+        File::create(path.join("fail.png")).unwrap();
+        create_dir(path.join("subdir")).expect("create subdir temp dir");
+        File::create(path.join("subdir").join("index.md")).unwrap();
+        File::create(path.join("subdir").join("example.js")).unwrap();
+
+        let assets = find_related_assets(path, &Config::default());
+        assert_eq!(assets.len(), 4);
+        assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 4);
+        assert_eq!(assets.iter().filter(|p| p.strip_prefix(path).unwrap() == Path::new("example.js")).count(), 1);
+        assert_eq!(assets.iter().filter(|p| p.strip_prefix(path).unwrap() == Path::new("graph.jpg")).count(), 1);
+        assert_eq!(assets.iter().filter(|p| p.strip_prefix(path).unwrap() == Path::new("fail.png")).count(), 1);
+        assert_eq!(assets.iter().filter(|p| p.strip_prefix(path).unwrap() == Path::new("subdir/example.js")).count(), 1);
     }
 
     #[test]
diff --git a/components/library/src/content/page.rs b/components/library/src/content/page.rs
index c8bcd0aa12..ab0f1cf5c7 100644
--- a/components/library/src/content/page.rs
+++ b/components/library/src/content/page.rs
@@ -276,7 +276,7 @@ impl Page {
     fn serialize_assets(&self, base_path: &Path) -> Vec<String> {
         self.assets
             .iter()
-            .filter_map(|asset| asset.file_name())
+            .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();
diff --git a/components/library/src/content/section.rs b/components/library/src/content/section.rs
index 5f9e93bfe8..4aa3c05a9f 100644
--- a/components/library/src/content/section.rs
+++ b/components/library/src/content/section.rs
@@ -195,7 +195,7 @@ impl Section {
     fn serialize_assets(&self) -> Vec<String> {
         self.assets
             .iter()
-            .filter_map(|asset| asset.file_name())
+            .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()
diff --git a/components/rendering/src/markdown.rs b/components/rendering/src/markdown.rs
index dd2dfdd274..c5161d91c7 100644
--- a/components/rendering/src/markdown.rs
+++ b/components/rendering/src/markdown.rs
@@ -74,13 +74,6 @@ fn starts_with_schema(s: &str) -> bool {
     PATTERN.is_match(s)
 }
 
-/// Colocated asset links refers to the files in the same directory,
-/// there it should be a filename only
-fn is_colocated_asset_link(link: &str) -> bool {
-    !link.contains('/')  // http://, ftp://, ../ etc
-        && !starts_with_schema(link)
-}
-
 /// Returns whether a link starts with an HTTP(s) scheme.
 fn is_external_link(link: &str) -> bool {
     link.starts_with("http:") || link.starts_with("https:")
@@ -111,8 +104,6 @@ fn fix_link(
                 return Err(format!("Relative link {} not found.", link).into());
             }
         }
-    } else if is_colocated_asset_link(link) {
-        format!("{}{}", context.current_page_permalink, link)
     } else {
         if is_external_link(link) {
             external_links.push(link.to_owned());
@@ -222,14 +213,6 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
                         code_block = None;
                         Event::Html("</code></pre>\n".into())
                     }
-                    Event::Start(Tag::Image(link_type, src, title)) => {
-                        if is_colocated_asset_link(&src) {
-                            let link = format!("{}{}", context.current_page_permalink, &*src);
-                            return Event::Start(Tag::Image(link_type, link.into(), title));
-                        }
-
-                        Event::Start(Tag::Image(link_type, src, title))
-                    }
                     Event::Start(Tag::Link(link_type, link, title)) if link.is_empty() => {
                         error = Some(Error::msg("There is a link that is missing a URL"));
                         Event::Start(Tag::Link(link_type, "#".into(), title))
diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs
index 040a6b1fce..17d0059f82 100644
--- a/components/site/src/lib.rs
+++ b/components/site/src/lib.rs
@@ -595,7 +595,7 @@ impl Site {
             self.copy_asset(
                 asset_path,
                 &current_path
-                    .join(asset_path.file_name().expect("Couldn't get filename from page asset")),
+                    .join(asset_path.strip_prefix(&page.file.path.parent().unwrap()).expect("Couldn't get filename from page asset")),
             )?;
         }
 
@@ -988,7 +988,7 @@ impl Site {
             self.copy_asset(
                 asset_path,
                 &output_path.join(
-                    asset_path.file_name().expect("Failed to get asset filename for section"),
+                    asset_path.strip_prefix(&section.file.path.parent().unwrap()).expect("Failed to get asset filename for section"),
                 ),
             )?;
         }

From 1890e387b1026f1556d5dc50c53586dfb25a897c Mon Sep 17 00:00:00 2001
From: Tim Schumacher <tim@tschumacher.net>
Date: Tue, 24 Aug 2021 11:52:25 +0200
Subject: [PATCH 2/2] Adjust tests

---
 components/rendering/tests/markdown.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/components/rendering/tests/markdown.rs b/components/rendering/tests/markdown.rs
index 2818598f24..73a3ebe595 100644
--- a/components/rendering/tests/markdown.rs
+++ b/components/rendering/tests/markdown.rs
@@ -935,7 +935,7 @@ fn can_make_permalinks_with_colocated_assets_for_link() {
         InsertAnchor::None,
     );
     let res = render_content("[an image](image.jpg)", &context).unwrap();
-    assert_eq!(res.body, "<p><a href=\"https://vincent.is/about/image.jpg\">an image</a></p>\n");
+    assert_eq!(res.body, "<p><a href=\"image.jpg\">an image</a></p>\n");
 }
 
 #[test]
@@ -953,7 +953,7 @@ fn can_make_permalinks_with_colocated_assets_for_image() {
     let res = render_content("![alt text](image.jpg)", &context).unwrap();
     assert_eq!(
         res.body,
-        "<p><img src=\"https://vincent.is/about/image.jpg\" alt=\"alt text\" /></p>\n"
+        "<p><img src=\"image.jpg\" alt=\"alt text\" /></p>\n"
     );
 }