use anyhow::{Result, ensure};
use can_dbc::MultiplexIndicator::Multiplexor;
use can_dbc::{Message, Signal};
use heck::{ToPascalCase, ToSnakeCase};
use crate::keywords;
pub fn enum_name(msg: &Message, signal: &Signal) -> String {
let signal_name = signal
.name
.trim_start_matches(|c: char| c.is_ascii_punctuation())
.to_pascal_case();
let msg_name = enum_variant_name(&msg.name);
format!("{msg_name}{signal_name}")
}
pub fn multiplexed_enum_variant_wrapper_name(switch_index: u64) -> String {
format!("M{switch_index}")
}
pub fn multiplex_enum_name(msg: &Message, multiplexor: &Signal) -> Result<String> {
ensure!(
matches!(multiplexor.multiplexer_indicator, Multiplexor),
"signal {multiplexor:?} is not the multiplexor",
);
Ok(format!(
"{}{}Index",
msg.name.to_pascal_case(),
multiplexor.name.to_pascal_case(),
))
}
pub fn multiplexed_enum_variant_name(
msg: &Message,
multiplexor: &Signal,
switch_index: u64,
) -> Result<String> {
ensure!(
matches!(multiplexor.multiplexer_indicator, Multiplexor),
"signal {multiplexor:?} is not the multiplexor",
);
Ok(format!(
"{}{}M{switch_index}",
msg.name.to_pascal_case(),
multiplexor.name.to_pascal_case(),
))
}
pub trait SignalExt {
fn field_name(&self) -> String;
}
impl SignalExt for Signal {
fn field_name(&self) -> String {
sanitize_name(&self.name, "x", ToSnakeCase::to_snake_case)
}
}
pub trait MessageExt {
fn type_name(&self) -> String;
}
impl MessageExt for Message {
fn type_name(&self) -> String {
sanitize_name(&self.name, "X", ToPascalCase::to_pascal_case)
}
}
pub fn node_field_name(name: &str) -> String {
sanitize_name(name, "x", ToSnakeCase::to_snake_case)
}
pub fn sanitize_name(x: &str, prefix: &str, to_case: fn(&str) -> String) -> String {
if keywords::is_keyword(x) || !x.starts_with(|c: char| c.is_ascii_alphabetic()) {
format!("{prefix}{}", to_case(x))
} else {
to_case(x)
}
}
pub fn type_name(x: &str) -> String {
sanitize_name(x, "X", ToPascalCase::to_pascal_case)
}
pub fn enum_variant_name(x: &str) -> String {
type_name(x) }
pub fn is_integer(val: f64) -> bool {
val.fract().abs() < f64::EPSILON
}
pub fn is_valid_ident(s: &str) -> bool {
syn::parse_str::<syn::Ident>(s).is_ok()
}
pub fn is_screaming_snake_case(s: &str) -> bool {
is_valid_ident(s) && !s.bytes().any(|b| b.is_ascii_lowercase())
}
pub fn is_valid_type_path(s: &str) -> bool {
matches!(
syn::parse_str::<syn::TypePath>(s),
Ok(tp) if tp.qself.is_none() && tp.path.leading_colon.is_none()
)
}
#[cfg(test)]
mod tests {
use super::{is_screaming_snake_case, is_valid_ident, is_valid_type_path};
#[test]
fn idents() {
assert!(is_valid_ident("data_id"));
assert!(is_valid_ident("E2E"));
assert!(!is_valid_ident("1bad"));
assert!(!is_valid_ident("_"));
assert!(!is_valid_ident("type")); assert!(!is_valid_ident("with-dash"));
}
#[test]
fn screaming_snake_case() {
assert!(is_screaming_snake_case("E2E_DATA_ID"));
assert!(!is_screaming_snake_case("mixedCase"));
assert!(!is_screaming_snake_case("new"));
}
#[test]
fn type_paths() {
assert!(is_valid_type_path("data_protection::E2EDataIdInfo"));
assert!(is_valid_type_path("crate::Foo"));
assert!(is_valid_type_path("my::Wrapper<u8>")); assert!(!is_valid_type_path("1bad::Type")); assert!(!is_valid_type_path("")); assert!(!is_valid_type_path("::foo")); assert!(!is_valid_type_path("<T as Trait>::X")); }
}