Skip to main content

readcon_core/
chemfiles_import.rs

1//! Chemfiles → CON conversion.
2//!
3//! Real implementation requires the `chemfiles` Cargo feature (links libchemfiles).
4//! Without it, path/memory helpers are still present and return
5//! [`ChemfilesImportError::FeatureDisabled`] so call sites compile uniformly.
6
7#[cfg(feature = "chemfiles")]
8#[path = "chemfiles_import_imp.rs"]
9mod imp;
10
11#[cfg(feature = "chemfiles")]
12pub use imp::*;
13
14#[cfg(not(feature = "chemfiles"))]
15mod stubs {
16    use std::fmt;
17    use std::path::Path;
18
19    use crate::types::ConFrame;
20
21    /// Prefix for unmapped chemfiles frame properties in CON metadata.
22    pub const CHEMFILES_EXTRA_PREFIX: &str = "chemfiles::";
23    /// Per-atom property bag key in frame metadata.
24    pub const CHEMFILES_ATOM_PROPS_KEY: &str = "chemfiles_atom_properties";
25    /// Display names in chemfiles / `atom_id` order.
26    pub const CHEMFILES_ATOM_NAMES_KEY: &str = "chemfiles_atom_names";
27    /// Atomic types in chemfiles / `atom_id` order.
28    pub const CHEMFILES_ATOM_TYPES_KEY: &str = "chemfiles_atom_types";
29
30    /// Errors from chemfiles I/O or conversion (or missing feature).
31    #[derive(Debug)]
32    pub enum ChemfilesImportError {
33        /// Atom / property count mismatch or other structural problem.
34        InvalidFrame(String),
35        /// I/O while reading a trajectory path.
36        Io(std::io::Error),
37        /// This build was compiled without the `chemfiles` Cargo feature.
38        FeatureDisabled,
39    }
40
41    impl fmt::Display for ChemfilesImportError {
42        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43            match self {
44                ChemfilesImportError::InvalidFrame(msg) => {
45                    write!(f, "invalid chemfiles frame: {msg}")
46                }
47                ChemfilesImportError::Io(e) => write!(f, "I/O error: {e}"),
48                ChemfilesImportError::FeatureDisabled => write!(
49                    f,
50                    "chemfiles support is not enabled in this build; rebuild with `--features chemfiles` \
51(Python: `maturin develop --features python,chemfiles` or install the `chemfiles` extra from source — see docs)"
52                ),
53            }
54        }
55    }
56
57    impl std::error::Error for ChemfilesImportError {
58        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
59            match self {
60                ChemfilesImportError::Io(e) => Some(e),
61                ChemfilesImportError::InvalidFrame(_) | ChemfilesImportError::FeatureDisabled => {
62                    None
63                }
64            }
65        }
66    }
67
68    impl From<std::io::Error> for ChemfilesImportError {
69        fn from(e: std::io::Error) -> Self {
70            ChemfilesImportError::Io(e)
71        }
72    }
73
74    fn disabled<T>() -> Result<T, ChemfilesImportError> {
75        Err(ChemfilesImportError::FeatureDisabled)
76    }
77
78    /// Open a trajectory with chemfiles and convert every step to [`ConFrame`].
79    ///
80    /// Stub without the `chemfiles` feature — always returns [`ChemfilesImportError::FeatureDisabled`].
81    pub fn con_frames_from_trajectory_path<P: AsRef<Path>>(
82        _path: P,
83    ) -> Result<Vec<ConFrame>, ChemfilesImportError> {
84        disabled()
85    }
86
87    /// Read the first frame from a trajectory path.
88    ///
89    /// Stub without the `chemfiles` feature — always returns [`ChemfilesImportError::FeatureDisabled`].
90    pub fn con_frame_from_trajectory_path<P: AsRef<Path>>(
91        _path: P,
92    ) -> Result<ConFrame, ChemfilesImportError> {
93        disabled()
94    }
95
96    /// Read a trajectory from an in-memory buffer (chemfiles memory reader).
97    ///
98    /// Stub without the `chemfiles` feature — always returns [`ChemfilesImportError::FeatureDisabled`].
99    pub fn con_frames_from_memory(
100        _data: &str,
101        _format: &str,
102    ) -> Result<Vec<ConFrame>, ChemfilesImportError> {
103        disabled()
104    }
105
106    /// Whether this build linked libchemfiles and implements import/selection.
107    pub const fn chemfiles_enabled() -> bool {
108        false
109    }
110}
111
112#[cfg(not(feature = "chemfiles"))]
113pub use stubs::*;
114
115#[cfg(feature = "chemfiles")]
116/// Whether this build linked libchemfiles and implements import/selection.
117pub const fn chemfiles_enabled() -> bool {
118    true
119}
120
121#[cfg(test)]
122mod stub_tests {
123    use super::*;
124
125    #[test]
126    fn chemfiles_enabled_matches_feature() {
127        assert_eq!(chemfiles_enabled(), cfg!(feature = "chemfiles"));
128    }
129
130    #[cfg(not(feature = "chemfiles"))]
131    #[test]
132    fn trajectory_path_stub_is_feature_disabled() {
133        let err = con_frame_from_trajectory_path("nope.xyz").unwrap_err();
134        assert!(matches!(err, ChemfilesImportError::FeatureDisabled));
135        let msg = err.to_string();
136        assert!(msg.contains("chemfiles"), "{msg}");
137    }
138}