diff --git a/src/kebab.rs b/src/kebab.rs index 92f60c7..75d64a5 100644 --- a/src/kebab.rs +++ b/src/kebab.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{lowercase, transform}; /// This trait defines a kebab case conversion. @@ -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>(pub T); + +impl> fmt::Display for AsKebabCase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + transform(self.0.as_ref(), lowercase, |f| write!(f, "-"), f) } } diff --git a/src/lib.rs b/src/lib.rs index 83dd9a4..9a9965c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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(s: &str, with_word: F, boundary: G) -> String +fn transform( + 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. @@ -83,7 +94,6 @@ where Uppercase, } - let mut out = String::new(); let mut first_word = true; for word in s.unicode_words() { @@ -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; @@ -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; @@ -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(()) } diff --git a/src/lower_camel.rs b/src/lower_camel.rs index 0c5f0d8..5962096 100644 --- a/src/lower_camel.rs +++ b/src/lower_camel.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{capitalize, lowercase, transform}; /// This trait defines a lower camel case conversion. @@ -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>(pub T); + +impl> fmt::Display for AsLowerCamelCase { + 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, ) } } diff --git a/src/shouty_kebab.rs b/src/shouty_kebab.rs index 12db1b5..d865d8c 100644 --- a/src/shouty_kebab.rs +++ b/src/shouty_kebab.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{transform, uppercase}; /// This trait defines a shouty kebab case conversion. @@ -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>(pub T); + +impl> fmt::Display for AsShoutyKebabCase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + transform(self.0.as_ref(), uppercase, |f| write!(f, "-"), f) } } diff --git a/src/shouty_snake.rs b/src/shouty_snake.rs index c3026a0..0146696 100644 --- a/src/shouty_snake.rs +++ b/src/shouty_snake.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{transform, uppercase}; /// This trait defines a shouty snake case conversion. @@ -34,7 +36,25 @@ impl 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>(pub T); + +impl> fmt::Display for AsShoutySnakeCase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + transform(self.0.as_ref(), uppercase, |f| write!(f, "_"), f) } } diff --git a/src/snake.rs b/src/snake.rs index ebb7113..8a466fe 100644 --- a/src/snake.rs +++ b/src/snake.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{lowercase, transform}; /// This trait defines a snake case conversion. @@ -32,7 +34,25 @@ impl 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>(pub T); + +impl> fmt::Display for AsSnakeCase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + transform(self.0.as_ref(), lowercase, |f| write!(f, "_"), f) } } diff --git a/src/title.rs b/src/title.rs index 4ea0b77..5932bfd 100644 --- a/src/title.rs +++ b/src/title.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{capitalize, transform}; /// This trait defines a title case conversion. @@ -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>(pub T); + +impl> fmt::Display for AsTitleCase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + transform(self.0.as_ref(), capitalize, |f| write!(f, " "), f) } } diff --git a/src/upper_camel.rs b/src/upper_camel.rs index 0143ba1..9fdd43a 100644 --- a/src/upper_camel.rs +++ b/src/upper_camel.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{capitalize, transform}; /// This trait defines an upper camel case conversion. @@ -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() } } @@ -37,6 +39,24 @@ impl 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>(pub T); + +impl> fmt::Display for AsUpperCamelCase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + transform(self.0.as_ref(), capitalize, |_| Ok(()), f) + } +} + #[cfg(test)] mod tests { use super::ToUpperCamelCase;