Skip to content

Commit

Permalink
Added AsXxxCase wrappers
Browse files Browse the repository at this point in the history
  • Loading branch information
SOF3 committed Jul 31, 2021
1 parent 71b47f1 commit 9c5835d
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 38 deletions.
22 changes: 21 additions & 1 deletion src/kebab.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use crate::{lowercase, transform};

/// This trait defines a kebab case conversion.
Expand All @@ -19,7 +21,25 @@ pub trait ToKebabCase: ToOwned {

impl ToKebabCase for str {
fn to_kebab_case(&self) -> Self::Owned {
transform(self, lowercase, |s| s.push('-'))
AsKebabCase(self).to_string()
}
}

/// This wrapper performs a kebab case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// use heck::AsKebabCase;
///
/// let sentence = "We are going to inherit the earth.";
/// assert_eq!(format!("{}", AsKebabCase(sentence)), "we-are-going-to-inherit-the-earth");
/// ```
pub struct AsKebabCase<T: AsRef<str>>(pub T);

impl<T: AsRef<str>> fmt::Display for AsKebabCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
transform(self.0.as_ref(), lowercase, |f| write!(f, "-"), f)
}
}

Expand Down
68 changes: 42 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,31 @@ mod snake;
mod title;
mod upper_camel;

pub use kebab::ToKebabCase;
pub use lower_camel::ToLowerCamelCase;
pub use shouty_kebab::ToShoutyKebabCase;
pub use shouty_snake::{ToShoutySnakeCase, ToShoutySnekCase};
pub use snake::{ToSnakeCase, ToSnekCase};
pub use title::ToTitleCase;
pub use upper_camel::{ToPascalCase, ToUpperCamelCase};
pub use kebab::{AsKebabCase, ToKebabCase};
pub use lower_camel::{AsLowerCamelCase, ToLowerCamelCase};
pub use shouty_kebab::{AsShoutyKebabCase, ToShoutyKebabCase};
pub use shouty_snake::{
AsShoutySnakeCase, AsShoutySnakeCase as AsShoutySnekCase, ToShoutySnakeCase, ToShoutySnekCase,
};
pub use snake::{AsSnakeCase, AsSnakeCase as AsSnekCase, ToSnakeCase, ToSnekCase};
pub use title::{AsTitleCase, ToTitleCase};
pub use upper_camel::{
AsUpperCamelCase, AsUpperCamelCase as AsPascalCase, ToPascalCase, ToUpperCamelCase,
};

use std::fmt;

use unicode_segmentation::UnicodeSegmentation;

fn transform<F, G>(s: &str, with_word: F, boundary: G) -> String
fn transform<F, G>(
s: &str,
mut with_word: F,
mut boundary: G,
f: &mut fmt::Formatter,
) -> fmt::Result
where
F: Fn(&str, &mut String),
G: Fn(&mut String),
F: FnMut(&str, &mut fmt::Formatter) -> fmt::Result,
G: FnMut(&mut fmt::Formatter) -> fmt::Result,
{
/// Tracks the current 'mode' of the transformation algorithm as it scans
/// the input string.
Expand All @@ -83,7 +94,6 @@ where
Uppercase,
}

let mut out = String::new();
let mut first_word = true;

for word in s.unicode_words() {
Expand Down Expand Up @@ -115,9 +125,9 @@ where
// not uppercase and next is uppercase
if next == '_' || (next_mode == WordMode::Lowercase && next.is_uppercase()) {
if !first_word {
boundary(&mut out);
boundary(f)?;
}
with_word(&word[init..next_i], &mut out);
with_word(&word[init..next_i], f)?;
first_word = false;
init = next_i;
mode = WordMode::Boundary;
Expand All @@ -126,11 +136,11 @@ where
// is lowercase, word boundary before
} else if mode == WordMode::Uppercase && c.is_uppercase() && next.is_lowercase() {
if !first_word {
boundary(&mut out);
boundary(f)?;
} else {
first_word = false;
}
with_word(&word[init..i], &mut out);
with_word(&word[init..i], f)?;
init = i;
mode = WordMode::Boundary;

Expand All @@ -141,42 +151,48 @@ where
} else {
// Collect trailing characters as a word
if !first_word {
boundary(&mut out);
boundary(f)?;
} else {
first_word = false;
}
with_word(&word[init..], &mut out);
with_word(&word[init..], f)?;
break;
}
}
}

