readcon-core 0.13.1

An oxidized single and multiple CON file reader and writer with FFI bindings for ergonomic C/C++ usage.
Documentation
//! Chemfiles selection grammar on CON frames.
//!
//! Real evaluation requires the `chemfiles` Cargo feature. Without it, APIs
//! return [`ChemfilesImportError::FeatureDisabled`](crate::chemfiles_import::ChemfilesImportError::FeatureDisabled).

#[cfg(not(feature = "chemfiles"))]
use crate::chemfiles_import::ChemfilesImportError;
#[cfg(not(feature = "chemfiles"))]
use crate::types::ConFrame;

/// One selection match (up to 4 atom indices, chemfiles-style).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectionMatch {
    /// Number of valid entries in [`Self::atoms`] (1–4).
    pub size: usize,
    /// Atom indices in CON `atom_data` order.
    pub atoms: [usize; 4],
}

impl SelectionMatch {
    /// Slice of valid atom indices for this match.
    pub fn indices(&self) -> &[usize] {
        &self.atoms[..self.size.min(4)]
    }
}

/// Result of evaluating a chemfiles selection string on a [`ConFrame`].
#[derive(Debug, Clone)]
pub struct SelectionResult {
    /// Selection string that was evaluated.
    pub selection: String,
    /// 1 = atom, 2 = pair/bond, 3 = angle, 4 = dihedral.
    pub context_size: usize,
    /// Matches in CON `atom_data` index space.
    pub matches: Vec<SelectionMatch>,
}

impl SelectionResult {
    /// First atom index of each match (chemfiles “primary” index).
    pub fn primary_indices(&self) -> Vec<usize> {
        self.matches
            .iter()
            .filter_map(|m| m.indices().first().copied())
            .collect()
    }
}

#[cfg(feature = "chemfiles")]
#[path = "chemfiles_selection_imp.rs"]
mod imp;

#[cfg(feature = "chemfiles")]
pub use imp::{
    apply_con_bonds_to_chemfiles_frame, chemfiles_frame_from_con_frame,
    evaluate_selection_on_chemfiles_frame, evaluate_selection_on_con_frame, parse_selection_string,
    select_atom_indices,
};

#[cfg(not(feature = "chemfiles"))]
/// Evaluate a chemfiles selection-language string on a [`ConFrame`].
///
/// Always returns [`ChemfilesImportError::FeatureDisabled`] without the feature.
pub fn evaluate_selection_on_con_frame(
    _selection: &str,
    _frame: &ConFrame,
) -> Result<SelectionResult, ChemfilesImportError> {
    Err(ChemfilesImportError::FeatureDisabled)
}

#[cfg(not(feature = "chemfiles"))]
/// Atom-context selection: sorted unique atom indices.
///
/// Always returns [`ChemfilesImportError::FeatureDisabled`] without the feature.
pub fn select_atom_indices(
    _selection: &str,
    _frame: &ConFrame,
) -> Result<Vec<usize>, ChemfilesImportError> {
    Err(ChemfilesImportError::FeatureDisabled)
}

#[cfg(not(feature = "chemfiles"))]
/// Parse-only check for a selection string (returns context size).
///
/// Always returns [`ChemfilesImportError::FeatureDisabled`] without the feature.
pub fn parse_selection_string(_selection: &str) -> Result<usize, ChemfilesImportError> {
    Err(ChemfilesImportError::FeatureDisabled)
}

#[cfg(all(test, not(feature = "chemfiles")))]
mod stub_tests {
    use super::*;
    use crate::chemfiles_import::ChemfilesImportError;
    use crate::types::ConFrameBuilder;

    #[test]
    fn selection_stub_is_feature_disabled() {
        let frame = ConFrameBuilder::new([10.0; 3], [90.0; 3]).build();
        let err = evaluate_selection_on_con_frame("name O", &frame).unwrap_err();
        assert!(matches!(err, ChemfilesImportError::FeatureDisabled));
    }
}