pub fn is_snake_case(name: &str) -> bool {
wolf_project::is_snake_case(name)
}
pub fn is_pascal_case(name: &str) -> bool {
name.starts_with(|c: char| c.is_ascii_uppercase())
&& name.chars().all(|c| c.is_ascii_alphanumeric())
}
pub(crate) fn to_snake(name: &str) -> String {
let mut out = String::new();
for (i, c) in name.chars().enumerate() {
if c.is_ascii_uppercase() {
if i > 0 && !out.ends_with('_') {
out.push('_');
}
out.push(c.to_ascii_lowercase());
} else if c == '_' || c == '-' {
if !out.is_empty() && !out.ends_with('_') {
out.push('_');
}
} else {
out.push(c);
}
}
out.trim_matches('_').to_string()
}
pub(crate) fn to_pascal(name: &str) -> String {
name.split(['_', '-'])
.filter(|p| !p.is_empty())
.map(|p| {
let mut chars = p.chars();
match chars.next() {
Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
None => String::new(),
}
})
.collect()
}
pub fn suggest<'a>(name: &str, candidates: impl Iterator<Item = &'a str>) -> Option<String> {
let limit = (name.chars().count() / 3).max(1);
let mut best: Option<(usize, &str)> = None;
for candidate in candidates {
if candidate == name {
continue;
}
let d = distance(name, candidate);
if d > limit {
continue;
}
best = match best {
Some((bd, bc)) if (bd, bc) <= (d, candidate) => Some((bd, bc)),
_ => Some((d, candidate)),
};
}
best.map(|(_, c)| c.to_string())
}
fn distance(a: &str, b: &str) -> usize {
let b: Vec<char> = b.chars().collect();
let mut row: Vec<usize> = (0..=b.len()).collect();
for (i, ca) in a.chars().enumerate() {
let mut prev = row.first().copied().unwrap_or(0);
if let Some(first) = row.first_mut() {
*first = i + 1;
}
for (j, cb) in b.iter().enumerate() {
let cost = usize::from(ca != *cb);
let above = row.get(j + 1).copied().unwrap_or(0);
let left = row.get(j).copied().unwrap_or(0);
let value = (above + 1).min(left + 1).min(prev + cost);
prev = above;
if let Some(cell) = row.get_mut(j + 1) {
*cell = value;
}
}
}
row.last().copied().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn styles() {
assert!(is_pascal_case("FileStorage"));
assert!(!is_pascal_case("fileStorage"));
assert!(!is_pascal_case("File_Storage"));
assert_eq!(to_snake("findUser"), "find_user");
assert_eq!(to_snake("FileStorage"), "file_storage");
assert_eq!(to_pascal("file_storage"), "FileStorage");
}
#[test]
fn suggestions() {
let names = ["print", "panic", "price"];
assert_eq!(
suggest("prnt", names.iter().copied()).as_deref(),
Some("print")
);
assert_eq!(suggest("zzz", names.iter().copied()), None);
}
}