use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::fmt::Write;
use std::iter::FromIterator;
use fastobo_derive_internal::FromStr;
use crate::ast::*;
use crate::error::CardinalityError;
use crate::error::SyntaxError;
use crate::parser::Cache;
use crate::parser::FromPair;
use crate::semantics::Identified;
use crate::semantics::Orderable;
use crate::syntax::pest::iterators::Pair;
use crate::syntax::Rule;
#[derive(Clone, Default, Debug, Hash, Eq, FromStr, PartialEq)]
pub struct OboDoc {
header: HeaderFrame,
entities: Vec<EntityFrame>,
}
impl OboDoc {
pub fn new() -> Self {
Default::default()
}
pub fn with_header(header: HeaderFrame) -> Self {
Self {
header,
entities: Default::default(),
}
}
#[must_use]
pub fn and_header(mut self, header: HeaderFrame) -> Self {
self.header = header;
self
}
pub fn with_entities(entities: Vec<EntityFrame>) -> Self {
Self {
header: Default::default(),
entities,
}
}
#[must_use]
pub fn and_entities(mut self, entities: Vec<EntityFrame>) -> Self {
self.entities = entities;
self
}
}
impl OboDoc {
pub fn header(&self) -> &HeaderFrame {
&self.header
}
pub fn header_mut(&mut self) -> &mut HeaderFrame {
&mut self.header
}
pub fn entities(&self) -> &Vec<EntityFrame> {
&self.entities
}
pub fn entities_mut(&mut self) -> &mut Vec<EntityFrame> {
&mut self.entities
}
#[inline]
pub fn is_empty(&self) -> bool {
self.header().is_empty() && self.entities().is_empty()
}
}
impl OboDoc {
pub fn assign_namespaces(&mut self) -> Result<(), CardinalityError> {
macro_rules! expand {
($frame:ident, $clause:ident, $ns:ident, $outer:lifetime) => {{
if !$frame
.iter()
.any(|clause| matches!(clause.as_ref(), $clause::Namespace(_)))
{
match $ns {
Err(e) => return Err(e.clone()),
Ok(&ns) => {
$frame.push(Line::from($clause::Namespace(Box::new(ns.clone()))))
}
}
}
}};
}
use self::EntityFrame::*;
let ns = self.header.default_namespace();
let ns_ref = ns.as_ref();
for entity in &mut self.entities {
match entity {
Term(x) => expand!(x, TermClause, ns_ref, 'outer),
Typedef(x) => expand!(x, TypedefClause, ns_ref, 'outer),
Instance(x) => expand!(x, InstanceClause, ns_ref, 'outer),
}
}
Ok(())
}
pub fn treat_xrefs(&mut self) {
use self::HeaderClause::*;
let entities = &mut self.entities;
crate::semantics::as_equivalent(entities, &IdentPrefix::new("BFO"));
crate::semantics::as_equivalent(entities, &IdentPrefix::new("RO"));
for clause in &self.header {
match clause {
TreatXrefsAsEquivalent(prefix) => crate::semantics::as_equivalent(entities, prefix),
TreatXrefsAsIsA(prefix) => crate::semantics::as_is_a(entities, prefix),
TreatXrefsAsHasSubclass(prefix) => {
crate::semantics::as_has_subclass(entities, prefix)
}
TreatXrefsAsGenusDifferentia(prefix, rel, cls) => {
crate::semantics::as_genus_differentia(entities, prefix, rel, cls)
}
TreatXrefsAsReverseGenusDifferentia(prefix, rel, cls) => {
crate::semantics::as_reverse_genus_differentia(entities, prefix, rel, cls)
}
TreatXrefsAsRelationship(prefix, rel) => {
crate::semantics::as_relationship(entities, prefix, rel)
}
_ => (),
}
}
}
pub fn is_fully_labeled(&self) -> bool {
self.entities.iter().all(|frame| match frame {
EntityFrame::Term(f) => f.name().is_ok(),
EntityFrame::Typedef(f) => f.name().is_ok(),
EntityFrame::Instance(f) => f.name().is_ok(),
})
}
}
impl AsRef<[EntityFrame]> for OboDoc {
fn as_ref(&self) -> &[EntityFrame] {
self.entities.as_slice()
}
}
impl AsRef<Vec<EntityFrame>> for OboDoc {
fn as_ref(&self) -> &Vec<EntityFrame> {
&self.entities
}
}
impl AsMut<Vec<EntityFrame>> for OboDoc {
fn as_mut(&mut self) -> &mut Vec<EntityFrame> {
&mut self.entities
}
}
impl Display for OboDoc {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.header.fmt(f)?;
if !self.header.is_empty() && !self.entities.is_empty() {
f.write_char('\n')?;
}
let mut entities = self.entities.iter().peekable();
while let Some(entity) = entities.next() {
entity.fmt(f)?;
if entities.peek().is_some() {
f.write_char('\n')?;
}
}
Ok(())
}
}
impl<E> FromIterator<E> for OboDoc
where
E: Into<EntityFrame>,
{
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = E>,
{
Self::with_entities(iter.into_iter().map(Into::into).collect())
}
}
impl Orderable for OboDoc {
fn sort(&mut self) {
self.header.sort_unstable();
self.entities
.sort_unstable_by(|e1, e2| e1.as_id().cmp(e2.as_id()));
for entity in &mut self.entities {
entity.sort()
}
}
fn is_sorted(&self) -> bool {
for i in 1..self.entities.len() {
if self.entities[i - 1].as_id() > self.entities[i].as_id() {
return false;
}
}
for entity in &self.entities {
if !entity.is_sorted() {
return false;
}
}
self.header.is_sorted()
}
}
impl<'i> FromPair<'i> for OboDoc {
const RULE: Rule = Rule::OboDoc;
unsafe fn from_pair_unchecked(
pair: Pair<'i, Rule>,
cache: &Cache,
) -> Result<Self, SyntaxError> {
let mut inner = pair.into_inner();
let mut entities = Vec::new();
let header = HeaderFrame::from_pair_unchecked(inner.next().unwrap(), cache)?;
let mut pair = inner.next().unwrap();
while pair.as_rule() != Rule::EOI {
entities.push(EntityFrame::from_pair_unchecked(pair, cache)?);
pair = inner.next().unwrap();
}
Ok(OboDoc { header, entities })
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::iter::FromIterator;
use std::str::FromStr;
use pretty_assertions::assert_eq;
use textwrap_macros::dedent;
#[test]
fn from_str() {
let doc = OboDoc::from_str("").unwrap();
self::assert_eq!(doc, Default::default());
let doc = OboDoc::from_str("\n\n").unwrap();
self::assert_eq!(doc, Default::default());
let doc = OboDoc::from_str(dedent!(
r#"
format-version: 1.2
[Term]
id: TEST:001
"#
))
.unwrap();
let header = HeaderFrame::from_iter(vec![HeaderClause::FormatVersion(Box::new(
UnquotedString::new("1.2"),
))]);
let term = TermFrame::new(ClassIdent::from(PrefixedIdent::new("TEST", "001")));
self::assert_eq!(doc, OboDoc::from_iter(Some(term)).and_header(header));
}
#[test]
fn to_string() {
let doc = OboDoc::default();
self::assert_eq!(doc.to_string(), "");
let doc = OboDoc::with_header(HeaderFrame::from(vec![
HeaderClause::FormatVersion(Box::new(UnquotedString::new("1.2"))),
HeaderClause::Remark(Box::new(UnquotedString::new("this is a test"))),
]));
self::assert_eq!(
doc.to_string(),
dedent!(
r#"
format-version: 1.2
remark: this is a test
"#
)
.trim_start_matches('\n')
);
}
#[test]
fn is_fully_labeled() {
let doc = OboDoc::from_str("[Term]\nid: TEST:001\n").unwrap();
assert!(!doc.is_fully_labeled());
let doc = OboDoc::from_str("[Term]\nid: TEST:001\nname: test item\n").unwrap();
assert!(doc.is_fully_labeled());
let doc = OboDoc::from_str(dedent!(
r#"
[Term]
id: TEST:001
name: test item
[Term]
id: TEST:002
name: test item two
"#
))
.unwrap();
assert!(doc.is_fully_labeled());
let doc = OboDoc::from_str(dedent!(
r#"
[Term]
id: TEST:001
name: test item
[Term]
id: TEST:002
"#
))
.unwrap();
assert!(!doc.is_fully_labeled());
let doc = OboDoc::from_str(dedent!(
r#"
[Term]
id: TEST:001
[Term]
id: TEST:002
name: test item two
"#
))
.unwrap();
assert!(!doc.is_fully_labeled());
}
}