weaver_lib/
routes.rs

1use std::path::{Path, PathBuf};
2
3pub fn route_from_path(content_dir: PathBuf, path: PathBuf) -> String {
4    // 1. Strip the base content directory prefix
5    let relative_path = match path.strip_prefix(&content_dir) {
6        Ok(p) => p,
7        Err(_) => {
8            // This should ideally not happen if paths are correctly managed.
9            // Or it means the path is outside the content directory.
10            // Handle this error case appropriately, e.g., panic, return an error, or log.
11            // For now, let's just panic and halt the build because this won't output something
12            // visible or usable to anyone.
13            panic!(
14                "Warning: Path {:?} is not within content directory {:?}",
15                path, content_dir
16            );
17        }
18    };
19
20    let mut route_parts: Vec<String> = relative_path
21        .components()
22        .filter_map(|c| {
23            // Filter out relative path components, and root/prefix components
24            match c {
25                std::path::Component::Normal(os_str) => Some(os_str.to_string_lossy().into_owned()),
26                _ => None,
27            }
28        })
29        .collect();
30
31    // 2. Handle file extension and "pretty URLs"
32    if let Some(last_segment) = route_parts.pop() {
33        let original_filename_path = Path::new(&last_segment);
34
35        if original_filename_path.file_stem().is_some() {
36            let stem = original_filename_path
37                .file_stem()
38                .unwrap()
39                .to_string_lossy();
40
41            if stem == "index" {
42                // If it's an index file, the URI is just its parent directory
43                // The parent directory is already represented by the remaining route_parts
44                // So, no need to add "index" to the route.
45                // Example: content/posts/index.md -> /posts/
46            } else {
47                // For other files, use the stem as the segment and add a trailing slash
48                // Example: content/posts/my-post.md -> /posts/my-post/
49                route_parts.push(stem.into_owned());
50            }
51        }
52    }
53
54    // 3. Join parts with forward slashes and ensure leading/trailing slashes
55    let mut route = format!("/{}", route_parts.join("/"));
56
57    // Ensure trailing slash for directories, unless it's the root '/'
58    if route.len() > 1 {
59        route.push('/');
60    }
61
62    // Special case for root index.md (e.g., content/index.md -> /)
63    // If the original relative_path was just "index.md"
64    if relative_path.to_string_lossy() == "index.md" {
65        route = "/".to_string();
66    }
67
68    route
69}
70
71#[cfg(test)]
72mod test {
73    use crate::Weaver;
74
75    use super::*;
76
77    use pretty_assertions::assert_eq;
78
79    #[test]
80    fn test_route_from_path() {
81        let base_path_wd = std::env::current_dir().unwrap().display().to_string();
82        let base_path = format!("{}/test_fixtures/config", base_path_wd);
83        let inst = Weaver::new(format!("{}/custom_config", base_path).into());
84
85        assert_eq!(
86            "/blog/post1/",
87            route_from_path(
88                inst.config.content_dir.clone().into(),
89                format!("{}/blog/post1.md", inst.config.content_dir).into()
90            )
91        );
92    }
93
94    #[test]
95    #[should_panic]
96    fn test_content_out_of_path() {
97        let base_path_wd = std::env::current_dir().unwrap().display().to_string();
98        let base_path = format!("{}/test_fixtures/config", base_path_wd);
99        let inst = Weaver::new(format!("{}/custom_config", base_path).into());
100        route_from_path(
101            inst.config.content_dir.clone().into(),
102            "madeup/blog/post1.md".into(),
103        );
104    }
105}