Skip to main content

molframe_core/io/format/
options.rs

1//! Shared parsing options and reader/writer traits.
2
3use super::super::input::{InputBuffer, Limits};
4use super::detect::Format;
5use crate::diagnostic::Diagnostic;
6use crate::diagnostic::{Diagnostics, Strictness};
7use crate::structure::Structure;
8
9/// How much irregularity a read tolerates, and what it does about it.
10#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
11#[non_exhaustive]
12pub enum ParseMode {
13    /// Anything violating the specification fails the read.
14    Strict,
15    /// Produce a structure and report what was wrong with it.
16    #[default]
17    Permissive,
18    /// Continue past local errors, marking the regions they affected.
19    Recover,
20}
21
22impl ParseMode {
23    /// The strictness this mode implies.
24    #[must_use]
25    pub const fn strictness(self) -> Strictness {
26        match self {
27            Self::Strict => Strictness::Strict,
28            Self::Permissive => Strictness::Medium,
29            Self::Recover => Strictness::Loose,
30        }
31    }
32}
33
34/// Policy for atom rows whose format-specific element field is absent or invalid.
35#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
36pub enum MissingElementPolicy {
37    /// Preserve the missing value as [`Element::UNKNOWN`](crate::Element::UNKNOWN).
38    #[default]
39    PreserveUnknown,
40    /// Explicitly apply the naming convention of the selected input format.
41    InferFromAtomName,
42}
43
44/// Policy for adjacent mmCIF atom groups whose residue identifiers are identical.
45#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
46pub enum AmbiguousResidueBoundaryPolicy {
47    /// Refuse to invent a residue boundary that the deposited identifiers do not define.
48    #[default]
49    Reject,
50    /// Explicitly split when an atom name repeats in the same alternate location.
51    InferFromFileOrder,
52}
53
54/// What a read should and should not bother doing.
55#[derive(Clone, Debug, Default)]
56pub struct ReadOptions {
57    /// Which format, or automatic detection.
58    pub format: Format,
59    /// How much irregularity to tolerate.
60    pub mode: ParseMode,
61    /// Read only the first model.
62    pub only_first_model: bool,
63    /// Read coordinates and skip everything else.
64    pub only_atomic_coords: bool,
65    /// Drop hydrogens while reading.
66    pub discard_hydrogens: bool,
67    /// What to do when a row does not declare a valid element.
68    pub missing_element_policy: MissingElementPolicy,
69    /// What to do when mmCIF residue identifiers do not define a boundary.
70    pub ambiguous_residue_boundary_policy: AmbiguousResidueBoundaryPolicy,
71    /// Ceilings that apply while reading.
72    pub limits: Limits,
73}
74
75impl ReadOptions {
76    /// Options that read everything, tolerating what the archive contains.
77    #[must_use]
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    /// Sets the format.
83    #[must_use]
84    pub const fn format(mut self, format: Format) -> Self {
85        self.format = format;
86        self
87    }
88
89    /// Sets how much irregularity to tolerate.
90    #[must_use]
91    pub const fn mode(mut self, mode: ParseMode) -> Self {
92        self.mode = mode;
93        self
94    }
95
96    /// Reads only the first model.
97    #[must_use]
98    pub const fn only_first_model(mut self, only: bool) -> Self {
99        self.only_first_model = only;
100        self
101    }
102
103    /// Reads coordinates and skips everything else.
104    #[must_use]
105    pub const fn only_atomic_coords(mut self, only: bool) -> Self {
106        self.only_atomic_coords = only;
107        self
108    }
109
110    /// Selects an explicit policy for missing or invalid element fields.
111    #[must_use]
112    pub const fn missing_element_policy(mut self, policy: MissingElementPolicy) -> Self {
113        self.missing_element_policy = policy;
114        self
115    }
116
117    /// Selects an explicit policy for ambiguous mmCIF residue boundaries.
118    #[must_use]
119    pub const fn ambiguous_residue_boundary_policy(
120        mut self,
121        policy: AmbiguousResidueBoundaryPolicy,
122    ) -> Self {
123        self.ambiguous_residue_boundary_policy = policy;
124        self
125    }
126
127    /// Drops hydrogens while reading.
128    #[must_use]
129    pub const fn discard_hydrogens(mut self, discard: bool) -> Self {
130        self.discard_hydrogens = discard;
131        self
132    }
133
134    /// Sets the ceilings that apply while reading.
135    #[must_use]
136    pub const fn limits(mut self, limits: Limits) -> Self {
137        self.limits = limits;
138        self
139    }
140
141    /// Finishes a read under this option set.
142    ///
143    /// # Errors
144    ///
145    /// Returns the ordered findings when at least one finding is an error under
146    /// the selected parse mode.
147    pub fn finish(
148        &self,
149        structure: Structure,
150        findings: impl IntoIterator<Item = Diagnostic>,
151    ) -> ReadResult {
152        let mut ordered = Diagnostics::new();
153        ordered.extend(findings);
154        let is_error = ordered.has_error(self.mode.strictness());
155        let findings = ordered.finish();
156        if is_error {
157            Err(findings)
158        } else {
159            Ok((structure, findings))
160        }
161    }
162}
163
164/// A structure and everything that was wrong with the file it came from.
165pub type ReadResult = Result<(Structure, Vec<Diagnostic>), Vec<Diagnostic>>;
166
167/// A reader for one format.
168pub trait Reader {
169    /// The format this reads.
170    const FORMAT: Format;
171
172    /// Reads a structure.
173    ///
174    /// # Errors
175    ///
176    /// Returns the findings that stopped the read.
177    fn read(input: &InputBuffer, options: &ReadOptions) -> ReadResult;
178}
179
180/// Which parts of a structure a write should include.
181pub trait Select {
182    /// Whether to write this model.
183    fn accept_model(&self, _model: crate::index::ModelIndex) -> bool {
184        true
185    }
186
187    /// Whether to write this chain.
188    fn accept_chain(&self, _chain: crate::index::ChainIndex) -> bool {
189        true
190    }
191
192    /// Whether to write this residue.
193    fn accept_residue(&self, _residue: crate::index::ResidueIndex) -> bool {
194        true
195    }
196
197    /// Whether to write this atom.
198    fn accept_atom(&self, _atom: crate::index::AtomIndex) -> bool {
199        true
200    }
201}
202
203/// A filter that writes everything.
204#[derive(Clone, Copy, Debug, Default)]
205pub struct SelectAll;
206
207impl Select for SelectAll {}