Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.

Commit 2b4a196

Browse files
committed
feat: enhance string conversion utilities with detailed documentation and minor adjustments
1 parent 0780b3e commit 2b4a196

File tree

1 file changed

+31
-20
lines changed

1 file changed

+31
-20
lines changed

src/lib.rs

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
//! Case conversion utilities for strings (PascalCase, camelCase, kebab-case, etc.).
2+
13
use urlogger::{LogLevel, log};
24

5+
/// Default characters that split a string into words (e.g. `foo-bar`, `foo_bar`).
36
const STR_SPLITTERS: &[char] = &['-', '_', '/', '.'];
47

8+
/// Returns whether `c` is uppercase. Returns `None` for ASCII digits.
59
pub fn is_uppercase(c: char) -> Option<bool> {
610
if c.is_ascii_digit() {
711
return None;
@@ -10,6 +14,10 @@ pub fn is_uppercase(c: char) -> Option<bool> {
1014
Some(c != lower)
1115
}
1216

17+
/// Splits a string into words at case boundaries and separators.
18+
///
19+
/// Handles camelCase, PascalCase, SCREAMING_SNAKE, etc. Uses `separators` (or
20+
/// `STR_SPLITTERS`) to split. Empty segments are preserved (e.g. `foo--bar` → `["foo","","bar"]`).
1321
pub fn split_by_case(s: &str, separators: Option<&[char]>) -> Vec<String> {
1422
let splitters: std::collections::HashSet<char> = separators
1523
.unwrap_or(STR_SPLITTERS)
@@ -37,7 +45,7 @@ pub fn split_by_case(s: &str, separators: Option<&[char]>) -> Vec<String> {
3745

3846
let is_upper = is_uppercase(c);
3947
if previous_splitter == Some(false) {
40-
// Case rising edge: 小写 -> 大写 (e.g. camel|Case)
48+
// Case rising edge: lower -> upper (e.g. camel|Case)
4149
if previous_upper == Some(false) && is_upper == Some(true) {
4250
if !buff.is_empty() {
4351
parts.push(std::mem::take(&mut buff));
@@ -47,12 +55,12 @@ pub fn split_by_case(s: &str, separators: Option<&[char]>) -> Vec<String> {
4755
previous_splitter = Some(false);
4856
continue;
4957
}
50-
// Case falling edge: 大写 -> 小写,且 buffer > 1 (e.g. AB|c -> A, Bc)
58+
// Case falling edge: upper -> lower, buffer.len() > 1 (e.g. AB|c A, Bc)
5159
if previous_upper == Some(true) && is_upper == Some(false) {
5260
let char_count = buff.chars().count();
5361
if char_count > 1 {
5462
let last_char = buff.chars().last().unwrap();
55-
// Byte index of the start of the last character
63+
// Byte index where the last character starts
5664
let new_len = buff
5765
.char_indices()
5866
.nth(char_count - 1)
@@ -82,6 +90,7 @@ pub fn split_by_case(s: &str, separators: Option<&[char]>) -> Vec<String> {
8290
parts
8391
}
8492

93+
/// Capitalizes the first character; rest unchanged.
8594
pub fn upper_first(s: &str) -> String {
8695
let mut chars = s.chars();
8796
if let Some(c) = chars.next() {
@@ -91,6 +100,7 @@ pub fn upper_first(s: &str) -> String {
91100
}
92101
}
93102

103+
/// Lowercases the first character; rest unchanged.
94104
pub fn lower_first(s: &str) -> String {
95105
let mut chars = s.chars();
96106
if let Some(c) = chars.next() {
@@ -100,7 +110,9 @@ pub fn lower_first(s: &str) -> String {
100110
}
101111
}
102112

103-
/// Convert a string to PascalCase.
113+
/// Converts a string to PascalCase.
114+
///
115+
/// With `normalize == true`, each part is lowercased before capitalizing.
104116
pub fn pascal_case(s: &str, normalize: bool) -> String {
105117
if s.is_empty() {
106118
return String::new();
@@ -117,32 +129,31 @@ pub fn pascal_case(s: &str, normalize: bool) -> String {
117129
.collect()
118130
}
119131

120-
/// Convert a string to camelCase.
121-
/// Uses `lower_first(pascal_case(s, normalize))` to match scule behavior.
132+
/// Converts a string to camelCase.
133+
///
134+
/// Uses `lower_first(pascal_case(s, normalize))`.
122135
pub fn camel_case(s: &str, normalize: bool) -> String {
123136
lower_first(&pascal_case(s, normalize))
124137
}
125138

126-
/// Convert a string to kebab-case.
127-
/// Splits by case, lowercases each part, joins with "-". Matches scule kebabCase.
139+
/// Converts a string to kebab-case (lowercase parts joined with `-`).
128140
pub fn kebab_case(s: &str) -> String {
129141
lower_case_join(s, "-")
130142
}
131143

132-
/// Convert a string to snake_case.
133-
/// Same as kebab_case but joins with "_". Matches scule snakeCase.
144+
/// Converts a string to snake_case (lowercase parts joined with `_`).
134145
pub fn snake_case(s: &str) -> String {
135146
lower_case_join(s, "_")
136147
}
137148

138-
/// Convert a string to flatcase (all lowercase, no separators).
139-
/// Same as kebab_case but joins with "". Matches scule flatCase.
149+
/// Converts a string to flatcase (all lowercase, no separators).
140150
pub fn flat_case(s: &str) -> String {
141151
lower_case_join(s, "")
142152
}
143153

144-
/// Convert a string to Train-Case (each word capitalized, joined by "-").
145-
/// Matches scule trainCase. With normalize, lowercases each part before capitalizing.
154+
/// Converts a string to Train-Case (each word capitalized, joined by `-`).
155+
///
156+
/// With `normalize == true`, lowercases each part before capitalizing.
146157
pub fn train_case(s: &str, normalize: bool) -> String {
147158
split_by_case(s, None)
148159
.into_iter()
@@ -158,6 +169,7 @@ pub fn train_case(s: &str, normalize: bool) -> String {
158169
.join("-")
159170
}
160171

172+
/// Splits by case, lowercases each part, joins with `joiner`.
161173
fn lower_case_join(s: &str, joiner: &str) -> String {
162174
split_by_case(s, None)
163175
.into_iter()
@@ -166,14 +178,15 @@ fn lower_case_join(s: &str, joiner: &str) -> String {
166178
.join(joiner)
167179
}
168180

169-
/// Words that stay lowercase in title case (a, an, and, as, at, but, by, for, if, in, is, nor, of, on, or, the, to, with)
181+
/// Minor words that remain lowercase in Title Case.
170182
const TITLE_CASE_EXCEPTIONS: &[&str] = &[
171183
"a", "an", "and", "as", "at", "but", "by", "for", "if", "in", "is", "nor", "of", "on", "or",
172184
"the", "to", "with",
173185
];
174186

175-
/// Convert a string to Title Case (like train-case but with spaces, minor words lowercase).
176-
/// Matches scule titleCase. With normalize, lowercases each part before capitalizing (except exceptions).
187+
/// Converts a string to Title Case (like train-case but with spaces, minor words lowercase).
188+
///
189+
/// With `normalize == true`, lowercases each part before capitalizing (except `TITLE_CASE_EXCEPTIONS`).
177190
pub fn title_case(s: &str, normalize: bool) -> String {
178191
split_by_case(s, None)
179192
.into_iter()
@@ -192,6 +205,7 @@ pub fn title_case(s: &str, normalize: bool) -> String {
192205
.join(" ")
193206
}
194207

208+
/// Returns a greeting string. Example: `hello("world")` → `"Hello, world!"`.
195209
pub fn hello(name: &str) -> String {
196210
log!(LogLevel::Info, "lib.rs");
197211
format!("Hello, {}!", name)
@@ -257,7 +271,6 @@ mod tests {
257271

258272
#[test]
259273
fn test_train_case() {
260-
// Same as scule: trainCase(input) - without normalize
261274
assert_eq!(train_case("", false), "");
262275
assert_eq!(train_case("f", false), "F");
263276
assert_eq!(train_case("foo", false), "Foo");
@@ -269,15 +282,13 @@ mod tests {
269282
assert_eq!(train_case("WWW-authenticate", false), "WWW-Authenticate");
270283
assert_eq!(train_case("WWWAuthenticate", false), "WWW-Authenticate");
271284

272-
// Same as scule: trainCase(input, { normalize: true })
273285
assert_eq!(train_case("AcceptCH", true), "Accept-Ch");
274286
assert_eq!(train_case("FOO_BAR", true), "Foo-Bar");
275287
assert_eq!(train_case("WWW-authenticate", true), "Www-Authenticate");
276288
}
277289

278290
#[test]
279291
fn test_title_case() {
280-
// Same as scule: titleCase(input) - without normalize
281292
assert_eq!(title_case("", false), "");
282293
assert_eq!(title_case("f", false), "F");
283294
assert_eq!(title_case("foo", false), "Foo");

0 commit comments

Comments
 (0)