use super::PdbHeaders;
use crate::fixed;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResidueId {
pub name: Box<str>,
pub chain: Box<str>,
pub sequence: i32,
pub insertion: Option<char>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SeqRes {
pub chain: Box<str>,
pub declared: Option<u32>,
pub residues: Vec<Box<str>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SsBond {
pub first: ResidueId,
pub second: ResidueId,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Link {
pub first_atom: Box<str>,
pub first: ResidueId,
pub second_atom: Box<str>,
pub second: ResidueId,
pub distance: Option<f64>,
}
pub(super) fn residue_id(
line: &str,
name: (usize, usize),
chain: usize,
sequence: (usize, usize),
insertion: usize,
) -> Option<ResidueId> {
let sequence = i32::try_from(fixed::integer(line, sequence.0, sequence.1)?).ok()?;
Some(ResidueId {
name: fixed::text(line, name.0, name.1).into(),
chain: fixed::text(line, chain, chain).into(),
sequence,
insertion: fixed::text(line, insertion, insertion).chars().next(),
})
}
impl PdbHeaders {
#[must_use]
pub fn seqres(&self) -> Vec<SeqRes> {
let mut chains: Vec<SeqRes> = Vec::new();
for record in self.named("SEQRES") {
let line = record.line();
let chain = fixed::text(line, 12, 12);
if !chains.iter().any(|seen| &*seen.chain == chain) {
chains.push(SeqRes {
chain: chain.into(),
declared: fixed::integer(line, 14, 17).and_then(|n| u32::try_from(n).ok()),
residues: Vec::new(),
});
}
let Some(entry) = chains.iter_mut().find(|seen| &*seen.chain == chain) else {
continue;
};
for slot in 0..13 {
let from = 20 + 4 * slot;
let name = fixed::text(line, from, from + 2);
if !name.is_empty() {
entry.residues.push(name.into());
}
}
}
chains
}
#[must_use]
pub fn ssbonds(&self) -> Vec<SsBond> {
self.named("SSBOND")
.filter_map(|record| {
let line = record.line();
Some(SsBond {
first: residue_id(line, (12, 14), 16, (18, 21), 22)?,
second: residue_id(line, (26, 28), 30, (32, 35), 36)?,
})
})
.collect()
}
#[must_use]
pub fn links(&self) -> Vec<Link> {
self.named("LINK")
.filter_map(|record| {
let line = record.line();
Some(Link {
first_atom: fixed::text(line, 13, 16).into(),
first: residue_id(line, (18, 20), 22, (23, 26), 27)?,
second_atom: fixed::text(line, 43, 46).into(),
second: residue_id(line, (48, 50), 52, (53, 56), 57)?,
distance: fixed::real(line, 74, 78),
})
})
.collect()
}
}
#[cfg(test)]
#[path = "typed_tests.rs"]
mod tests;