@@ -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
@@ -415,21 +431,136 @@ impl str {
415431 }
416432 }
417433 return s;
434+ }
418435
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 { 'σ' }
436+ /// Returns the titlecase equivalent of this string slice,
437+ /// which is assumed to represent a single word,
438+ /// as a new [`String`].
439+ ///
440+ /// Essentially, this consists of uppercasing the first cased letter
441+ /// (with [`char::to_titlecase()`]), and lowercasing everything that follows.
442+ ///
443+ /// 'Titlecase' is defined according to the terms of
444+ /// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34082)
445+ /// of the Unicode standard.
446+ ///
447+ /// Since some characters can expand into multiple characters when changing
448+ /// the case, this function returns a [`String`] instead of modifying the
449+ /// parameter in-place.
450+ ///
451+ /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent
452+ /// casing of Greek sigma. However, like that method, it does not handle locale-specific
453+ /// casing, like Turkish and Azeri I/ı/İ/i. See its documentation
454+ /// for more information.
455+ ///
456+ /// This method does not perform any kind of word segmentation.
457+ ///
458+ /// # Examples
459+ ///
460+ /// Basic usage:
461+ ///
462+ /// ```
463+ /// #![feature(titlecase)]
464+ /// let s = "HELLO";
465+ ///
466+ /// assert_eq!("Hello", s.word_to_titlecase());
467+ /// ```
468+ ///
469+ /// The first *cased* letter is uppercased:
470+ ///
471+ /// ```
472+ /// #![feature(titlecase)]
473+ /// let the_night_before_christmas = "'twas";
474+ ///
475+ /// assert_eq!("'Twas", the_night_before_christmas.word_to_titlecase());
476+ /// ```
477+ ///
478+ /// Languages without case are not changed:
479+ ///
480+ /// ```
481+ /// #![feature(titlecase)]
482+ /// let new_year = "农历新年";
483+ ///
484+ /// assert_eq!(new_year, new_year.word_to_titlecase());
485+ /// ```
486+ ///
487+ /// Georgian uppercase ("Mtavruli") letters are not used in titlecase:
488+ ///
489+ /// ```
490+ /// #![feature(titlecase)]
491+ /// let georgian = "ერთობაშია";
492+ ///
493+ /// assert_eq!(georgian, georgian.word_to_titlecase());
494+ /// ```
495+ ///
496+ /// No word segmentation is performed,
497+ /// so only the first cased letter in the whole string gets uppercased:
498+ ///
499+ /// ```
500+ /// #![feature(titlecase)]
501+ /// let blazingly_fast = "ferris and I";
502+ ///
503+ /// assert_eq!("Ferris and i", blazingly_fast.word_to_titlecase());
504+ /// ```
505+ ///
506+ /// Tricky examples, with sigma:
507+ ///
508+ /// ```
509+ /// #![feature(titlecase)]
510+ /// let odysseus = "ὈΔΥΣΣΕΎΣ";
511+ ///
512+ /// assert_eq!("Ὀδυσσεύς", odysseus.word_to_titlecase());
513+ ///
514+ /// let odysseus_king_of_ithaca = "Ο ΟΔΥΣΣΈΑΣ ΒΑΣΙΛΙΆΣ ΤΗΣ ΙΘΆΚΗΣ";
515+ ///
516+ /// assert_eq!("Ο οδυσσέας βασιλιάς της ιθάκης", odysseus_king_of_ithaca.word_to_titlecase());
517+ /// ```
518+ #[ cfg( not( no_global_oom_handling) ) ]
519+ #[ rustc_allow_incoherent_impl]
520+ #[ must_use = "this returns the titlecase word as a new String, \
521+ without modifying the original"]
522+ #[ unstable( feature = "titlecase" , issue = "153892" ) ]
523+ pub fn word_to_titlecase ( & self ) -> String {
524+ // FIXME: add ASCII fast path
525+
526+ let mut s = String :: with_capacity ( self . len ( ) ) ;
527+ let mut chars = self . char_indices ( ) ;
528+
529+ ' until_first_cased_char: for ( _, c) in chars. by_ref ( ) {
530+ if c. is_cased ( ) {
531+ s. extend ( c. to_titlecase ( ) ) ;
532+ break ' until_first_cased_char;
533+ } else {
534+ s. push ( c) ;
535+ }
425536 }
426537
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 ,
538+ for ( i, c) in chars {
539+ if c == 'Σ' {
540+ // Σ maps to σ, except at the end of a word where it maps to ς.
541+ // This is the only conditional (contextual) but language-independent mapping
542+ // in `SpecialCasing.txt`,
543+ // so hard-code it rather than have a generic "condition" mechanism.
544+ // See https://github.com/rust-lang/rust/issues/26035
545+ let sigma_lowercase = map_uppercase_sigma ( self , i) ;
546+ s. push ( sigma_lowercase) ;
547+ } else {
548+ match conversions:: to_lower ( c) {
549+ [ a, '\0' , _] => s. push ( a) ,
550+ [ a, b, '\0' ] => {
551+ s. push ( a) ;
552+ s. push ( b) ;
553+ }
554+ [ a, b, c] => {
555+ s. push ( a) ;
556+ s. push ( b) ;
557+ s. push ( c) ;
558+ }
559+ }
431560 }
432561 }
562+
563+ s
433564 }
434565
435566 /// Returns the uppercase equivalent of this string slice, as a new [`String`].
0 commit comments