use super::super::input::{InputBuffer, Limits};
use super::detect::Format;
use crate::diagnostic::Diagnostic;
use crate::diagnostic::{Diagnostics, Strictness};
use crate::structure::Structure;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[non_exhaustive]
pub enum ParseMode {
Strict,
#[default]
Permissive,
Recover,
}
impl ParseMode {
#[must_use]
pub const fn strictness(self) -> Strictness {
match self {
Self::Strict => Strictness::Strict,
Self::Permissive => Strictness::Medium,
Self::Recover => Strictness::Loose,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum InputDigest {
#[default]
Skip,
Sha256,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum MissingElementPolicy {
#[default]
PreserveUnknown,
InferFromAtomName,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum AmbiguousResidueBoundaryPolicy {
#[default]
Reject,
InferFromFileOrder,
}
pub const REQUIRED_CIF_CATEGORIES: [&str; 5] = [
"atom_site",
"entity",
"entity_poly",
"entity_poly_seq",
"struct_asym",
];
pub const REQUIRED_PDB_RECORDS: [&str; 6] = ["ATOM", "HETATM", "MODEL", "ENDMDL", "TER", "END"];
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum CategoryFilter {
#[default]
All,
Only(Vec<Box<str>>),
Except(Vec<Box<str>>),
}
impl CategoryFilter {
#[must_use]
pub fn only<I, S>(names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<Box<str>>,
{
Self::Only(names.into_iter().map(Into::into).collect())
}
#[must_use]
pub fn except<I, S>(names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<Box<str>>,
{
Self::Except(names.into_iter().map(Into::into).collect())
}
#[must_use]
pub fn admits(&self, name: &str) -> bool {
let listed = |names: &[Box<str>]| names.iter().any(|item| item.eq_ignore_ascii_case(name));
match self {
Self::All => true,
Self::Only(names) => listed(names),
Self::Except(names) => !listed(names),
}
}
#[must_use]
pub fn keeps_category(&self, name: &str) -> bool {
self.admits(name)
|| REQUIRED_CIF_CATEGORIES
.iter()
.any(|required| required.eq_ignore_ascii_case(name))
}
#[must_use]
pub fn keeps_record(&self, name: &str) -> bool {
self.admits(name)
|| REQUIRED_PDB_RECORDS
.iter()
.any(|required| required.eq_ignore_ascii_case(name))
}
}
#[derive(Clone, Debug, Default)]
pub struct ReadOptions {
pub format: Format,
pub mode: ParseMode,
pub only_first_model: bool,
pub only_atomic_coords: bool,
pub discard_hydrogens: bool,
pub missing_element_policy: MissingElementPolicy,
pub ambiguous_residue_boundary_policy: AmbiguousResidueBoundaryPolicy,
pub limits: Limits,
pub categories: CategoryFilter,
pub input_digest: InputDigest,
}
impl ReadOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn format(mut self, format: Format) -> Self {
self.format = format;
self
}
#[must_use]
pub const fn mode(mut self, mode: ParseMode) -> Self {
self.mode = mode;
self
}
#[must_use]
pub const fn only_first_model(mut self, only: bool) -> Self {
self.only_first_model = only;
self
}
#[must_use]
pub const fn only_atomic_coords(mut self, only: bool) -> Self {
self.only_atomic_coords = only;
self
}
#[must_use]
pub const fn missing_element_policy(mut self, policy: MissingElementPolicy) -> Self {
self.missing_element_policy = policy;
self
}
#[must_use]
pub const fn ambiguous_residue_boundary_policy(
mut self,
policy: AmbiguousResidueBoundaryPolicy,
) -> Self {
self.ambiguous_residue_boundary_policy = policy;
self
}
#[must_use]
pub const fn discard_hydrogens(mut self, discard: bool) -> Self {
self.discard_hydrogens = discard;
self
}
#[must_use]
pub fn categories(mut self, categories: CategoryFilter) -> Self {
self.categories = categories;
self
}
#[must_use]
pub const fn digest_input(mut self, digest: bool) -> Self {
self.input_digest = if digest {
InputDigest::Sha256
} else {
InputDigest::Skip
};
self
}
#[must_use]
pub const fn limits(mut self, limits: Limits) -> Self {
self.limits = limits;
self
}
pub fn finish(
&self,
structure: Structure,
findings: impl IntoIterator<Item = Diagnostic>,
) -> ReadResult {
let mut ordered = Diagnostics::new();
ordered.extend(findings);
let is_error = ordered.has_error(self.mode.strictness());
let findings = ordered.finish();
if is_error {
Err(findings)
} else {
Ok((structure, findings))
}
}
}
pub type ReadResult = Result<(Structure, Vec<Diagnostic>), Vec<Diagnostic>>;
pub trait Reader {
const FORMAT: Format;
fn read(input: &InputBuffer, options: &ReadOptions) -> ReadResult;
}
pub trait Select {
fn accept_model(&self, _model: crate::index::ModelIndex) -> bool {
true
}
fn accept_chain(&self, _chain: crate::index::ChainIndex) -> bool {
true
}
fn accept_residue(&self, _residue: crate::index::ResidueIndex) -> bool {
true
}
fn accept_atom(&self, _atom: crate::index::AtomIndex) -> bool {
true
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SelectAll;
impl Select for SelectAll {}
#[cfg(test)]
#[path = "options_tests.rs"]
mod tests;