blob: d432f329d6621f060e30420edca47a6a5e2b2a38 [file] [log] [blame] [edit]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl_ir::Ident;
pub trait SplitIdent {
fn split(&self) -> Split<'_>;
}
impl SplitIdent for Ident {
fn split(&self) -> Split<'_> {
Split { str: self.non_canonical() }
}
}
pub struct Split<'a> {
str: &'a str,
}
impl<'a> Iterator for Split<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let mut char_indices = self.str.char_indices().skip_while(|(_, c)| *c == '_').peekable();
let (start, mut prev) = char_indices.next()?;
let mut end = self.str.len();
while let Some((index, current)) = char_indices.next() {
if current == '_' {
end = index;
break;
}
let prev_lower = prev.is_ascii_lowercase();
let prev_digit = prev.is_ascii_digit();
let current_upper = current.is_ascii_uppercase();
let next_lower = char_indices.peek().is_some_and(|(_, c)| c.is_ascii_lowercase());
let is_first_uppercase = (prev_lower || prev_digit) && current_upper;
let is_last_uppercase = current_upper && next_lower;
if is_first_uppercase || is_last_uppercase {
end = index;
break;
}
prev = current;
}
let result = &self.str[start..end];
self.str = &self.str[end..];
Some(result)
}
}
#[cfg(test)]
mod tests {
use fidl_ir::Ident;
use super::*;
const TEST_CASES: &[&str] = &[
"foo_bar",
"foo__bar",
"FooBar",
"fooBar",
"FOOBar",
"__foo_bar",
"foo123bar",
"foO123bar",
"foo_123bar",
"FOO123Bar",
"FOO123bar",
];
#[test]
fn split() {
const EXPECTEDS: [&[&str]; TEST_CASES.len()] = [
&["foo", "bar"],
&["foo", "bar"],
&["Foo", "Bar"],
&["foo", "Bar"],
&["FOO", "Bar"],
&["foo", "bar"],
&["foo123bar"],
&["fo", "O123bar"],
&["foo", "123bar"],
&["FOO123", "Bar"],
&["FOO123bar"],
];
for (case, expected) in TEST_CASES.iter().zip(EXPECTEDS.iter()) {
assert_eq!(
&Ident::from_str(case).split().collect::<Vec<_>>(),
expected,
"{case} did not split correctly",
);
}
}
#[test]
fn snake() {
const EXPECTEDS: [&str; TEST_CASES.len()] = [
"foo_bar",
"foo_bar",
"foo_bar",
"foo_bar",
"foo_bar",
"foo_bar",
"foo123bar",
"fo_o123bar",
"foo_123bar",
"foo123_bar",
"foo123bar",
];
for (case, expected) in TEST_CASES.iter().zip(EXPECTEDS.iter()) {
assert_eq!(
&Ident::from_str(case).snake(),
expected,
"{case} was not transformed to snake case correctly",
);
}
}
#[test]
fn camel() {
const EXPECTEDS: [&str; TEST_CASES.len()] = [
"FooBar",
"FooBar",
"FooBar",
"FooBar",
"FooBar",
"FooBar",
"Foo123bar",
"FoO123bar",
"Foo123bar",
"Foo123Bar",
"Foo123bar",
];
for (case, expected) in TEST_CASES.iter().zip(EXPECTEDS.iter()) {
assert_eq!(
&Ident::from_str(case).camel(),
expected,
"{case} was not transformed to camel case correctly",
);
}
}
#[test]
fn screaming_snake() {
const EXPECTEDS: [&str; TEST_CASES.len()] = [
"FOO_BAR",
"FOO_BAR",
"FOO_BAR",
"FOO_BAR",
"FOO_BAR",
"FOO_BAR",
"FOO123BAR",
"FO_O123BAR",
"FOO_123BAR",
"FOO123_BAR",
"FOO123BAR",
];
for (case, expected) in TEST_CASES.iter().zip(EXPECTEDS.iter()) {
assert_eq!(
&Ident::from_str(case).screaming_snake(),
expected,
"{case} was not transformed to screaming snake case correctly",
);
}
}
}