Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added AsXxxCase wrappers #33

Merged
merged 1 commit into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have actually preferred simply doing pub use upper_camel::*; here though.


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