Skip to content

Commit

Permalink
Merge branch 'main' into binary
Browse files Browse the repository at this point in the history
  • Loading branch information
janos committed Jan 8, 2024
2 parents a12a394 + 3c402d4 commit 8927142
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 70 deletions.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ This library implements the following functions that return appropriate styles o
Benchmarks run `cargo bench` on MacBook Pro M1Pro yield these timings:

```
camel time: [1.0097 µs 1.0164 µs 1.0241 µs]
pascal time: [1.0299 µs 1.0371 µs 1.0444 µs]
snake time: [681.45 ns 685.85 ns 689.92 ns]
camel_snake time: [1.1460 µs 1.1534 µs 1.1606 µs]
screaming_snake time: [1.0004 µs 1.0057 µs 1.0108 µs]
kebab time: [717.66 ns 726.00 ns 734.36 ns]
camel_kebab time: [1.1163 µs 1.1238 µs 1.1309 µs]
screaming_kebab time: [999.09 ns 1.0103 µs 1.0210 µs]
title time: [1.0305 µs 1.0412 µs 1.0518 µs]
lower time: [640.24 ns 650.07 ns 659.39 ns]
screaming time: [929.30 ns 935.17 ns 941.02 ns]
camel time: [878.76 ns 884.24 ns 889.59 ns]
pascal time: [858.82 ns 866.89 ns 874.65 ns]
snake time: [537.22 ns 540.21 ns 543.24 ns]
camel_snake time: [870.81 ns 876.18 ns 881.71 ns]
screaming_snake time: [551.08 ns 552.52 ns 553.97 ns]
kebab time: [537.33 ns 540.59 ns 543.96 ns]
camel_kebab time: [863.78 ns 871.67 ns 879.36 ns]
screaming_kebab time: [554.04 ns 555.31 ns 556.61 ns]
lower time: [530.32 ns 531.99 ns 533.61 ns]
title time: [877.86 ns 884.77 ns 891.13 ns]
screaming time: [560.80 ns 563.78 ns 566.98 ns]
```

## CLI
Expand Down
162 changes: 104 additions & 58 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
//! - `snake("--camel-snake-kebab")` returns `camel_snake_kebab`
//! - `screaming("--camel-snake-kebab")` returns `CAMEL SNAKE KEBAB`

use std::usize;
use std::{fmt::Write, usize};

/// *Camel* case is the practice of writing compound words
/// or phrases such that each word or abbreviation in the
Expand All @@ -49,19 +49,15 @@ use std::usize;
///
/// Example: `camelSnakeKebab`.
pub fn camel(s: &str) -> String {
let mut w = words(s);
camel_slice(&mut w, 1);
w.join("")
casbab(s, to_titlecase, to_lowercase)
}

/// *Pascal* case is a variant of Camel case writing where
/// the first letter of the first word is always capitalized.
///
/// Example: `CamelSnakeKebab`.
pub fn pascal(s: &str) -> String {
let mut w = words(s);
camel_slice(&mut w, 0);
w.join("")
casbab(s, to_titlecase, to_titlecase)
}

