use itertools::Itertools;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fmt::Display;
use std::path::Path;
use std::sync::Arc;
use std::{fs, io};
use crate::prelude::*;
pub enum NexusBlock {
TREE,
NONE,
}
pub trait AnnotationHandler {
fn handle(&self, raw: &str) -> Option<Arc<str>>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct KeepRawAnnotations;
impl AnnotationHandler for KeepRawAnnotations {
fn handle(&self, raw: &str) -> Option<Arc<str>> {
Some(Arc::from(raw))
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DiscardAnnotations;
impl AnnotationHandler for DiscardAnnotations {
fn handle(&self, _raw: &str) -> Option<Arc<str>> {
None
}
}
impl<F> AnnotationHandler for F
where
F: Fn(&str) -> Option<Arc<str>>,
{
fn handle(&self, raw: &str) -> Option<Arc<str>> {
self(raw)
}
}
pub trait AnnotationWriter {
fn render<'a>(&self, annotation: &'a str) -> Option<Cow<'a, str>>;
}
impl AnnotationWriter for KeepRawAnnotations {
fn render<'a>(&self, annotation: &'a str) -> Option<Cow<'a, str>> {
Some(Cow::Borrowed(annotation))
}
}
impl AnnotationWriter for DiscardAnnotations {
fn render<'a>(&self, _annotation: &'a str) -> Option<Cow<'a, str>> {
None
}
}
impl<F> AnnotationWriter for F
where
F: Fn(&str) -> Option<String>,
{
fn render<'a>(&self, annotation: &'a str) -> Option<Cow<'a, str>> {
self(annotation).map(Cow::Owned)
}
}
pub trait Newick: RootedTree {
fn from_newick_with<H: AnnotationHandler>(
newick_str: &[u8],
annotations: H,
) -> std::io::Result<Self>;
fn from_newick(newick_str: &[u8]) -> std::io::Result<Self> {
Self::from_newick_with(newick_str, KeepRawAnnotations)
}
fn subtree_to_newick_with<H: AnnotationWriter>(
&self,
node_id: TreeNodeID<Self>,
annotations: H,
) -> impl Display;
fn subtree_to_newick(&self, node_id: TreeNodeID<Self>) -> impl Display {
self.subtree_to_newick_with(node_id, KeepRawAnnotations)
}
fn to_newick_with<H: AnnotationWriter>(&self, annotations: H) -> impl Display {
format!(
"{};",
self.subtree_to_newick_with(self.get_root_id(), annotations)
)
}
fn to_newick(&self) -> impl Display {
format!("{};", self.subtree_to_newick(self.get_root_id()))
}
fn to_file(&self, p: &Path) -> io::Result<()> {
assert!(p.extension() == Some(OsStr::new("nwk")));
fs::write(p, self.to_newick().to_string().as_bytes())
}
fn from_file(p: &Path) -> io::Result<Self> {
assert!(p.extension() == Some(OsStr::new("nwk")));
let nwk_string = fs::read_to_string(p)?
.as_bytes()
.iter()
.copied()
.take_while(|x| *x != b';')
.collect_vec();
Self::from_newick(nwk_string.as_slice())
}
}
pub trait Nexus: Newick {
fn from_nexus(p: String) -> std::io::Result<Self> {
let file_lines = p.lines().collect_vec();
if file_lines.first() != Some(&"#NEXUS") {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
NexusError::InvalidHeader,
));
}
let mut tree_block = String::new();
let mut curr_block = NexusBlock::NONE;
for line in file_lines {
let line_words = line
.split_ascii_whitespace()
.map(|x| x.to_ascii_lowercase())
.collect_vec();
match line_words.first().map(String::as_str) {
None => continue,
Some("begin") => {
curr_block = match line_words.get(1).map(String::as_str) {
Some("trees;") => NexusBlock::TREE,
_ => NexusBlock::NONE,
};
}
Some("end;") => curr_block = NexusBlock::NONE,
Some(_) => {
if matches!(curr_block, NexusBlock::TREE) {
tree_block.push_str(line);
}
}
}
}
let first_tree = tree_block
.split(';')
.next()
.and_then(|def| def.split_once('='))
.map(|(_, newick)| newick.split_whitespace().collect::<String>())
.filter(|newick| !newick.is_empty())
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
NexusError::MissingTreeBlock,
)
})?;
Self::from_newick(format!("{first_tree};").as_bytes())
}
fn from_nexus_file(p: &Path) -> std::io::Result<Self> {
assert!(p.extension() == Some(OsStr::new("nex")));
let file_data = fs::read_to_string(p)?;
Self::from_nexus(file_data)
}
fn to_nexus(&self) -> io::Result<String> {
Ok(format!(
"#NEXUS\n\nBEGIN TREES;\n\tTree tree={}\nEND;",
self.to_newick()
))
}
fn to_nexus_file(&self, p: &Path) -> io::Result<()> {
assert!(p.extension() == Some(OsStr::new("nex")));
fs::write(p, self.to_nexus()?.as_bytes())
}
}