embassy/stm32-gen-features/src/lib.rs
2021-10-26 17:33:28 +02:00

216 lines
6.2 KiB
Rust

//! FIXME discuss about which errors to print and when to panic
use std::{iter::FilterMap, path::Path, slice::Iter};
const SUPPORTED_FAMILIES: [&str; 11] = [
"stm32f0",
"stm32f1",
"stm32f4",
"stm32f7",
"stm32g0",
"stm32l0",
"stm32l1",
"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<String>)>,
fn(&(String, Vec<String>)) -> Option<(&String, &Vec<String>)>,
>;
trait FilterSupported {
fn supported(&self) -> SupportedIter;
}
impl FilterSupported for &[(String, Vec<String>)] {
/// 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<String>)> {
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<String> {
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<String>)]) -> String {
let mut result = String::new();
for (chip_name, cores) in names_and_cores.supported() {
if cores.len() > 1 {
for core_name in cores.iter() {
result += &format!(
"{chip}_{core} = [ \"stm32-metapac/{chip}_{core}\" ]\n",
chip = chip_name,
core = core_name
);
}
} else {
result += &format!("{chip} = [ \"stm32-metapac/{chip}\" ]\n", chip = chip_name);
}
}
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<String>)]) -> String {
let mut result = String::new();
for (chip_name, cores) in names_and_cores {
if cores.len() > 1 {
for core_name in cores {
result += &format!("{}_{} = []\n", chip_name, core_name);
}
} else {
result += &format!("{} = []\n", chip_name);
}
}
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: &str) -> String {
let (before, after) = split_cargo_toml_contents(previous_text);
before.to_owned() + SEPARATOR_START + HELP + new_contents + 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 new_contents = String::from("a = [\"b\"]\n");
assert_eq!(generate_cargo_toml_file(initial, &new_contents), expected);
}
#[test]
#[should_panic]
fn does_not_generate_if_separators_are_missing() {
let initial = "\
before
# END GENERATED FEATURES
after
";
let new_contents = String::from("a = [\"b\"]\n");
generate_cargo_toml_file(initial, &new_contents);
}
}