/// *Snake* case is the practice of writing compound words
Expand All @@ -71,30 +67,23 @@ pub fn pascal(s: &str) -> String {
///
/// Example: `camel_snake_kebab`.
pub fn snake(s: &str) -> String {
let (head, tail) = head_tail_count(s, '_');
"_".repeat(head) + words(s).join("_").as_str() + "_".repeat(tail).as_str()
casbab_wrap(s, '_', to_lowercase)
}

/// *Camel snake* case is a variant of Camel case with
/// each element's first letter uppercased.
///
/// Example: `Camel_Snake_Kebab`.
pub fn camel_snake(s: &str) -> String {
let (head, tail) = head_tail_count(s, '_');
let mut w = words(s);
camel_slice(&mut w, 0);
"_".repeat(head) + w.join("_").as_str() + "_".repeat(tail).as_str()
casbab_wrap(s, '_', to_titlecase)
}

/// *Screaming snake* case is a variant of Camel case with
/// all letters uppercased.
///
/// Example: `CAMEL_SNAKE_KEBAB`.
pub fn screaming_snake(s: &str) -> String {
let (head, tail) = head_tail_count(s, '_');
let mut w = words(s);
scream_slice(&mut w);
"_".repeat(head) + w.join("_").as_str() + "_".repeat(tail).as_str()
casbab_wrap(s, '_', to_uppercase)
}

/// *Kebab* case is the practice of writing compound words
Expand All @@ -104,30 +93,23 @@ pub fn screaming_snake(s: &str) -> String {
///
/// Example: `camel-snake-kebab`.
pub fn kebab(s: &str) -> String {
let (head, tail) = head_tail_count(s, '-');
"-".repeat(head) + words(s).join("-").as_str() + "-".repeat(tail).as_str()
casbab_wrap(s, '-', to_lowercase)
}

/// *Camel kebab* case is a variant of Kebab case with
/// each element's first letter uppercased.
///
/// Example: `Camel-Snake-Kebab`.
pub fn camel_kebab(s: &str) -> String {
let (head, tail) = head_tail_count(s, '-');
let mut w = words(s);
camel_slice(&mut w, 0);
"-".repeat(head) + w.join("-").as_str() + "-".repeat(tail).as_str()
casbab_wrap(s, '-', to_titlecase)
}

/// *Screaming kebab* case is a variant of Kebab case with
/// all letters uppercased.
///
/// Example: `CAMEL-SNAKE-KEBAB`.
pub fn screaming_kebab(s: &str) -> String {
let (head, tail) = head_tail_count(s, '-');
let mut w = words(s);
scream_slice(&mut w);
"-".repeat(head) + w.join("-").as_str() + "-".repeat(tail).as_str()
casbab_wrap(s, '-', to_uppercase)
}

/// *Lower* is returning detected words, not in a compound
Expand All @@ -136,7 +118,83 @@ pub fn screaming_kebab(s: &str) -> String {
///
/// Example: `camel snake kebab`.
pub fn lower(s: &str) -> String {
words(s).join(" ")
casbab_separate(s, ' ', to_lowercase)
}

fn casbab(
s: &str,
transform: fn(&str) -> String,
transform_first_word: fn(&str) -> String,
) -> String {
let mut r = String::new();
let mut s = s;
let (w, rest) = first_word(s);
r += &transform_first_word(w);
s = rest;
loop {
let (w, rest) = first_word(s);
if w.is_empty() {
break r;
}
r += &transform(w);
if rest.is_empty() {
break r;
}
s = rest;
}
}

fn casbab_separate(s: &str, separator: char, transform: fn(&str) -> String) -> String {
let mut r = String::new();
let mut s = s;
let (w, rest) = first_word(s);
r += &transform(w);
s = rest;
loop {
let (w, rest) = first_word(s);
if w.is_empty() {
break r;
}
_ = r.write_char(separator);
r += &transform(w);
if rest.is_empty() {
break r;
}
s = rest;
}
}

fn casbab_wrap(s: &str, separator: char, transform: fn(&str) -> String) -> String {
let mut r = String::new();

let (head, tail) = head_tail_count(s, separator);

for _ in 0..head {
_ = r.write_char(separator);
}

let mut s = s;
let (w, rest) = first_word(s);
r += &transform(w);
s = rest;
loop {
let (w, rest) = first_word(s);
if w.is_empty() {
break;
}
_ = r.write_char(separator);
r += &transform(w);
if rest.is_empty() {
break;
}
s = rest;
}

for _ in 0..tail {
_ = r.write_char(separator);
}

r
}

/// *Title* is returning detected words, not in a compound
Expand All @@ -146,9 +204,7 @@ pub fn lower(s: &str) -> String {
///
/// Example: `Camel Snake Kebab`.
pub fn title(s: &str) -> String {
let mut w = words(s);
camel_slice(&mut w, 0);
w.join(" ")
casbab_separate(s, ' ', to_titlecase)
}

/// *Screaming* is returning detected words, not in a compound
Expand All @@ -157,23 +213,20 @@ pub fn title(s: &str) -> String {
///
/// Example: `CAMEL SNAKE KEBAB`.
pub fn screaming(s: &str) -> String {
let mut w = words(s);
scream_slice(&mut w);
w.join(" ")
casbab_separate(s, ' ', to_uppercase)
}

fn words(s: &str) -> Vec<String> {
fn first_word(s: &str) -> (&str, &str) {
let mut start: usize = 0;
let l = s.len();
let mut prev_lower = false;
let mut prev_upper = false;
let mut prev_upper_location: usize = 0;

let mut words: Vec<String> = Vec::new();
for (i, c) in s.char_indices() {
if c == '-' || c == '_' || c == ' ' {
if start != i {
words.push(s[start..i].to_lowercase());
return (&s[start..i], &s[i..]);
};
start = i + 1;
prev_lower = false;
Expand All @@ -187,7 +240,7 @@ fn words(s: &str) -> Vec<String> {
prev_upper_location = i;
if prev_lower {
if start != i {
words.push(s[start..i].to_lowercase())
return (&s[start..i], &s[i..]);
}
start = i;
prev_lower = false;
Expand All @@ -196,7 +249,7 @@ fn words(s: &str) -> Vec<String> {
prev_lower = true;
if prev_upper && prev_upper_location > 0 {
if start != prev_upper_location {
words.push(s[start..prev_upper_location].to_lowercase())
return (&s[start..prev_upper_location], &s[prev_upper_location..]);
}
start = prev_upper_location;
prev_upper = false;
Expand All @@ -205,31 +258,24 @@ fn words(s: &str) -> Vec<String> {
}
}
if start != l {
words.push(s[start..].to_lowercase());
return (&s[start..], "");
}
words
("", "")
}

fn scream_slice(s: &mut [String]) {
for e in s {
*e = e.to_uppercase()
}
fn to_lowercase(s: &str) -> String {
s.to_lowercase()
}

fn camel_slice(s: &mut [String], start: usize) {
for e in s.iter_mut().skip(start) {
to_titlecase(e);
}
fn to_uppercase(s: &str) -> String {
s.to_uppercase()
}

fn to_titlecase(e: &mut String) {
*e = match e.chars().next() {
None => e.to_owned(),
Some(f) => {
e.replace_range(..f.len_utf8(), &f.to_uppercase().to_string()[..]);
e.to_owned()
}
};
fn to_titlecase(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => s.to_string(),
Some(f) => f.to_uppercase().to_string() + &chars.as_str().to_lowercase(),
}
}

fn head_tail_count(s: &str, sub: char) -> (usize, usize) {
Expand Down
2 changes: 1 addition & 1 deletion tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ fn casbab_test() {
screaming: "ОВО ЈЕ BRAVE NEW СВЕТ".to_string(),
},
Case {
input: vec!["".to_string()],
input: vec!["".to_string(), " ".to_string(), " ".to_string()],
camel: "".to_string(),
pascal: "".to_string(),
snake: "".to_string(),
Expand Down

0 comments on commit 8927142

Please sign in to comment.