@@ -185,6 +185,22 @@ where
185185 result
186186}
187187
188+ /// Helper for final sigma lowercase
189+ fn map_uppercase_sigma ( from : & str , i : usize ) -> char {
190+ fn case_ignorable_then_cased < I : Iterator < Item = char > > ( iter : I ) -> bool {
191+ match iter. skip_while ( |& c| c. is_case_ignorable ( ) ) . next ( ) {
192+ Some ( c) => c. is_cased ( ) ,
193+ None => false ,
194+ }
195+ }
196+
197+ // See https://www.unicode.org/versions/latest/core-spec/chapter-3/#G54277
198+ // for the definition of `Final_Sigma`.
199+ let is_word_final = case_ignorable_then_cased ( from[ ..i] . chars ( ) . rev ( ) )
200+ && !case_ignorable_then_cased ( from[ i + const { 'Σ' . len_utf8 ( ) } ..] . chars ( ) ) ;
201+ if is_word_final { 'ς' } else { 'σ' }
202+ }
203+
188204#[ stable( feature = "rust1" , since = "1.0.0" ) ]
189205impl Borrow < str > for String {
190206 #[ inline]
@@ -345,7 +361,7 @@ impl str {
345361 ///
346362 /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent
347363 /// casing of Greek sigma. However, like that method, it does not handle locale-specific
348- /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation
364+ /// casing, like Turkish and Azeri I/ı/İ/i. See its documentation
349365 /// for more information.
350366 ///
351367 /// # Examples
@@ -358,7 +374,7 @@ impl str {
358374 /// assert_eq!("hello", s.to_lowercase());
359375 /// ```
360376 ///
361- /// A tricky example , with sigma:
377+ /// Tricky examples , with sigma:
362378 ///
363379 /// ```
364380 /// let sigma = "Σ";
@@ -369,6 +385,10 @@ impl str {
369385 /// let odysseus = "ὈΔΥΣΣΕΎΣ";
370386 ///
371387 /// assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
388+ ///
389+ /// let odysseus_king_of_ithaca = "Ο ΟΔΥΣΣΈΑΣ ΒΑΣΙΛΙΆΣ ΤΗΣ ΙΘΆΚΗΣ";
390+ ///
391+ /// assert_eq!("ο οδυσσέας βασιλιάς της ιθάκης", odysseus_king_of_ithaca.to_lowercase());
372392 /// ```
373393 ///
374394 /// Languages without case are not changed:
@@ -415,21 +435,136 @@ impl str {
415435 }
416436 }
417437 return s;
438+ }
418439
419- fn map_uppercase_sigma ( from : & str , i : usize ) -> char {
420- // See https://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G33992
421- // for the definition of `Final_Sigma`.
422- let is_word_final = case_ignorable_then_cased ( from[ ..i] . chars ( ) . rev ( ) )
423- && !case_ignorable_then_cased ( from[ i + const { 'Σ' . len_utf8 ( ) } ..] . chars ( ) ) ;
424- if is_word_final { 'ς' } else { 'σ' }
440+ /// Returns the titlecase equivalent of this string slice,
441+ /// which is assumed to represent a single word,
442+ /// as a new [`String`].
443+ ///
444+ /// Essentially, this consists of uppercasing the first cased letter
445+ /// (with [`char::to_titlecase()`]), and lowercasing everything that follows.
446+ ///
447+ /// 'Titlecase' is defined according to the terms of
448+ /// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34082)
449+ /// of the Unicode standard.
450+ ///
451+ /// Since some characters can expand into multiple characters when changing
452+ /// the case, this function returns a [`String`] instead of modifying the
453+ /// parameter in-place.
454+ ///
455+ /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent
456+ /// casing of Greek sigma. However, like that method, it does not handle locale-specific
457+ /// casing, like Turkish and Azeri I/ı/İ/i. See its documentation
458+ /// for more information.
459+ ///
460+ /// This method does not perform any kind of word segmentation.
461+ ///
462+ /// # Examples
463+ ///
464+ /// Basic usage:
465+ ///
466+ /// ```
467+ /// #![feature(titlecase)]
468+ /// let s = "HELLO";
469+ ///
470+ /// assert_eq!("Hello", s.word_to_titlecase());
471+ /// ```
472+ ///
473+ /// The first *cased* letter is uppercased:
474+ ///
475+ /// ```
476+ /// #![feature(titlecase)]
477+ /// let the_night_before_christmas = "'twas";
478+ ///
479+ /// assert_eq!("'Twas", the_night_before_christmas.word_to_titlecase());
480+ /// ```
481+ ///
482+ /// Languages without case are not changed:
483+ ///
484+ /// ```
485+ /// #![feature(titlecase)]
486+ /// let new_year = "农历新年";
487+ ///
488+ /// assert_eq!(new_year, new_year.word_to_titlecase());
489+ /// ```
490+ ///
491+ /// Georgian uppercase ("Mtavruli") letters are not used in titlecase:
492+ ///
493+ /// ```
494+ /// #![feature(titlecase)]
495+ /// let georgian = "ერთობაშია";
496+ ///
497+ /// assert_eq!(georgian, georgian.word_to_titlecase());
498+ /// ```
499+ ///
500+ /// No word segmentation is performed,
501+ /// so only the first cased letter in the whole string gets uppercased:
502+ ///
503+ /// ```
504+ /// #![feature(titlecase)]
505+ /// let blazingly_fast = "ferris and I";
506+ ///
507+ /// assert_eq!("Ferris and i", blazingly_fast.word_to_titlecase());
508+ /// ```
509+ ///
510+ /// Tricky examples, with sigma:
511+ ///
512+ /// ```
513+ /// #![feature(titlecase)]
514+ /// let odysseus = "ὈΔΥΣΣΕΎΣ";
515+ ///
516+ /// assert_eq!("Ὀδυσσεύς", odysseus.word_to_titlecase());
517+ ///
518+ /// let odysseus_king_of_ithaca = "Ο ΟΔΥΣΣΈΑΣ ΒΑΣΙΛΙΆΣ ΤΗΣ ΙΘΆΚΗΣ";
519+ ///
520+ /// assert_eq!("Ο οδυσσέας βασιλιάς της ιθάκης", odysseus_king_of_ithaca.word_to_titlecase());
521+ /// ```
522+ #[ cfg( not( no_global_oom_handling) ) ]
523+ #[ rustc_allow_incoherent_impl]
524+ #[ must_use = "this returns the titlecase word as a new String, \
525+ without modifying the original"]
526+ #[ unstable( feature = "titlecase" , issue = "153892" ) ]
527+ pub fn word_to_titlecase ( & self ) -> String {
528+ // FIXME: add ASCII fast path
529+
530+ let mut s = String :: with_capacity ( self . len ( ) ) ;
531+ let mut chars = self . char_indices ( ) ;
532+
533+ ' until_first_cased_char: for ( _, c) in chars. by_ref ( ) {
534+ if c. is_cased ( ) {
535+ s. extend ( c. to_titlecase ( ) ) ;
536+ break ' until_first_cased_char;
537+ } else {
538+ s. push ( c) ;
539+ }
425540 }
426541
427- fn case_ignorable_then_cased < I : Iterator < Item = char > > ( iter : I ) -> bool {
428- match iter. skip_while ( |& c| c. is_case_ignorable ( ) ) . next ( ) {
429- Some ( c) => c. is_cased ( ) ,
430- None => false ,
542+ for ( i, c) in chars {
543+ if c == 'Σ' {
544+ // Σ maps to σ, except at the end of a word where it maps to ς.
545+ // This is the only conditional (contextual) but language-independent mapping
546+ // in `SpecialCasing.txt`,
547+ // so hard-code it rather than have a generic "condition" mechanism.
548+ // See https://github.com/rust-lang/rust/issues/26035
549+ let sigma_lowercase = map_uppercase_sigma ( self , i) ;
550+ s. push ( sigma_lowercase) ;
551+ } else {
552+ match conversions:: to_lower ( c) {
553+ [ a, '\0' , _] => s. push ( a) ,
554+ [ a, b, '\0' ] => {
555+ s. push ( a) ;
556+ s. push ( b) ;
557+ }
558+ [ a, b, c] => {
559+ s. push ( a) ;
560+ s. push ( b) ;
561+ s. push ( c) ;
562+ }
563+ }
431564 }
432565 }
566+
567+ s
433568 }
434569
435570 /// Returns the uppercase equivalent of this string slice, as a new [`String`].
0 commit comments