use super::callbacks::ParseCallbacks;
use heck::ToSnakeCase;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Clone)]
pub struct GeneratorConfig {
pub derives: Vec<String>,
pub raw_lines: Vec<String>,
pub header: Option<String>,
pub ctypes_prefix: Option<String>,
pub emit_rerun_if_changed: bool,
pub parse_callbacks: Option<Arc<dyn ParseCallbacks>>,
pub output_dir: Option<PathBuf>,
pub input_file: Option<PathBuf>,
pub output_file: Option<PathBuf>,
pub allowlist: Vec<String>,
pub blocklist: Vec<String>,
pub allowlist_recursively: bool,
pub package_search_paths: Vec<PathBuf>,
}
impl GeneratorConfig {
#[must_use]
pub fn new() -> Self {
Self {
derives: Vec::new(),
raw_lines: Vec::new(),
header: None,
ctypes_prefix: None,
emit_rerun_if_changed: false,
parse_callbacks: None,
output_dir: None,
input_file: None,
output_file: None,
allowlist: Vec::new(),
blocklist: Vec::new(),
allowlist_recursively: false,
package_search_paths: Vec::new(),
}
}
#[must_use]
pub fn transform_item_name(
&self,
name: &str,
package: &str,
interface_kind: super::InterfaceKind,
) -> String {
if let Some(cb) = &self.parse_callbacks {
use super::callbacks::ItemInfo;
let info = ItemInfo::new(
name.to_string(),
String::new(),
package.to_string(),
interface_kind,
);
if let Some(transformed) = cb.item_name(&info) {
return sanitize_rust_identifier(&transformed);
}
}
sanitize_rust_identifier(name)
}
#[must_use]
pub fn transform_field_name(
&self,
name: &str,
parent_name: &str,
package: &str,
field_type: &str,
ros_type_name: &str,
array_size: Option<u32>,
) -> String {
if let Some(cb) = &self.parse_callbacks {
use super::callbacks::FieldInfo;
let info = FieldInfo::new(
name.to_string(),
field_type.to_string(),
parent_name.to_string(),
package.to_string(),
ros_type_name.to_string(),
array_size,
None, None, None, None, );
if let Some(transformed) = cb.field_name(&info) {
return sanitize_rust_identifier(&transformed);
}
}
sanitize_rust_identifier(name)
}
#[must_use]
pub fn transform_module_name(
&self,
name: &str,
package: Option<&str>,
interface_kind: Option<super::InterfaceKind>,
) -> String {
if let Some(cb) = &self.parse_callbacks
&& let (Some(pkg), Some(ik)) = (package, interface_kind)
{
use super::callbacks::ItemInfo;
let info = ItemInfo::new(name.to_string(), String::new(), pkg.to_string(), ik);
if let Some(transformed) = cb.module_name(&info) {
return sanitize_rust_identifier(&transformed.to_snake_case());
}
}
sanitize_rust_identifier(&name.to_snake_case())
}
#[must_use]
pub fn should_include_item(&self, name: &str) -> bool {
if self.blocklist.iter().any(|pattern| name.contains(pattern)) {
return false;
}
if self.allowlist.is_empty() {
return true;
}
self.allowlist.iter().any(|pattern| name.contains(pattern))
}
}
impl Default for GeneratorConfig {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn sanitize_rust_identifier(name: &str) -> String {
if is_rust_keyword(name) {
return format!("r#{name}");
}
let sanitized: String = name
.chars()
.map(|c| {
if c.is_alphanumeric() || c == '_' {
c
} else {
'_'
}
})
.collect();
if sanitized.chars().next().is_some_and(|c| c.is_ascii_digit()) {
format!("_{sanitized}")
} else {
sanitized
}
}
#[must_use]
fn is_rust_keyword(s: &str) -> bool {
matches!(
s,
"as" | "break"
| "const"
| "continue"
| "crate"
| "else"
| "enum"
| "extern"
| "false"
| "fn"
| "for"
| "if"
| "impl"
| "in"
| "let"
| "loop"
| "match"
| "mod"
| "move"
| "mut"
| "pub"
| "ref"
| "return"
| "self"
| "Self"
| "static"
| "struct"
| "super"
| "trait"
| "true"
| "type"
| "unsafe"
| "use"
| "where"
| "while"
| "async"
| "await"
| "dyn"
| "abstract"
| "become"
| "box"
| "do"
| "final"
| "macro"
| "override"
| "priv"
| "typeof"
| "unsized"
| "virtual"
| "yield"
| "try"
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_keywords() {
assert_eq!(sanitize_rust_identifier("type"), "r#type");
assert_eq!(sanitize_rust_identifier("match"), "r#match");
assert_eq!(sanitize_rust_identifier("async"), "r#async");
}
#[test]
fn test_sanitize_valid_names() {
assert_eq!(sanitize_rust_identifier("valid_name"), "valid_name");
assert_eq!(sanitize_rust_identifier("ValidName"), "ValidName");
assert_eq!(sanitize_rust_identifier("name123"), "name123");
}
#[test]
fn test_sanitize_invalid_chars() {
assert_eq!(sanitize_rust_identifier("invalid-name"), "invalid_name");
assert_eq!(sanitize_rust_identifier("invalid.name"), "invalid_name");
assert_eq!(sanitize_rust_identifier("invalid name"), "invalid_name");
}
#[test]
fn test_sanitize_starts_with_digit() {
assert_eq!(sanitize_rust_identifier("123name"), "_123name");
assert_eq!(sanitize_rust_identifier("456"), "_456");
}
}