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 15, 2021
1 parent 093d56f commit 21d7a3f
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 34 deletions.
25 changes: 24 additions & 1 deletion src/camel.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

/// This trait defines a camel case conversion.
///
/// In CamelCase, word boundaries are indicated by capital letters, including
Expand All @@ -22,7 +24,28 @@ pub trait CamelCase: ToOwned {

impl CamelCase for str {
fn to_camel_case(&self) -> String {
::transform(self, ::capitalize, |_| {})
AsCamelCase(self).to_string()
}
}

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

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

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

/// This trait defines a kebab case conversion.
///
/// In kebab-case, word boundaries are indicated by hyphens.
Expand All @@ -21,7 +23,28 @@ pub trait KebabCase: ToOwned {

impl KebabCase 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:
///
/// ```
/// extern crate heck;
/// fn main() {
/// 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
57 changes: 32 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,21 @@ mod shouty_snake;
mod snake;
mod title;

pub use camel::CamelCase;
pub use kebab::KebabCase;
pub use mixed::MixedCase;
pub use shouty_snake::{ShoutySnakeCase, ShoutySnekCase};
pub use snake::{SnakeCase, SnekCase};
pub use title::TitleCase;
pub use camel::{AsCamelCase, CamelCase};
pub use kebab::{AsKebabCase, KebabCase};
pub use mixed::{AsMixedCase, MixedCase};
pub use shouty_snake::{AsShoutySnakeCase, AsShoutySnekCase, ShoutySnakeCase, ShoutySnekCase};
pub use snake::{AsSnakeCase, AsSnekCase, SnakeCase, SnekCase};
pub use title::{AsTitleCase, TitleCase};

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 @@ -75,7 +77,6 @@ where
Uppercase,
}

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

for word in s.unicode_words() {
Expand Down Expand Up @@ -105,18 +106,18 @@ where
// Word boundary after if next is underscore or current is
// not uppercase and next is uppercase
if next == '_' || (next_mode == WordMode::Lowercase && next.is_uppercase()) {
if !first_word { boundary(&mut out); }
with_word(&word[init..next_i], &mut out);
if !first_word { boundary(&mut *f)?; }
with_word(&word[init..next_i], &mut *f)?;
first_word = false;
init = next_i;
mode = WordMode::Boundary;

// Otherwise if current and previous are uppercase and next
// is lowercase, word boundary before
} else if mode == WordMode::Uppercase && c.is_uppercase() && next.is_lowercase() {
if !first_word { boundary(&mut out); }
if !first_word { boundary(&mut *f)?; }
else { first_word = false; }
with_word(&word[init..i], &mut out);
with_word(&word[init..i], &mut *f)?;
init = i;
mode = WordMode::Boundary;

Expand All @@ -126,40 +127,46 @@ where
}
} else {
// Collect trailing characters as a word
if !first_word { boundary(&mut out); }
if !first_word { boundary(&mut *f)?; }
else { first_word = false; }
with_word(&word[init..], &mut out);
with_word(&word[init..], &mut *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..], &mut *f)?;
}
}

Ok(())
}
34 changes: 30 additions & 4 deletions src/mixed.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

/// This trait defines a mixed case conversion.
///
/// In mixedCase, word boundaries are indicated by capital letters, excepting
Expand All @@ -22,10 +24,34 @@ pub trait MixedCase: ToOwned {

impl MixedCase for str {
fn to_mixed_case(&self) -> String {
::transform(self, |s, out| {
if out.is_empty() { ::lowercase(s, out); }
else { ::capitalize(s, out) }
}, |_| {})
AsMixedCase(self).to_string()
}
}

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

impl<T: AsRef<str>> fmt::Display for AsMixedCase<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut first = true;
::transform(self.0.as_ref(), |s, out| {
if first {
first = false;
::lowercase(s, out)
} else { ::capitalize(s, out) }
}, |_| Ok(()), f)
}
}

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

/// This trait defines a shouty snake case conversion.
///
/// In SHOUTY_SNAKE_CASE, word boundaries are indicated by underscores and all
Expand Down Expand Up @@ -37,10 +39,34 @@ impl<T: ?Sized + ShoutySnakeCase> ShoutySnekCase for T {

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

/// This wrapper performs a camel case conversion in [`fmt::Display`].
///
/// ## Example:
///
/// ```
/// extern crate heck;
/// fn main() {
/// 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)
}
}

/// Oh heck, AsShoutySnekCase is an alias for AsShoutySnakeCase. See AsShoutySnakeCase for more documentation.
pub use AsShoutySnakeCase as AsShoutySnekCase;

#[cfg(test)]
mod tests {
use super::ShoutySnakeCase;
Expand Down
28 changes: 27 additions & 1 deletion src/snake.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

/// This trait defines a camel case conversion.
///
/// In snake_case, word boundaries are indicated by underscores.
Expand Down Expand Up @@ -34,10 +36,34 @@ impl<T: ?Sized + SnakeCase> SnekCase for T {

impl SnakeCase 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:
///
/// ```
/// extern crate heck;
/// fn main() {
/// 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)
}
}

/// Oh heck, AsSnekCase is an alias for AsSnakeCase. See AsSnakeCase for more documentation.
pub use AsSnakeCase as AsSnekCase;

#[cfg(test)]
mod tests {
use super::SnakeCase;
Expand Down
25 changes: 24 additions & 1 deletion src/title.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

/// This trait defines a title case conversion.
///
/// In Title Case, word boundaries are indicated by spaces, and every word is
Expand All @@ -22,7 +24,28 @@ pub trait TitleCase: ToOwned {

impl TitleCase 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:
///
/// ```
/// extern crate heck;
/// fn main() {
/// 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

0 comments on commit 21d7a3f

Please sign in to comment.