mod import;
mod syntax;
use std::collections::BTreeMap;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use phasesmith_crystallography::{CellError, SpaceGroup, SymmetryError, UnitCell};
use crate::SpaceGroupLookupError;
pub use import::{parse_cif_text, read_cif_file};
pub const NATIVE_CIF_BACKEND: &str = "phasesmith-native";
pub const NATIVE_CIF_BACKEND_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CifReadLimits {
pub max_bytes: usize,
pub max_blocks: usize,
pub max_loop_rows: usize,
pub max_atom_sites: usize,
}
impl Default for CifReadLimits {
fn default() -> Self {
Self {
max_bytes: 16 * 1024 * 1024,
max_blocks: 100,
max_loop_rows: 1_000_000,
max_atom_sites: 100_000,
}
}
}
impl CifReadLimits {
pub fn validate(self) -> Result<(), CifIoError> {
if self.max_bytes == 0
|| self.max_blocks == 0
|| self.max_loop_rows == 0
|| self.max_atom_sites == 0
{
return Err(CifIoError::InvalidLimits);
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CifDiagnosticSeverity {
Warning,
Error,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CifDiagnostic {
pub severity: CifDiagnosticSeverity,
pub code: String,
pub message: String,
pub tag: Option<String>,
pub row: Option<usize>,
}
impl CifDiagnostic {
fn warning(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
severity: CifDiagnosticSeverity::Warning,
code: code.into(),
message: message.into(),
tag: None,
row: None,
}
}
fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tag = Some(tag.into());
self
}
fn with_row(mut self, row: usize) -> Self {
self.row = Some(row);
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CifStructureSource {
pub format: String,
pub block_name: String,
pub backend: String,
pub backend_version: String,
pub source_path: Option<PathBuf>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DisplacementConvention {
CifU,
CifB,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CifAnisotropicDisplacement {
pub u_cif_angstrom2: [f64; 6],
pub source_convention: DisplacementConvention,
pub standard_uncertainty: [Option<f64>; 6],
}
#[derive(Clone, Debug, PartialEq)]
pub struct CifAtomSite {
pub site_id: String,
pub source_label: String,
pub type_symbol: String,
pub element_symbol: String,
pub fractional_xyz: [f64; 3],
pub occupancy: f64,
pub u_iso_angstrom2: Option<f64>,
pub anisotropic_displacement: Option<CifAnisotropicDisplacement>,
pub charge: Option<i32>,
pub isotope: Option<u32>,
pub disorder_group: Option<String>,
pub fractional_xyz_standard_uncertainty: [Option<f64>; 3],
pub occupancy_standard_uncertainty: Option<f64>,
pub u_iso_standard_uncertainty: Option<f64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CifStructure {
pub structure_id: String,
pub name: String,
pub cell: UnitCell,
pub space_group: SpaceGroup,
pub sites: Vec<CifAtomSite>,
pub source: CifStructureSource,
pub cell_standard_uncertainties: [Option<f64>; 6],
pub diagnostics: Vec<CifDiagnostic>,
pub metadata: BTreeMap<String, String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CifReadResult {
pub structure: CifStructure,
pub diagnostics: Vec<CifDiagnostic>,
pub selected_block: String,
pub available_blocks: Vec<String>,
}
#[derive(Debug)]
pub enum CifIoError {
InvalidLimits,
ByteLimitExceeded {
actual: u64,
maximum: usize,
},
Io(std::io::Error),
Syntax {
message: String,
line: Option<usize>,
},
Limit {
message: String,
},
Import {
message: String,
},
Unsupported {
feature: String,
message: String,
},
SpaceGroup(SpaceGroupLookupError),
Cell(CellError),
Symmetry(SymmetryError),
}
impl Display for CifIoError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidLimits => formatter.write_str("all CIF read limits must be positive"),
Self::ByteLimitExceeded { actual, maximum } => {
write!(
formatter,
"CIF input exceeds max_bytes: {actual} > {maximum}"
)
}
Self::Io(error) => Display::fmt(error, formatter),
Self::Syntax {
message,
line: Some(line),
} => write!(formatter, "invalid CIF syntax at line {line}: {message}"),
Self::Syntax {
message,
line: None,
} => write!(formatter, "invalid CIF syntax: {message}"),
Self::Limit { message }
| Self::Import { message }
| Self::Unsupported { message, .. } => formatter.write_str(message),
Self::SpaceGroup(error) => Display::fmt(error, formatter),
Self::Cell(error) => Display::fmt(error, formatter),
Self::Symmetry(error) => Display::fmt(error, formatter),
}
}
}
impl Error for CifIoError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(error) => Some(error),
Self::SpaceGroup(error) => Some(error),
Self::Cell(error) => Some(error),
Self::Symmetry(error) => Some(error),
_ => None,
}
}
}
fn import_error(message: impl Into<String>) -> CifIoError {
CifIoError::Import {
message: message.into(),
}
}