use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::fmt::Write;
use fastobo_derive_internal::FromStr;
use crate::ast::IdentType;
use crate::error::SyntaxError;
use crate::parser::Cache;
use crate::parser::FromPair;
use crate::parser::QuickFind;
use crate::syntax::pest::iterators::Pair;
use crate::syntax::Rule;
use super::escape;
use super::unescape;
#[derive(Clone, Debug, Eq, FromStr, Hash, Ord, PartialEq)]
pub struct PrefixedIdent {
prefix: IdentType,
local: IdentType,
}
impl PrefixedIdent {
pub fn new<P, L>(prefix: P, local: L) -> Self
where
P: Into<IdentType>,
L: Into<IdentType>,
{
Self {
prefix: prefix.into(),
local: local.into(),
}
}
pub fn is_canonical(&self) -> bool {
super::prefix::is_canonical(self.prefix())
&& self.local().chars().all(|c| c.is_ascii_digit())
}
pub fn prefix(&self) -> &str {
&self.prefix
}
pub fn local(&self) -> &str {
&self.local
}
}
impl Display for PrefixedIdent {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
escape(f, self.prefix())
.and_then(|_| f.write_char(':'))
.and_then(|_| escape(f, self.local()))
}
}
impl<'i> FromPair<'i> for PrefixedIdent {
const RULE: Rule = Rule::PrefixedId;
unsafe fn from_pair_unchecked(
pair: Pair<'i, Rule>,
cache: &Cache,
) -> Result<Self, SyntaxError> {
let mut inners = pair.into_inner();
let prefix = inners.next().unwrap();
let local = inners.next().unwrap();
let p = if prefix.as_str().quickfind(b'\\').is_some() {
let mut p = String::with_capacity(prefix.as_str().len());
unescape(&mut p, prefix.as_str()).expect("cannot contain invalid escape characters");
Cow::Owned(p)
} else {
Cow::Borrowed(prefix.as_str())
};
let l = if local.as_str().quickfind(b'\\').is_some() {
let mut l = String::with_capacity(local.as_str().len());
unescape(&mut l, local.as_str()).expect("cannot contain invalid escape characters");
Cow::Owned(l)
} else {
Cow::Borrowed(local.as_str())
};
Ok(Self::new(cache.intern(&p), cache.intern(&l)))
}
}
impl PartialOrd for PrefixedIdent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.prefix().partial_cmp(other.prefix()) {
None => None,
Some(Ordering::Equal) => self.local().partial_cmp(other.local()),
Some(ord) => Some(ord),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::str::FromStr;
use std::string::ToString;
#[test]
fn from_str() {
let actual = PrefixedIdent::from_str("GO:0046154").unwrap();
let expected = PrefixedIdent::new("GO", "0046154");
assert_eq!(actual, expected);
let actual = PrefixedIdent::from_str("PSI:MS").unwrap();
let expected = PrefixedIdent::new("PSI", "MS");
assert_eq!(actual, expected);
let actual = PrefixedIdent::from_str("CAS:22325-47-9").unwrap();
let expected = PrefixedIdent::new("CAS", "22325-47-9");
assert_eq!(actual, expected);
let actual = PrefixedIdent::from_str("web:https\\://example.com").unwrap();
let expected = PrefixedIdent::new("web", "https://example.com");
assert_eq!(actual, expected);
assert!(PrefixedIdent::from_str("[Term]").is_err());
assert!(PrefixedIdent::from_str("").is_err());
assert!(PrefixedIdent::from_str("Some\nthing:spanning").is_err());
assert!(PrefixedIdent::from_str("GO:0046154 remaining").is_err());
}
#[test]
fn to_string() {
let id = PrefixedIdent::new("GO", "0046154");
assert_eq!(id.to_string(), "GO:0046154");
let id_url = PrefixedIdent::new("web", "https://example.com");
assert_eq!(id_url.to_string(), "web:https\\://example.com")
}
}