* resetted and re-did the changes * fmt * upgraded to syn2 for sea-orm-codegen * Rename `bae2` * Drop the use of `#[sea_orm(table_name = "col_name")]` in `DeriveColumn` * unified derived input metadata parsing filter * Propagate errors --------- Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>
199 lines
6.3 KiB
Rust
199 lines
6.3 KiB
Rust
use heck::ToUpperCamelCase;
|
|
use quote::format_ident;
|
|
use syn::{punctuated::Punctuated, token::Comma, Field, Ident, Meta};
|
|
|
|
pub(crate) fn field_not_ignored(field: &Field) -> bool {
|
|
for attr in field.attrs.iter() {
|
|
if let Some(ident) = attr.path().get_ident() {
|
|
if ident != "sea_orm" {
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
|
|
for meta in list.iter() {
|
|
if let Meta::Path(path) = meta {
|
|
if let Some(name) = path.get_ident() {
|
|
if name == "ignore" {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub(crate) fn format_field_ident(field: Field) -> Ident {
|
|
format_ident!("{}", field.ident.unwrap().to_string())
|
|
}
|
|
|
|
pub(crate) fn trim_starting_raw_identifier<T>(string: T) -> String
|
|
where
|
|
T: ToString,
|
|
{
|
|
string
|
|
.to_string()
|
|
.trim_start_matches(RAW_IDENTIFIER)
|
|
.to_string()
|
|
}
|
|
|
|
pub(crate) fn escape_rust_keyword<T>(string: T) -> String
|
|
where
|
|
T: ToString,
|
|
{
|
|
let string = string.to_string();
|
|
if RUST_KEYWORDS.iter().any(|s| s.eq(&string)) {
|
|
format!("r#{string}")
|
|
} else if RUST_SPECIAL_KEYWORDS.iter().any(|s| s.eq(&string)) {
|
|
format!("{string}_")
|
|
} else {
|
|
string
|
|
}
|
|
}
|
|
|
|
/// Turn a string to PascalCase while escaping all special characters in ASCII words.
|
|
///
|
|
/// (camel_case is used here to match naming of heck.)
|
|
///
|
|
/// In ActiveEnum, string_value will be PascalCased and made
|
|
/// an identifier in {Enum}Variant.
|
|
///
|
|
/// However Rust only allows for XID_Start char followed by
|
|
/// XID_Continue characters as identifiers; this causes a few
|
|
/// problems:
|
|
///
|
|
/// - `string_value = ""` will cause a panic;
|
|
/// - `string_value` containing only non-alphanumerics will become `""`
|
|
/// and cause the above panic;
|
|
/// - `string_values`:
|
|
/// - `"A B"`
|
|
/// - `"A B"`
|
|
/// - `"A_B"`
|
|
/// - `"A_ B"`
|
|
/// shares the same identifier of `"AB"`;
|
|
///
|
|
/// This function does the PascelCase conversion with a few special escapes:
|
|
/// - Non-Unicode Standard Annex #31 compliant characters will converted to their hex notation;
|
|
/// - `"_"` into `"0x5F"`;
|
|
/// - `" "` into `"0x20"`;
|
|
/// - Empty strings will become special keyword of `"__Empty"`
|
|
///
|
|
/// Note that this does NOT address:
|
|
///
|
|
/// - case-sensitivity. String value "ABC" and "abc" remains
|
|
/// conflicted after .camel_case().
|
|
///
|
|
/// Example Conversions:
|
|
///
|
|
/// ```ignore
|
|
/// assert_eq!(camel_case_with_escaped_non_uax31(""), "__Empty");
|
|
/// assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x20");
|
|
/// assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x200x20");
|
|
/// assert_eq!(camel_case_with_escaped_non_uax31("_"), "_0x5F");
|
|
/// assert_eq!(camel_case_with_escaped_non_uax31("foobar"), "Foobar");
|
|
/// assert_eq!(camel_case_with_escaped_non_uax31("foo bar"), "Foo0x20bar");
|
|
/// ```
|
|
pub(crate) fn camel_case_with_escaped_non_uax31<T>(string: T) -> String
|
|
where
|
|
T: ToString,
|
|
{
|
|
let additional_chars_to_replace: [char; 2] = ['_', ' '];
|
|
|
|
let mut rebuilt = string
|
|
.to_string()
|
|
.chars()
|
|
.enumerate()
|
|
.map(|(pos, char_)| {
|
|
if !additional_chars_to_replace.contains(&char_)
|
|
&& match pos {
|
|
0 => unicode_ident::is_xid_start(char_),
|
|
_ => unicode_ident::is_xid_continue(char_),
|
|
}
|
|
{
|
|
char_.to_string()
|
|
} else {
|
|
format!("{:#X}", char_ as u32)
|
|
}
|
|
})
|
|
.reduce(
|
|
// Join the "characters" (now strings)
|
|
// back together
|
|
|lhs, rhs| lhs + rhs.as_str(),
|
|
)
|
|
.map_or(
|
|
// if string_value is ""
|
|
// Make sure the default does NOT go through camel_case,
|
|
// as the __ will be removed! The underscores are
|
|
// what guarantees this being special case avoiding
|
|
// all potential conflicts.
|
|
String::from("__Empty"),
|
|
|s| s.to_upper_camel_case(),
|
|
);
|
|
|
|
if rebuilt
|
|
.chars()
|
|
.next()
|
|
.map(char::is_numeric)
|
|
.unwrap_or(false)
|
|
{
|
|
rebuilt = String::from("_") + &rebuilt;
|
|
}
|
|
|
|
rebuilt
|
|
}
|
|
|
|
pub(crate) const RAW_IDENTIFIER: &str = "r#";
|
|
|
|
pub(crate) const RUST_KEYWORDS: [&str; 49] = [
|
|
"as", "async", "await", "break", "const", "continue", "dyn", "else", "enum", "extern", "false",
|
|
"fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
|
|
"return", "static", "struct", "super", "trait", "true", "type", "union", "unsafe", "use",
|
|
"where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv",
|
|
"try", "typeof", "unsized", "virtual", "yield",
|
|
];
|
|
|
|
pub(crate) const RUST_SPECIAL_KEYWORDS: [&str; 3] = ["crate", "Self", "self"];
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_non_uax31_escape() {
|
|
// Test empty string
|
|
assert_eq!(camel_case_with_escaped_non_uax31(""), "__Empty");
|
|
|
|
// Test additional_chars_to_replace (to_camel_case related characters)
|
|
assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x20");
|
|
|
|
// Test additional_chars_to_replace (multiples. ensure distinct from single)
|
|
assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x200x20");
|
|
|
|
// Test additional_chars_to_replace (udnerscores)
|
|
assert_eq!(camel_case_with_escaped_non_uax31("_"), "_0x5F");
|
|
|
|
// Test typical use case
|
|
assert_eq!(camel_case_with_escaped_non_uax31("foobar"), "Foobar");
|
|
|
|
// Test spaced words distinct from non-spaced
|
|
assert_eq!(camel_case_with_escaped_non_uax31("foo bar"), "Foo0x20bar");
|
|
|
|
// Test undescored words distinct from non-spaced and spaced
|
|
assert_eq!(camel_case_with_escaped_non_uax31("foo_bar"), "Foo0x5Fbar");
|
|
|
|
// Test leading numeric characters
|
|
assert_eq!(camel_case_with_escaped_non_uax31("1"), "_0x31");
|
|
|
|
// Test escaping also works on full string following lead numeric character
|
|
// This was previously a fail condition.
|
|
assert_eq!(
|
|
camel_case_with_escaped_non_uax31("1 2 3"),
|
|
"_0x310x2020x203"
|
|
);
|
|
}
|
|
}
|