@@ -208,6 +208,23 @@ where
208208 result
209209}
210210
211+ /// Helper for final sigma lowercase
212+ #[ cfg( not( no_global_oom_handling) ) ]
213+ fn map_uppercase_sigma ( from : & str , i : usize ) -> char {
214+ fn case_ignorable_then_cased < I : Iterator < Item = char > > ( iter : I ) -> bool {
215+ match iter. skip_while ( |& c| c. is_case_ignorable ( ) ) . next ( ) {
216+ Some ( c) => c. is_cased ( ) ,
217+ None => false ,
218+ }
219+ }
220+
221+ // See https://www.unicode.org/versions/latest/core-spec/chapter-3/#G54277
222+ // for the definition of `Final_Sigma`.
223+ let is_word_final = case_ignorable_then_cased ( from[ ..i] . chars ( ) . rev ( ) )
224+ && !case_ignorable_then_cased ( from[ i + const { 'Σ' . len_utf8 ( ) } ..] . chars ( ) ) ;
225+ if is_word_final { 'ς' } else { 'σ' }
226+ }
227+
211228#[ stable( feature = "rust1" , since = "1.0.0" ) ]
212229impl Borrow < str > for String {
213230 #[ inline]
@@ -368,20 +385,20 @@ impl str {
368385 ///
369386 /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent
370387 /// casing of Greek sigma. However, like that method, it does not handle locale-specific
371- /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation
388+ /// casing, like Turkish and Azeri I/ı/İ/i. See its documentation
372389 /// for more information.
373390 ///
374391 /// # Examples
375392 ///
376393 /// Basic usage:
377394 ///
378395 /// ```
379- /// let s = "HELLO";
396+ /// let s = "HELLO WORLD ";
380397 ///
381- /// assert_eq!("hello", s.to_lowercase());
398+ /// assert_eq!("hello world ", s.to_lowercase());
382399 /// ```
383400 ///
384- /// A tricky example , with sigma:
401+ /// Tricky examples , with sigma:
385402 ///
386403 /// ```
387404 /// let sigma = "Σ";
@@ -392,6 +409,10 @@ impl str {
392409 /// let odysseus = "ὈΔΥΣΣΕΎΣ";
393410 ///
394411 /// assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
412+ ///
413+ /// let odysseus_king_of_ithaca = "Ο ΟΔΥΣΣΈΑΣ ΒΑΣΙΛΙΆΣ ΤΗΣ ΙΘΆΚΗΣ";
414+ ///
415+ /// assert_eq!("ο οδυσσέας βασιλιάς της ιθάκης", odysseus_king_of_ithaca.to_lowercase());
395416 /// ```
396417 ///
397418 /// Languages without case are not changed:
@@ -438,21 +459,136 @@ impl str {
438459 }
439460 }
440461 return s;
462+ }
441463
442- fn map_uppercase_sigma ( from : & str , i : usize ) -> char {
443- // See https://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G33992
444- // for the definition of `Final_Sigma`.
445- let is_word_final = case_ignorable_then_cased ( from[ ..i] . chars ( ) . rev ( ) )
446- && !case_ignorable_then_cased ( from[ i + const { 'Σ' . len_utf8 ( ) } ..] . chars ( ) ) ;
447- if is_word_final { 'ς' } else { 'σ' }
464+ /// Returns the titlecase equivalent of this string slice,
465+ /// which is assumed to represent a single word,
466+ /// as a new [`String`].
467+ ///
468+ /// Essentially, this consists of uppercasing the first cased letter
469+ /// (with [`char::to_titlecase()`]), and lowercasing everything that follows.
470+ ///
471+ /// 'Titlecase' is defined according to the terms of
472+ /// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34082)
473+ /// of the Unicode standard.
474+ ///
475+ /// Since some characters can expand into multiple characters when changing
476+ /// the case, this function returns a [`String`] instead of modifying the
477+ /// parameter in-place.
478+ ///
479+ /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent
480+ /// casing of Greek sigma. However, like that method, it does not handle locale-specific
481+ /// casing, like Turkish and Azeri I/ı/İ/i. See its documentation
482+ /// for more information.
483+ ///
484+ /// This method does not perform any kind of word segmentation.
485+ ///
486+ /// # Examples
487+ ///
488+ /// Basic usage:
489+ ///
490+ /// ```
491+ /// #![feature(titlecase)]
492+ /// let s = "HELLO WORLD";
493+ ///
494+ /// assert_eq!("Hello world", s.word_to_titlecase());
495+ /// ```
496+ ///
497+ /// The first *cased* letter is uppercased:
498+ ///
499+ /// ```
500+ /// #![feature(titlecase)]
501+ /// let the_night_before_christmas = "'twas";
502+ ///
503+ /// assert_eq!("'Twas", the_night_before_christmas.word_to_titlecase());
504+ /// ```
505+ ///
506+ /// Languages without case are not changed:
507+ ///
508+ /// ```
509+ /// #![feature(titlecase)]
510+ /// let new_year = "农历新年";
511+ ///
512+ /// assert_eq!(new_year, new_year.word_to_titlecase());
513+ /// ```
514+ ///
515+ /// Georgian uppercase ("Mtavruli") letters are not used in titlecase:
516+ ///
517+ /// ```
518+ /// #![feature(titlecase)]
519+ /// let georgian = "ერთობაშია";
520+ ///
521+ /// assert_eq!(georgian, georgian.word_to_titlecase());
522+ /// ```
523+ ///
524+ /// No word segmentation is performed,
525+ /// so only the first cased letter in the whole string gets uppercased:
526+ ///
527+ /// ```
528+ /// #![feature(titlecase)]
529+ /// let blazingly_fast = "ferris and I";
530+ ///
531+ /// assert_eq!("Ferris and i", blazingly_fast.word_to_titlecase());
532+ /// ```
533+ ///
534+ /// Tricky examples, with sigma:
535+ ///
536+ /// ```
537+ /// #![feature(titlecase)]
538+ /// let odysseus = "ὈΔΥΣΣΕΎΣ";
539+ ///
540+ /// assert_eq!("Ὀδυσσεύς", odysseus.word_to_titlecase());
541+ ///
542+ /// let odysseus_king_of_ithaca = "Ο ΟΔΥΣΣΈΑΣ ΒΑΣΙΛΙΆΣ ΤΗΣ ΙΘΆΚΗΣ";
543+ ///
544+ /// assert_eq!("Ο οδυσσέας βασιλιάς της ιθάκης", odysseus_king_of_ithaca.word_to_titlecase());
545+ /// ```
546+ #[ cfg( not( no_global_oom_handling) ) ]
547+ #[ rustc_allow_incoherent_impl]
548+ #[ must_use = "this returns the titlecase word as a new String, \
549+ without modifying the original"]
550+ #[ unstable( feature = "titlecase" , issue = "153892" ) ]
551+ pub fn word_to_titlecase ( & self ) -> String {
552+ // FIXME: add ASCII fast path
553+
554+ let mut s = String :: with_capacity ( self . len ( ) ) ;
555+ let mut chars = self . char_indices ( ) ;
556+
557+ ' until_first_cased_char: for ( _, c) in chars. by_ref ( ) {
558+ if c. is_cased ( ) {
559+ s. extend ( c. to_titlecase ( ) ) ;
560+ break ' until_first_cased_char;
561+ } else {
562+ s. push ( c) ;
563+ }
448564 }
449565
450- fn case_ignorable_then_cased < I : Iterator < Item = char > > ( iter : I ) -> bool {
451- match iter. skip_while ( |& c| c. is_case_ignorable ( ) ) . next ( ) {
452- Some ( c) => c. is_cased ( ) ,
453- None => false ,
566+ for ( i, c) in chars {
567+ if c == 'Σ' {
568+ // Σ maps to σ, except at the end of a word where it maps to ς.
569+ // This is the only conditional (contextual) but language-independent mapping
570+ // in `SpecialCasing.txt`,
571+ // so hard-code it rather than have a generic "condition" mechanism.
572+ // See https://github.com/rust-lang/rust/issues/26035
573+ let sigma_lowercase = map_uppercase_sigma ( self , i) ;
574+ s. push ( sigma_lowercase) ;
575+ } else {
576+ match conversions:: to_lower ( c) {
577+ [ a, '\0' , _] => s. push ( a) ,
578+ [ a, b, '\0' ] => {
579+ s. push ( a) ;
580+ s. push ( b) ;
581+ }
582+ [ a, b, c] => {
583+ s. push ( a) ;
584+ s. push ( b) ;
585+ s. push ( c) ;
586+ }
587+ }
454588 }
455589 }
590+
591+ s
456592 }
457593
458594 /// Returns the uppercase equivalent of this string slice, as a new [`String`].
@@ -474,9 +610,9 @@ impl str {
474610 /// Basic usage:
475611 ///
476612 /// ```
477- /// let s = "hello";
613+ /// let s = "hello world ";
478614 ///
479- /// assert_eq!("HELLO", s.to_uppercase());
615+ /// assert_eq!("HELLO WORLD ", s.to_uppercase());
480616 /// ```
481617 ///
482618 /// Scripts without case are not changed:
0 commit comments