mod car;
mod cif;
mod cjson;
mod cml;
mod mol2;
mod pdb;
mod sdf;
mod xsd;
mod xyz;
mod gaussian_input;
mod vasp_input;
mod extxyz;
use gut::fs::*;
type FileReader = BufReader<File>;
pub(self) use gchemol_core::{Atom, AtomKind, Bond, BondKind, Lattice, Molecule, Vector3f};
pub(self) use gut::prelude::*;
pub(self) mod parser {
pub use gchemol_parser::parsers::*;
pub use gchemol_parser::partition::{Partitions, Preceded, ReadAction, ReadContext, ReadPart, Terminated};
pub use gchemol_parser::TextReader;
}
pub(self) trait ChemicalFile: ParseMolecule {
fn ftype(&self) -> &str;
fn possible_extensions(&self) -> Vec<&str>;
fn format_molecule(&self, mol: &Molecule) -> Result<String> {
bail!("not implemented yet")
}
fn parsable(&self, filename: &Path) -> bool {
let filename = format!("{}", filename.display());
let filename = filename.to_lowercase();
for s in self.possible_extensions() {
if filename.ends_with(&s.to_lowercase()) {
return true;
}
}
false
}
fn describe(&self) {
println!("filetype: {:?}, possible extensions: {:?}", self.ftype(), self.possible_extensions());
}
}
pub(self) trait ParseMolecule {
fn parse_molecule(&self, input: &str) -> Result<Molecule>;
}
use gchemol_parser::TextReader;
macro_rules! cf_parse {
($chemical_file:expr, $parsed_mols_iter:expr, $reader:expr) => {
$parsed_mols_iter = {
let cf = $chemical_file();
let iter = cf.partitions($reader)?.map(move |part| cf.parse_molecule(part.as_str()));
Some(iter)
}
};
}
impl ChemicalFileParser {
pub fn parse_molecules_from<R>(&self, r: TextReader<R>) -> Result<impl Iterator<Item = Molecule>>
where
R: BufRead + Seek,
{
let mut p1 = None;
let mut p2 = None;
let mut p3 = None;
let mut p4 = None;
let mut p5 = None;
let mut p6 = None;
let mut p7 = None;
let mut p8 = None;
let mut p9 = None;
let mut p10 = None;
let mut p11 = None;
let mut p12 = None;
match self.0.as_str() {
"text/xyz" => cf_parse!(XyzFile, p1, r),
"text/pxyz" => cf_parse!(PlainXyzFile, p2, r),
"text/mol2" => cf_parse!(Mol2File, p3, r),
"text/cif" => cf_parse!(CifFile, p4, r),
"text/sdf" => cf_parse!(SdfFile, p5, r),
"text/pdb" => cf_parse!(PdbFile, p6, r),
"vasp/input" => cf_parse!(PoscarFile, p7, r),
"gaussian/input" => cf_parse!(GaussianInputFile, p8, r),
"xml/xsd" => cf_parse!(XsdFile, p9, r),
"xml/cml" => cf_parse!(CmlFile, p10, r),
"text/car" => cf_parse!(CarFile, p11, r),
"text/cjson" => cf_parse!(ChemicalJsonFile, p12, r),
_ => bail!("No available parser found"),
}
Ok(p1
.into_iter()
.flatten()
.chain(p2.into_iter().flatten())
.chain(p3.into_iter().flatten())
.chain(p4.into_iter().flatten())
.chain(p5.into_iter().flatten())
.chain(p6.into_iter().flatten())
.chain(p7.into_iter().flatten())
.chain(p8.into_iter().flatten())
.chain(p9.into_iter().flatten())
.chain(p10.into_iter().flatten())
.chain(p11.into_iter().flatten())
.chain(p12.into_iter().flatten())
.filter_map(|parsed| match parsed {
Ok(mol) => Some(mol),
Err(e) => {
eprintln!("found parsing error: {:?}", e);
None
}
}))
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! cf_impl_partitions {
($chemical_file:ident) => {
mod impl_partitions {
use super::$chemical_file;
use crate::formats::*;
impl $chemical_file {
pub fn partitions<R: BufRead + Seek>(&self, mut r: TextReader<R>) -> Result<impl Iterator<Item = String>> {
let mut s = String::new();
let _ = r.read_to_string(&mut s)?;
Ok(Some(s).into_iter())
}
}
}
};
}
use self::car::CarFile;
use self::cif::CifFile;
use self::cjson::ChemicalJsonFile;
use self::cml::CmlFile;
use self::xsd::XsdFile;
use self::xyz::PlainXyzFile;
use self::xyz::XyzFile;
pub use self::gaussian_input::GaussianInputFile;
pub use self::mol2::Mol2File;
pub use self::pdb::PdbFile;
pub use self::sdf::SdfFile;
pub use self::vasp_input::PoscarFile;
pub(super) struct ChemicalFileParser(pub String);
impl ChemicalFileParser {
pub fn new(fmt: &str) -> Self {
Self(fmt.to_owned())
}
pub fn guess_from_path(path: &Path) -> Option<Self> {
guess_chemical_file_format_from_path(path).map(move |cf| Self::new(cf.ftype()))
}
pub fn guess_format_from_path(path: &Path) -> Option<String> {
guess_chemical_file_format_from_path(path).map(move |cf| cf.ftype().to_owned())
}
pub fn guess(path: &Path, fmt: Option<&str>) -> Option<Self> {
guess_chemical_file_format(path, fmt).map(|cf| Self::new(cf.ftype()))
}
pub fn parse_molecules(&self, path: &Path) -> Result<impl Iterator<Item = Molecule>> {
let r = TextReader::try_from_path(path).context("Parse molecules from path failed")?;
self.parse_molecules_from(r)
}
}
pub(super) fn write_chemical_file<'a>(
path: &Path,
mols: impl IntoIterator<Item = &'a Molecule>,
fmt: Option<&str>,
) -> Result<()> {
if let Some(cf) = guess_chemical_file_format(path, fmt) {
let mut fp = File::create(path).with_context(|| format!("Failed to create file: {:?}", path))?;
for mol in mols {
let s = cf.format_molecule(mol)?;
fp.write(s.as_bytes());
}
} else {
bail!("No suitable chemical file format found for {:?}", path);
}
Ok(())
}
pub(super) fn format_as_chemical_file(mol: &Molecule, fmt: &str) -> Result<String> {
if let Some(cf) = guess_chemical_file_format_from_ftype(fmt) {
return cf.format_molecule(mol);
}
bail!("No suitable chemical file format found for {:}", fmt);
}
macro_rules! avail_parsers {
() => {
vec![
Box::new(self::xyz::XyzFile()),
Box::new(self::xyz::PlainXyzFile()),
Box::new(self::mol2::Mol2File()),
Box::new(self::cif::CifFile()),
Box::new(self::vasp_input::PoscarFile()),
Box::new(self::gaussian_input::GaussianInputFile()),
Box::new(self::sdf::SdfFile()),
Box::new(self::pdb::PdbFile()),
Box::new(self::xsd::XsdFile()),
Box::new(self::car::CarFile()),
Box::new(self::cml::CmlFile()),
Box::new(self::cjson::ChemicalJsonFile()),
]
};
}
fn guess_chemical_file_format_from_ftype(fmt: &str) -> Option<Box<dyn ChemicalFile>> {
let backends: Vec<Box<dyn ChemicalFile>> = avail_parsers!();
for x in backends {
if x.ftype() == fmt.to_lowercase() {
return Some(x);
}
}
None
}
fn guess_chemical_file_format_from_path(filename: &Path) -> Option<Box<dyn ChemicalFile>> {
let backends: Vec<Box<dyn ChemicalFile>> = avail_parsers!();
for x in backends {
if x.parsable(filename) {
return Some(x);
}
}
None
}
fn guess_chemical_file_format(filename: &Path, fmt: Option<&str>) -> Option<Box<dyn ChemicalFile>> {
fmt.and_then(|fmt| guess_chemical_file_format_from_ftype(fmt))
.or_else(|| guess_chemical_file_format_from_path(filename))
}
pub fn describe_backends() {
let backends: Vec<Box<dyn ChemicalFile>> = avail_parsers!();
for cf in backends {
cf.describe();
}
}
#[test]
fn test_backends() {
let f = "/tmp/test.xyz";
let cf = guess_chemical_file_format(f.as_ref(), None).expect("guess xyz");
assert_eq!(cf.ftype(), "text/xyz");
let f = "/tmp/test";
let cf = guess_chemical_file_format(f.as_ref(), Some("text/xyz")).expect("guess xyz ftype");
assert_eq!(cf.ftype(), "text/xyz");
let f = "/tmp/test.poscar";
let cf = guess_chemical_file_format(f.as_ref(), None).expect("guess xyz ftype");
assert_eq!(cf.ftype(), "vasp/input");
}
pub use extxyz::ExtxyzFile;