Skip to main content

readcon_core/
chemfiles_selection.rs

1//! Chemfiles selection grammar on CON frames.
2//!
3//! Real evaluation requires the `chemfiles` Cargo feature. Without it, APIs
4//! return [`ChemfilesImportError::FeatureDisabled`](crate::chemfiles_import::ChemfilesImportError::FeatureDisabled).
5
6#[cfg(not(feature = "chemfiles"))]
7use crate::chemfiles_import::ChemfilesImportError;
8#[cfg(not(feature = "chemfiles"))]
9use crate::types::ConFrame;
10
11/// One selection match (up to 4 atom indices, chemfiles-style).
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct SelectionMatch {
14    /// Number of valid entries in [`Self::atoms`] (1–4).
15    pub size: usize,
16    /// Atom indices in CON `atom_data` order.
17    pub atoms: [usize; 4],
18}
19
20impl SelectionMatch {
21    /// Slice of valid atom indices for this match.
22    pub fn indices(&self) -> &[usize] {
23        &self.atoms[..self.size.min(4)]
24    }
25}
26
27/// Result of evaluating a chemfiles selection string on a [`ConFrame`].
28#[derive(Debug, Clone)]
29pub struct SelectionResult {
30    /// Selection string that was evaluated.
31    pub selection: String,
32    /// 1 = atom, 2 = pair/bond, 3 = angle, 4 = dihedral.
33    pub context_size: usize,
34    /// Matches in CON `atom_data` index space.
35    pub matches: Vec<SelectionMatch>,
36}
37
38impl SelectionResult {
39    /// First atom index of each match (chemfiles “primary” index).
40    pub fn primary_indices(&self) -> Vec<usize> {
41        self.matches
42            .iter()
43            .filter_map(|m| m.indices().first().copied())
44            .collect()
45    }
46}
47
48#[cfg(feature = "chemfiles")]
49#[path = "chemfiles_selection_imp.rs"]
50mod imp;
51
52#[cfg(feature = "chemfiles")]
53pub use imp::{
54    apply_con_bonds_to_chemfiles_frame, chemfiles_frame_from_con_frame,
55    evaluate_selection_on_chemfiles_frame, evaluate_selection_on_con_frame, parse_selection_string,
56    select_atom_indices,
57};
58
59#[cfg(not(feature = "chemfiles"))]
60/// Evaluate a chemfiles selection-language string on a [`ConFrame`].
61///
62/// Always returns [`ChemfilesImportError::FeatureDisabled`] without the feature.
63pub fn evaluate_selection_on_con_frame(
64    _selection: &str,
65    _frame: &ConFrame,
66) -> Result<SelectionResult, ChemfilesImportError> {
67    Err(ChemfilesImportError::FeatureDisabled)
68}
69
70#[cfg(not(feature = "chemfiles"))]
71/// Atom-context selection: sorted unique atom indices.
72///
73/// Always returns [`ChemfilesImportError::FeatureDisabled`] without the feature.
74pub fn select_atom_indices(
75    _selection: &str,
76    _frame: &ConFrame,
77) -> Result<Vec<usize>, ChemfilesImportError> {
78    Err(ChemfilesImportError::FeatureDisabled)
79}
80
81#[cfg(not(feature = "chemfiles"))]
82/// Parse-only check for a selection string (returns context size).
83///
84/// Always returns [`ChemfilesImportError::FeatureDisabled`] without the feature.
85pub fn parse_selection_string(_selection: &str) -> Result<usize, ChemfilesImportError> {
86    Err(ChemfilesImportError::FeatureDisabled)
87}
88
89#[cfg(all(test, not(feature = "chemfiles")))]
90mod stub_tests {
91    use super::*;
92    use crate::chemfiles_import::ChemfilesImportError;
93    use crate::types::ConFrameBuilder;
94
95    #[test]
96    fn selection_stub_is_feature_disabled() {
97        let frame = ConFrameBuilder::new([10.0; 3], [90.0; 3]).build();
98        let err = evaluate_selection_on_con_frame("name O", &frame).unwrap_err();
99        assert!(matches!(err, ChemfilesImportError::FeatureDisabled));
100    }
101}