#[inline]
pub(crate) fn obj_string(t: &aingle_graph::Triple) -> Option<String> {
if let Some(s) = t.object_string() {
Some(s.to_string())
} else {
t.object_node()
.map(|n| strip_brackets(&n.to_string()).to_string())
}
}
pub(crate) fn strip_brackets(s: &str) -> &str {
s.trim_start_matches('<').trim_end_matches('>')
}
pub(crate) fn basename(path: &str) -> String {
let file = path.rsplit(['/', '\\']).next().unwrap_or(path);
file.rsplit_once('.')
.map(|(s, _)| s)
.unwrap_or(file)
.to_string()
}
pub(crate) async fn provenance_anchor_for(
state: &crate::state::AppState,
src: &str,
) -> Option<String> {
#[cfg(feature = "dag")]
{
match crate::service::dag::history_by_subject(state, src, 1).await {
Ok(a) => a.first().filter(|x| x.signed).map(|x| x.hash.clone()),
Err(_) => None,
}
}
#[cfg(not(feature = "dag"))]
{
let _ = (state, src);
None
}
}
fn path_without_ext(path: &str) -> String {
if let Some(idx) = path.rfind('/') {
let dir = &path[..=idx]; let file = &path[idx + 1..];
let stem = file.rsplit_once('.').map(|(s, _)| s).unwrap_or(file);
format!("{dir}{stem}")
} else {
path.rsplit_once('.')
.map(|(s, _)| s)
.unwrap_or(path)
.to_string()
}
}
pub(crate) fn resolve_link_target(
target: &str,
note_set: &std::collections::BTreeSet<&str>,
by_base: &std::collections::BTreeMap<String, String>,
) -> Option<String> {
let t_norm = target.replace('\\', "/");
let t_ref: &str = &t_norm;
if note_set.contains(t_ref) {
return Some(t_norm);
}
if t_norm.contains('/') {
let t_ne = path_without_ext(t_ref);
for &p in note_set.iter() {
let p_norm = p.replace('\\', "/");
if path_without_ext(&p_norm) == t_ne {
return Some(p.to_string());
}
}
}
by_base.get(&basename(t_ref)).cloned()
}
#[cfg(test)]
mod tests {
use std::collections::{BTreeMap, BTreeSet};
use super::resolve_link_target;
#[test]
fn exact_path_match() {
let notes = ["a/note.md".to_string(), "b/note.md".to_string()];
let note_set: BTreeSet<&str> = notes.iter().map(|s| s.as_str()).collect();
let mut by_base: BTreeMap<String, String> = BTreeMap::new();
by_base.insert("note".to_string(), "a/note.md".to_string());
assert_eq!(
resolve_link_target("b/note.md", ¬e_set, &by_base).as_deref(),
Some("b/note.md")
);
}
#[test]
fn path_qualified_resolves_correct_note_not_alphabetical_first() {
let notes = ["a/note.md".to_string(), "b/note.md".to_string()];
let note_set: BTreeSet<&str> = notes.iter().map(|s| s.as_str()).collect();
let mut by_base: BTreeMap<String, String> = BTreeMap::new();
by_base.insert("note".to_string(), "a/note.md".to_string());
assert_eq!(
resolve_link_target("b/note", ¬e_set, &by_base).as_deref(),
Some("b/note.md"),
"path-qualified target must not collapse to the alphabetically-first basename match"
);
}
#[test]
fn bare_basename_unique_fallback() {
let notes = ["dir/note.md".to_string()];
let note_set: BTreeSet<&str> = notes.iter().map(|s| s.as_str()).collect();
let mut by_base: BTreeMap<String, String> = BTreeMap::new();
by_base.insert("note".to_string(), "dir/note.md".to_string());
assert_eq!(
resolve_link_target("note", ¬e_set, &by_base).as_deref(),
Some("dir/note.md")
);
}
}