use bindgen::callbacks::ItemInfo;
pub use convert_case::Case;
use convert_case::Casing as _;
pub use regex::Regex;
use std::collections::HashMap;
use crate::callbacks::{EnumVariantValue, ParseCallbacks};
#[derive(Debug, Default)]
pub struct IdentRenamer {
pub remove: Option<Vec<Regex>>,
pub renames: HashMap<String, String>,
pub case: Option<Case<'static>>,
}
impl IdentRenamer {
#[must_use]
pub fn default_case(case: Case<'static>) -> Self {
Self {
case: Some(case),
..Default::default()
}
}
fn apply(&self, val: &str) -> String {
let mut val = val.to_owned();
if let Some(remove) = &self.remove {
for re in remove {
val = re.replace(&val, "").into();
}
}
if let Some(new_val) = self.renames.get(val.as_str()) {
new_val.to_string()
} else if let Some(case) = self.case {
val.to_case(case)
} else {
val
}
}
}
#[derive(Debug, Default)]
pub struct Renamer {
debug: bool,
item_renames: HashMap<String, String>,
item_renames_ext: Vec<(Regex, IdentRenamer)>,
enum_renames: Vec<(Option<Regex>, IdentRenamer)>,
}
impl Renamer {
#[must_use]
pub fn new(debug: bool) -> Self {
Self {
debug,
..Default::default()
}
}
#[must_use]
pub fn get_regex_str(&self) -> String {
self.item_renames_ext
.iter()
.map(|(re, _)| re.as_str())
.chain(self.item_renames.keys().map(String::as_str))
.fold(String::new(), |mut acc, re| {
if !acc.is_empty() {
acc.push('|');
}
acc.push_str(re);
acc
})
}
pub fn rename_item(
&mut self,
c_name: impl AsRef<str>,
rust_name: impl AsRef<str>,
) {
self.item_renames
.insert(c_name.as_ref().into(), rust_name.as_ref().into());
}
pub fn rename_many(&mut self, c_name: Regex, renamer: IdentRenamer) {
assert!(
!c_name.as_str().contains('^'),
"Regex must not contain '^' symbol"
);
assert!(
!c_name.as_str().contains('$'),
"Regex must not contain '$' symbol"
);
self.item_renames_ext.push((c_name, renamer));
}
pub fn rename_enum_val(
&mut self,
enum_c_name: Option<&str>,
val_renamer: IdentRenamer,
) {
self.enum_renames.push((
enum_c_name
.map(|v| Regex::new(v).expect("Invalid enum_c_name regex")),
val_renamer,
));
}
}
impl ParseCallbacks for Renamer {
fn enum_variant_name(
&self,
enum_name: Option<&str>,
value: &str,
_variant_value: EnumVariantValue,
) -> Option<String> {
self.enum_renames
.iter()
.filter_map(|(re, rn)| match (enum_name, re) {
(Some(enum_name), Some(re)) if re.is_match(enum_name) => Some(rn),
(None, None) => Some(rn),
_ => None,
})
.map(|rn| rn.apply(value))
.next()
.or_else(|| {
if self.debug {
let name = enum_name.unwrap_or_default();
println!("cargo::warning=Unrecognized enum variant {name} :: {value}");
}
None
})
}
fn item_name(&self, item_name: ItemInfo<'_>) -> Option<String> {
let item_name = item_name.name;
self.item_renames
.get(item_name)
.map(ToString::to_string)
.or_else(|| {
self.item_renames_ext
.iter()
.filter_map(|(re, rn)| {
if re.is_match(item_name) {
Some(rn)
} else {
None
}
})
.map(|rn| rn.apply(item_name))
.next()
})
.or_else(|| {
if self.debug {
println!("cargo::warning=Unrecognized item {item_name}");
}
None
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_regex_str() {
let mut cb = Renamer::new(false);
cb.rename_item("bar", "baz");
cb.rename_many(Regex::new(r"foo.*").unwrap(), IdentRenamer::default());
cb.rename_many(Regex::new("bas").unwrap(), IdentRenamer::default());
assert_eq!(cb.get_regex_str(), "foo.*|bas|bar");
}
}