use crate::document::Catalog;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum XrefStyle {
Full,
Short,
Basic,
}
impl XrefStyle {
pub(crate) fn parse(value: &str) -> Self {
match value {
"full" => Self::Full,
"short" => Self::Short,
_ => Self::Basic,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct XrefSignifier {
pub label: String,
pub emphasize: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolvedReference {
pub href: String,
pub text: Option<String>,
pub signifier: Option<XrefSignifier>,
}
impl ResolvedReference {
pub fn new(href: String, text: Option<String>) -> Self {
Self {
href,
text,
signifier: None,
}
}
pub fn from_entry(href: String, entry: &crate::document::RefEntry) -> Self {
Self {
href,
text: entry.reftext.clone(),
signifier: entry.signifier.clone(),
}
}
pub fn with_signifier(mut self, signifier: XrefSignifier) -> Self {
self.signifier = Some(signifier);
self
}
}
#[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::from_entry(format!("#{target}"), entry));
}
if let Some(id) = self.catalog.resolve_id(target) {
return self
.catalog
.get_ref(&id)
.map(|entry| ResolvedReference::from_entry(format!("#{id}"), entry));
}
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()
);
}
}