out
Ok(())
}

fn lowercase(s: &str, out: &mut String) {
fn lowercase(s: &str, f: &mut fmt::Formatter) -> fmt::Result {
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == 'Σ' && chars.peek().is_none() {
out.push('ς');
write!(f, "ς")?;
} else {
out.extend(c.to_lowercase());
write!(f, "{}", c.to_lowercase())?;
}
}

Ok(())
}

fn uppercase(s: &str, out: &mut String) {
fn uppercase(s: &str, f: &mut fmt::Formatter) -> fmt::Result {
for c in s.chars() {
out.extend(c.to_uppercase())
write!(f, "{}", c.to_uppercase())?;
}

Ok(())
}

fn capitalize(s: &str, out: &mut String) {
fn capitalize(s: &str, f: &mut fmt::Formatter) -> fmt::Result {
let mut char_indices = s.char_indices();
if let Some((_, c)) = char_indices.next() {
out.extend(c.to_uppercase());
write!(f, "{}", c.to_uppercase())?;
if let Some((i, _)) = char_indices.next() {
lowercase(&s[i..], out);
lowercase(&s[i..], f)?;
}
}

Ok(())
}
35 changes: 29 additions & 6 deletions src/lower_camel.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use crate::{capitalize, lowercase, transform};

/// This trait defines a lower camel case conversion.
Expand All @@ -20,16 +22,37 @@ pub trait ToLowerCamelCase: ToOwned {

impl ToLowerCamelCase for str {
fn to_lower_camel_case(&self) -> String {
AsLowerCamelCase(self).to_string()
}
}

/// This wrapper performs a lower camel case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// use heck::AsLowerCamelCase;
///
/// let sentence = "It is we who built these palaces and cities.";
/// assert_eq!(format!("{}", AsLowerCamelCase(sentence)), "itIsWeWhoBuiltThesePalacesAndCities");
/// ```
pub struct AsLowerCamelCase<T: AsRef<str>>(pub T);

impl<T: AsRef<str>> fmt::Display for AsLowerCamelCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut first = true;
transform(
self,
|s, out| {
if out.is_empty() {
lowercase(s, out);
self.0.as_ref(),
|s, f| {
if first {
first = false;
lowercase(s, f)
} else {
capitalize(s, out)
capitalize(s, f)
}
},
|_| {},
|_| Ok(()),
f,
)
}
}
Expand Down
22 changes: 21 additions & 1 deletion src/shouty_kebab.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use crate::{transform, uppercase};

/// This trait defines a shouty kebab case conversion.
Expand All @@ -20,7 +22,25 @@ pub trait ToShoutyKebabCase: ToOwned {

impl ToShoutyKebabCase for str {
fn to_shouty_kebab_case(&self) -> Self::Owned {
transform(self, uppercase, |s| s.push('-'))
AsShoutyKebabCase(self).to_string()
}
}

/// This wrapper performs a kebab case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// use heck::AsShoutyKebabCase;
///
/// let sentence = "We are going to inherit the earth.";
/// assert_eq!(format!("{}", AsShoutyKebabCase(sentence)), "WE-ARE-GOING-TO-INHERIT-THE-EARTH");
/// ```
pub struct AsShoutyKebabCase<T: AsRef<str>>(pub T);

impl<T: AsRef<str>> fmt::Display for AsShoutyKebabCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
transform(self.0.as_ref(), uppercase, |f| write!(f, "-"), f)
}
}

Expand Down
22 changes: 21 additions & 1 deletion src/shouty_snake.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use crate::{transform, uppercase};

/// This trait defines a shouty snake case conversion.
Expand Down Expand Up @@ -34,7 +36,25 @@ impl<T: ?Sized + ToShoutySnakeCase> ToShoutySnekCase for T {

impl ToShoutySnakeCase for str {
fn to_shouty_snake_case(&self) -> Self::Owned {
transform(self, uppercase, |s| s.push('_'))
AsShoutySnakeCase(self).to_string()
}
}

/// This wrapper performs a shouty snake case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// use heck::AsShoutySnakeCase;
///
/// let sentence = "That world is growing in this minute.";
/// assert_eq!(format!("{}", AsShoutySnakeCase(sentence)), "THAT_WORLD_IS_GROWING_IN_THIS_MINUTE");
/// ```
pub struct AsShoutySnakeCase<T: AsRef<str>>(pub T);

impl<T: AsRef<str>> fmt::Display for AsShoutySnakeCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
transform(self.0.as_ref(), uppercase, |f| write!(f, "_"), f)
}
}

Expand Down
22 changes: 21 additions & 1 deletion src/snake.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use crate::{lowercase, transform};

/// This trait defines a snake case conversion.
Expand Down Expand Up @@ -32,7 +34,25 @@ impl<T: ?Sized + ToSnakeCase> ToSnekCase for T {

impl ToSnakeCase for str {
fn to_snake_case(&self) -> String {
transform(self, lowercase, |s| s.push('_'))
AsSnakeCase(self).to_string()
}
}

/// This wrapper performs a snake case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// use heck::AsSnakeCase;
///
/// let sentence = "We carry a new world here, in our hearts.";
/// assert_eq!(format!("{}", AsSnakeCase(sentence)), "we_carry_a_new_world_here_in_our_hearts");
/// ```
pub struct AsSnakeCase<T: AsRef<str>>(pub T);

impl<T: AsRef<str>> fmt::Display for AsSnakeCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
transform(self.0.as_ref(), lowercase, |f| write!(f, "_"), f)
}
}

