Links: Find Linked Files resolves parent folders in a saved path

A saved link path such as `Disk:Projects:Art::photo.png` (classic Mac OS)
or `/old/Projects/Art/../photo.png` names a file in `Projects`. Ranking
the files found compared `Art` first, so a file under a folder named
`Art` came before one under `Projects`. The `::` runs and `..` components
are now resolved before the folder names are compared.
This commit is contained in:
dotCooCoo
2026-11-06 00:24:44 +07:00
committed by bflatastic
parent feee0865de
commit bed4154597
2 changed files with 52 additions and 2 deletions
+21 -2
View File
@@ -177,9 +177,28 @@ fn find(s: &mut Session, p: &Value) -> Result<Value> {
/// How many folder names the file at `found` has in common with the saved path `saved` (whichever
/// system's separators it uses, [`vectorcraft_doc::links::path_separators`]), counted from the
/// innermost out up to the first that differs, compared in lowercase.
/// innermost out up to the first that differs, compared in lowercase. The saved path's parent
/// components are resolved first: `..` in a `/` or `\` path, and each colon after the first in a
/// run of colons in a classic Mac OS path (`Disk:Projects:Art::photo.png` is in `Projects`).
fn shared_folders(saved: &str, found: &Path) -> usize {
let saved = saved.rsplit(vectorcraft_doc::links::path_separators(saved)).skip(1).filter(|c| !c.is_empty());
let separators = vectorcraft_doc::links::path_separators(saved);
let classic = separators == [':'];
let mut up = 0usize;
let saved = saved.rsplit(separators).skip(1).filter(move |c| {
let parent = if classic { c.is_empty() } else { *c == ".." };
if parent {
up += 1;
return false;
}
if c.is_empty() || *c == "." && !classic {
return false;
}
if up > 0 {
up -= 1;
return false;
}
true
});
let found = found.parent().into_iter().flat_map(|p| p.components().rev()).map_while(|c| match c {
Component::Normal(n) => n.to_str(),
_ => None,
+31
View File
@@ -153,6 +153,37 @@ fn a_classic_mac_path_is_found_by_its_name_and_folders() {
assert_eq!(found(&r, "photo.png", &root), files.map(|(p, k, c)| (p.to_string(), k.to_string(), c)));
}
/// The folders a saved path shares with a file found are counted after its parent components are
/// resolved: `Art::` in a classic Mac OS path and `Art/..` in any other path go back to `Projects`.
#[test]
fn parent_components_in_a_saved_path_are_resolved_before_ranking() {
for (tag, saved) in [("classic-up", "Old Disk:Projects:Art::photo.png"), ("slash-up", "/old/Projects/Art/../photo.png")] {
let root = folder(tag);
let mut s = session_for(&root);
let photo = png(50, 30, BLUE);
write(&root.join("orig/photo.png"), &photo);
let id = placed(&mut s, &root.join("orig/photo.png"));
s.edit("saved path", |d, _| {
if let NodeKind::Image(im) = &mut d.node_mut(NodeId(id)).unwrap().kind {
im.link.as_mut().unwrap().path = saved.into();
}
Ok(())
})
.unwrap();
std::fs::remove_dir_all(root.join("orig")).unwrap();
let search = root.join("search");
let mut changed = photo.clone();
*changed.last_mut().unwrap() ^= 1;
write(&search.join("Art/photo.png"), &changed);
write(&search.join("Projects/photo.png"), &changed);
s.execute("links.findFiles", &json!({"folder": search})).unwrap();
let r = finished(&mut s);
assert_eq!(r["state"].as_str(), Some("done"), "{saved}: {r}");
let files = [("search/Projects/photo.png", "name", false), ("search/Art/photo.png", "name", false)];
assert_eq!(found(&r, "photo.png", &root), files.map(|(p, k, c)| (p.to_string(), k.to_string(), c)), "{saved}");
}
}
/// Without `ids`, a search looks for the files that can't be found; with them, for those objects'
/// files, whatever their status.
#[test]