mod derive;
mod derive_client;
mod derive_from_row;
mod derive_model_trait;
mod derive_model_with_pk;
mod derive_relation_loader;
mod enum_gen;
mod fields;
mod filters;
mod model;
mod relation_accessors;
mod type_gen;
mod view;
pub use derive::derive_model_impl;
pub use enum_gen::generate_enum_module;
#[allow(unused_imports)]
pub use model::generate_model_module;
pub use model::generate_model_module_with_style;
pub use type_gen::generate_type_module;
pub use view::generate_view_module;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::types::{to_pascal_case, to_snake_case};
pub fn generate_doc_comment(doc: Option<&str>) -> TokenStream {
match doc {
Some(doc) => {
let lines: Vec<_> = doc.lines().map(|line| line.trim()).collect();
let doc_lines = lines.iter().map(|line| {
quote! { #[doc = #line] }
});
quote! { #(#doc_lines)* }
}
None => TokenStream::new(),
}
}
pub fn snake_ident(name: &str) -> proc_macro2::Ident {
let snake = to_snake_case(name);
if is_rust_keyword(&snake) {
format_ident!("r#{}", snake)
} else {
format_ident!("{}", snake)
}
}
pub fn pascal_ident(name: &str) -> proc_macro2::Ident {
format_ident!("{}", to_pascal_case(name))
}
#[allow(dead_code)]
pub fn raw_ident(name: &str) -> proc_macro2::Ident {
format_ident!("r#{}", name)
}
fn is_rust_keyword(s: &str) -> bool {
matches!(
s,
"abstract"
| "as"
| "async"
| "await"
| "become"
| "box"
| "break"
| "const"
| "continue"
| "do"
| "dyn"
| "else"
| "enum"
| "extern"
| "false"
| "final"
| "fn"
| "for"
| "gen"
| "if"
| "impl"
| "in"
| "let"
| "loop"
| "macro"
| "match"
| "mod"
| "move"
| "mut"
| "override"
| "priv"
| "pub"
| "ref"
| "return"
| "static"
| "struct"
| "trait"
| "true"
| "try"
| "type"
| "typeof"
| "unsafe"
| "unsized"
| "use"
| "virtual"
| "where"
| "while"
| "yield"
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_doc_comment() {
let doc = generate_doc_comment(Some("This is a test.\nSecond line."));
let code = doc.to_string();
assert!(code.contains("This is a test"));
assert!(code.contains("Second line"));
}
#[test]
fn test_generate_doc_comment_none() {
let doc = generate_doc_comment(None);
assert!(doc.is_empty());
}
#[test]
fn test_snake_ident() {
let ident = snake_ident("UserProfile");
assert_eq!(ident.to_string(), "user_profile");
}
#[test]
fn snake_ident_escapes_reserved_keywords_as_raw_idents() {
assert_eq!(snake_ident("type").to_string(), "r#type");
assert_eq!(snake_ident("match").to_string(), "r#match");
assert_eq!(snake_ident("use").to_string(), "r#use");
assert_eq!(snake_ident("loop").to_string(), "r#loop");
assert_eq!(snake_ident("move").to_string(), "r#move");
}
#[test]
fn snake_ident_does_not_escape_non_keywords_that_merely_start_with_one() {
assert_eq!(snake_ident("type_id").to_string(), "type_id");
assert_eq!(snake_ident("matches").to_string(), "matches");
}
#[test]
fn test_pascal_ident() {
let ident = pascal_ident("user_profile");
assert_eq!(ident.to_string(), "UserProfile");
}
}