use quote::quote;
pub(crate) fn pascal_to_snake(s: &str) -> String {
let mut result = String::new();
let chars: Vec<char> = s.chars().collect();
for (i, &c) in chars.iter().enumerate() {
if c.is_ascii_uppercase() {
if i > 0 {
let prev = chars[i - 1];
let prev_lower_or_digit = prev.is_ascii_lowercase() || prev.is_ascii_digit();
let next_lower = i + 1 < chars.len() && chars[i + 1].is_ascii_lowercase();
if prev_lower_or_digit || (prev.is_ascii_uppercase() && next_lower) {
result.push('_');
}
}
result.extend(c.to_lowercase());
} else {
result.push(c);
}
}
result
}
pub(crate) 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"
| "gen"
)
}
pub(crate) fn is_valid_method_name(s: &str) -> bool {
if let Some(bare) = s.strip_prefix("r#") {
!matches!(bare, "self" | "Self" | "super" | "crate")
} else {
!is_rust_keyword(s)
}
}
pub(crate) fn make_method_ident(name: &str, span: proc_macro2::Span) -> syn::Ident {
if let Some(bare) = name.strip_prefix("r#") {
syn::Ident::new_raw(bare, span)
} else {
syn::Ident::new(name, span)
}
}
pub(crate) struct GenericsParts<'a> {
pub gen_params: &'a syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>,
pub param_uses: Vec<proc_macro2::TokenStream>,
pub type_param_idents: Vec<&'a syn::Ident>,
pub user_predicates: Vec<&'a syn::WherePredicate>,
pub ty_generics: proc_macro2::TokenStream,
}
pub(crate) fn split_generics(generics: &syn::Generics) -> Result<GenericsParts<'_>, syn::Error> {
if let Some(lt) = generics.lifetimes().next() {
return Err(syn::Error::new_spanned(
lt,
"#[derive(DefaultGenerator)] does not support lifetime parameters: a borrowing \
type cannot be produced by the 'static generator DefaultGenerator requires",
));
}
let param_uses = generics
.params
.iter()
.map(|p| match p {
syn::GenericParam::Type(t) => {
let ident = &t.ident;
quote! { #ident }
}
syn::GenericParam::Const(c) => {
let ident = &c.ident;
quote! { #ident }
}
syn::GenericParam::Lifetime(_) => unreachable!("lifetimes rejected above"),
})
.collect();
let type_param_idents = generics.type_params().map(|t| &t.ident).collect();
let user_predicates = generics
.where_clause
.as_ref()
.map(|w| w.predicates.iter().collect())
.unwrap_or_default();
let (_, ty_generics, _) = generics.split_for_impl();
Ok(GenericsParts {
gen_params: &generics.params,
param_uses,
type_param_idents,
user_predicates,
ty_generics: quote! { #ty_generics },
})
}
pub(crate) fn default_gen_bounds(
types: &[&syn::Type],
lifetime: proc_macro2::TokenStream,
) -> Vec<proc_macro2::TokenStream> {
types
.iter()
.map(|ty| {
quote! {
#ty: ::hegel::generators::DefaultGenerator,
<#ty as ::hegel::generators::DefaultGenerator>::Generator: Send + Sync + #lifetime
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::{is_valid_method_name, pascal_to_snake};
#[test]
fn test_is_valid_method_name() {
assert!(!is_valid_method_name("type"));
assert!(!is_valid_method_name("super"));
assert!(is_valid_method_name("type_"));
assert!(is_valid_method_name("read_write"));
assert!(is_valid_method_name("Type"));
assert!(is_valid_method_name("r#type"));
assert!(is_valid_method_name("r#async"));
assert!(is_valid_method_name("r#gen"));
assert!(!is_valid_method_name("r#super"));
assert!(!is_valid_method_name("r#self"));
assert!(!is_valid_method_name("r#crate"));
assert!(!is_valid_method_name("r#Self"));
assert!(is_valid_method_name("r#Type"));
assert!(is_valid_method_name("r#read_write"));
assert!(is_valid_method_name("r#type_"));
}
#[test]
fn test_pascal_to_snake() {
assert_eq!(pascal_to_snake("Circle"), "circle");
assert_eq!(pascal_to_snake("Red"), "red");
assert_eq!(pascal_to_snake("A"), "a");
assert_eq!(pascal_to_snake("a"), "a");
assert_eq!(pascal_to_snake("ReadWrite"), "read_write");
assert_eq!(pascal_to_snake("WithValue"), "with_value");
assert_eq!(pascal_to_snake("WithFields"), "with_fields");
assert_eq!(pascal_to_snake("VecVariant"), "vec_variant");
assert_eq!(pascal_to_snake("HTTPServer"), "http_server");
assert_eq!(pascal_to_snake("IOError"), "io_error");
assert_eq!(pascal_to_snake("XMLParser"), "xml_parser");
assert_eq!(pascal_to_snake("XMLHttpRequest"), "xml_http_request");
assert_eq!(pascal_to_snake("AB"), "ab");
assert_eq!(pascal_to_snake("ABC"), "abc");
assert_eq!(pascal_to_snake("IO"), "io");
assert_eq!(pascal_to_snake("circle"), "circle");
assert_eq!(pascal_to_snake("read_write"), "read_write");
assert_eq!(pascal_to_snake("Vec3D"), "vec3_d");
assert_eq!(pascal_to_snake("Abc123Def"), "abc123_def");
assert_eq!(pascal_to_snake("FIELD_NAME11"), "field_name11");
assert_eq!(pascal_to_snake("Abc123DEF456"), "abc123_def456");
assert_eq!(pascal_to_snake("Ab"), pascal_to_snake("AB"));
assert_eq!(pascal_to_snake("IOError"), pascal_to_snake("IoError"));
}
}