diff --git a/python/fastsim/tests/test_resources.py b/python/fastsim/tests/test_resources.py new file mode 100644 index 00000000..44e888c9 --- /dev/null +++ b/python/fastsim/tests/test_resources.py @@ -0,0 +1,27 @@ +"""Test getting resource lists via Rust API""" +import unittest + +import fastsim as fsim +from fastsim import cycle, vehicle + +class TestListResources(unittest.TestCase): + def test_list_resources_for_cycle(self): + "check if list_resources works for RustCycle" + c = cycle.Cycle.from_dict({ + "cycSecs": [0.0, 1.0], + "cycMps": [0.0, 0.0]}) + rc = c.to_rust() + resources = rc.list_resources() + self.assertTrue(len(resources) > 0) + + def test_list_resources_for_vehicles(self): + "check if list_resources works for RustVehicle" + # NOTE: at the time of writing this test, + # there are no vehicle assets in resources. + # Therefore, we expect to get an empty vector. + # If resources are committed, this test should + # fail and we should use the following assert: + # self.assertTrue(len(resources) > 0) + rv = vehicle.Vehicle.from_vehdb(1).to_rust() + resources = rv.list_resources() + self.assertTrue(len(resources) == 0) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index f487422e..ccc4531a 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -607,6 +607,12 @@ impl RustCycleCache { pub fn get_delta_elev_m(&self) -> Vec { self.delta_elev_m().to_vec() } + + #[pyo3(name = "list_resources")] + /// list available cycle resources + pub fn list_resources_py(&self) -> Vec { + RustCycle::list_resources() + } )] /// Struct for containing: /// * time_s, cycle time, $s$ diff --git a/rust/fastsim-core/src/lib.rs b/rust/fastsim-core/src/lib.rs index 649187d5..35a1fb14 100644 --- a/rust/fastsim-core/src/lib.rs +++ b/rust/fastsim-core/src/lib.rs @@ -53,9 +53,6 @@ pub mod vehicle_import; pub mod vehicle_thermal; pub mod vehicle_utils; -#[cfg(feature = "dev-proc-macros")] -pub use dev_proc_macros as proc_macros; -#[cfg(not(feature = "dev-proc-macros"))] pub use fastsim_proc_macros as proc_macros; #[cfg_attr(feature = "pyo3", pyo3imports::pyfunction)] diff --git a/rust/fastsim-core/src/resources.rs b/rust/fastsim-core/src/resources.rs index 00fd59a6..da2282cc 100644 --- a/rust/fastsim-core/src/resources.rs +++ b/rust/fastsim-core/src/resources.rs @@ -2,3 +2,40 @@ use include_dir::{include_dir, Dir}; pub const RESOURCES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources"); + +/// List the available resources in the resources directory +/// - subdir: &str, a subdirectory to choose from the resources directory +/// NOTE: if subdir cannot be resolved, returns an empty list +/// RETURNS: a vector of strings for resources that can be loaded +pub fn list_resources(subdir: &str) -> Vec { + if subdir.is_empty() { + Vec::::new() + } else if let Some(resources_path) = RESOURCES_DIR.get_dir(subdir) { + let mut file_names: Vec = resources_path + .files() + .filter_map(|entry| entry.path().file_name()?.to_str().map(String::from)) + .collect(); + file_names.sort(); + file_names + } else { + Vec::::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_list_resources() { + let result = list_resources("cycles"); + assert!(result.len() == 3); + assert!(result[0] == "HHDDTCruiseSmooth.csv"); + // NOTE: at the time of writing this test, there is no + // vehicles subdirectory. The agreed-upon behavior in + // that case is that list_resources should return an + // empty vector of string. + let another_result = list_resources("vehicles"); + assert!(another_result.len() == 0); + } +} diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 5d5c6c0b..63d1a3e8 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -1,4 +1,4 @@ -use crate::imports::*; +use crate::{imports::*, resources}; use std::collections::HashMap; use ureq; @@ -13,6 +13,14 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { Ok(()) } + /// List available (compiled) resources (stored in the rust binary) + /// RESULT: + /// vector of string of resource names that can be loaded + #[cfg(feature = "resources")] + fn list_resources() -> Vec { + resources::list_resources(Self::RESOURCE_PREFIX) + } + /// Read (deserialize) an object from a resource file packaged with the `fastsim-core` crate /// /// # Arguments: diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index edecda66..4eabfe27 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -83,6 +83,12 @@ lazy_static! { self.clone() } + #[pyo3(name = "list_resources")] + /// list available vehicle resources + pub fn list_resources_py(&self) -> Vec { + RustVehicle::list_resources() + } + #[staticmethod] #[pyo3(name = "mock_vehicle")] fn mock_vehicle_py() -> Self { @@ -771,7 +777,8 @@ impl RustVehicle { self.modern_max = MODERN_MAX; } let modern_diff = self.modern_max - arrmax(&LARGE_BASELINE_EFF); - let large_baseline_eff_adj: Vec = LARGE_BASELINE_EFF.iter().map(|x| x + modern_diff).collect(); + let large_baseline_eff_adj: Vec = + LARGE_BASELINE_EFF.iter().map(|x| x + modern_diff).collect(); let mc_kw_adj_perc = max( 0.0, min( @@ -1068,8 +1075,8 @@ impl RustVehicle { } None => Self::VEHICLE_DIRECTORY_URL.to_string() + vehicle_file_name.as_ref(), }; - let mut vehicle = - Self::from_url(&url_internal, false).with_context(|| "Could not parse vehicle from url")?; + let mut vehicle = Self::from_url(&url_internal, false) + .with_context(|| "Could not parse vehicle from url")?; let vehicle_origin = "Vehicle from ".to_owned() + url_internal.as_str(); vehicle.doc = Some(vehicle_origin); Ok(vehicle)