1use super::super::input::{InputBuffer, Limits};
4use super::detect::Format;
5use crate::diagnostic::Diagnostic;
6use crate::diagnostic::{Diagnostics, Strictness};
7use crate::structure::Structure;
8
9#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
11#[non_exhaustive]
12pub enum ParseMode {
13 Strict,
15 #[default]
17 Permissive,
18 Recover,
20}
21
22impl ParseMode {
23 #[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#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
36pub enum InputDigest {
37 #[default]
40 Skip,
41 Sha256,
43}
44
45#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
47pub enum MissingElementPolicy {
48 #[default]
50 PreserveUnknown,
51 InferFromAtomName,
53}
54
55#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
57pub enum AmbiguousResidueBoundaryPolicy {
58 #[default]
60 Reject,
61 InferFromFileOrder,
63}
64
65pub const REQUIRED_CIF_CATEGORIES: [&str; 5] = [
67 "atom_site",
68 "entity",
69 "entity_poly",
70 "entity_poly_seq",
71 "struct_asym",
72];
73
74pub const REQUIRED_PDB_RECORDS: [&str; 6] = ["ATOM", "HETATM", "MODEL", "ENDMDL", "TER", "END"];
76
77#[derive(Clone, Debug, Default, PartialEq, Eq)]
84pub enum CategoryFilter {
85 #[default]
87 All,
88 Only(Vec<Box<str>>),
90 Except(Vec<Box<str>>),
92}
93
94impl CategoryFilter {
95 #[must_use]
97 pub fn only<I, S>(names: I) -> Self
98 where
99 I: IntoIterator<Item = S>,
100 S: Into<Box<str>>,
101 {
102 Self::Only(names.into_iter().map(Into::into).collect())
103 }
104
105 #[must_use]
107 pub fn except<I, S>(names: I) -> Self
108 where
109 I: IntoIterator<Item = S>,
110 S: Into<Box<str>>,
111 {
112 Self::Except(names.into_iter().map(Into::into).collect())
113 }
114
115 #[must_use]
117 pub fn admits(&self, name: &str) -> bool {
118 let listed = |names: &[Box<str>]| names.iter().any(|item| item.eq_ignore_ascii_case(name));
119 match self {
120 Self::All => true,
121 Self::Only(names) => listed(names),
122 Self::Except(names) => !listed(names),
123 }
124 }
125
126 #[must_use]
128 pub fn keeps_category(&self, name: &str) -> bool {
129 self.admits(name)
130 || REQUIRED_CIF_CATEGORIES
131 .iter()
132 .any(|required| required.eq_ignore_ascii_case(name))
133 }
134
135 #[must_use]
137 pub fn keeps_record(&self, name: &str) -> bool {
138 self.admits(name)
139 || REQUIRED_PDB_RECORDS
140 .iter()
141 .any(|required| required.eq_ignore_ascii_case(name))
142 }
143}
144
145#[derive(Clone, Debug, Default)]
147pub struct ReadOptions {
148 pub format: Format,
150 pub mode: ParseMode,
152 pub only_first_model: bool,
154 pub only_atomic_coords: bool,
156 pub discard_hydrogens: bool,
158 pub missing_element_policy: MissingElementPolicy,
160 pub ambiguous_residue_boundary_policy: AmbiguousResidueBoundaryPolicy,
162 pub limits: Limits,
164 pub categories: CategoryFilter,
166 pub input_digest: InputDigest,
169}
170
171impl ReadOptions {
172 #[must_use]
174 pub fn new() -> Self {
175 Self::default()
176 }
177
178 #[must_use]
180 pub const fn format(mut self, format: Format) -> Self {
181 self.format = format;
182 self
183 }
184
185 #[must_use]
187 pub const fn mode(mut self, mode: ParseMode) -> Self {
188 self.mode = mode;
189 self
190 }
191
192 #[must_use]
194 pub const fn only_first_model(mut self, only: bool) -> Self {
195 self.only_first_model = only;
196 self
197 }
198
199 #[must_use]
201 pub const fn only_atomic_coords(mut self, only: bool) -> Self {
202 self.only_atomic_coords = only;
203 self
204 }
205
206 #[must_use]
208 pub const fn missing_element_policy(mut self, policy: MissingElementPolicy) -> Self {
209 self.missing_element_policy = policy;
210 self
211 }
212
213 #[must_use]
215 pub const fn ambiguous_residue_boundary_policy(
216 mut self,
217 policy: AmbiguousResidueBoundaryPolicy,
218 ) -> Self {
219 self.ambiguous_residue_boundary_policy = policy;
220 self
221 }
222
223 #[must_use]
225 pub const fn discard_hydrogens(mut self, discard: bool) -> Self {
226 self.discard_hydrogens = discard;
227 self
228 }
229
230 #[must_use]
233 pub fn categories(mut self, categories: CategoryFilter) -> Self {
234 self.categories = categories;
235 self
236 }
237
238 #[must_use]
240 pub const fn digest_input(mut self, digest: bool) -> Self {
241 self.input_digest = if digest {
242 InputDigest::Sha256
243 } else {
244 InputDigest::Skip
245 };
246 self
247 }
248
249 #[must_use]
251 pub const fn limits(mut self, limits: Limits) -> Self {
252 self.limits = limits;
253 self
254 }
255
256 pub fn finish(
263 &self,
264 structure: Structure,
265 findings: impl IntoIterator<Item = Diagnostic>,
266 ) -> ReadResult {
267 let mut ordered = Diagnostics::new();
268 ordered.extend(findings);
269 let is_error = ordered.has_error(self.mode.strictness());
270 let findings = ordered.finish();
271 if is_error {
272 Err(findings)
273 } else {
274 Ok((structure, findings))
275 }
276 }
277}
278
279pub type ReadResult = Result<(Structure, Vec<Diagnostic>), Vec<Diagnostic>>;
281
282pub trait Reader {
284 const FORMAT: Format;
286
287 fn read(input: &InputBuffer, options: &ReadOptions) -> ReadResult;
293}
294
295pub trait Select {
297 fn accept_model(&self, _model: crate::index::ModelIndex) -> bool {
299 true
300 }
301
302 fn accept_chain(&self, _chain: crate::index::ChainIndex) -> bool {
304 true
305 }
306
307 fn accept_residue(&self, _residue: crate::index::ResidueIndex) -> bool {
309 true
310 }
311
312 fn accept_atom(&self, _atom: crate::index::AtomIndex) -> bool {
314 true
315 }
316}
317
318#[derive(Clone, Copy, Debug, Default)]
320pub struct SelectAll;
321
322impl Select for SelectAll {}
323
324#[cfg(test)]
325#[path = "options_tests.rs"]
326mod tests;