Skip to main content

bids_core/
lib.rs

1#![deny(unsafe_code)]
2//! Core types and abstractions for working with BIDS datasets.
3//!
4//! This crate provides the foundational building blocks used by all other `bids-*` crates:
5//!
6//! - [`Entity`] and [`EntityValue`] — BIDS entity definitions (subject, session, task, run, …)
7//!   with regex-based extraction from file paths and typed value coercion.
8//! - [`BidsFile`] — Representation of a single file in a BIDS dataset, with automatic
9//!   file type detection, entity extraction, suffix/extension parsing, and companion
10//!   file lookup.
11//! - [`BidsMetadata`] — Ordered key-value metadata dictionary backed by `IndexMap`,
12//!   supporting typed access (`get_f64`, `get_str`, `get_array`, …) and deserialization
13//!   into arbitrary structs.
14//! - [`DatasetDescription`] — Typed representation of `dataset_description.json` with
15//!   validation, derivative detection, and legacy field migration.
16//! - [`Config`] — Layout configuration defining entity patterns and path templates,
17//!   loadable from built-in configs (`bids`, `derivatives`) or custom JSON files.
18//! - [`PaddedInt`] — Zero-padded integer type that preserves formatting (e.g., `"02"`)
19//!   while comparing numerically.
20//! - [`BidsError`] — Comprehensive error enum covering I/O, JSON, validation, entity,
21//!   filter, database, and path-building errors.
22//!
23//! # BIDS Entities
24//!
25//! BIDS filenames encode metadata as key-value pairs separated by underscores:
26//!
27//! ```text
28//! sub-01_ses-02_task-rest_run-01_bold.nii.gz
29//! ^^^^^^ ^^^^^^ ^^^^^^^^^ ^^^^^^ ^^^^
30//! subject session  task     run   suffix
31//! ```
32//!
33//! The [`mod@entities`] module defines the canonical entity ordering and provides
34//! functions to parse entities from paths, sort them, and coerce values to
35//! the correct types (string, padded integer, float, boolean).
36//!
37//! # Example
38//!
39//! ```
40//! use bids_core::{BidsFile, Config, DatasetDescription};
41//!
42//! // Parse entities from a filename
43//! let config = Config::bids();
44//! let entities = bids_core::entities::parse_file_entities(
45//!     "sub-01/eeg/sub-01_task-rest_eeg.edf",
46//!     &config.entities,
47//! );
48//! assert_eq!(entities.get("subject").unwrap().as_str_lossy(), "01");
49//! assert_eq!(entities.get("task").unwrap().as_str_lossy(), "rest");
50//! ```
51
52pub mod config;
53pub mod dataset_description;
54pub mod entities;
55pub mod error;
56pub mod file;
57pub mod genetic;
58pub mod hed;
59pub mod metadata;
60pub mod padded_int;
61pub mod timeseries;
62pub mod utils;
63
64pub use config::Config;
65pub use dataset_description::DatasetDescription;
66pub use entities::{Entities, Entity, EntityValue, StringEntities};
67pub use error::{BidsError, Result};
68pub use file::{BidsFile, CopyMode, FileType};
69pub use metadata::BidsMetadata;
70pub use padded_int::PaddedInt;
71pub use utils::{collect_associated_files, convert_json_keys, matches_entities};
72
73/// Helper for modality crates: try to read from a companion file path.
74///
75/// Returns `Ok(None)` if the file doesn't exist, `Ok(Some(T))` if it does
76/// and is readable, or `Err(...)` on I/O errors.
77pub fn try_read_companion<T, F>(path: &std::path::Path, reader: F) -> Result<Option<T>>
78where
79    F: FnOnce(&std::path::Path) -> Result<T>,
80{
81    if path.exists() {
82        Ok(Some(reader(path)?))
83    } else {
84        Ok(None)
85    }
86}
87
88/// Convenience macro for building an [`Entities`] map inline.
89///
90/// # Example
91///
92/// ```
93/// use bids_core::entities;
94///
95/// let ents = entities! {
96///     "subject" => "01",
97///     "task" => "rest",
98///     "suffix" => "eeg",
99///     "extension" => ".edf",
100/// };
101/// assert_eq!(ents.get("subject").unwrap().as_str_lossy(), "01");
102/// assert_eq!(ents.len(), 4);
103/// ```
104#[macro_export]
105macro_rules! entities {
106    ($($key:expr => $val:expr),* $(,)?) => {{
107        let mut map = $crate::Entities::new();
108        $(
109            map.insert($key.to_string(), $crate::EntityValue::from($val));
110        )*
111        map
112    }};
113}