use super::normalize_symbol;
use crate::atom::{ATOMIC_SYMBOLS, Atom, Bond};
use std::io::{self, BufRead, BufReader, Read};
fn parse_atom_line(line: &str, atom_count: &mut usize) -> Option<Atom> {
let mut iter = line.split_whitespace();
let x = iter.next()?.parse().ok()?;
let y = iter.next()?.parse().ok()?;
let z = iter.next()?.parse().ok()?;
let symbol = iter.next()?;
let atomic_number = ATOMIC_SYMBOLS
.iter()
.position(|&s| s == normalize_symbol(symbol))?
+ 1;
*atom_count += 1;
let mut atom = Atom::new(*atom_count, atomic_number as u8, x, y, z);
atom.name = symbol.to_string();
Some(atom)
}
fn parse_bond_line(line: &str) -> Option<Bond> {
let mut iter = line.split_whitespace();
let atom1 = iter.next()?.parse().ok()?;
let atom2 = iter.next()?.parse().ok()?;
let mut order = iter.next()?.parse().ok()?;
let is_aromatic = order == 4;
if is_aromatic {
order = 1;
}
Some(Bond {
atom1,
atom2,
order,
is_aromatic,
})
}
pub fn parse<P: Read>(reader: BufReader<P>) -> io::Result<(Vec<Atom>, Vec<Bond>)> {
let mut atom_count = 0;
let mut atoms = Vec::new();
let mut bonds = Vec::new();
for line in reader.lines().skip(4) {
let line = line?;
if line.contains("M END") {
break;
}
if let Some(atom) = parse_atom_line(&line, &mut atom_count) {
atoms.push(atom);
}
if let Some(bond) = parse_bond_line(&line) {
bonds.push(bond);
}
}
Ok((atoms, bonds))
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use std::fs::File;
#[rstest]
#[case("data/benzene_3d.mol", 12, 12)]
#[case("data/benzene_arom.mol", 12, 12)]
#[case("data/benzene.mol", 6, 6)]
#[case("data/tep.mol", 46, 50)]
#[case("data/corrole.mol", 37, 41)]
fn test_mol_files(#[case] filename: &str, #[case] atom_len: usize, #[case] bond_len: usize) {
let file = File::open(filename).unwrap();
let reader = BufReader::new(file);
let (atoms, bonds) = parse(reader).unwrap();
assert_eq!(atoms.len(), atom_len);
assert_eq!(bonds.len(), bond_len);
}
}