//! FIXME discuss about which errors to print and when to panic use std::{collections::HashMap, iter::FilterMap, path::Path, slice::Iter}; const SUPPORTED_FAMILIES: [&str; 8] = [ "stm32f0", "stm32f4", "stm32g0", "stm32l0", "stm32l4", "stm32h7", "stm32wb55", "stm32wl55", ]; const SEPARATOR_START: &str = "# BEGIN GENERATED FEATURES\n"; const SEPARATOR_END: &str = "# END GENERATED FEATURES\n"; const HELP: &str = "# Generated by stm32-gen-features. DO NOT EDIT.\n"; /// True if the chip named `name` is supported else false fn is_supported(name: &str) -> bool { SUPPORTED_FAMILIES .iter() .any(|family| name.starts_with(family)) } type SupportedIter<'a> = FilterMap< Iter<'a, (String, Vec)>, fn(&(String, Vec)) -> Option<(&String, &Vec)>, >; trait FilterSupported { fn supported(&self) -> SupportedIter; } impl FilterSupported for &[(String, Vec)] { /// Get a new Vec with only the supported chips fn supported(&self) -> SupportedIter { self.iter() .filter_map(|(name, cores)| is_supported(name).then(|| (name, cores))) } } /// Get the list of all the chips and their supported cores /// /// Print errors to `stderr` when something is returned by the glob but is not in the returned /// [`Vec`] /// /// This function is slow because all the yaml files are parsed. pub fn chip_names_and_cores() -> Vec<(String, Vec)> { glob::glob("../stm32-data/data/chips/*.yaml") .unwrap() .filter_map(|entry| entry.map_err(|e| eprintln!("{:?}", e)).ok()) .filter_map(|entry| { if let Some(name) = entry.file_stem().and_then(|stem| stem.to_str()) { Some((name.to_lowercase(), chip_cores(&entry))) } else { eprintln!("{:?} is not a regular file", entry); None } }) .collect() } /// Get the list of the cores of a chip by its associated file /// /// # Panic /// Panics if the file does not exist or if it contains yaml syntax errors. /// Panics if "cores" is not an array. fn chip_cores(path: &Path) -> Vec { let file_contents = std::fs::read_to_string(path).unwrap(); let doc = &yaml_rust::YamlLoader::load_from_str(&file_contents).unwrap()[0]; doc["cores"] .as_vec() .unwrap_or_else(|| panic!("{:?}:[cores] is not an array", path)) .iter() .enumerate() .map(|(i, core)| { core["name"] .as_str() .unwrap_or_else(|| panic!("{:?}:[cores][{}][name] is not a string", path, i)) .to_owned() }) .collect() } /// Generate data needed in `../embassy-stm32/Cargo.toml` /// /// Print errors to `stderr` when something is returned by the glob but is not in the returned /// [`Vec`] /// /// # Panic /// Panics if a file contains yaml syntax errors or if a value does not have a consistent type pub fn embassy_stm32_needed_data( names_and_cores: &[(String, Vec)], ) -> HashMap> { let mut result = HashMap::new(); for (chip_name, cores) in names_and_cores.supported() { if cores.len() > 1 { for core_name in cores.iter() { let key = format!("{}_{}", chip_name, core_name); let value = vec![format!("stm32-metapac/{}_{}", chip_name, core_name)]; result.insert(key, value); } } else { let key = chip_name.to_string(); let value = vec![format!("stm32-metapac/{}", chip_name)]; result.insert(key, value); } } result } /// Generate data needed in `../stm32-metapac/Cargo.toml` /// /// Print errors to `stderr` when something is returned by the glob but is not in the returned /// [`Vec`] /// /// # Panic /// Panics if a file contains yaml syntax errors or if a value does not have a consistent type pub fn stm32_metapac_needed_data( names_and_cores: &[(String, Vec)], ) -> HashMap> { let mut result = HashMap::new(); for (chip_name, cores) in names_and_cores { if cores.len() > 1 { for core_name in cores { let key = format!("{}_{}", chip_name, core_name); result.insert(key, vec![]); } } else { result.insert(chip_name.clone(), vec![]); } } result } /// Get contents before and after generated contents /// /// # Panic /// Panics when a separator cound not be not found fn split_cargo_toml_contents(contents: &str) -> (&str, &str) { let (before, remainder) = contents .split_once(SEPARATOR_START) .unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_START)); let (_, after) = remainder .split_once(SEPARATOR_END) .unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_END)); (before, after) } /// Generates new contents for Cargo.toml /// /// # Panic /// Panics when a separator cound not be not found pub fn generate_cargo_toml_file( previous_text: &str, new_contents: &HashMap>, ) -> String { let (before, after) = split_cargo_toml_contents(previous_text); let generated_content = toml::to_string(new_contents).unwrap(); before.to_owned() + SEPARATOR_START + HELP + &generated_content + SEPARATOR_END + after } #[cfg(test)] mod tests { use super::*; #[test] fn stm32f407vg_is_supported() { assert!(is_supported("stm32f407vg")) } #[test] fn abcdef_is_not_supported() { assert!(!is_supported("abcdef")) } #[test] #[ignore] fn stm32f407vg_yaml_file_exists_and_is_supported() { assert!(chip_names_and_cores() .as_slice() .supported() .into_iter() .any(|(name, _)| { name == "stm32f407vg" })) } #[test] fn keeps_text_around_separators() { let initial = "\ before # BEGIN GENERATED FEATURES # END GENERATED FEATURES after "; let expected = "\ before # BEGIN GENERATED FEATURES # Generated by stm32-gen-features. DO NOT EDIT. a = [\"b\"] # END GENERATED FEATURES after "; let map = HashMap::from([(String::from("a"), vec![String::from("b")])]); assert_eq!(generate_cargo_toml_file(initial, &map), expected); } #[test] #[should_panic] fn does_not_generate_if_separators_are_missing() { let initial = "\ before # END GENERATED FEATURES after "; let map = HashMap::from([(String::from("a"), vec![String::from("b")])]); generate_cargo_toml_file(initial, &map); } }