kira-mmcif 0.2.0

Low-level, streaming mmCIF/BinaryCIF parser focused on protein coordinates.
Documentation
use crate::bcif;
use crate::error::MmCifError;
use crate::model::Structure;
use crate::parser;
use std::fs::File;
use std::io::BufReader;
#[cfg(feature = "gzip")]
use std::io::Read;
use std::path::Path;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructureFormat {
    Mmcif,
    Bcif,
}

pub fn read_structure<P: AsRef<Path>>(path: P) -> Result<Structure, MmCifError> {
    let path = path.as_ref();
    match detect_format(path) {
        Some(format) => read_structure_with_format(path, format),
        None => {
            if let Ok(text) = read_mmcif_structure(path) {
                return Ok(text);
            }
            read_bcif_structure(path)
        }
    }
}

pub fn read_structure_with_format<P: AsRef<Path>>(
    path: P,
    format: StructureFormat,
) -> Result<Structure, MmCifError> {
    match format {
        StructureFormat::Mmcif => read_mmcif_structure(path),
        StructureFormat::Bcif => read_bcif_structure(path),
    }
}

pub fn read_mmcif_structure<P: AsRef<Path>>(path: P) -> Result<Structure, MmCifError> {
    let path = path.as_ref();
    if is_gzipped(path) {
        #[cfg(feature = "gzip")]
        {
            let file = File::open(path)?;
            let decoder = flate2::read::GzDecoder::new(file);
            let reader = BufReader::with_capacity(parser::READ_BUFFER_CAPACITY, decoder);
            return parser::parse_structure(reader);
        }
        #[cfg(not(feature = "gzip"))]
        {
            return Err(MmCifError::UnsupportedFormat(
                "gzip support not enabled (build with feature `gzip`)".to_string(),
            ));
        }
    }
    let file = File::open(path)?;
    let reader = BufReader::with_capacity(parser::READ_BUFFER_CAPACITY, file);
    parser::parse_structure(reader)
}

pub fn read_bcif_structure<P: AsRef<Path>>(path: P) -> Result<Structure, MmCifError> {
    let path = path.as_ref();
    if is_gzipped(path) {
        #[cfg(feature = "gzip")]
        {
            let file = File::open(path)?;
            let mut decoder = flate2::read::GzDecoder::new(file);
            let mut bytes = Vec::new();
            decoder.read_to_end(&mut bytes)?;
            return bcif::read_bcif_bytes(&bytes);
        }
        #[cfg(not(feature = "gzip"))]
        {
            return Err(MmCifError::UnsupportedFormat(
                "gzip support not enabled (build with feature `gzip`)".to_string(),
            ));
        }
    }
    bcif::read_bcif_structure(path)
}

fn is_gzipped(path: &Path) -> bool {
    path.extension()
        .map(|e| e.eq_ignore_ascii_case("gz"))
        .unwrap_or(false)
}

fn detect_format(path: &Path) -> Option<StructureFormat> {
    let probe = if is_gzipped(path) {
        path.file_stem().map(Path::new).map(|p| p.to_path_buf())
    } else {
        None
    };
    let target = probe.as_deref().unwrap_or(path);
    let ext = target.extension()?.to_string_lossy().to_ascii_lowercase();
    match ext.as_str() {
        "cif" | "mmcif" => Some(StructureFormat::Mmcif),
        "bcif" => Some(StructureFormat::Bcif),
        _ => None,
    }
}