use crate::document::Catalog;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolvedReference {
pub href: String,
pub text: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReferenceWarning {
pub target: String,
pub kind: ReferenceWarningKind,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ReferenceWarningKind {
Unresolved,
}
#[non_exhaustive]
pub struct ResolutionContext<'a> {
pub target: &'a str,
pub provided_text: Option<&'a str>,
}
pub trait ReferenceResolver {
fn resolve(&self, context: &ResolutionContext<'_>) -> Option<ResolvedReference>;
}
#[derive(Clone, Copy, Debug)]
pub struct CatalogResolver<'a> {
catalog: &'a Catalog,
}
impl<'a> CatalogResolver<'a> {
pub fn new(catalog: &'a Catalog) -> Self {
Self { catalog }
}
}
impl ReferenceResolver for CatalogResolver<'_> {
fn resolve(&self, context: &ResolutionContext<'_>) -> Option<ResolvedReference> {
let target = context.target;
if target.contains('#') {
return None;
}
if let Some(entry) = self.catalog.get_ref(target) {
return Some(ResolvedReference {
href: format!("#{target}"),
text: entry.reftext.clone(),
});
}
if let Some(id) = self.catalog.resolve_id(target) {
let text = self.catalog.get_ref(&id).and_then(|e| e.reftext.clone());
return Some(ResolvedReference {
href: format!("#{id}"),
text,
});
}
None
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::document::RefType;
fn catalog_with(id: &str, reftext: Option<&str>, ref_type: RefType) -> Catalog {
let mut catalog = Catalog::new();
catalog.register_ref(id, reftext, ref_type).unwrap();
catalog
}
#[test]
fn resolves_by_id() {
let catalog = catalog_with("later", Some("The Later Section"), RefType::Section);
let resolver = CatalogResolver::new(&catalog);
let resolved = resolver
.resolve(&ResolutionContext {
target: "later",
provided_text: None,
})
.unwrap();
assert_eq!(resolved.href, "#later");
assert_eq!(resolved.text.as_deref(), Some("The Later Section"));
}
#[test]
fn resolves_by_reftext() {
let catalog = catalog_with("later", Some("The Later Section"), RefType::Section);
let resolver = CatalogResolver::new(&catalog);
let resolved = resolver
.resolve(&ResolutionContext {
target: "The Later Section",
provided_text: None,
})
.unwrap();
assert_eq!(resolved.href, "#later");
assert_eq!(resolved.text.as_deref(), Some("The Later Section"));
}
#[test]
fn unresolved_returns_none() {
let catalog = Catalog::new();
let resolver = CatalogResolver::new(&catalog);
assert!(
resolver
.resolve(&ResolutionContext {
target: "missing",
provided_text: None,
})
.is_none()
);
}
#[test]
fn path_bearing_target_left_unresolved() {
let catalog = catalog_with("frag", Some("Fragment"), RefType::Anchor);
let resolver = CatalogResolver::new(&catalog);
assert!(
resolver
.resolve(&ResolutionContext {
target: "other-page.adoc#frag",
provided_text: None,
})
.is_none()
);
}
}