use std::{fs, path::Path};
use anyhow::{Context, Result};
use regex::Regex;
#[derive(Clone, Copy, Default)]
pub(crate) struct Forms {
pub hal_split: bool,
pub hal_flat: bool,
pub pac_split: bool,
pub pac_flat: bool,
pub summary: bool,
pub pinreport: bool,
}
fn slugify(name: &str) -> String {
let mut s = name.to_lowercase();
s = s.replace(['(', ')', '[', ']', '-', ' ', '.'], "_");
let re = Regex::new(r"[^a-z0-9_]").unwrap();
let s = re.replace_all(&s, "");
let re2 = Regex::new(r"_+").unwrap();
re2.replace_all(&s, "_").trim_matches('_').to_string()
}
fn family_from_slug(slug: &str) -> Option<String> {
let re = Regex::new(r"^stm32([a-z])(\d)").unwrap();
re.captures(slug)
.map(|c| format!("stm32-{}{}", &c[1], &c[2]))
}
pub(crate) fn scan_tree(src: &Path) -> Result<Vec<(String, Forms)>> {
let mut out = vec![];
for entry in fs::read_dir(src).with_context(|| format!("reading {}", src.display()))? {
let entry = entry?;
if !entry.file_type()?.is_dir() {
continue;
}
let mcu_dir = entry.path();
let slug = slugify(entry.file_name().to_string_lossy().as_ref());
let mut f = Forms::default();
let has = |p: &Path| p.exists();
if has(&mcu_dir.join("hal/split.rs")) {
f.hal_split = true;
}
if has(&mcu_dir.join("hal/flat.rs")) {
f.hal_flat = true;
}
if has(&mcu_dir.join("pac/split.rs")) {
f.pac_split = true;
}
if has(&mcu_dir.join("pac/flat.rs")) {
f.pac_flat = true;
}
if has(&mcu_dir.join("summary.rs")) {
f.summary = true;
}
if has(&mcu_dir.join("pinreport.rs")) {
f.pinreport = true;
}
out.push((slug, f));
}
out.sort_by(|a, b| a.0.cmp(&b.0));
Ok(out)
}
pub(crate) fn emit_lib_rs(
src: impl AsRef<Path>,
out: impl AsRef<Path>,
prelude: Option<(&str, &str)>,
features: &[String],
family_prefix: Option<&str>,
inline_includes: bool,
) -> Result<()> {
let src = src.as_ref();
let mcus = scan_tree(src)?;
let mut s = String::new();
s.push_str("//! Auto-generated by rlvgl-creator — DO NOT EDIT\n#![no_std]\n\n");
if let Some(default_form) = prelude {
s.push_str("/// Ergonomic re-exports of the default form.\npub mod prelude {\n");
for (slug, forms) in &mcus {
match default_form {
("hal", "split")
if forms.hal_split
&& features.contains(&"hal".to_string())
&& features.contains(&"split".to_string()) =>
{
s.push_str(&format!(
" #[cfg(all(feature=\"hal\", feature=\"split\"))]\n pub mod {slug} {{ pub use crate::{slug}::hal::split::*; }}\n",
));
}
("pac", "split")
if forms.pac_split
&& features.contains(&"pac".to_string())
&& features.contains(&"split".to_string()) =>
{
s.push_str(&format!(
" #[cfg(all(feature=\"pac\", feature=\"split\"))]\n pub mod {slug} {{ pub use crate::{slug}::pac::split::*; }}\n",
));
}
("hal", "flat")
if forms.hal_flat
&& features.contains(&"hal".to_string())
&& features.contains(&"flat".to_string()) =>
{
s.push_str(&format!(
" #[cfg(all(feature=\"hal\", feature=\"flat\"))]\n pub mod {slug} {{ pub use crate::{slug}::hal::flat::*; }}\n",
));
}
("pac", "flat")
if forms.pac_flat
&& features.contains(&"pac".to_string())
&& features.contains(&"flat".to_string()) =>
{
s.push_str(&format!(
" #[cfg(all(feature=\"pac\", feature=\"flat\"))]\n pub mod {slug} {{ pub use crate::{slug}::pac::flat::*; }}\n",
));
}
_ => {}
}
}
s.push_str("}\n\n");
}
for (slug, forms) in mcus {
if !inline_includes {
if let (Some(prefix), Some(fam)) = (family_prefix, family_from_slug(&slug)) {
let fam = fam.strip_prefix("stm32-").unwrap_or(&fam);
s.push_str(&format!("#[cfg(feature=\"{}{}\")]\n", prefix, fam));
}
s.push_str(&format!("pub mod {slug};\n\n"));
continue;
}
if let (Some(prefix), Some(fam)) = (family_prefix, family_from_slug(&slug)) {
let fam = fam.strip_prefix("stm32-").unwrap_or(&fam);
s.push_str(&format!("#[cfg(feature=\"{}{}\")]\n", prefix, fam));
}
s.push_str(&format!("pub mod {slug} {{\n"));
if (forms.hal_split || forms.hal_flat) && features.contains(&"hal".to_string()) {
s.push_str(" #[cfg(feature=\"hal\")] pub mod hal {\n");
if forms.hal_split && features.contains(&"split".to_string()) {
if inline_includes {
s.push_str(&format!(
" #[cfg(feature=\"split\")] pub mod split {{ include!(\"{slug}/hal/split.rs\"); }}\n",
));
} else {
s.push_str(" #[cfg(feature=\"split\")] pub mod split;\n");
}
}
if forms.hal_flat && features.contains(&"flat".to_string()) {
if inline_includes {
s.push_str(&format!(
" #[cfg(feature=\"flat\")] pub mod flat {{ include!(\"{slug}/hal/flat.rs\"); }}\n",
));
} else {
s.push_str(" #[cfg(feature=\"flat\")] pub mod flat;\n");
}
}
s.push_str(" }\n");
}
if (forms.pac_split || forms.pac_flat) && features.contains(&"pac".to_string()) {
s.push_str(" #[cfg(feature=\"pac\")] pub mod pac {\n");
if forms.pac_split && features.contains(&"split".to_string()) {
if inline_includes {
s.push_str(&format!(
" #[cfg(feature=\"split\")] pub mod split {{ include!(\"{slug}/pac/split.rs\"); }}\n",
));
} else {
s.push_str(" #[cfg(feature=\"split\")] pub mod split;\n");
}
}
if forms.pac_flat && features.contains(&"flat".to_string()) {
if inline_includes {
s.push_str(&format!(
" #[cfg(feature=\"flat\")] pub mod flat {{ include!(\"{slug}/pac/flat.rs\"); }}\n",
));
} else {
s.push_str(" #[cfg(feature=\"flat\")] pub mod flat;\n");
}
}
s.push_str(" }\n");
}
if forms.summary && features.contains(&"summaries".to_string()) {
if inline_includes {
s.push_str(&format!(
" #[cfg(feature=\"summaries\")] pub mod summary {{ include!(\"{slug}/summary.rs\"); }}\n",
));
} else {
s.push_str(" #[cfg(feature=\"summaries\")] pub mod summary;\n");
}
}
if forms.pinreport && features.contains(&"pinreport".to_string()) {
if inline_includes {
s.push_str(&format!(
" #[cfg(feature=\"pinreport\")] pub mod pinreport {{ include!(\"{slug}/pinreport.rs\"); }}\n",
));
} else {
s.push_str(" #[cfg(feature=\"pinreport\")] pub mod pinreport;\n");
}
}
s.push_str("}\n\n");
}
fs::write(out, s).context("writing lib.rs")
}