pub const CAP: usize = 128;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Casing {
Lower,
Upper,
Pascal,
Camel,
Snake,
ScreamingSnake,
Kebab,
ScreamingKebab,
}
impl Casing {
#[must_use]
pub const fn from_name(name: &str) -> Self {
let b = name.as_bytes();
if byte_eq(b, b"lowercase") {
Self::Lower
} else if byte_eq(b, b"UPPERCASE") {
Self::Upper
} else if byte_eq(b, b"PascalCase") {
Self::Pascal
} else if byte_eq(b, b"camelCase") {
Self::Camel
} else if byte_eq(b, b"snake_case") {
Self::Snake
} else if byte_eq(b, b"SCREAMING_SNAKE_CASE") {
Self::ScreamingSnake
} else if byte_eq(b, b"kebab-case") {
Self::Kebab
} else if byte_eq(b, b"SCREAMING-KEBAB-CASE") {
Self::ScreamingKebab
} else {
panic!(
"bourne: unknown rename_all value. Expected one of: lowercase, UPPERCASE, \
PascalCase, camelCase, snake_case, SCREAMING_SNAKE_CASE, kebab-case, \
SCREAMING-KEBAB-CASE"
)
}
}
#[must_use]
pub const fn convert(self, src: &str) -> Renamed {
let s = src.as_bytes();
let mut buf = [0u8; CAP];
let mut len = 0usize;
let mut word = 0usize;
let mut word_started = false;
let mut i = 0usize;
while i < s.len() {
let b = s[i];
if b == b'_' || b == b'-' {
if word_started {
word += 1;
word_started = false;
}
i += 1;
continue;
}
if i > 0 && is_upper(b) {
let prev = s[i - 1];
if (is_lower(prev) || prev.is_ascii_digit()) && word_started {
word += 1;
word_started = false;
}
}
if !word_started {
match self {
Self::Snake | Self::ScreamingSnake if word > 0 => {
len = Renamed::push(&mut buf, len, b'_');
}
Self::Kebab | Self::ScreamingKebab if word > 0 => {
len = Renamed::push(&mut buf, len, b'-');
}
_ => {}
}
}
let first_in_word = !word_started;
let out = match self {
Self::Lower | Self::Snake | Self::Kebab => b.to_ascii_lowercase(),
Self::Upper | Self::ScreamingSnake | Self::ScreamingKebab => b.to_ascii_uppercase(),
Self::Pascal => {
if first_in_word {
b.to_ascii_uppercase()
} else {
b.to_ascii_lowercase()
}
}
Self::Camel => {
if first_in_word && word > 0 {
b.to_ascii_uppercase()
} else {
b.to_ascii_lowercase()
}
}
};
len = Renamed::push(&mut buf, len, out);
word_started = true;
i += 1;
}
Renamed { buf, len }
}
#[must_use]
pub const fn rename(src: &str, name: &str) -> Renamed {
Self::from_name(name).convert(src)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Renamed {
buf: [u8; CAP],
len: usize,
}
impl Renamed {
#[must_use]
pub const fn as_str(&self) -> &str {
match core::str::from_utf8(self.buf.split_at(self.len).0) {
Ok(s) => s,
Err(_) => panic!("bourne: rename_all produced non-UTF-8 (converter bug)"),
}
}
const fn push(buf: &mut [u8; CAP], len: usize, b: u8) -> usize {
assert!(len < CAP, "bourne: rename_all key exceeds capacity");
buf[len] = b;
len + 1
}
}
const fn is_upper(b: u8) -> bool {
b.is_ascii_uppercase()
}
const fn is_lower(b: u8) -> bool {
b.is_ascii_lowercase()
}
const fn byte_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}