Expand Down
22 changes: 21 additions & 1 deletion src/title.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use crate::{capitalize, transform};

/// This trait defines a title case conversion.
Expand All @@ -20,7 +22,25 @@ pub trait ToTitleCase: ToOwned {

impl ToTitleCase for str {
fn to_title_case(&self) -> String {
transform(self, capitalize, |s| s.push(' '))
AsTitleCase(self).to_string()
}
}

/// This wrapper performs a title case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// use heck::AsTitleCase;
///
/// let sentence = "We have always lived in slums and holes in the wall.";
/// assert_eq!(format!("{}", AsTitleCase(sentence)), "We Have Always Lived In Slums And Holes In The Wall");
/// ```
pub struct AsTitleCase<T: AsRef<str>>(pub T);

impl<T: AsRef<str>> fmt::Display for AsTitleCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
transform(self.0.as_ref(), capitalize, |f| write!(f, " "), f)
}
}

Expand Down
22 changes: 21 additions & 1 deletion src/upper_camel.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use crate::{capitalize, transform};

/// This trait defines an upper camel case conversion.
Expand All @@ -20,7 +22,7 @@ pub trait ToUpperCamelCase: ToOwned {

impl ToUpperCamelCase for str {
fn to_upper_camel_case(&self) -> String {
transform(self, capitalize, |_| {})
AsUpperCamelCase(self).to_string()
}
}

Expand All @@ -37,6 +39,24 @@ impl<T: ?Sized + ToUpperCamelCase> ToPascalCase for T {
}
}

/// This wrapper performs a upper camel case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// use heck::AsUpperCamelCase;
///
/// let sentence = "We are not in the least afraid of ruins.";
/// assert_eq!(format!("{}", AsUpperCamelCase(sentence)), "WeAreNotInTheLeastAfraidOfRuins");
/// ```
pub struct AsUpperCamelCase<T: AsRef<str>>(pub T);

impl<T: AsRef<str>> fmt::Display for AsUpperCamelCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
transform(self.0.as_ref(), capitalize, |_| Ok(()), f)
}
}

#[cfg(test)]
mod tests {
use super::ToUpperCamelCase;
Expand Down

0 comments on commit 9c5835d

Please sign in to comment.