use super::*;
pub fn name_path<A: AstAdapter + ?Sized>(adapter: &A, node: NodeId) -> String {
let mut parts = Vec::new();
let mut cur = Some(node);
while let Some(n) = cur {
if adapter.parent(n).is_none() {
break;
}
if let Some(t) = adapter.name(n) {
parts.push(t);
}
cur = adapter.parent(n);
}
parts.reverse();
format!("/{}", parts.join("/"))
}
pub fn is_url(text: &str) -> bool {
url::Url::parse(text).is_ok_and(|u| u.has_host() || u.cannot_be_a_base()) && text.contains(':')
}
pub fn canonical_identity(text: &str) -> String {
if is_url(text)
&& let Ok(mut u) = url::Url::parse(text)
{
u.set_fragment(None);
return u.to_string();
}
if !text.starts_with('/') {
return text.to_string();
}
fold_path(std::path::Path::new(text), text)
}
pub fn canonical_identity_at_open(text: &str) -> String {
if is_url(text) || text.starts_with('/') {
return canonical_identity(text);
}
let looks_like_path =
text.starts_with("./") || text.starts_with("../") || text == "." || text == "..";
if !looks_like_path && !std::path::Path::new(text).exists() {
return text.to_string();
}
let base = std::env::current_dir().unwrap_or_default();
fold_path(&base.join(text), text)
}
fn fold_path(joined: &std::path::Path, text: &str) -> String {
let mut parts: Vec<std::ffi::OsString> = Vec::new();
for c in joined.components() {
match c {
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
parts.pop();
}
std::path::Component::RootDir | std::path::Component::Prefix(_) => {
parts.clear();
}
std::path::Component::Normal(p) => parts.push(p.to_os_string()),
}
}
let mut out = String::from("/");
out.push_str(
&parts
.iter()
.map(|p| p.to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("/"),
);
if text.ends_with('/') && !out.ends_with('/') {
out.push('/');
}
out
}
pub(super) fn absolute_identity(reference: &str, base: Option<&str>) -> String {
if is_url(reference) {
return canonical_identity(reference);
}
match base {
Some(b) if is_url(b) => match url::Url::parse(b).and_then(|u| u.join(reference)) {
Ok(mut u) => {
u.set_fragment(None);
u.to_string()
}
Err(_) => canonical_identity(reference),
},
Some(b) if b.starts_with('/') && !reference.starts_with('/') => {
let dir = if b.ends_with('/') {
b.to_string()
} else {
b.rsplit_once('/')
.map_or("/".to_string(), |(d, _)| format!("{d}/"))
};
canonical_identity(&format!("{dir}{reference}"))
}
_ => canonical_identity(reference),
}
}
pub(super) fn identity_of(adapter: &impl AstAdapter, from: NodeId) -> Option<String> {
let mut document = from;
loop {
let p = adapter.provenance(document);
let at_root = p.path.as_deref().is_none_or(|x| x.is_empty() || x == "/");
if at_root {
break;
}
match adapter.parent(document) {
Some(q) => document = q,
None => break,
}
}
let entries = adapter.tree_identities();
let mut names: Vec<String> = Vec::new();
let mut at = Some(document);
while let Some(n) = at {
if let Some((identity, _)) = entries.iter().find(|(_, node)| *node == n) {
return Some(match identity.chars().last() {
Some(sep) if !sep.is_alphanumeric() && n != document => {
let mut id = identity.clone();
names.reverse();
id.push_str(&names.join(&sep.to_string()));
id
}
_ => identity.clone(),
});
}
if let Some(name) = adapter.name(n) {
names.push(name);
}
at = adapter.parent(n);
}
adapter
.provenance(document)
.source
.map(|s| canonical_identity(&s))
}
pub(super) fn find_tree(
adapter: &impl AstAdapter,
document: &str,
from: NodeId,
trace: &Trace,
outer: Option<&Scope<'_>>,
witness: &[Option<NodeId>],
marks: &[Mark],
register: &[Reg],
) -> Result<NodeId, Option<String>> {
let (identity, within) = match document.find("!/") {
Some(i) => (&document[..i], Some(&document[i + 1..])),
None => (document, None),
};
let base = identity_of(adapter, from);
let anchored = base.is_some() || is_url(identity);
let identity = absolute_identity(identity, base.as_deref());
let found = ref_target(&identity)
.or_else(|| adapter.document_by_ref(&identity))
.or_else(|| {
let entries = adapter.tree_identities();
if let Some((_, n)) = entries
.iter()
.find(|(i, _)| canonical_identity(i) == identity)
{
return Some(*n);
}
let mut bases: Vec<(String, NodeId)> = entries
.into_iter()
.filter(|(i, _)| i.chars().last().is_some_and(|c| !c.is_alphanumeric()))
.map(|(i, n)| (canonical_identity(&i), n))
.filter(|(i, _)| identity.starts_with(i.as_str()))
.collect();
bases.sort_by_key(|(i, _)| std::cmp::Reverse(i.len()));
let (base, node) = bases.into_iter().next()?;
let sep = base.chars().last()?;
let mut cur = node;
for seg in identity[base.len()..].split(sep).filter(|s| !s.is_empty()) {
cur = adapter.children_named(cur, seg).into_iter().next()?;
}
Some(cur)
});
let Some(node) = found else {
return Err(anchored.then_some(identity));
};
let Some(path) = within else {
return Ok(node);
};
let elems =
crate::parser::parse_scope_path(path).map_err(|_| Some(format!("{identity}!{path}")))?;
navigate_from(
&elems.0, adapter, node, trace, outer, witness, marks, register,
)
.into_iter()
.next()
.ok_or_else(|| Some(format!("{identity}!{path}")))
}