use crate::StrictEq;
use chrono::NaiveDate;
use derive_more::{Display, IsVariant};
use percent_encoding::percent_decode;
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
collections::HashMap,
convert::{TryFrom, TryInto},
};
use uriparse::{Scheme, URIReference, URIReferenceError};
mod anchor;
pub use anchor::Anchor;
mod description;
pub use description::Description;
mod data;
pub use data::LinkData;
#[derive(
Clone,
Debug,
Display,
Eq,
PartialEq,
Hash,
IsVariant,
Serialize,
Deserialize,
)]
pub enum Link<'a> {
#[display(fmt = "{}", data)]
Wiki { data: LinkData<'a> },
#[display(fmt = "{}", data)]
IndexedInterWiki { index: u32, data: LinkData<'a> },
#[display(fmt = "{}", data)]
NamedInterWiki {
name: Cow<'a, str>,
data: LinkData<'a>,
},
#[display(fmt = "{}", "data.description.as_ref().map(ToString::to_string).unwrap_or_else(|| date.to_string())")]
Diary { date: NaiveDate, data: LinkData<'a> },
#[display(fmt = "{}", data)]
Raw { data: LinkData<'a> },
#[display(fmt = "{}", data)]
Transclusion { data: LinkData<'a> },
}
impl<'a> Link<'a> {
pub fn new_wiki_link<
U: Into<URIReference<'a>>,
D: Into<Option<Description<'a>>>,
>(
uri_ref: U,
description: D,
) -> Self {
Self::Wiki {
data: LinkData::new(uri_ref.into(), description.into(), None),
}
}
pub fn try_new_wiki_link<
U: TryInto<URIReference<'a>, Error = URIReferenceError>,
D: Into<Option<Description<'a>>>,
>(
uri_ref: U,
description: D,
) -> Result<Self, URIReferenceError> {
Ok(Self::new_wiki_link(uri_ref.try_into()?, description))
}
pub fn new_indexed_interwiki_link<
U: Into<URIReference<'a>>,
D: Into<Option<Description<'a>>>,
>(
index: u32,
uri_ref: U,
description: D,
) -> Self {
Self::IndexedInterWiki {
index,
data: LinkData::new(uri_ref.into(), description.into(), None),
}
}
pub fn try_new_indexed_interwiki_link<
U: TryInto<URIReference<'a>, Error = URIReferenceError>,
D: Into<Option<Description<'a>>>,
>(
index: u32,
uri_ref: U,
description: D,
) -> Result<Self, URIReferenceError> {
Ok(Self::new_indexed_interwiki_link(
index,
uri_ref.try_into()?,
description,
))
}
pub fn new_named_interwiki_link<
S: Into<Cow<'a, str>>,
U: Into<URIReference<'a>>,
D: Into<Option<Description<'a>>>,
>(
name: S,
uri_ref: U,
description: D,
) -> Self {
Self::NamedInterWiki {
name: name.into(),
data: LinkData::new(uri_ref.into(), description.into(), None),
}
}
pub fn try_new_named_interwiki_link<
S: Into<Cow<'a, str>>,
U: TryInto<URIReference<'a>, Error = URIReferenceError>,
D: Into<Option<Description<'a>>>,
>(
name: S,
uri_ref: U,
description: D,
) -> Result<Self, URIReferenceError> {
Ok(Self::new_named_interwiki_link(
name,
uri_ref.try_into()?,
description,
))
}
pub fn new_diary_link<
D: Into<Option<Description<'a>>>,
A: Into<Option<Anchor<'a>>>,
>(
date: NaiveDate,
description: D,
anchor: A,
) -> Self {
let empty_uri_ref = URIReference::try_from(
anchor
.into()
.as_ref()
.map_or_else(String::new, Anchor::to_encoded_uri_fragment)
.as_str(),
)
.unwrap()
.into_owned();
Self::Diary {
date,
data: LinkData::new(empty_uri_ref, description.into(), None),
}
}
pub fn new_raw_link<U: Into<URIReference<'a>>>(uri_ref: U) -> Self {
Self::Raw {
data: LinkData::from(uri_ref.into()),
}
}
pub fn try_new_raw_link<
U: TryInto<URIReference<'a>, Error = URIReferenceError>,
>(
uri_ref: U,
) -> Result<Self, URIReferenceError> {
Ok(Self::new_raw_link(uri_ref.try_into()?))
}
pub fn new_transclusion_link<
U: Into<URIReference<'a>>,
D: Into<Option<Description<'a>>>,
P: Into<Option<HashMap<Cow<'a, str>, Cow<'a, str>>>>,
>(
uri_ref: U,
description: D,
properties: P,
) -> Self {
Self::Transclusion {
data: LinkData::new(
uri_ref.into(),
description.into(),
properties.into(),
),
}
}
pub fn try_new_transclusion_link<
U: TryInto<URIReference<'a>, Error = URIReferenceError>,
D: Into<Option<Description<'a>>>,
P: Into<Option<HashMap<Cow<'a, str>, Cow<'a, str>>>>,
>(
uri_ref: U,
description: D,
properties: P,
) -> Result<Self, URIReferenceError> {
Ok(Self::new_transclusion_link(
uri_ref.try_into()?,
description,
properties,
))
}
pub fn data(&self) -> &LinkData<'a> {
match self {
Self::Wiki { data } => data,
Self::IndexedInterWiki { data, .. } => data,
Self::NamedInterWiki { data, .. } => data,
Self::Diary { data, .. } => data,
Self::Raw { data } => data,
Self::Transclusion { data } => data,
}
}
pub fn into_data(self) -> LinkData<'a> {
match self {
Self::Wiki { data } => data,
Self::IndexedInterWiki { data, .. } => data,
Self::NamedInterWiki { data, .. } => data,
Self::Diary { data, .. } => data,
Self::Raw { data } => data,
Self::Transclusion { data } => data,
}
}
pub fn description(&self) -> Option<&Description<'a>> {
self.data().description.as_ref()
}
pub fn into_description(self) -> Option<Description<'a>> {
self.into_data().description
}
pub fn to_description_or_fallback(&self) -> Option<Description<'a>> {
if let Some(desc) = self.description() {
Some(desc.clone())
} else if matches!(self, Link::Raw { .. } | Link::Transclusion { .. }) {
None
} else if let Link::Diary { date, .. } = self {
Some(Description::from(format!(
"diary:{}",
date.format("%Y-%m-%d")
)))
} else {
Some(Description::from(
percent_decode(self.data().uri_ref.to_string().as_bytes())
.decode_utf8_lossy()
.to_string(),
))
}
}
pub fn properties(&self) -> Option<&HashMap<Cow<'a, str>, Cow<'a, str>>> {
self.data().properties.as_ref()
}
pub fn into_properties(
self,
) -> Option<HashMap<Cow<'a, str>, Cow<'a, str>>> {
self.into_data().properties
}
pub fn has_anchor(&self) -> bool {
self.data().has_anchor()
}
pub fn to_anchor(&self) -> Option<Anchor<'_>> {
self.data().to_anchor()
}
pub fn scheme(&self) -> Option<&Scheme<'_>> {
self.data().scheme()
}
pub fn date(&self) -> Option<NaiveDate> {
match self {
Self::Diary { date, .. } => Some(*date),
_ => None,
}
}
pub fn index(&self) -> Option<u32> {
match self {
Self::IndexedInterWiki { index, .. } => Some(*index),
_ => None,
}
}
pub fn name(&self) -> Option<&str> {
match self {
Self::NamedInterWiki { name, .. } => Some(name.as_ref()),
_ => None,
}
}
}
impl Link<'_> {
pub fn to_borrowed(&self) -> Link {
use self::Cow::*;
match self {
Self::Wiki { data } => Link::Wiki {
data: data.to_borrowed(),
},
Self::IndexedInterWiki { index, data } => Link::IndexedInterWiki {
index: *index,
data: data.to_borrowed(),
},
Self::NamedInterWiki { name, data } => Link::NamedInterWiki {
name: Borrowed(match name {
Borrowed(x) => *x,
Owned(x) => x.as_str(),
}),
data: data.to_borrowed(),
},
Self::Diary { date, data } => Link::Diary {
date: *date,
data: data.to_borrowed(),
},
Self::Raw { data } => Link::Raw {
data: data.to_borrowed(),
},
Self::Transclusion { data } => Link::Transclusion {
data: data.to_borrowed(),
},
}
}
pub fn into_owned(self) -> Link<'static> {
use self::Cow::*;
match self {
Self::Wiki { data } => Link::Wiki {
data: data.into_owned(),
},
Self::IndexedInterWiki { index, data } => Link::IndexedInterWiki {
index,
data: data.into_owned(),
},
Self::NamedInterWiki { name, data } => Link::NamedInterWiki {
name: Owned(name.into_owned()),
data: data.into_owned(),
},
Self::Diary { date, data } => Link::Diary {
date,
data: data.into_owned(),
},
Self::Raw { data } => Link::Raw {
data: data.into_owned(),
},
Self::Transclusion { data } => Link::Transclusion {
data: data.into_owned(),
},
}
}
}
impl<'a> StrictEq for Link<'a> {
fn strict_eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Wiki { data: a }, Self::Wiki { data: b }) => a.strict_eq(b),
(
Self::IndexedInterWiki {
index: a1,
data: a2,
},
Self::IndexedInterWiki {
index: b1,
data: b2,
},
) => a1 == b1 && a2.strict_eq(b2),
(
Self::NamedInterWiki { name: a1, data: a2 },
Self::NamedInterWiki { name: b1, data: b2 },
) => a1 == b1 && a2.strict_eq(b2),
(
Self::Diary { date: a1, data: a2 },
Self::Diary { date: b1, data: b2 },
) => a1 == b1 && a2.strict_eq(b2),
(Self::Raw { data: a }, Self::Raw { data: b }) => a.strict_eq(b),
(
Self::Transclusion { data: a },
Self::Transclusion { data: b },
) => a.strict_eq(b),
_ => false,
}
}
}