#[cfg(feature = "mmcif")]
use formats::{cif_write_findings, write_mmcif_to_with_options};
use molframe_core::diagnostic::{Code, Diagnostic, Findings};
use molframe_core::io::{Format, InputBuffer, OutputOptions, OutputSink, ReadOptions};
use molframe_core::structure::Structure as CoreStructure;
use std::io::Write;
use std::path::Path;
use crate::structure::Structure;
mod formats;
#[cfg(feature = "chemistry")]
pub use formats::read_component_dictionary;
#[cfg(feature = "geometry")]
pub use formats::transform;
#[cfg(feature = "pdb")]
pub use formats::write_pdb;
#[cfg(feature = "mmcif")]
pub use formats::{read_document, write_mmcif, write_mmcif_with_options};
#[cfg(feature = "bcif")]
pub use formats::{write_bcif, write_bcif_with_options};
#[cfg(feature = "mmcif")]
mod cif_family;
#[cfg(feature = "mmcif")]
mod extensions;
#[cfg(any(
feature = "mmcif",
feature = "pdb",
feature = "bcif",
feature = "modelcif"
))]
mod structure_batches;
#[cfg(any(
feature = "mmcif",
feature = "pdb",
feature = "bcif",
feature = "modelcif"
))]
pub use structure_batches::{StructureBatchReader, open_structure_batches};
#[cfg(feature = "bcif")]
use cif_family::read_bcif_buffer;
#[cfg(feature = "mmcif")]
use cif_family::read_mmcif_buffer;
pub fn read(path: impl AsRef<Path>) -> Result<Structure, Findings> {
read_with_diagnostics(path).map(|(structure, _)| structure)
}
pub fn read_with_diagnostics(
path: impl AsRef<Path>,
) -> Result<(Structure, Vec<Diagnostic>), Findings> {
read_with_options(path, &ReadOptions::new())
}
pub fn read_with_options(
path: impl AsRef<Path>,
options: &ReadOptions,
) -> Result<(Structure, Vec<Diagnostic>), Findings> {
let path = path.as_ref();
let input = InputBuffer::open(path, options.limits).map_err(Findings::from)?;
let name = path.file_name().and_then(|name| name.to_str());
read_buffer(&input, name, options)
}
pub fn read_bytes(
bytes: Vec<u8>,
name: Option<&str>,
options: &ReadOptions,
) -> Result<(Structure, Vec<Diagnostic>), Findings> {
let input = InputBuffer::from_bytes(bytes);
read_buffer(&input, name, options)
}
pub fn write(path: impl AsRef<Path>, structure: &Structure) -> Result<(), Findings> {
write_with_options(path, structure, &WriteOptions::canonical())
}
pub fn write_with_options(
path: impl AsRef<Path>,
structure: &Structure,
options: &WriteOptions,
) -> Result<(), Findings> {
let path = path.as_ref();
let name = path.file_name().and_then(|name| name.to_str());
let Some(format) = name.and_then(Format::from_name) else {
return Err(Findings::from(
Diagnostic::new(Code::E1001).with_context("name", path.display().to_string()),
));
};
let mut output = OutputSink::create(path, options.output).map_err(Findings::from)?;
write_stream(&mut output, structure, format, options)?;
output.finish().map_err(Findings::from)
}
#[derive(Clone, Debug)]
pub struct WriteOptions {
output: OutputOptions,
#[cfg(feature = "mmcif")]
cif: molframe_cif::CifWriteOptions,
#[cfg(feature = "pdb")]
pdb: molframe_pdb::PdbOptions,
}
impl WriteOptions {
#[must_use]
pub fn canonical() -> Self {
Self {
output: OutputOptions::default(),
#[cfg(feature = "mmcif")]
cif: molframe_cif::CifWriteOptions::new(),
#[cfg(feature = "pdb")]
pdb: molframe_pdb::PdbOptions::new(),
}
}
#[must_use]
pub fn with_output(mut self, output: OutputOptions) -> Self {
self.output = output;
self
}
#[cfg(feature = "mmcif")]
#[must_use]
pub fn with_cif(mut self, cif: molframe_cif::CifWriteOptions) -> Self {
self.cif = cif;
self
}
#[cfg(feature = "pdb")]
#[must_use]
pub fn with_pdb(mut self, pdb: molframe_pdb::PdbOptions) -> Self {
self.pdb = pdb;
self
}
#[must_use]
pub const fn output(&self) -> &OutputOptions {
&self.output
}
#[cfg(feature = "mmcif")]
#[must_use]
pub const fn cif(&self) -> &molframe_cif::CifWriteOptions {
&self.cif
}
#[cfg(feature = "pdb")]
#[must_use]
pub const fn pdb(&self) -> &molframe_pdb::PdbOptions {
&self.pdb
}
}
fn write_stream<W: Write>(
output: &mut W,
structure: &Structure,
format: Format,
options: &WriteOptions,
) -> Result<(), Findings> {
#[cfg(not(any(feature = "mmcif", feature = "bcif", feature = "pdb")))]
let _ = (output, structure, options);
match format {
#[cfg(feature = "mmcif")]
Format::Mmcif => write_mmcif_to_with_options(structure, options.cif(), output)
.map_err(|error| cif_write_findings(&error)),
#[cfg(feature = "bcif")]
Format::BinaryCif => molframe_bcif::write_structure_to_with_memory_limit(
structure.engine(),
options.cif(),
options.output().memory_limit_bytes,
output,
)
.map_err(Findings::from),
#[cfg(feature = "pdb")]
Format::Pdb => molframe_pdb::write_to(structure.engine(), options.pdb(), output)
.map_err(Findings::from),
#[cfg(feature = "pdb")]
Format::Mmtf => {
molframe_pdb::write_mmtf_to(structure.engine(), output).map_err(Findings::from)
}
#[cfg(feature = "pdb")]
Format::Pqr => molframe_pdb::write_pqr_to(structure.engine(), options.pdb(), output)
.map_err(Findings::from),
#[cfg(feature = "pdb")]
Format::Pdbqt => molframe_pdb::write_pdbqt_to(structure.engine(), options.pdb(), output)
.map_err(Findings::from),
other => Err(unsupported_writer(other).into()),
}
}
pub fn read_buffer(
input: &InputBuffer,
name: Option<&str>,
options: &ReadOptions,
) -> Result<(Structure, Vec<Diagnostic>), Findings> {
dispatch_read(input, name, options)
.map(|(structure, diagnostics)| (structure.into(), diagnostics))
}
fn dispatch_read(
input: &InputBuffer,
name: Option<&str>,
options: &ReadOptions,
) -> Result<(CoreStructure, Vec<Diagnostic>), Findings> {
let format = Format::detect(options.format, input, name).map_err(Findings::from)?;
match format {
#[cfg(feature = "mmcif")]
Format::Mmcif => read_mmcif_buffer(input, options),
#[cfg(feature = "mmcif")]
Format::Pdbml => read_pdbml_buffer(input, options),
#[cfg(feature = "bcif")]
Format::BinaryCif => read_bcif_buffer(input, options),
#[cfg(feature = "pdb")]
Format::Pdb => molframe_pdb::read(input, options).map_err(Findings::from),
#[cfg(feature = "pdb")]
Format::Mmtf => molframe_pdb::read_mmtf(input, options).map_err(Findings::from),
#[cfg(feature = "pdb")]
Format::Pqr => molframe_pdb::read_pqr(input, options).map_err(Findings::from),
#[cfg(feature = "pdb")]
Format::Pdbqt => molframe_pdb::read_pdbqt(input, options).map_err(Findings::from),
other => Err(unsupported(other).into()),
}
}
#[cfg(feature = "mmcif")]
fn read_pdbml_buffer(
input: &InputBuffer,
options: &ReadOptions,
) -> Result<(CoreStructure, Vec<Diagnostic>), Findings> {
match molframe_cif::read_pdbml(input.as_bytes(), options) {
Ok((document, structure, findings)) => {
extensions::attach_materialized_cif_metadata(&document, structure, findings, options)
.map_err(Findings::from)
}
Err(molframe_cif::PdbmlReadError::Findings(findings)) => Err(findings.into()),
Err(error) => Err(Findings::from(
Diagnostic::new(Code::E1102)
.with_message("PDBML/XML could not be decoded")
.with_context("decoder", error.to_string()),
)),
}
}
fn unsupported(format: Format) -> Diagnostic {
Diagnostic::new(Code::E1001)
.with_message("this build does not include a reader for the detected format")
.with_context("format", format.name())
.with_context("crate", crate_for(format))
}
fn unsupported_writer(format: Format) -> Diagnostic {
Diagnostic::new(Code::E1001)
.with_message("this format has no bounded incremental writer")
.with_context("format", format.name())
.with_context("crate", crate_for(format))
}
fn crate_for(format: Format) -> &'static str {
match format {
Format::Mmcif | Format::Pdbml => "molframe-cif",
Format::BinaryCif => "molframe-bcif",
Format::Pdb | Format::Pqr | Format::Pdbqt | Format::Mmtf => "molframe-pdb",
_ => "unlinked-format",
}
}
#[cfg(test)]
#[path = "facade/facade_tests.rs"]
mod tests;