use std::iter;
use convert_case::{Boundary, Case, Casing};
use convert_case_extras::is_case;
pub(crate) const RAW_IDENTIFIER_PREFIX: &str = "r#";
pub(crate) const LIFETIME_TICK_PREFIX: char = '\'';
pub(crate) const UNDERSCORE: char = '_';
pub(crate) const SNAKE_PASCAL_BOUNDARIES: [Boundary; 6] = [
Boundary::DigitLower,
Boundary::DigitUpper,
Boundary::LowerDigit,
Boundary::UpperDigit,
Boundary::Custom {
condition: |str| {
str.first()
.and_then(|str| str.chars().next())
.is_some_and(|char| !char.is_ascii_alphanumeric())
},
start: 0,
len: 1,
},
Boundary::Custom {
condition: |str| {
str.get(1)
.and_then(|str| str.chars().next())
.is_some_and(char::is_uppercase)
},
start: 1,
len: 0,
},
];
pub(crate) trait ConvertCasing {
type Output;
fn convert_casing(&self, casing: Case) -> Self::Output;
}
impl ConvertCasing for str {
type Output = String;
fn convert_casing(&self, casing: Case) -> Self::Output {
let identifier = self;
let identifier = identifier
.strip_prefix(LIFETIME_TICK_PREFIX)
.unwrap_or(identifier);
let (identifier, is_raw) = identifier
.strip_prefix(RAW_IDENTIFIER_PREFIX)
.map(|rest| (rest, true))
.unwrap_or((identifier, false));
let leading_underscore_count = identifier
.chars()
.take_while(|&char| char == UNDERSCORE)
.count();
let trailing_underscore_count = if leading_underscore_count < identifier.len() {
identifier
.chars()
.rev()
.take_while(|&char| char == UNDERSCORE)
.count()
} else {
0
};
let identifier =
&identifier[leading_underscore_count..identifier.len() - trailing_underscore_count];
let boundaries = if is_case(identifier, Case::Constant) {
Case::Constant.boundaries()
} else {
&SNAKE_PASCAL_BOUNDARIES
};
let converter = identifier.remove_empty().set_boundaries(boundaries);
is_raw
.then_some(RAW_IDENTIFIER_PREFIX.chars())
.into_iter()
.flatten()
.chain(iter::repeat_n(UNDERSCORE, leading_underscore_count))
.chain(converter.to_case(casing).chars())
.chain(iter::repeat_n(UNDERSCORE, trailing_underscore_count))
.collect()
}
}
#[cfg(feature = "syn")]
use proc_macro2::Span;
#[cfg(feature = "syn")]
use syn::Ident;
#[cfg(feature = "syn")]
pub(crate) trait ConstructRawHandled {
fn new_raw_handled(string: &str, span: Span) -> Self;
}
#[cfg(feature = "syn")]
impl ConstructRawHandled for Ident {
fn new_raw_handled(string: &str, span: Span) -> Self {
if string.is_raw_identifier() {
Ident::new_raw(&string[2..], span)
} else {
Ident::new(string, span)
}
}
}
#[cfg(feature = "syn")]
pub(crate) trait IsRaw {
fn is_raw_identifier(&self) -> bool;
}
#[cfg(feature = "syn")]
impl IsRaw for str {
fn is_raw_identifier(&self) -> bool {
self.starts_with(RAW_IDENTIFIER_PREFIX)
}
}
#[cfg(feature = "syn")]
impl IsRaw for Ident {
fn is_raw_identifier(&self) -> bool {
self.to_string().is_raw_identifier()
}
}