knuckles_parse/records/
mod.rs

1//! PDB record type definitions and parsers.
2//!
3//! This module contains all the supported PDB record types and their parsing implementations.
4//! Each record type has its own submodule with specific parsing logic and data structures.
5
6/// Anisotropic temperature factor records (ANISOU)
7pub mod anisotropic;
8/// Atom coordinate records (ATOM/HETATM)
9pub mod atom;
10/// Connectivity records (CONECT)
11pub mod connect;
12/// Crystal structure parameters (CRYST1)
13pub mod crystal;
14/// Database reference records (DBREF)
15pub mod dbref;
16/// Hetero-compound records (HET)
17pub mod het;
18/// Hetero-compound name records (HETNAM)
19pub mod hetnam;
20/// Model records (MODEL)
21pub mod model;
22/// Modified residue records (MODRES)
23pub mod modres;
24/// Matrix transformation records (MTRIX1/2/3)
25pub mod mtrixn;
26/// Number of models records (NUMMDL)
27pub mod nummdl;
28/// Original coordinate system transformation records (ORIGX1/2/3)
29pub mod origxn;
30/// Scale matrix records (SCALE1/2/3)
31pub mod scalen;
32/// Sequence differences records (SEQADV)
33pub mod seqadv;
34/// Residue sequence records (SEQRES)
35pub mod seqres;
36/// Chain termination records (TER)
37pub mod term;
38
39#[cfg(feature = "serde")]
40use serde::{Deserialize, Serialize};
41
42#[cfg(feature = "python")]
43use pyo3::prelude::*;
44
45/// Represents all supported PDB record types.
46///
47/// This enum encompasses all the different types of records that can be found in a PDB file.
48/// Each variant contains the specific data structure for that record type.
49///
50/// # Record Types
51///
52/// - `Anisou` - Anisotropic temperature factor records
53/// - `Atom` - Standard atom coordinate records
54/// - `Connect` - Connectivity records showing bonds between atoms
55/// - `Crystal` - Crystallographic unit cell parameters
56/// - `DBRef` - Database reference records
57/// - `Het` - Hetero-compound records
58/// - `Hetatm` - Hetero-atom coordinate records (uses same structure as `Atom`)
59/// - `Hetnam` - Hetero-compound name records
60/// - `Nummdl` - Number of models in the file
61/// - `MtrixN` - Transformation matrix records (N = 1, 2, or 3)
62/// - `Model` - Model records for multi-model structures
63/// - `Modres` - Modified residue records
64/// - `OrigxN` - Original coordinate system transformation (N = 1, 2, or 3)
65/// - `ScaleN` - Scale matrix records (N = 1, 2, or 3)
66/// - `Seqres` - Residue sequence records
67/// - `Seqadv` - Sequence differences from database
68/// - `Term` - Chain termination records
69/// - `Endmdl` - End of model marker (no associated data)
70///
71/// # Example
72///
73/// ```rust
74/// use knuckles_parse::records::Record;
75///
76/// let line = "ATOM      1  N   ALA A   1      20.154  16.967  27.462  1.00 11.18           N";
77/// let record = Record::try_from(line).unwrap();
78///
79/// match record {
80///     Record::Atom(atom) => println!("Found atom: {}", atom.name),
81///     Record::Hetatm(hetatm) => println!("Found hetatm: {}", hetatm.name),
82///     _ => println!("Other record type"),
83/// }
84/// ```
85#[derive(Debug)]
86#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87#[cfg_attr(feature = "python", pyclass)]
88pub enum Record {
89    /// Anisotropic temperature factor record
90    Anisou(anisotropic::AnisotropicRecord),
91    /// Standard atom coordinate record
92    Atom(atom::AtomRecord),
93    /// Connectivity record showing bonds between atoms
94    Connect(connect::ConnectRecord),
95    /// Crystallographic unit cell parameters
96    Crystal(crystal::CrystalRecord),
97    /// Database reference record
98    DBRef(dbref::DBRefRecord),
99    /// Hetero-compound record
100    Het(het::HetRecord),
101    /// Hetero-atom coordinate record (same structure as Atom)
102    Hetatm(atom::AtomRecord),
103    /// Hetero-compound name record
104    Hetnam(hetnam::HetnamRecord),
105    /// Number of models record
106    Nummdl(nummdl::NummdlRecord),
107    /// Transformation matrix record (MTRIX1, MTRIX2, or MTRIX3)
108    MtrixN(mtrixn::MtrixN),
109    /// Model record for multi-model structures
110    Model(model::ModelRecord),
111    /// Modified residue record
112    Modres(modres::ModresRecord),
113    /// Original coordinate system transformation (ORIGX1, ORIGX2, or ORIGX3)
114    OrigxN(origxn::OrigxN),
115    /// Scale matrix record (SCALE1, SCALE2, or SCALE3)
116    ScaleN(scalen::ScaleN),
117    /// Residue sequence record
118    Seqres(seqres::SeqresRecord),
119    /// Sequence differences from database
120    Seqadv(seqadv::SeqAdvRecord),
121    /// Chain termination record
122    Term(term::TermRecord),
123    /// End of model marker
124    Endmdl(),
125}
126#[cfg(feature = "python")]
127macro_rules! debug_match {
128    ($self:expr, { $($variant:ident($value:ident)),* }) => {
129        match $self {
130            $(Self::$variant($value) => format!("{:?}", $value),)*
131            Self::Endmdl() => "ENDMDL".to_string(),
132        }
133    };
134}
135
136#[cfg(feature = "python")]
137#[pymethods]
138impl Record {
139    #[new]
140    fn python_new(str: &str) -> Self {
141        match Self::try_from(str) {
142            Ok(item) => item,
143            Err(_) => panic!("Unable to parse line"),
144        }
145    }
146    #[getter]
147    fn record(&self, py: Python) -> Py<PyAny> {
148        match self {
149            Self::Atom(atom) => atom.clone().into_pyobject(py).unwrap().into_any().into(),
150            Self::Anisou(anisou) => anisou.clone().into_pyobject(py).unwrap().into_any().into(),
151            Self::Connect(connect) => connect.clone().into_pyobject(py).unwrap().into_any().into(),
152            Self::Crystal(crystal) => crystal.clone().into_pyobject(py).unwrap().into_any().into(),
153            Self::DBRef(dbref) => dbref.clone().into_pyobject(py).unwrap().into_any().into(),
154            Self::Endmdl() => py.None(),
155            Self::Hetatm(atom) => atom.clone().into_pyobject(py).unwrap().into_any().into(),
156            Self::Het(het) => het.clone().into_pyobject(py).unwrap().into_any().into(),
157            Self::Hetnam(hetnam) => hetnam.clone().into_pyobject(py).unwrap().into_any().into(),
158            Self::Nummdl(nummdl) => nummdl.clone().into_pyobject(py).unwrap().into_any().into(),
159            Self::Model(model) => model.clone().into_pyobject(py).unwrap().into_any().into(),
160            Self::Modres(modres) => modres.clone().into_pyobject(py).unwrap().into_any().into(),
161            Self::MtrixN(mtrix) => mtrix.clone().into_pyobject(py).unwrap().into_any().into(),
162            Self::OrigxN(origxn) => origxn.clone().into_pyobject(py).unwrap().into_any().into(),
163            Self::ScaleN(scalen) => scalen.clone().into_pyobject(py).unwrap().into_any().into(),
164            Self::Seqadv(seqadv) => seqadv.clone().into_pyobject(py).unwrap().into_any().into(),
165            Self::Seqres(seqres) => seqres.clone().into_pyobject(py).unwrap().into_any().into(),
166            Self::Term(term) => term.clone().into_pyobject(py).unwrap().into_any().into(),
167        }
168    }
169
170    fn __repr__(&self) -> String {
171        debug_match!(
172            self,
173            {
174                Anisou(anisou),
175                Atom(atom),
176                Connect(connect),
177                Crystal(crystal),
178                DBRef(dbref),
179                Het(het),
180                Hetatm(atom),
181                Hetnam(hetnam),
182                Model(model),
183                Modres(modres),
184                MtrixN(mtrix),
185                Nummdl(nummdl),
186                OrigxN(origxn),
187                ScaleN(scalen),
188                Seqadv(seqadv),
189                Seqres(seqres),
190                Term(term)
191            }
192
193        )
194    }
195}
196
197impl TryFrom<&str> for Record {
198    type Error = &'static str;
199
200    /// Parse a PDB line into the appropriate Record variant.
201    ///
202    /// This method examines the first 6 characters of the line to determine the record type
203    /// and then delegates to the appropriate record-specific parser.
204    ///
205    /// # Arguments
206    ///
207    /// * `line` - A single line from a PDB file
208    ///
209    /// # Returns
210    ///
211    /// - `Ok(Record)` - Successfully parsed record
212    /// - `Err(&'static str)` - Error message if parsing failed
213    ///
214    /// # Errors
215    ///
216    /// Returns an error if:
217    /// - The line is shorter than 6 characters
218    /// - The record type is not recognized
219    /// - The line format is invalid for the detected record type
220    ///
221    /// # Example
222    ///
223    /// ```rust
224    /// use knuckles_parse::records::Record;
225    ///
226    /// let line = "ATOM      1  N   ALA A   1      20.154  16.967  27.462  1.00 11.18           N";
227    /// let record = Record::try_from(line).unwrap();
228    /// ```
229    fn try_from(line: &str) -> Result<Self, Self::Error> {
230        match &line.get(0..6) {
231            Some(item) => match *item {
232                "ANISOU" => Ok(Record::Anisou(anisotropic::AnisotropicRecord::from(line))),
233                "ATOM  " => Ok(Record::Atom(atom::AtomRecord::from(line))),
234                "CONECT" => Ok(Record::Connect(connect::ConnectRecord::from(line))),
235                "CRYST1" => Ok(Record::Crystal(crystal::CrystalRecord::from(line))),
236                "DBREF " => Ok(Record::DBRef(dbref::DBRefRecord::from(line))),
237                "ENDMDL" => Ok(Record::Endmdl()),
238                "HETATM" => Ok(Record::Hetatm(atom::AtomRecord::from(line))),
239                "HET   " => Ok(Record::Het(het::HetRecord::from(line))),
240                "HETNAM" => Ok(Record::Hetnam(hetnam::HetnamRecord::from(line))),
241                "MTRIX1" | "MTRIX2" | "MTRIX3" => Ok(Record::MtrixN(mtrixn::MtrixN::from(line))),
242                "MODEL " => Ok(Record::Model(model::ModelRecord::from(line))),
243                "MODRES" => Ok(Record::Modres(modres::ModresRecord::from(line))),
244                "NUMMDL" => Ok(Record::Nummdl(nummdl::NummdlRecord::from(line))),
245                "ORIGX1" | "ORIGX2" | "ORIGX3" => Ok(Record::OrigxN(origxn::OrigxN::from(line))),
246                "SCALE1" | "SCALE2" | "SCALE3" => Ok(Record::ScaleN(scalen::ScaleN::from(line))),
247                "SEQRES" => Ok(Record::Seqres(seqres::SeqresRecord::from(line))),
248                "TER   " => Ok(Record::Term(term::TermRecord::from(line))),
249                _ => Err("Unknown record type"),
250            },
251            None => Err("Unable to parse line"),
252        }
253    }
254}
255
256impl std::fmt::Display for Record {
257    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258        #[cfg(feature = "serde")]
259        match serde_json::to_string(&self) {
260            Ok(item) => write!(f, "{}", item),
261            Err(_) => Err(std::fmt::Error),
262        }
263        #[cfg(not(feature = "serde"))]
264        match self {
265            Record::Anisou(anisotropic) => write!(f, "{:?}", anisotropic),
266            Record::Atom(atom) => write!(f, "{:?}", atom),
267            Record::Connect(connect) => write!(f, "{:?}", connect),
268            Record::Crystal(crystal) => write!(f, "{:?}", crystal),
269            Record::DBRef(dbref) => write!(f, "{:?}", dbref),
270            Record::Endmdl() => write!(f, "ENDMDL"),
271            Record::Hetatm(atom) => write!(f, "{:?}", atom),
272            Record::Hetnam(hetnam) => write!(f, "{:?}", hetnam),
273            Record::Het(het) => write!(f, "{:?}", het),
274            Record::MtrixN(mtrix) => write!(f, "{:?}", mtrix),
275            Record::Model(model) => write!(f, "{:?}", model),
276            Record::Modres(modres) => write!(f, "{:?}", modres),
277            Record::Nummdl(nummdl) => write!(f, "{:?}", nummdl),
278            Record::OrigxN(origxn) => write!(f, "{:?}", origxn),
279            Record::ScaleN(scalen) => write!(f, "{:?}", scalen),
280            Record::Seqres(seqres) => write!(f, "{:?}", seqres),
281            Record::Seqadv(seqadv) => write!(f, "{:?}", seqadv),
282            Record::Term(term) => write!(f, "{:?}", term),
283        }
284    }
285}
286
287// impl From<&str> for Record {
288//     fn from(line: &str) -> Self {
289//         Self::try_from(line).unwrap()
290//     }
291// }