#[cfg(feature = "mmcif")]
use formats::{cif_write_findings, write_mmcif_to_with_options};
use molframe_core::ExecutionContext;
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;
pub use read_in::{read_buffer_in, read_bytes_in, read_with_options_in};
#[cfg(feature = "geometry")]
pub use formats::transform;
#[cfg(feature = "chemistry")]
pub use formats::{open_component_dictionary, read_component_dictionary};
#[cfg(feature = "mmcif")]
pub use formats::{
read_document, write_mmcif, write_mmcif_with_findings, write_mmcif_with_options,
};
#[cfg(feature = "bcif")]
pub use formats::{write_bcif, write_bcif_with_findings, write_bcif_with_options};
#[cfg(feature = "pdb")]
pub use formats::{
write_pdb, write_pdb_with_findings, write_pdbqt_with_findings, write_pqr_with_findings,
};
#[cfg(feature = "mmcif")]
mod cif_family;
mod dispatch;
#[cfg(feature = "mmcif")]
mod extensions;
#[cfg(feature = "pdb")]
mod pdb_symmetry;
mod read_in;
#[cfg(feature = "chemistry")]
mod small_molecule;
#[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 = "mmcif")]
use dispatch::unsupported;
use dispatch::{dispatch_read, unsupported_writer};
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_encoded_bytes(bytes, options.limits).map_err(Findings::from)?;
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()),
));
};
write_as(path, structure, format, options)
}
pub fn write_as(
path: impl AsRef<Path>,
structure: &Structure,
format: Format,
options: &WriteOptions,
) -> Result<(), Findings> {
let path = path.as_ref();
let mut output = OutputSink::create(path, options.output).map_err(Findings::from)?;
if let Err(findings) = write_stream(&mut output, structure, format, options) {
drop(output);
let _ = std::fs::remove_file(path);
return Err(findings);
}
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 _ = options;
#[cfg(not(any(
feature = "mmcif",
feature = "bcif",
feature = "pdb",
feature = "chemistry"
)))]
let _ = (output, structure);
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(),
&formats::complete_cif_options(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(),
&formats::complete_pdb_options(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),
#[cfg(feature = "chemistry")]
Format::Sdf => small_molecule::write_sdf_to(structure.engine(), output),
other => Err(unsupported_writer(other).into()),
}
}
pub fn read_buffer(
input: &InputBuffer,
name: Option<&str>,
options: &ReadOptions,
) -> Result<(Structure, Vec<Diagnostic>), Findings> {
read_buffer_in(input, name, options, &ExecutionContext::default())
}
#[cfg(all(feature = "chemistry", feature = "spatial"))]
pub fn perceive(structure: &Structure) -> Result<(Structure, Vec<Diagnostic>), Findings> {
perceive_in(structure, &ExecutionContext::default())
}
#[cfg(all(feature = "chemistry", feature = "spatial"))]
pub fn perceive_in(
structure: &Structure,
context: &ExecutionContext,
) -> Result<(Structure, Vec<Diagnostic>), Findings> {
enrich_read(
Ok((structure.engine().clone(), Vec::new())),
&ReadOptions::new(),
context,
)
.map(|(structure, diagnostics)| (structure.into(), diagnostics))
}
#[cfg(all(feature = "chemistry", feature = "spatial"))]
fn enrich_model(
structure: CoreStructure,
findings: &mut Vec<Diagnostic>,
context: &ExecutionContext,
) -> Result<CoreStructure, Findings> {
let structure = match molframe_chem::TableStore::global()
.select_optional::<molframe_chem::BondReach>("--bond-reach")
{
Ok(Some(reach)) => {
match molframe_chem::perceive_bonds_in(&structure, &reach.provided.table, context) {
Ok(structure) => structure,
Err(finding)
if matches!(finding.code(), Code::E1902 | Code::E1904 | Code::E7001) =>
{
return Err(Findings::from(finding));
}
Err(finding) => {
findings.push(finding);
structure
}
}
}
Ok(None) => {
findings.push(Diagnostic::new(Code::W3207));
structure
}
Err(error) => {
findings.push(Diagnostic::from(error));
structure
}
};
let structure = match molframe_chem::annotate_standard_components(&structure) {
Ok(structure) => structure,
Err(finding) => {
findings.push(finding);
structure
}
};
let (states, sources) = merge_secondary(
structure.data(),
molframe_chem::assign_secondary_structure_in(&structure, context),
);
let mut data = structure.data().clone();
data.secondary_structure = states.into();
data.secondary_source = sources.into();
Ok(CoreStructure::new(data))
}
#[cfg(all(feature = "chemistry", feature = "spatial"))]
fn merge_secondary(
data: &molframe_core::StructureData,
inferred: Vec<molframe_core::SecondaryAssignment>,
) -> (
Vec<molframe_core::SecondaryStructure>,
Vec<molframe_core::SecondarySource>,
) {
inferred
.into_iter()
.enumerate()
.map(|(residue, inferred)| {
let current = match (
data.secondary_structure.get(residue),
data.secondary_source.get(residue),
) {
(Some(state), Some(source)) => molframe_core::SecondaryAssignment {
state: *state,
source: *source,
},
_ => molframe_core::SecondaryAssignment::default(),
};
let kept = if inferred.source.rank() > current.source.rank() {
inferred
} else {
current
};
(kept.state, kept.source)
})
.unzip()
}
#[cfg(all(feature = "chemistry", feature = "spatial"))]
use enrich::enrich_read;
#[cfg(not(all(feature = "chemistry", feature = "spatial")))]
fn enrich_read(
result: Result<(CoreStructure, Vec<Diagnostic>), Findings>,
_options: &ReadOptions,
_context: &ExecutionContext,
) -> Result<(CoreStructure, Vec<Diagnostic>), Findings> {
result
}
#[cfg(all(feature = "chemistry", feature = "spatial"))]
mod enrich;
#[cfg(all(test, feature = "pdb"))]
#[path = "facade/input_digest_tests.rs"]
mod input_digest_tests;
#[cfg(all(test, feature = "pdb"))]
#[path = "facade/pdb_symmetry_tests.rs"]
mod pdb_symmetry_tests;
#[cfg(all(test, feature = "chemistry", feature = "mmcif", feature = "pdb"))]
#[path = "facade/small_molecule_tests.rs"]
mod small_molecule_tests;
#[cfg(test)]
#[path = "facade/facade_tests.rs"]
mod tests;
#[cfg(all(test, feature = "pdb"))]
#[path = "facade/write_as_tests.rs"]
mod write_as_tests;
#[cfg(all(test, feature = "pdb", feature = "gzip"))]
#[path = "facade/compressed_tests.rs"]
mod compressed_tests;