use std::collections::HashSet;
use scraps_libs::markdown::query::WikiRef;
use scraps_libs::model::{key::ScrapKey, scrap::Scrap};
use super::schema::{EdgeKind, KeyJson, LinkEdge};
pub fn link_edges(scraps: &[Scrap]) -> Vec<LinkEdge> {
let keys: HashSet<ScrapKey> = scraps.iter().map(Scrap::self_key).collect();
let keys = &keys;
scraps
.iter()
.flat_map(|scrap| {
let from = KeyJson::from(&scrap.self_key());
scrap.refs().iter().filter_map(move |r| {
let (to, kind, heading, line) = match r {
WikiRef::Link(l) => {
(ScrapKey::from(l), EdgeKind::Link, l.heading.clone(), l.line)
}
WikiRef::Embed(e) => (
ScrapKey::from_path_str(&embed_path(&e.ctx_path, &e.title)),
EdgeKind::Embed,
e.heading.clone(),
e.line,
),
WikiRef::MdLink(m) => {
(m.resolve(scrap.ctx())?, EdgeKind::Link, m.heading(), m.line)
}
WikiRef::Tag(_) => return None,
};
Some(LinkEdge {
from: from.clone(),
resolved: keys.contains(&to),
to: KeyJson::from(&to),
kind,
heading,
line,
})
})
})
.collect()
}
fn embed_path(ctx_path: &[String], title: &str) -> String {
if ctx_path.is_empty() {
title.to_string()
} else {
format!("{}/{}", ctx_path.join("/"), title)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_resolves_links_and_embeds_and_skips_tags() {
let scraps = vec![
Scrap::new(
"a",
&None,
"line one\n[[b#Sec]] #[[tag]]\n![[Ctx/c]] [[missing]]",
),
Scrap::new("b", &None, "## Sec"),
Scrap::new("c", &Some("Ctx".into()), ""),
];
let edges = link_edges(&scraps);
assert_eq!(edges.len(), 3);
assert_eq!(edges[0].to.stem, "b");
assert_eq!(edges[0].kind, EdgeKind::Link);
assert_eq!(edges[0].heading.as_deref(), Some("Sec"));
assert_eq!(edges[0].line, 2);
assert!(edges[0].resolved);
assert_eq!(edges[1].kind, EdgeKind::Embed);
assert_eq!(edges[1].to.ctx.as_deref(), Some("Ctx"));
assert!(edges[1].resolved);
assert_eq!(edges[2].to.stem, "missing");
assert!(!edges[2].resolved);
assert!(edges.iter().all(|e| e.from.stem == "a"));
}
#[test]
fn it_resolves_markdown_links_from_the_linking_scrap() {
let scraps = vec [r](../metrics/revenue.md#definition)\n[g](./gone.md) [out](../../out.md)",
),
Scrap::new("customers", &Some("tables".into()), ""),
Scrap::new("revenue", &Some("metrics".into()), "# Definition"),
];
let edges = link_edges(&scraps);
assert_eq!(edges.len(), 3);
assert!(edges.iter().all(|e| e.kind == EdgeKind::Link));
assert_eq!(edges[0].to.stem, "customers");
assert_eq!(edges[0].to.ctx.as_deref(), Some("tables"));
assert!(edges[0].resolved);
assert_eq!(edges[1].to.ctx.as_deref(), Some("metrics"));
assert_eq!(edges[1].heading.as_deref(), Some("definition"));
assert!(edges[1].resolved);
assert_eq!(edges[2].to.stem, "gone");
assert_eq!(edges[2].line, 2);
assert!(!edges[2].resolved);
}
#[test]
fn it_keeps_duplicate_occurrences() {
let scraps = vec![Scrap::new("a", &None, "[[a]] [[a]]")];
assert_eq!(link_edges(&scraps).len(), 2);
}
}