1use super::PdbHeaders;
7use crate::fixed;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct ResidueId {
12 pub name: Box<str>,
14 pub chain: Box<str>,
16 pub sequence: i32,
18 pub insertion: Option<char>,
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct SeqRes {
25 pub chain: Box<str>,
27 pub declared: Option<u32>,
29 pub residues: Vec<Box<str>>,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct SsBond {
36 pub first: ResidueId,
38 pub second: ResidueId,
40}
41
42#[derive(Clone, Debug, PartialEq)]
44pub struct Link {
45 pub first_atom: Box<str>,
47 pub first: ResidueId,
49 pub second_atom: Box<str>,
51 pub second: ResidueId,
53 pub distance: Option<f64>,
55}
56
57pub(super) fn residue_id(
59 line: &str,
60 name: (usize, usize),
61 chain: usize,
62 sequence: (usize, usize),
63 insertion: usize,
64) -> Option<ResidueId> {
65 let sequence = i32::try_from(fixed::integer(line, sequence.0, sequence.1)?).ok()?;
66 Some(ResidueId {
67 name: fixed::text(line, name.0, name.1).into(),
68 chain: fixed::text(line, chain, chain).into(),
69 sequence,
70 insertion: fixed::text(line, insertion, insertion).chars().next(),
71 })
72}
73
74impl PdbHeaders {
75 #[must_use]
77 pub fn seqres(&self) -> Vec<SeqRes> {
78 let mut chains: Vec<SeqRes> = Vec::new();
79 for record in self.named("SEQRES") {
80 let line = record.line();
81 let chain = fixed::text(line, 12, 12);
82 if !chains.iter().any(|seen| &*seen.chain == chain) {
83 chains.push(SeqRes {
84 chain: chain.into(),
85 declared: fixed::integer(line, 14, 17).and_then(|n| u32::try_from(n).ok()),
86 residues: Vec::new(),
87 });
88 }
89 let Some(entry) = chains.iter_mut().find(|seen| &*seen.chain == chain) else {
90 continue;
91 };
92 for slot in 0..13 {
94 let from = 20 + 4 * slot;
95 let name = fixed::text(line, from, from + 2);
96 if !name.is_empty() {
97 entry.residues.push(name.into());
98 }
99 }
100 }
101 chains
102 }
103
104 #[must_use]
106 pub fn ssbonds(&self) -> Vec<SsBond> {
107 self.named("SSBOND")
108 .filter_map(|record| {
109 let line = record.line();
110 Some(SsBond {
111 first: residue_id(line, (12, 14), 16, (18, 21), 22)?,
112 second: residue_id(line, (26, 28), 30, (32, 35), 36)?,
113 })
114 })
115 .collect()
116 }
117
118 #[must_use]
120 pub fn links(&self) -> Vec<Link> {
121 self.named("LINK")
122 .filter_map(|record| {
123 let line = record.line();
124 Some(Link {
125 first_atom: fixed::text(line, 13, 16).into(),
126 first: residue_id(line, (18, 20), 22, (23, 26), 27)?,
127 second_atom: fixed::text(line, 43, 46).into(),
128 second: residue_id(line, (48, 50), 52, (53, 56), 57)?,
129 distance: fixed::real(line, 74, 78),
130 })
131 })
132 .collect()
133 }
134}
135
136#[cfg(test)]
137#[path = "typed_tests.rs"]
138mod tests;