use heck::ToUpperCamelCase; use itertools::Itertools; use regex::Regex; use std::collections::HashMap; use std::ffi::OsStr; use std::fs; use std::path::Path; fn main() { println!("cargo:rerun-if-changed=src/action/include.rs"); println!("cargo:rerun-if-changed=src/action/core/"); println!("cargo:rerun-if-changed=src/action/core/mod.rs"); println!("cargo:rerun-if-changed=src/action/extra/"); println!("cargo:rerun-if-changed=src/action/extra/mod.rs"); let core = fs::read_dir("src/action/core/") .unwrap() .into_iter() .map(|p| p.unwrap().path()) .filter(|p| p.is_file()) .filter(|p| p.extension().unwrap_or_else(|| OsStr::new("")).to_str() == Some("rs")) .map(|p| { p.with_extension("") .file_name() .unwrap() .to_str() .unwrap() .to_string() }) .filter(|n| n != "mod") .sorted() .collect_vec(); let core_stateful = core .clone() .into_iter() .filter(|n| { fs::read_to_string( Path::new("src/action/core/").join(Path::new(n).with_extension("rs")), ) .unwrap() .contains(&format!("Stateful{}", n.to_upper_camel_case())) }) .collect_vec(); let extra = fs::read_dir("src/action/extra/") .unwrap() .into_iter() .map(|p| p.unwrap().path()) .filter(|p| p.is_file()) .filter(|p| p.extension().unwrap_or_else(|| OsStr::new("")).to_str() == Some("rs")) .map(|p| { p.with_extension("") .file_name() .unwrap() .to_str() .unwrap() .to_string() }) .filter(|n| n != "mod") .sorted() .collect_vec(); let extra_stateful = extra .clone() .into_iter() .filter(|n| { fs::read_to_string( Path::new("src/action/extra/").join(Path::new(n).with_extension("rs")), ) .unwrap() .contains(&format!("Stateful{}", n.to_upper_camel_case())) }) .collect_vec(); let re = Regex::new(r"^//@[ \t]*([[:alpha:]][[:word:]]*)[ \t]*$").unwrap(); let mut features: HashMap> = HashMap::new(); for action in core.iter() { fs::read_to_string( Path::new("src/action/core/").join(Path::new(action).with_extension("rs")), ) .unwrap() .lines() .map_while(|p| re.captures(p).map(|c| c[1].to_string())) .for_each(|f| { features.entry(action.clone()).or_default().push(f); }); } for action in extra.iter() { fs::read_to_string( Path::new("src/action/extra/").join(Path::new(action).with_extension("rs")), ) .unwrap() .lines() .map_while(|p| re.captures(p).map(|c| c[1].to_string())) .for_each(|f| { features.entry(action.clone()).or_default().push(f); }); } for (_, v) in features.iter_mut() { v.sort(); } let content = format!( "\ // This file is automatically generated by the crate build script.\n\ // DO NOT MODIFY THIS FILE MANUALLY! CHANGES WILL BE REVERTED.\n\ {}\ {}\ ", if core.is_empty() { "" } else { "\n" }, core.iter() .map(|n| { if let Some(f) = features.get(n) { format!("#[cfg(feature = \"{}\")]\npub mod {n};\n", f.join(", ")) } else { format!("pub mod {n};\n") } }) .collect::>() .join(""), ); let path = "src/action/core/mod.rs"; match fs::read_to_string(path) { Ok(current) if current == content => {} _ => { fs::write(path, content) .expect("Failed to generate src/action/core/mod.rs automatically!"); } } let content = format!( "\ // This file is automatically generated by the crate build script.\n\ // DO NOT MODIFY THIS FILE MANUALLY! CHANGES WILL BE REVERTED.\n\ {}\ {}\ ", if extra.is_empty() { "" } else { "\n" }, extra .iter() .map(|n| if let Some(f) = features.get(n) { format!("#[cfg(feature = \"{}\")]\npub mod {n};\n", f.join(", ")) } else { format!("pub mod {n};\n") }) .collect::>() .join(""), ); let path = "src/action/extra/mod.rs"; match fs::read_to_string(path) { Ok(current) if current == content => {} _ => { fs::write(path, content) .expect("Failed to generate src/action/extra/mod.rs automatically!"); } } let has_actions = !core.is_empty(); let has_stateful = !core.is_empty(); let content = format!( "\ // This file is automatically generated by the crate build script.\n\ // DO NOT MODIFY THIS FILE MANUALLY! CHANGES WILL BE REVERTED.\n\n\ include_actions!({}{}{});\n\n\ include_stateful_actions!({}{}{});\n\ ", core.into_iter() .map(|n| { if let Some(f) = features.get(&n) { format!( "\n core::{n}@({}),", f.iter().map(|f| format!("\"{f}\"")).join(", ") ) } else { format!("\n core::{n}@(),") } }) .collect::>() .join(""), extra .into_iter() .map(|n| { if let Some(f) = features.get(&n) { format!( "\n extra::{n}@({}),", f.iter().map(|f| format!("\"{f}\"")).join(", ") ) } else { format!("\n extra::{n}@(),") } }) .collect::>() .join(""), if has_actions { "\n".to_owned() } else { "".to_owned() }, core_stateful .into_iter() .map(|n| { if let Some(f) = features.get(&n) { format!( "\n core::{n}@({}),", f.iter().map(|f| format!("\"{f}\"")).join(", ") ) } else { format!("\n core::{n}@(),") } }) .collect::>() .join(""), extra_stateful .into_iter() .map(|n| { if let Some(f) = features.get(&n) { format!( "\n extra::{n}@({}),", f.iter().map(|f| format!("\"{f}\"")).join(", ") ) } else { format!("\n extra::{n}@(),") } }) .collect::>() .join(""), if has_stateful { "\n".to_owned() } else { "".to_owned() } ); let path = "src/action/include.rs"; match fs::read_to_string(path) { Ok(current) if current == content => {} _ => { fs::write(path, content) .expect("Failed to generate src/action/include.rs automatically!"); } } }