use std::{ collections::HashMap, path::{Path, PathBuf}, }; 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)) } /// Get the yaml file names and the associated chip names for supported chips /// /// Print errors to `stderr` when something is returned by the glob but is not in the returned /// [`Vec`] fn supported_chip_yaml_files_with_names() -> Vec<(PathBuf, String)> { glob::glob("../stm32-data/data/chips/*.yaml") .expect("bad glob pattern") .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()) { if is_supported(name) { let owned_name = name.to_lowercase(); Some((entry, owned_name)) } else { eprintln!("{} is not supported", name); None } } else { eprintln!("{:?} is not a regural 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 /// /// # None /// Returns none if "cores" is not an array fn chip_cores(path: &Path) -> Option> { 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().cloned() } /// Load the list of chips /// /// # Panic /// Panics if a file contains yaml syntax errors or if a value does not have a consistent type pub fn load_chip_list() -> HashMap> { let mut result = HashMap::new(); for (path, name) in supported_chip_yaml_files_with_names() { let cores = chip_cores(&path).unwrap_or_else(|| panic!("{}[cores] is not an array", name)); if cores.len() > 1 { for (i, core) in cores.into_iter().enumerate() { let core_name = core["name"] .as_str() .unwrap_or_else(|| panic!("{}[cores][{}][name] is not a string", name, i)); let key = format!("{}_{}", name, core_name); let value = vec![format!("stm32-metapac/{}_{}", name, core_name)]; result.insert(key, value); } } else { let value = vec![format!("stm32-metapac/{}", &name)]; result.insert(name, value); } } 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] fn stm32f407vg_yaml_file_exists() { assert!(supported_chip_yaml_files_with_names() .into_iter() .any(|(path, name)| { name == "stm32f407vg" && path.to_str() == Some("../stm32-data/data/chips/STM32F407VG.yaml") })) } #[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); } }