use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Mutex;
pub static CONFIG: Lazy<Mutex<Config>> = Lazy::new(|| Mutex::new(Config::new()));
#[derive(Debug, Clone)]
pub struct Config {
pub cross_sections: HashMap<String, String>,
pub default_cross_section: Option<String>,
}
impl Config {
pub fn new() -> Self {
Config {
cross_sections: HashMap::new(),
default_cross_section: None,
}
}
pub fn set_cross_section(&mut self, nuclide_or_keyword: &str, path: Option<&str>) {
const ACCEPTABLE_KEYWORDS: &[&str] = &["tendl-21", "fendl-3.2c"];
match path {
Some(p) => {
self.cross_sections.insert(nuclide_or_keyword.to_string(), p.to_string());
}
None => {
if !ACCEPTABLE_KEYWORDS.contains(&nuclide_or_keyword) {
panic!("Invalid cross section keyword: '{}'. Acceptable keywords are: {}", nuclide_or_keyword, ACCEPTABLE_KEYWORDS.join(", "));
}
self.default_cross_section = Some(nuclide_or_keyword.to_string());
}
}
}
pub fn get_cross_section(&self, nuclide: &str) -> Option<String> {
self.cross_sections.get(nuclide).cloned()
.or_else(|| self.default_cross_section.clone())
}
pub fn set_cross_sections<T>(&mut self, input: T)
where
T: IntoCrossSectionsInput,
{
input.apply(self);
}
pub fn clear(&mut self) {
self.cross_sections.clear();
self.default_cross_section = None;
}
}
pub trait IntoCrossSectionsInput {
fn apply(self, config: &mut Config);
}
impl IntoCrossSectionsInput for HashMap<String, String> {
fn apply(self, config: &mut Config) {
const ACCEPTABLE_KEYWORDS: &[&str] = &["tendl-21", "fendl-3.2c"];
for (nuclide, path) in self {
if ACCEPTABLE_KEYWORDS.contains(&path.as_str()) {
config.default_cross_section = Some(path.clone());
}
config.cross_sections.insert(nuclide, path);
}
}
}
impl IntoCrossSectionsInput for &str {
fn apply(self, config: &mut Config) {
const ACCEPTABLE_KEYWORDS: &[&str] = &["tendl-21", "fendl-3.2c"];
if !ACCEPTABLE_KEYWORDS.contains(&self) {
panic!("Invalid cross section keyword: '{}'. Acceptable keywords are: {}", self, ACCEPTABLE_KEYWORDS.join(", "));
}
config.default_cross_section = Some(self.to_string());
}
}
impl IntoCrossSectionsInput for String {
fn apply(self, config: &mut Config) {
IntoCrossSectionsInput::apply(self.as_str(), config);
}
}
impl Config {
pub fn global() -> std::sync::MutexGuard<'static, Self> {
CONFIG.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_cross_section_global_keyword() {
let mut config = Config::new();
config.set_cross_section("tendl-21", None);
assert_eq!(config.default_cross_section, Some("tendl-21".to_string()));
}
#[test]
#[should_panic(expected = "Invalid cross section keyword")]
fn test_set_cross_section_invalid_keyword() {
let mut config = Config::new();
config.set_cross_section("invalid-keyword", None);
}
#[test]
fn test_set_and_get_cross_section_for_nuclide() {
let mut config = Config::new();
config.set_cross_section("Li6", Some("/path/to/Li6.json"));
assert_eq!(config.get_cross_section("Li6"), Some("/path/to/Li6.json".to_string()));
}
#[test]
fn test_get_cross_section_fallback_to_global() {
let mut config = Config::new();
config.set_cross_section("tendl-21", None);
assert_eq!(config.get_cross_section("Fe56"), Some("tendl-21".to_string()));
}
#[test]
fn test_set_cross_section_path_to_file() {
let mut config = Config::new();
config.set_cross_section("Fe56", Some("../../tests/Fe56.json"));
assert_eq!(config.get_cross_section("Fe56"), Some("../../tests/Fe56.json".to_string()));
}
#[test]
fn test_set_cross_section_single_keyword() {
let mut config = Config::new();
config.set_cross_section("Fe56", Some("tendl-21"));
assert_eq!(config.get_cross_section("Fe56"), Some("tendl-21".to_string()));
}
#[test]
fn test_set_cross_sections_multiple() {
let mut config = Config::new();
let cross_sections = std::collections::HashMap::from([
("Li7".to_string(), "../../tests/Li7.json".to_string()),
("Li6".to_string(), "tendl-21".to_string()),
]);
config.set_cross_sections(cross_sections);
assert_eq!(config.get_cross_section("Li7"), Some("../../tests/Li7.json".to_string()));
assert_eq!(config.get_cross_section("Li6"), Some("tendl-21".to_string()));
}
#[test]
fn test_set_cross_section_global_keyword_for_all() {
let mut config = Config::new();
config.set_cross_section("tendl-21", None);
assert_eq!(config.get_cross_section("Li6"), Some("tendl-21".to_string()));
assert_eq!(config.get_cross_section("Fe56"), Some("tendl-21".to_string()));
}
#[test]
fn test_set_cross_sections_with_string_keyword() {
let mut config = Config::new();
config.set_cross_sections("tendl-21");
assert_eq!(config.default_cross_section, Some("tendl-21".to_string()));
assert_eq!(config.get_cross_section("Li6"), Some("tendl-21".to_string()));
assert_eq!(config.get_cross_section("Fe56"), Some("tendl-21".to_string()));
}
#[test]
fn test_set_cross_sections_with_hashmap() {
let mut config = Config::new();
let cross_sections = std::collections::HashMap::from([
("Li6".to_string(), "../../tests/Li6.json".to_string()),
("Fe56".to_string(), "tendl-21".to_string()),
]);
config.set_cross_sections(cross_sections);
assert_eq!(config.get_cross_section("Li6"), Some("../../tests/Li6.json".to_string()));
assert_eq!(config.get_cross_section("Fe56"), Some("tendl-21".to_string()));
assert_eq!(config.default_cross_section, Some("tendl-21".to_string()));
}
#[test]
#[should_panic(expected = "Invalid cross section keyword")]
fn test_set_cross_sections_invalid_string_keyword() {
let mut config = Config::new();
config.set_cross_sections("invalid-keyword");
}
}