use ref_map::*;
use std::borrow::Cow;
use std::fmt::{self, Display};
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct PageRef<'t> {
pub site: Option<Cow<'t, str>>,
pub page: Cow<'t, str>,
}
impl<'t> PageRef<'t> {
#[inline]
pub fn page_and_site<S1, S2>(site: S1, page: S2) -> Self
where
S1: Into<Cow<'t, str>>,
S2: Into<Cow<'t, str>>,
{
PageRef {
site: Some(site.into()),
page: page.into(),
}
}
#[inline]
pub fn page_only<S>(page: S) -> Self
where
S: Into<Cow<'t, str>>,
{
PageRef {
site: None,
page: page.into(),
}
}
#[inline]
pub fn site(&self) -> Option<&str> {
self.site.ref_map(|s| s.as_ref())
}
#[inline]
pub fn page(&self) -> &str {
self.page.as_ref()
}
#[inline]
pub fn fields(&self) -> (Option<&str>, &str) {
(self.site(), self.page())
}
pub fn fields_or<'a>(&'a self, current_site: &'a str) -> (&'a str, &'a str) {
(self.site().unwrap_or(current_site), self.page())
}
pub fn parse(s: &'t str) -> Result<PageRef<'t>, PageRefParseError> {
let s = s.trim();
if s.is_empty() {
return Err(PageRefParseError);
}
let result = match s.find(':') {
Some(0) => {
let idx = match s[1..].find(':') {
Some(0) | None => return Err(PageRefParseError),
Some(idx) => idx + 1,
};
let site = s[1..idx].trim();
let page = s[idx + 1..].trim();
PageRef::page_and_site(site, page)
}
Some(_) => PageRef::page_only(s),
None => PageRef::page_only(s),
};
Ok(result)
}
pub fn to_owned(&self) -> PageRef<'static> {
macro_rules! owned {
($value:expr) => {
Cow::Owned($value.as_ref().to_owned())
};
}
let site = self.site.ref_map(|value| owned!(value));
let page = owned!(self.page);
PageRef { site, page }
}
}
impl Display for PageRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(site) = self.site() {
write!(f, ":{}:", &site)?;
}
write!(f, "{}", &self.page)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct PageRefParseError;
#[test]
fn page_ref() {
macro_rules! test {
($input:expr $(,)?) => {
test!($input => None)
};
($input:expr, $expected:expr $(,)?) => {
test!($input => Some($expected))
};
($input:expr => $expected:expr) => {{
let actual = PageRef::parse($input);
let expected = $expected.ok_or(PageRefParseError);
println!("Input: {:?}", $input);
println!("Output: {:?}", actual);
println!();
assert_eq!(actual, expected, "Actual parse results don't match expected");
}};
}
test!("");
test!(":page");
test!("::page");
test!("page", PageRef::page_only("page"));
test!("component:page", PageRef::page_only("component:page"));
test!(
"deleted:secret:fragment:page",
PageRef::page_only("deleted:secret:fragment:page"),
);
test!(":scp-wiki:page", PageRef::page_and_site("scp-wiki", "page"));
test!(
":scp-wiki:component:page",
PageRef::page_and_site("scp-wiki", "component:page"),
);
test!(
":scp-wiki:deleted:secret:fragment:page",
PageRef::page_and_site("scp-wiki", "deleted:secret:fragment:page"),
);
}
#[cfg(test)]
mod prop {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(4096))]
#[test]
fn page_ref_prop(s in r"[a-zA-Z_:.]*") {
let _ = PageRef::parse(&s);
}
}
}