concinnity_host/store/
source.rs1use std::path::{Path, PathBuf};
16
17pub fn find_in(assets_dir: &Path, filename: &str) -> Option<String> {
20 walk(assets_dir, filename).map(|p| p.to_string_lossy().into_owned())
21}
22
23pub fn resolve_source_path(source: &str, assets_dir: Option<&Path>) -> String {
28 let Some(assets_dir) = assets_dir else {
29 return source.to_string();
30 };
31 if !is_bare(source) {
32 return source.to_string();
33 }
34 walk(assets_dir, source)
35 .unwrap_or_else(|| assets_dir.join(source))
36 .to_string_lossy()
37 .into_owned()
38}
39
40fn is_bare(source: &str) -> bool {
42 Path::new(source)
43 .parent()
44 .map(|d| d.as_os_str().is_empty())
45 .unwrap_or(true)
46}
47
48fn walk(dir: &Path, filename: &str) -> Option<PathBuf> {
49 let Ok(entries) = std::fs::read_dir(dir) else {
50 return None;
51 };
52 for entry in entries.flatten() {
53 let path = entry.path();
54 if path.is_file() && path.file_name().and_then(|n| n.to_str()) == Some(filename) {
55 return Some(path);
56 }
57 if path.is_dir()
58 && let Some(found) = walk(&path, filename)
59 {
60 return Some(found);
61 }
62 }
63 None
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn bare_filenames_are_distinguished_from_paths() {
72 assert!(is_bare("sky.hdr"));
73 assert!(!is_bare("hdri/sky.hdr"));
74 assert!(!is_bare("/abs/sky.hdr"));
75 assert!(!is_bare("../sky.hdr"));
76 }
77
78 #[test]
79 fn a_path_with_a_directory_component_is_used_verbatim() {
80 let dir = tempfile::tempdir().unwrap();
81 assert_eq!(
84 resolve_source_path("hdri/sky.hdr", Some(dir.path())),
85 "hdri/sky.hdr"
86 );
87 assert_eq!(
88 resolve_source_path("/abs/sky.hdr", Some(dir.path())),
89 "/abs/sky.hdr"
90 );
91 }
92
93 #[test]
94 fn a_bare_filename_is_found_in_a_nested_subdirectory() {
95 let dir = tempfile::tempdir().unwrap();
96 let nested = dir.path().join("hdri").join("outdoor");
97 std::fs::create_dir_all(&nested).unwrap();
98 let target = nested.join("sky.hdr");
99 std::fs::write(&target, b"x").unwrap();
100
101 assert_eq!(
102 resolve_source_path("sky.hdr", Some(dir.path())),
103 target.to_string_lossy().into_owned()
104 );
105 }
106
107 #[test]
108 fn a_missing_bare_filename_falls_back_to_the_assets_dir() {
109 let dir = tempfile::tempdir().unwrap();
110 assert_eq!(
111 resolve_source_path("cn_no_such_asset.hdr", Some(dir.path())),
112 dir.path()
113 .join("cn_no_such_asset.hdr")
114 .to_string_lossy()
115 .into_owned()
116 );
117 }
118
119 #[test]
122 fn resolving_without_an_assets_dir_returns_the_source_verbatim() {
123 assert_eq!(resolve_source_path("sky.hdr", None), "sky.hdr");
124 assert_eq!(resolve_source_path("hdri/sky.hdr", None), "hdri/sky.hdr");
125 }
126
127 #[test]
128 fn resolving_against_a_missing_assets_dir_still_names_the_file() {
129 let missing = Path::new("/nonexistent/cn/assets");
132 assert_eq!(
133 resolve_source_path("sky.hdr", Some(missing)),
134 missing.join("sky.hdr").to_string_lossy().into_owned()
135 );
136 }
137
138 #[test]
139 fn walk_misses_cleanly_on_an_unreadable_directory() {
140 assert_eq!(walk(Path::new("/nonexistent/cn/assets"), "x.png"), None);
141 }
142
143 #[test]
144 fn find_in_returns_the_first_match_and_misses_cleanly() {
145 let dir = tempfile::tempdir().unwrap();
146 let nested = dir.path().join("hdri");
147 std::fs::create_dir_all(&nested).unwrap();
148 std::fs::write(nested.join("sky.hdr"), b"x").unwrap();
149
150 assert_eq!(
151 find_in(dir.path(), "sky.hdr").as_deref(),
152 nested.join("sky.hdr").to_str()
153 );
154 assert_eq!(find_in(dir.path(), "cn_test_no_such_asset.json"), None);
155 }
156}