@@ -43,6 +43,14 @@ impl PyString {
43
43
}
44
44
}
45
45
46
+ impl From < & str > for PyString {
47
+ fn from ( s : & str ) -> PyString {
48
+ PyString {
49
+ value : s. to_string ( ) ,
50
+ }
51
+ }
52
+ }
53
+
46
54
pub type PyStringRef = PyRef < PyString > ;
47
55
48
56
impl fmt:: Display for PyString {
@@ -396,9 +404,33 @@ impl PyString {
396
404
}
397
405
}
398
406
407
+ /// Return a titlecased version of the string where words start with an
408
+ /// uppercase character and the remaining characters are lowercase.
399
409
#[ pymethod]
400
410
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
402
434
}
403
435
404
436
#[ pymethod]
@@ -609,13 +641,34 @@ impl PyString {
609
641
vm. ctx . new_tuple ( new_tup)
610
642
}
611
643
644
+ /// Return `true` if the sequence is ASCII titlecase and the sequence is not
645
+ /// empty, `false` otherwise.
612
646
#[ pymethod]
613
647
fn istitle ( & self , _vm : & VirtualMachine ) -> bool {
614
648
if self . value . is_empty ( ) {
615
- false
616
- } else {
617
- self . value . split ( ' ' ) . all ( |word| word == make_title ( word) )
649
+ return false ;
618
650
}
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
619
672
}
620
673
621
674
#[ pymethod]
@@ -967,22 +1020,55 @@ fn adjust_indices(
967
1020
}
968
1021
}
969
1022
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\n Titlecased 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\t a Titlecase String" ,
1067
+ "Not--a Titlecase String" ,
1068
+ "NOT" ,
1069
+ ] ;
1070
+ for s in neg {
1071
+ assert ! ( !PyString :: from( s) . istitle( & vm) ) ;
985
1072
}
986
1073
}
987
- titled_str
988
1074
}
0 commit comments