use bumpalo::Bump;
use lazy_static::lazy_static;
use std::collections::{HashMap, HashSet};
lazy_static! {
static ref STRICT_KEYWORDS: HashSet<&'static str> = (&[
"abstract", "as", "async", "await", "become", "break", "box", "const", "continue", "crate",
"do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in",
"let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
"return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "typeof",
"unsafe", "unsized", "use", "virtual", "where", "while", "yield",
])
.into_iter()
.map(|&s| s)
.collect();
}
pub struct FieldNameSource {
used: HashSet<String>,
}
impl FieldNameSource {
pub fn new() -> Self {
FieldNameSource {
used: HashSet::new(),
}
}
pub fn get_struct_field_name<'a>(&'a mut self, entity_model_name: &str) -> &'a str {
self.get_field_name(entity_model_name, to_snake_case)
}
pub fn get_enum_variant_name<'a>(&'a mut self, entity_model_name: &str) -> &'a str {
self.get_field_name(entity_model_name, to_pascal_case)
}
fn get_field_name<'a>(
&'a mut self,
entity_model_name: &str,
convert_case: impl Fn(&str) -> String,
) -> &'a str {
let mut cased = convert_case(entity_model_name);
while STRICT_KEYWORDS.contains(cased.as_str()) || self.used.contains(&cased) {
cased.push('_');
}
let cloned = cased.clone();
self.used.insert(cased);
self.used.get(&cloned).unwrap()
}
}
pub struct ModChildNameSource<'ar> {
used: HashSet<&'ar str>,
type_lookup: HashMap<&'ar str, &'ar str>,
const_lookup: HashMap<&'ar str, &'ar str>,
arena: &'ar Bump,
}
impl<'ar> ModChildNameSource<'ar> {
pub fn new(arena: &'ar Bump) -> Self {
ModChildNameSource {
used: HashSet::new(),
type_lookup: HashMap::new(),
const_lookup: HashMap::new(),
arena,
}
}
pub fn get_const_item_name(&mut self, entity_model_name: &'ar str) -> &'ar str {
Self::get_item_name(
entity_model_name,
to_screaming_snake_case,
self.arena,
&mut self.used,
&mut self.const_lookup,
)
}
pub fn get_type_name(&mut self, entity_model_name: &'ar str) -> &'ar str {
Self::get_item_name(
entity_model_name,
to_pascal_case,
self.arena,
&mut self.used,
&mut self.type_lookup,
)
}
fn get_item_name(
entity_model_name: &'ar str,
convert_case: impl Fn(&str) -> String,
arena: &'ar Bump,
used: &mut HashSet<&'ar str>,
lookup: &mut HashMap<&'ar str, &'ar str>,
) -> &'ar str {
if let Some(name) = lookup.get(entity_model_name) {
return name;
}
let cased = arena.alloc(convert_case(entity_model_name));
while STRICT_KEYWORDS.contains(cased.as_str()) || used.contains(cased.as_str()) {
cased.push('_');
}
used.insert(cased.as_str());
lookup.insert(entity_model_name, cased.as_str());
cased.as_str()
}
}
pub fn to_snake_case(s: &str) -> String {
let mut out = String::new();
let mut first = true;
for c in s.chars() {
if c.is_uppercase() && !first {
out.push('_');
}
out.extend(c.to_lowercase());
first = false;
}
out
}
pub fn to_screaming_snake_case(s: &str) -> String {
let mut out = String::new();
let mut prev_char_lowercase = false;
for c in s.chars() {
if c.is_uppercase() && prev_char_lowercase {
out.push('_');
}
prev_char_lowercase = c.is_lowercase();
out.extend(c.to_uppercase());
}
out
}
pub fn to_pascal_case(s: &str) -> String {
let mut out = String::new();
let mut prev_char_was_letter = false;
for c in s.chars() {
if c == '_' {
prev_char_was_letter = false;
} else if !c.is_alphabetic() {
prev_char_was_letter = false;
out.push(c);
} else {
if prev_char_was_letter {
out.push(c);
} else {
out.extend(c.to_uppercase());
}
prev_char_was_letter = true;
}
}
out
}