Skip to content

Commit 8839461

Browse files
str: improve {is,}title impl and add tests
1 parent 16644da commit 8839461

File tree

1 file changed

+106
-20
lines changed

1 file changed

+106
-20
lines changed

vm/src/obj/objstr.rs

Lines changed: 106 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ impl PyString {
4343
}
4444
}
4545

46+
impl From<&str> for PyString {
47+
fn from(s: &str) -> PyString {
48+
PyString {
49+
value: s.to_string(),
50+
}
51+
}
52+
}
53+
4654
pub type PyStringRef = PyRef<PyString>;
4755

4856
impl fmt::Display for PyString {
@@ -396,9 +404,33 @@ impl PyString {
396404
}
397405
}
398406

407+
/// Return a titlecased version of the string where words start with an
408+
/// uppercase character and the remaining characters are lowercase.
399409
#[pymethod]
400410
fn title(&self, _vm: &VirtualMachine) -> String {
401-
make_title(&self.value)
411+
let mut title = String::new();
412+
let mut previous_is_cased = false;
413+
for c in self.value.chars() {
414+
if c.is_lowercase() {
415+
if !previous_is_cased {
416+
title.extend(c.to_uppercase());
417+
} else {
418+
title.push(c);
419+
}
420+
previous_is_cased = true;
421+
} else if c.is_uppercase() {
422+
if previous_is_cased {
423+
title.extend(c.to_lowercase());
424+
} else {
425+
title.push(c);
426+
}
427+
previous_is_cased = true;
428+
} else {
429+
previous_is_cased = false;
430+
title.push(c);
431+
}
432+
}
433+
title
402434
}
403435

404436
#[pymethod]
@@ -609,13 +641,34 @@ impl PyString {
609641
vm.ctx.new_tuple(new_tup)
610642
}
611643

644+
/// Return `true` if the sequence is ASCII titlecase and the sequence is not
645+
/// empty, `false` otherwise.
612646
#[pymethod]
613647
fn istitle(&self, _vm: &VirtualMachine) -> bool {
614648
if self.value.is_empty() {
615-
false
616-
} else {
617-
self.value.split(' ').all(|word| word == make_title(word))
649+
return false;
618650
}
651+
652+
let mut cased = false;
653+
let mut previous_is_cased = false;
654+
for c in self.value.chars() {
655+
if c.is_uppercase() {
656+
if previous_is_cased {
657+
return false;
658+
}
659+
previous_is_cased = true;
660+
cased = true;
661+
} else if c.is_lowercase() {
662+
if !previous_is_cased {
663+
return false;
664+
}
665+
previous_is_cased = true;
666+
cased = true;
667+
} else {
668+
previous_is_cased = false;
669+
}
670+
}
671+
cased
619672
}
620673

621674
#[pymethod]
@@ -967,22 +1020,55 @@ fn adjust_indices(
9671020
}
9681021
}
9691022

970-
// helper function to title strings
971-
fn make_title(s: &str) -> String {
972-
let mut titled_str = String::new();
973-
let mut capitalize_char: bool = true;
974-
for c in s.chars() {
975-
if c.is_alphabetic() {
976-
if !capitalize_char {
977-
titled_str.push(c);
978-
} else if capitalize_char {
979-
titled_str.push(c.to_ascii_uppercase());
980-
capitalize_char = false;
981-
}
982-
} else {
983-
titled_str.push(c);
984-
capitalize_char = true;
1023+
#[cfg(test)]
1024+
mod tests {
1025+
use super::*;
1026+
1027+
#[test]
1028+
fn str_title() {
1029+
let vm = VirtualMachine::new();
1030+
1031+
let tests = vec![
1032+
(" Hello ", " hello "),
1033+
("Hello ", "hello "),
1034+
("Hello ", "Hello "),
1035+
("Format This As Title String", "fOrMaT thIs aS titLe String"),
1036+
("Format,This-As*Title;String", "fOrMaT,thIs-aS*titLe;String"),
1037+
("Getint", "getInt"),
1038+
("Greek Ωppercases ...", "greek ωppercases ..."),
1039+
];
1040+
for (title, input) in tests {
1041+
assert_eq!(PyString::from(input).title(&vm), String::from(title));
1042+
}
1043+
}
1044+
1045+
#[test]
1046+
fn str_istitle() {
1047+
let vm = VirtualMachine::new();
1048+
1049+
let pos = vec![
1050+
"A",
1051+
"A Titlecased Line",
1052+
"A\nTitlecased Line",
1053+
"A Titlecased, Line",
1054+
"Greek Ωppercases ...",
1055+
];
1056+
1057+
for s in pos {
1058+
assert!(PyString::from(s).istitle(&vm));
1059+
}
1060+
1061+
let neg = vec![
1062+
"",
1063+
"a",
1064+
"\n",
1065+
"Not a capitalized String",
1066+
"Not\ta Titlecase String",
1067+
"Not--a Titlecase String",
1068+
"NOT",
1069+
];
1070+
for s in neg {
1071+
assert!(!PyString::from(s).istitle(&vm));
9851072
}
9861073
}
987-
titled_str
9881074
}

0 commit comments

Comments
 (0)