use std::fmt;
use std::path::Path;
use std::sync::OnceLock;
use crate::Composition;
use crate::error::{Error, Result};
use crate::ffi::bridge as sys;
pub struct UnimodTable {
inner: Table,
}
enum Table {
Static(&'static sys::UnimodTable),
Owned(cxx::UniquePtr<sys::UnimodTable>),
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct UnimodEntry {
pub id: u32,
pub name: String,
pub mono_mass: f64,
pub composition: Composition,
}
impl UnimodTable {
pub fn embedded() -> &'static UnimodTable {
static EMBEDDED: OnceLock<UnimodTable> = OnceLock::new();
EMBEDDED.get_or_init(|| UnimodTable {
inner: Table::Static(
sys::unimod_embedded().expect("parsing the embedded Unimod table"),
),
})
}
pub fn from_csv(csv: &str) -> Result<UnimodTable> {
Ok(UnimodTable {
inner: Table::Owned(sys::unimod_from_csv(csv)?),
})
}
pub fn from_path(path: impl AsRef<Path>) -> Result<UnimodTable> {
let path = path.as_ref();
let path = path.to_str().ok_or_else(|| {
Error::invalid(format!(
"Unimod table path is not UTF-8: {}",
path.display()
))
})?;
if path.is_empty() {
return Err(Error::invalid("empty Unimod table path"));
}
Ok(UnimodTable {
inner: Table::Static(sys::unimod_for_path(path)?),
})
}
pub(crate) fn raw(&self) -> &sys::UnimodTable {
match &self.inner {
Table::Static(t) => t,
Table::Owned(t) => t.as_ref().expect("non-null"),
}
}
pub fn capacity(&self) -> usize {
sys::unimod_size(self.raw())
}
pub fn lookup(&self, id: u32) -> Option<UnimodEntry> {
let mut name = String::new();
let mut mono_mass = 0.0;
let mut first = Vec::new();
let mut counts = Vec::new();
let found = sys::unimod_lookup(
self.raw(),
id,
&mut name,
&mut mono_mass,
&mut first,
&mut counts,
)
.expect("allocation failed");
if !found {
return None;
}
let composition = first
.iter()
.zip(counts)
.map(|(&f, c)| {
(
crate::Element::from_first_index(f).expect("valid element"),
c,
)
})
.collect();
Some(UnimodEntry {
id,
name,
mono_mass,
composition,
})
}
pub fn entries(&self) -> impl Iterator<Item = UnimodEntry> + '_ {
(0..self.capacity() as u32).filter_map(|id| self.lookup(id))
}
}
impl fmt::Debug for UnimodTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnimodTable")
.field("capacity", &self.capacity())
.finish_non_exhaustive()
}
}