diff --git a/README.md b/README.md index 91fdd2f..dc089a6 100644 --- a/README.md +++ b/README.md @@ -14,31 +14,31 @@ A small library for selecting the best match for user's preferred locales from a use locale_match::bcp47::best_matching_locale; -let available_locales = vec!["en-US", "en-GB", "ru-UA", "fr-FR", "it"]; -let user_locales = vec!["ru-RU", "ru", "en-US", "en"]; +let available_locales = ["en-US", "en-GB", "ru-UA", "fr-FR", "it"]; +let user_locales = ["ru-RU", "ru", "en-US", "en"]; -let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +let best_match = best_matching_locale(available_locales, user_locales); // "ru-UA" is the best match for the highest-priority user locale "ru-RU" -assert_eq!(best_match, Some("ru-UA".to_string())); +assert_eq!(best_match, Some("ru-UA")); -let available_locales = vec!["en", "pt-BR", "pt-PT", "es"]; -let user_locales = vec!["pt", "en"]; +let available_locales = ["en", "pt-BR", "pt-PT", "es"]; +let user_locales = ["pt", "en"]; -let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +let best_match = best_matching_locale(available_locales, user_locales); // "pt-BR" is the first best match for the highest-priority user locale "pt" -assert_eq!(best_match, Some("pt-BR".to_string())); +assert_eq!(best_match, Some("pt-BR")); -let available_locales = vec!["zh", "zh-cmn", "zh-cmn-Hans"]; -let user_locales = vec!["zh-Hans"]; +let available_locales = ["zh", "zh-cmn", "zh-cmn-Hans"]; +let user_locales = ["zh-Hans"]; -let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +let best_match = best_matching_locale(available_locales, user_locales); // Empty extended language subtag in "zh-Hans" matches any extended language, e.g. "cmn" -assert_eq!(best_match, Some("zh-cmn-Hans".to_string())); +assert_eq!(best_match, Some("zh-cmn-Hans")); ``` ### POSIX @@ -47,28 +47,28 @@ assert_eq!(best_match, Some("zh-cmn-Hans".to_string())); use locale_match::posix::best_matching_locale; -let available_locales = vec!["en_US", "en_GB", "ru_UA", "fr_FR", "it"]; -let user_locales = vec!["ru_RU", "ru", "en_US", "en"]; +let available_locales = ["en_US", "en_GB", "ru_UA", "fr_FR", "it"]; +let user_locales = ["ru_RU", "ru", "en_US", "en"]; -let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +let best_match = best_matching_locale(available_locales, user_locales); // "ru_UA" is the best match for the highest-priority user locale "ru_RU" assert_eq!(best_match, Some("ru_UA")); -let available_locales = vec!["en", "pt_BR", "pt_PT", "es"]; -let user_locales = vec!["pt", "en"]; +let available_locales = ["en", "pt_BR", "pt_PT", "es"]; +let user_locales = ["pt", "en"]; -let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +let best_match = best_matching_locale(available_locales, user_locales); // "pt_BR" is the first best match for the highest-priority user locale "pt" assert_eq!(best_match, Some("pt_BR")); -let available_locales = vec!["fr", "fr_FR", "fr_CA.UTF-8"]; -let user_locales = vec!["fr.UTF-8"]; +let available_locales = ["fr", "fr_FR", "fr_CA.UTF-8"]; +let user_locales = ["fr.UTF-8"]; -let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +let best_match = best_matching_locale(available_locales, user_locales); // Empty territory in "fr.UTF-8" matches any territory, e.g. "CA" assert_eq!(best_match, Some("fr_CA.UTF-8")); diff --git a/src/bcp47.rs b/src/bcp47.rs index 61b7528..ace98a7 100644 --- a/src/bcp47.rs +++ b/src/bcp47.rs @@ -53,7 +53,7 @@ use language_tags::LanguageTag; /// If no match is found, [`None`] is returned. /// /// The returned locale is guaranteed to EXACTLY match one of the available locales. -/// For example, `best_matching_locale(&["EN"].iter(), &["en"].iter())` will return `Some("EN")`. +/// For example, `best_matching_locale(["EN"], ["en"])` will return `Some("EN")`. /// /// # Examples /// @@ -61,48 +61,49 @@ use language_tags::LanguageTag; /// use locale_match::bcp47::best_matching_locale; /// /// -/// let available_locales = vec!["en-US", "en-GB", "ru-UA", "fr-FR", "it"]; -/// let user_locales = vec!["ru-RU", "ru", "en-US", "en"]; +/// let available_locales = ["en-US", "en-GB", "ru-UA", "fr-FR", "it"]; +/// let user_locales = ["ru-RU", "ru", "en-US", "en"]; /// -/// let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +/// let best_match = best_matching_locale(available_locales, user_locales); /// /// // "ru-UA" is the best match for the highest-priority user locale "ru-RU" -/// assert_eq!(best_match, Some("ru-UA".to_string())); +/// assert_eq!(best_match, Some("ru-UA")); /// /// -/// let available_locales = vec!["en", "pt-BR", "pt-PT", "es"]; -/// let user_locales = vec!["pt", "en"]; +/// let available_locales = ["en", "pt-BR", "pt-PT", "es"]; +/// let user_locales = ["pt", "en"]; /// -/// let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +/// let best_match = best_matching_locale(available_locales, user_locales); /// /// // "pt-BR" is the first best match for the highest-priority user locale "pt" -/// assert_eq!(best_match, Some("pt-BR".to_string())); +/// assert_eq!(best_match, Some("pt-BR")); /// /// -/// let available_locales = vec!["zh", "zh-cmn", "zh-cmn-Hans"]; -/// let user_locales = vec!["zh-Hans"]; +/// let available_locales = ["zh", "zh-cmn", "zh-cmn-Hans"]; +/// let user_locales = ["zh-Hans"]; /// -/// let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +/// let best_match = best_matching_locale(available_locales, user_locales); /// /// // Empty extended language subtag in "zh-Hans" matches any extended language, e.g. "cmn" -/// assert_eq!(best_match, Some("zh-cmn-Hans".to_string())); +/// assert_eq!(best_match, Some("zh-cmn-Hans")); /// ``` -pub fn best_matching_locale(available_locales: impl Iterator, user_locales: impl Iterator) -> Option +pub fn best_matching_locale(available_locales: impl IntoIterator, user_locales: impl IntoIterator) -> Option where T1: AsRef, T2: AsRef { - let available_tags = available_locales + let available_tags = available_locales.into_iter() .filter_map(|l| LanguageTag::parse(l.as_ref()).ok().map(|tag| (l, tag))) - .collect::>(); + .collect::>(); - user_locales + user_locales.into_iter() .filter_map(|locale| LanguageTag::parse(locale.as_ref()).ok()) .find_map(|user_tag| available_tags.iter() + .enumerate() .rev() // For max_by_key to return the first tag with max score - .filter(|(_, aval_tag)| aval_tag.primary_language() == user_tag.primary_language()) - .max_by_key(|(_, aval_tag)| { + .filter(|(_, (_, aval_tag))| aval_tag.primary_language() == user_tag.primary_language()) + .max_by_key(|(_, (_, aval_tag))| { let mut score = 0; for (aval, user, weight) in [ (aval_tag.extended_language(), user_tag.extended_language(), 32), @@ -120,8 +121,9 @@ where } score }) + .map(|(i, _)| i) ) - .map(|(aval_locale, _)| aval_locale.as_ref().to_string()) + .map(|i| available_tags.into_iter().nth(i).unwrap().0) } #[cfg(test)] @@ -131,98 +133,124 @@ mod tests { #[test] fn test_best_matching_locale() { - fn assert_best_match(available_locales: &[&str], user_locales: &[&str], expected: Option<&str>) { - assert_eq!(best_matching_locale(available_locales.iter(), user_locales.iter()).as_deref(), expected); + fn case(available_locales: impl IntoIterator, user_locales: impl IntoIterator, expected: Option) + where + T1: AsRef + PartialEq + std::fmt::Debug, + T2: AsRef + { + assert_eq!(best_matching_locale(available_locales, user_locales), expected); } // One best match - assert_best_match(&["en-US", "ru-RU"], &["ru", "en"], Some("ru-RU")); - assert_best_match(&["en-US", "ru-RU"], &["en", "ru"], Some("en-US")); - assert_best_match(&["en-US", "en-GB", "ru-UA", "fr-FR", "it"], &["ru-RU", "ru", "en-US", "en"], Some("ru-UA")); - assert_best_match(&["ru-RU", "sq-AL", "eu-ES"], &["en-US", "en", "sq-XK", "sq"], Some("sq-AL")); - assert_best_match(&["lv-LV", "ru-RU", "lt-LT", "mn-MN", "ku-TR"], &["fr", "fr-FR", "ml", "si", "id", "ku-IQ"], Some("ku-TR")); - assert_best_match(&["st-LS", "sn-ZW", "en-US"], &["zu-ZA", "st-ZA", "en"], Some("st-LS")); + case(["en-US", "ru-RU"], ["ru", "en"], Some("ru-RU")); + case(["en-US", "ru-RU"], ["en", "ru"], Some("en-US")); + case(["en-US", "en-GB", "ru-UA", "fr-FR", "it"], ["ru-RU", "ru", "en-US", "en"], Some("ru-UA")); + case(["ru-RU", "sq-AL", "eu-ES"], ["en-US", "en", "sq-XK", "sq"], Some("sq-AL")); + case(["lv-LV", "ru-RU", "lt-LT", "mn-MN", "ku-TR"], ["fr", "fr-FR", "ml", "si", "id", "ku-IQ"], Some("ku-TR")); + case(["st-LS", "sn-ZW", "en-US"], ["zu-ZA", "st-ZA", "en"], Some("st-LS")); // Multiple best matches - assert_best_match(&["en-US", "en-GB", "ru-UA", "fr-FR", "it"], &["en-US", "en", "ru-RU", "ru"], Some("en-US")); - assert_best_match(&["en", "pt-BR", "pt-PT", "es"], &["pt", "en"], Some("pt-BR")); - assert_best_match(&["ku-TR", "ku-IQ", "ku-IR"], &["ku", "en"], Some("ku-TR")); - assert_best_match(&["en-US", "ru-RU", "mn-CN", "sn-ZW", "en", "ru", "mn-MN", "sn"], &["mn", "ru", "en", "sn"], Some("mn-CN")); + case(["en-US", "en-GB", "ru-UA", "fr-FR", "it"], ["en-US", "en", "ru-RU", "ru"], Some("en-US")); + case(["en", "pt-BR", "pt-PT", "es"], ["pt", "en"], Some("pt-BR")); + case(["ku-TR", "ku-IQ", "ku-IR"], ["ku", "en"], Some("ku-TR")); + case(["en-US", "ru-RU", "mn-CN", "sn-ZW", "en", "ru", "mn-MN", "sn"], ["mn", "ru", "en", "sn"], Some("mn-CN")); // Identical - assert_best_match(&["en"], &["en"], Some("en")); - assert_best_match(&["en-US"], &["en-US"], Some("en-US")); - assert_best_match(&["en-US", "ru-RU"], &["en-US", "ru-RU"], Some("en-US")); - assert_best_match(&["st-LS", "sn-ZW", "en-US"], &["st-LS", "sn-ZW", "en-US"], Some("st-LS")); - assert_best_match(&["ku-TR", "ku-IQ", "ku-IR"], &["ku-TR", "ku-IQ", "ku-IR"], Some("ku-TR")); - assert_best_match(&["lv-LV", "ru-RU", "lt-LT", "mn-MN", "ku-TR"], &["lv-LV", "ru-RU", "lt-LT", "mn-MN", "ku-TR"], Some("lv-LV")); + case(["en"], ["en"], Some("en")); + case(["en-US"], ["en-US"], Some("en-US")); + case(["en-US", "ru-RU"], ["en-US", "ru-RU"], Some("en-US")); + case(["st-LS", "sn-ZW", "en-US"], ["st-LS", "sn-ZW", "en-US"], Some("st-LS")); + case(["ku-TR", "ku-IQ", "ku-IR"], ["ku-TR", "ku-IQ", "ku-IR"], Some("ku-TR")); + case(["lv-LV", "ru-RU", "lt-LT", "mn-MN", "ku-TR"], ["lv-LV", "ru-RU", "lt-LT", "mn-MN", "ku-TR"], Some("lv-LV")); // One available locale - assert_best_match(&["kk"], &["en", "en-US", "fr-FR", "fr", "it", "pt", "ru-RU", "es-ES", "kk-KZ"], Some("kk")); + case(["kk"], ["en", "en-US", "fr-FR", "fr", "it", "pt", "ru-RU", "es-ES", "kk-KZ"], Some("kk")); // One user locale - assert_best_match(&["en", "en-US", "fr-FR", "fr", "it", "pt", "ru-RU", "es-ES", "kk-KZ", "pt"], &["pt-PT"], Some("pt")); + case(["en", "en-US", "fr-FR", "fr", "it", "pt", "ru-RU", "es-ES", "kk-KZ", "pt"], ["pt-PT"], Some("pt")); // Not found - assert_best_match(&["en", "en-US", "fr-FR", "fr", "it", "pt", "es-ES", "kk-KZ", "pt"], &["ru"], None); - assert_best_match(&["en", "en-US", "fr-FR", "fr", "pt"], &["id"], None); - assert_best_match(&["ru", "be", "uk", "kk"], &["en"], None); + case(["en", "en-US", "fr-FR", "fr", "it", "pt", "es-ES", "kk-KZ", "pt"], ["ru"], None); + case(["en", "en-US", "fr-FR", "fr", "pt"], ["id"], None); + case(["ru", "be", "uk", "kk"], ["en"], None); // Empty available locales - assert_best_match(&[], &["en", "fr", "it", "pt"], None); + case(&[] as &[&str], &["en", "fr", "it", "pt"], None); // Empty user locales - assert_best_match(&["en", "fr", "it", "pt"], &[], None); + case(["en", "fr", "it", "pt"], &[] as &[&str], None); // Both lists empty - assert_best_match(&[], &[], None); + case(&[] as &[&str], &[] as &[&str], None); // More subtags - assert_best_match(&["zh", "zh-cmn", "zh-cmn-Hans"], &["zh-cmn-SG"], Some("zh-cmn")); - assert_best_match(&["zh", "zh-cmn", "zh-cmn-Hans", "zh-cmn-Hans-SG"], &["zh-cmn-SG"], Some("zh-cmn-Hans-SG")); - assert_best_match(&["zh", "zh-cmn", "zh-cmn-Hans-SG"], &["zh-Hans"], Some("zh-cmn-Hans-SG")); - assert_best_match(&["zh", "zh-cmn", "zh-cmn-Hans", "zh-cmn-Hans-SG"], &["zh-Hans"], Some("zh-cmn-Hans")); - assert_best_match(&["zh", "zh-cmn", "zh-cmn-Hans", "zh-cmn-Hans-SG"], &["zh-SG"], Some("zh-cmn-Hans-SG")); + case(["zh", "zh-cmn", "zh-cmn-Hans"], ["zh-cmn-SG"], Some("zh-cmn")); + case(["zh", "zh-cmn", "zh-cmn-Hans", "zh-cmn-Hans-SG"], ["zh-cmn-SG"], Some("zh-cmn-Hans-SG")); + case(["zh", "zh-cmn", "zh-cmn-Hans-SG"], ["zh-Hans"], Some("zh-cmn-Hans-SG")); + case(["zh", "zh-cmn", "zh-cmn-Hans", "zh-cmn-Hans-SG"], ["zh-Hans"], Some("zh-cmn-Hans")); + case(["zh", "zh-cmn", "zh-cmn-Hans", "zh-cmn-Hans-SG"], ["zh-SG"], Some("zh-cmn-Hans-SG")); // Extensions - assert_best_match(&["zh", "he"], &["he-IL-u-ca-hebrew-tz-jeruslm", "zh"], Some("he")); - assert_best_match(&["zh", "he-IL-u-ca-hebrew-tz-jeruslm-nu-latn"], &["he", "zh"], Some("he-IL-u-ca-hebrew-tz-jeruslm-nu-latn")); - assert_best_match(&["ar-u-nu-latn", "ar"], &["ar-u-no-latn", "ar", "en-US", "en"], Some("ar-u-nu-latn")); - assert_best_match(&["fr-FR-u-em-text", "gsw-u-em-emoji"], &["gsw-u-em-text"], Some("gsw-u-em-emoji")); + case(["zh", "he"], ["he-IL-u-ca-hebrew-tz-jeruslm", "zh"], Some("he")); + case(["zh", "he-IL-u-ca-hebrew-tz-jeruslm-nu-latn"], ["he", "zh"], Some("he-IL-u-ca-hebrew-tz-jeruslm-nu-latn")); + case(["ar-u-nu-latn", "ar"], ["ar-u-no-latn", "ar", "en-US", "en"], Some("ar-u-nu-latn")); + case(["fr-FR-u-em-text", "gsw-u-em-emoji"], ["gsw-u-em-text"], Some("gsw-u-em-emoji")); // Malformed - assert_best_match(&["en-US-SUS-BUS-VUS-GUS"], &["en"], None); - assert_best_match(&["en-abcdefghijklmnopqrstuvwxyz"], &["en"], None); - assert_best_match(&["ru-ЖЖЯЯ"], &["ru"], None); - assert_best_match(&["ru--"], &["ru"], None); - assert_best_match(&[" en"], &["en"], None); - assert_best_match(&["", "@", "!!!", "721345"], &["en", "", "@", "!!!", "721345"], None); + case(["en-US-SUS-BUS-VUS-GUS"], ["en"], None); + case(["en-abcdefghijklmnopqrstuvwxyz"], ["en"], None); + case(["ru-ЖЖЯЯ"], ["ru"], None); + case(["ru--"], ["ru"], None); + case([" en"], ["en"], None); + case(["", "@", "!!!", "721345"], ["en", "", "@", "!!!", "721345"], None); // Repeating - assert_best_match(&["en", "en", "en", "en"], &["ru-RU", "ru", "en-US", "en"], Some("en")); - assert_best_match(&["en-US", "en-GB", "ru-UA", "fr-FR", "it"], &["kk", "ru", "pt", "ru"], Some("ru-UA")); + case(["en", "en", "en", "en"], ["ru-RU", "ru", "en-US", "en"], Some("en")); + case(["en-US", "en-GB", "ru-UA", "fr-FR", "it"], ["kk", "ru", "pt", "ru"], Some("ru-UA")); // Littered - assert_best_match(&["!!!!!!", "qwydgn12i6i", "ЖЖяяЖяЬЬЬ", "en-US", "!*&^^&*", "qweqweqweqwe-qweqwe", "ru-RU", "@@", "@"], &["ru", "en"], Some("ru-RU")); - assert_best_match(&["", "", "", "zh", "", "", "", "", "", "he", "", ""], &["he-IL-u-ca-hebrew-tz-jeruslm", "", "", "zh"], Some("he")); - assert_best_match(&["bla-!@#", "12345", "en-US", "en-GB", "ru-UA", "fr-FR", "it"], &["bla-!@#", "12345", "en-US", "en", "ru-RU", "ru"], Some("en-US")); + case(["!!!!!!", "qwydgn12i6i", "ЖЖяяЖяЬЬЬ", "en-US", "!*&^^&*", "qweqweqweqwe-qweqwe", "ru-RU", "@@", "@"], ["ru", "en"], Some("ru-RU")); + case(["", "", "", "zh", "", "", "", "", "", "he", "", ""], ["he-IL-u-ca-hebrew-tz-jeruslm", "", "", "zh"], Some("he")); + case(["bla-!@#", "12345", "en-US", "en-GB", "ru-UA", "fr-FR", "it"], ["bla-!@#", "12345", "en-US", "en", "ru-RU", "ru"], Some("en-US")); // Special characters - assert_best_match(&["\0", "\x01", "\x02"], &["\0", "\x01", "\x02"], None); - assert_best_match(&["en\0"], &["en\0", "en-US", "en"], None); - assert_best_match(&["sq\0", "ru-RU", "sq-AL", "eu-ES"], &["en-US", "en", "sq-XK", "sq"], Some("sq-AL")); - assert_best_match(&["en-US", "ru-RU\x03"], &["ru", "en"], Some("en-US")); - assert_best_match(&["\0", "\x01\x02\x03\x04", "sq\0", "ru-RU", "sq-AL", "eu-ES"], &["en-US", "\x06", "en", "sq-XK", "sq", "\0"], Some("sq-AL")); - assert_best_match(&["en-US", "ru-RU\x03", "\x09\x09\x09\x09\x09", "\x0a\x09\x08\x07\x01\x00"], &["\x01", "\x02", "\x03", "\x04", "ru", "en"], Some("en-US")); + case(["\0", "\x01", "\x02"], ["\0", "\x01", "\x02"], None); + case(["en\0"], ["en\0", "en-US", "en"], None); + case(["sq\0", "ru-RU", "sq-AL", "eu-ES"], ["en-US", "en", "sq-XK", "sq"], Some("sq-AL")); + case(["en-US", "ru-RU\x03"], ["ru", "en"], Some("en-US")); + case(["\0", "\x01\x02\x03\x04", "sq\0", "ru-RU", "sq-AL", "eu-ES"], ["en-US", "\x06", "en", "sq-XK", "sq", "\0"], Some("sq-AL")); + case(["en-US", "ru-RU\x03", "\x09\x09\x09\x09\x09", "\x0a\x09\x08\x07\x01\x00"], ["\x01", "\x02", "\x03", "\x04", "ru", "en"], Some("en-US")); // Various letter cases - assert_best_match(&["EN"], &["en"], Some("EN")); - assert_best_match(&["En"], &["EN"], Some("En")); - assert_best_match(&["Ru-rU"], &["en", "ru"], Some("Ru-rU")); - assert_best_match(&["rU-rU"], &["en", "Ru"], Some("rU-rU")); - assert_best_match(&["zh", "zh-cmn", "zH-cMn-hANS-Sg"], &["zh-Hans"], Some("zH-cMn-hANS-Sg")); - assert_best_match(&["zh", "zh-cmn", "zH-cMn-hANS-Sg"], &["ZH-HANS"], Some("zH-cMn-hANS-Sg")); - assert_best_match(&["zh", "he-IL-u-ca-HEBREW-tz-Jeruslm-nu-LaTn"], &["he", "zh"], Some("he-IL-u-ca-HEBREW-tz-Jeruslm-nu-LaTn")); - assert_best_match(&["zh", "HE-il-u-cA-HeBrEw-tz-Jeruslm-nu-LaTN"], &["he", "zh"], Some("HE-il-u-cA-HeBrEw-tz-Jeruslm-nu-LaTN")); + case(["EN"], ["en"], Some("EN")); + case(["En"], ["EN"], Some("En")); + case(["Ru-rU"], ["en", "ru"], Some("Ru-rU")); + case(["rU-rU"], ["en", "Ru"], Some("rU-rU")); + case(["zh", "zh-cmn", "zH-cMn-hANS-Sg"], ["zh-Hans"], Some("zH-cMn-hANS-Sg")); + case(["zh", "zh-cmn", "zH-cMn-hANS-Sg"], ["ZH-HANS"], Some("zH-cMn-hANS-Sg")); + case(["zh", "he-IL-u-ca-HEBREW-tz-Jeruslm-nu-LaTn"], ["he", "zh"], Some("he-IL-u-ca-HEBREW-tz-Jeruslm-nu-LaTn")); + case(["zh", "HE-il-u-cA-HeBrEw-tz-Jeruslm-nu-LaTN"], ["he", "zh"], Some("HE-il-u-cA-HeBrEw-tz-Jeruslm-nu-LaTN")); + + // Various template parameter types + // &str and &&str + case(["en-US", "ru-RU"], ["ru", "en"], Some("ru-RU")); + case(&["en-US", "ru-RU"], ["ru", "en"], Some(&"ru-RU")); + case(["en-US", "ru-RU"], &["ru", "en"], Some("ru-RU")); + case(&["en-US", "ru-RU"], &["ru", "en"], Some(&"ru-RU")); + case([&"en-US", &"ru-RU"], ["ru", "en"], Some(&"ru-RU")); + // String and &String + case(["en-US".to_string(), "ru-RU".to_string()], ["ru", "en"], Some("ru-RU".to_string())); + case(&["en-US".to_string(), "ru-RU".to_string()], ["ru", "en"], Some(&"ru-RU".to_string())); + // Cow + use std::borrow::Cow; + case([Cow::Owned("en-US".to_string()), Cow::Borrowed("ru-RU")], ["ru", "en"], Some(Cow::Borrowed("ru-RU"))); + case([Cow::Borrowed("en-US"), Cow::Owned("ru-RU".to_string())], ["ru", "en"], Some(Cow::Owned("ru-RU".to_string()))); + // Rc and Arc + use std::rc::Rc; + use std::sync::Arc; + case([Rc::from("en-US"), Rc::from("ru-RU")], ["ru", "en"], Some(Rc::from("ru-RU"))); + case([Arc::from("en-US"), Arc::from("ru-RU")], ["ru", "en"], Some(Arc::from("ru-RU"))); + // Box + case([Box::from("en-US"), Box::from("ru-RU")], ["ru", "en"], Some(Box::from("ru-RU"))); } } \ No newline at end of file diff --git a/src/posix.rs b/src/posix.rs index 9604467..3731301 100644 --- a/src/posix.rs +++ b/src/posix.rs @@ -52,7 +52,7 @@ /// If no match is found, [`None`] is returned. /// /// The returned locale is guaranteed to EXACTLY match one of the available locales. -/// For example, `best_matching_locale(&["EN"].iter(), &["en"].iter())` will return `Some("EN")`. +/// For example, `best_matching_locale(["EN"], ["en"])` will return `Some("EN")`. /// /// # Examples /// @@ -60,53 +60,54 @@ /// use locale_match::posix::best_matching_locale; /// /// -/// let available_locales = vec!["en_US", "en_GB", "ru_UA", "fr_FR", "it"]; -/// let user_locales = vec!["ru_RU", "ru", "en_US", "en"]; +/// let available_locales = ["en_US", "en_GB", "ru_UA", "fr_FR", "it"]; +/// let user_locales = ["ru_RU", "ru", "en_US", "en"]; /// -/// let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +/// let best_match = best_matching_locale(available_locales, user_locales); /// /// // "ru_UA" is the best match for the highest-priority user locale "ru_RU" /// assert_eq!(best_match, Some("ru_UA")); /// /// -/// let available_locales = vec!["en", "pt_BR", "pt_PT", "es"]; -/// let user_locales = vec!["pt", "en"]; +/// let available_locales = ["en", "pt_BR", "pt_PT", "es"]; +/// let user_locales = ["pt", "en"]; /// -/// let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +/// let best_match = best_matching_locale(available_locales, user_locales); /// /// // "pt_BR" is the first best match for the highest-priority user locale "pt" /// assert_eq!(best_match, Some("pt_BR")); /// /// -/// let available_locales = vec!["fr", "fr_FR", "fr_CA.UTF-8"]; -/// let user_locales = vec!["fr.UTF-8"]; +/// let available_locales = ["fr", "fr_FR", "fr_CA.UTF-8"]; +/// let user_locales = ["fr.UTF-8"]; /// -/// let best_match = best_matching_locale(available_locales.iter(), user_locales.iter()); +/// let best_match = best_matching_locale(available_locales, user_locales); /// /// // Empty territory in "fr.UTF-8" matches any territory, e.g. "CA" /// assert_eq!(best_match, Some("fr_CA.UTF-8")); /// ``` -pub fn best_matching_locale<'a, 'b, T1, T2>(available_locales: impl Iterator, user_locales: impl Iterator) -> Option<&'a str> +pub fn best_matching_locale(available_locales: impl IntoIterator, user_locales: impl IntoIterator) -> Option where - T1: AsRef + 'a, - T2: AsRef + 'b + T1: AsRef, + T2: AsRef { - let available_parsed_locales = available_locales - .map(|l| PosixLocale::parse(l.as_ref())) - .collect::>(); + let available_parsed_locales = available_locales.into_iter() + .map(|l| PosixLocale::parse(l)) + .collect::>>(); - user_locales - .map(|locale| PosixLocale::parse(locale.as_ref())) + user_locales.into_iter() + .map(|locale| PosixLocale::parse(locale)) .find_map(|user_locale| available_parsed_locales.iter() + .enumerate() .rev() // For max_by_key to return the first locale with max score - .filter(|aval_locale| aval_locale.language.eq_ignore_ascii_case(user_locale.language)) - .max_by_key(|aval_locale| { + .filter(|(_, aval_locale)| aval_locale.language().eq_ignore_ascii_case(user_locale.language())) + .max_by_key(|(_, aval_locale)| { let mut score = 0; for (aval, user, weight) in [ - (aval_locale.territory, user_locale.territory, 4), - (aval_locale.codeset, user_locale.codeset, 2), - (aval_locale.modifier, user_locale.modifier, 1), + (aval_locale.territory(), user_locale.territory(), 4), + (aval_locale.codeset(), user_locale.codeset(), 2), + (aval_locale.modifier(), user_locale.modifier(), 1), ] { match (aval, user) { (Some(a), Some(u)) if a.eq_ignore_ascii_case(u) => score += weight, @@ -115,20 +116,20 @@ where } score }) + .map(|(i, _)| i) ) - .map(|aval_locale| aval_locale.locale) + .map(|i| available_parsed_locales.into_iter().nth(i).unwrap().into_inner()) } /// A POSIX locale as described in [The Open Group Base Specifications Issue 8 - 8. Environment Variables](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap08.html). -struct PosixLocale<'a> { - locale: &'a str, - language: &'a str, - territory: Option<&'a str>, - codeset: Option<&'a str>, - modifier: Option<&'a str>, +struct PosixLocale> { + locale: T, + language_end: usize, + territory_end: usize, + codeset_end: usize, } -impl<'a> PosixLocale<'a> { +impl> PosixLocale { const TERRITORY_DELIMITER: char = '_'; const CODESET_DELIMITER: char = '.'; const MODIFIER_DELIMITER: char = '@'; @@ -138,17 +139,32 @@ impl<'a> PosixLocale<'a> { /// The `locale` string should be in the form `language[_territory][.codeset][@modifier]`. /// /// The function does not perform any validation on the input string. - fn parse(locale: &'a str) -> Self { - let codeset_end = locale.find(Self::MODIFIER_DELIMITER).unwrap_or(locale.len()); - let territory_end = locale.find(Self::CODESET_DELIMITER).unwrap_or(codeset_end); - let language_end = locale.find(Self::TERRITORY_DELIMITER).unwrap_or(territory_end); - Self { - locale, - language: &locale[..language_end], - territory: locale.get(language_end + 1..territory_end), - codeset: locale.get(territory_end + 1..codeset_end), - modifier: locale.get(codeset_end + 1..) - } + fn parse(locale: T) -> Self { + let locale_ref = locale.as_ref(); + let codeset_end = locale_ref.find(Self::MODIFIER_DELIMITER).unwrap_or(locale_ref.len()); + let territory_end = locale_ref.find(Self::CODESET_DELIMITER).unwrap_or(codeset_end); + let language_end = locale_ref.find(Self::TERRITORY_DELIMITER).unwrap_or(territory_end); + Self { locale, language_end, territory_end, codeset_end } + } + + fn language(&self) -> &str { + &self.locale.as_ref()[0..self.language_end] + } + + fn territory(&self) -> Option<&str> { + self.locale.as_ref().get(self.language_end + 1..self.territory_end) + } + + fn codeset(&self) -> Option<&str> { + self.locale.as_ref().get(self.territory_end + 1..self.codeset_end) + } + + fn modifier(&self) -> Option<&str> { + self.locale.as_ref().get(self.codeset_end + 1..) + } + + fn into_inner(self) -> T { + self.locale } } @@ -159,195 +175,221 @@ mod tests { #[test] fn test_best_matching_locale() { - fn assert_best_match(available_locales: &[&str], user_locales: &[&str], expected: Option<&str>) { - assert_eq!(best_matching_locale(available_locales.iter(), user_locales.iter()).as_deref(), expected); + fn case(available_locales: impl IntoIterator, user_locales: impl IntoIterator, expected: Option) + where + T1: AsRef + PartialEq + std::fmt::Debug, + T2: AsRef + { + assert_eq!(best_matching_locale(available_locales, user_locales), expected); } // One best match - assert_best_match(&["en_US", "ru_RU"], &["ru", "en"], Some("ru_RU")); - assert_best_match(&["en_US", "ru_RU"], &["en", "ru"], Some("en_US")); - assert_best_match(&["en_US", "en_GB", "ru_UA", "fr_FR", "it"], &["ru_RU", "ru", "en_US", "en"], Some("ru_UA")); - assert_best_match(&["ru_RU", "sq_AL", "eu_ES"], &["en_US", "en", "sq_XK", "sq"], Some("sq_AL")); - assert_best_match(&["lv_LV", "ru_RU", "lt_LT", "mn_MN", "ku_TR"], &["fr", "fr_FR", "ml", "si", "id", "ku_IQ"], Some("ku_TR")); - assert_best_match(&["st_LS", "sn_ZW", "en_US"], &["zu_ZA", "st_ZA", "en"], Some("st_LS")); + case(["en_US", "ru_RU"], ["ru", "en"], Some("ru_RU")); + case(["en_US", "ru_RU"], ["en", "ru"], Some("en_US")); + case(["en_US", "en_GB", "ru_UA", "fr_FR", "it"], ["ru_RU", "ru", "en_US", "en"], Some("ru_UA")); + case(["ru_RU", "sq_AL", "eu_ES"], ["en_US", "en", "sq_XK", "sq"], Some("sq_AL")); + case(["lv_LV", "ru_RU", "lt_LT", "mn_MN", "ku_TR"], ["fr", "fr_FR", "ml", "si", "id", "ku_IQ"], Some("ku_TR")); + case(["st_LS", "sn_ZW", "en_US"], ["zu_ZA", "st_ZA", "en"], Some("st_LS")); // Multiple best matches - assert_best_match(&["en_US", "en_GB", "ru_UA", "fr_FR", "it"], &["en_US", "en", "ru_RU", "ru"], Some("en_US")); - assert_best_match(&["en", "pt_BR", "pt_PT", "es"], &["pt", "en"], Some("pt_BR")); - assert_best_match(&["ku_TR", "ku_IQ", "ku_IR"], &["ku", "en"], Some("ku_TR")); - assert_best_match(&["en_US", "ru_RU", "mn_CN", "sn_ZW", "en", "ru", "mn_MN", "sn"], &["mn", "ru", "en", "sn"], Some("mn_CN")); + case(["en_US", "en_GB", "ru_UA", "fr_FR", "it"], ["en_US", "en", "ru_RU", "ru"], Some("en_US")); + case(["en", "pt_BR", "pt_PT", "es"], ["pt", "en"], Some("pt_BR")); + case(["ku_TR", "ku_IQ", "ku_IR"], ["ku", "en"], Some("ku_TR")); + case(["en_US", "ru_RU", "mn_CN", "sn_ZW", "en", "ru", "mn_MN", "sn"], ["mn", "ru", "en", "sn"], Some("mn_CN")); // Identical - assert_best_match(&["en"], &["en"], Some("en")); - assert_best_match(&["en_US"], &["en_US"], Some("en_US")); - assert_best_match(&["en_US", "ru_RU"], &["en_US", "ru_RU"], Some("en_US")); - assert_best_match(&["st_LS", "sn_ZW", "en_US"], &["st_LS", "sn_ZW", "en_US"], Some("st_LS")); - assert_best_match(&["ku_TR", "ku_IQ", "ku_IR"], &["ku_TR", "ku_IQ", "ku_IR"], Some("ku_TR")); - assert_best_match(&["lv_LV", "ru_RU", "lt_LT", "mn_MN", "ku_TR"], &["lv_LV", "ru_RU", "lt_LT", "mn_MN", "ku_TR"], Some("lv_LV")); + case(["en"], ["en"], Some("en")); + case(["en_US"], ["en_US"], Some("en_US")); + case(["en_US", "ru_RU"], ["en_US", "ru_RU"], Some("en_US")); + case(["st_LS", "sn_ZW", "en_US"], ["st_LS", "sn_ZW", "en_US"], Some("st_LS")); + case(["ku_TR", "ku_IQ", "ku_IR"], ["ku_TR", "ku_IQ", "ku_IR"], Some("ku_TR")); + case(["lv_LV", "ru_RU", "lt_LT", "mn_MN", "ku_TR"], ["lv_LV", "ru_RU", "lt_LT", "mn_MN", "ku_TR"], Some("lv_LV")); // More complicated cases - assert_best_match(&["en_US", "ru_RU.UTF-8"], &["ru", "en"], Some("ru_RU.UTF-8")); - assert_best_match(&["en_US", "ru.UTF-8", "ru_RU.UTF-8"], &["ru.UTF-8", "en"], Some("ru.UTF-8")); - assert_best_match(&["en_US", "ru_RU.UTF-8", "ru.UTF-8"], &["ru.UTF-8", "en"], Some("ru_RU.UTF-8")); - assert_best_match(&["en_US", "ru.UTF-8@dict", "ru_UA"], &["ru_UA.UTF-8@dict", "en"], Some("ru_UA")); - assert_best_match(&["en_US@dict", "ru_RU"], &["en", "ru"], Some("en_US@dict")); - assert_best_match(&["en_US.CP1252", "en_GB.UTF-8", "ru_UA@icase", "fr_FR@euro", "it.UTF-8"], &["ru_RU.KOI8-R", "ru@icase", "en_US.UTF-8", "en.CP1252"], Some("ru_UA@icase")); - assert_best_match(&["fr", "fr_FR", "fr_CA.UTF-8"], &["fr.UTF-8"], Some("fr_CA.UTF-8")); - assert_best_match(&["en", "pt_BR@dict", "pt_PT@icase", "es"], &["pt.CP1252@euro", "en.UTF-8@dict"], Some("pt_BR@dict")); - assert_best_match(&["en_US", "ru_RU", "mn_CN.UTF-8", "sn_ZW", "en", "ru", "mn_MN@dict", "sn"], &["mn.UTF-8@dict", "ru", "en", "sn"], Some("mn_CN.UTF-8")); + case(["en_US", "ru_RU.UTF-8"], ["ru", "en"], Some("ru_RU.UTF-8")); + case(["en_US", "ru.UTF-8", "ru_RU.UTF-8"], ["ru.UTF-8", "en"], Some("ru.UTF-8")); + case(["en_US", "ru_RU.UTF-8", "ru.UTF-8"], ["ru.UTF-8", "en"], Some("ru_RU.UTF-8")); + case(["en_US", "ru.UTF-8@dict", "ru_UA"], ["ru_UA.UTF-8@dict", "en"], Some("ru_UA")); + case(["en_US@dict", "ru_RU"], ["en", "ru"], Some("en_US@dict")); + case(["en_US.CP1252", "en_GB.UTF-8", "ru_UA@icase", "fr_FR@euro", "it.UTF-8"], ["ru_RU.KOI8-R", "ru@icase", "en_US.UTF-8", "en.CP1252"], Some("ru_UA@icase")); + case(["fr", "fr_FR", "fr_CA.UTF-8"], ["fr.UTF-8"], Some("fr_CA.UTF-8")); + case(["en", "pt_BR@dict", "pt_PT@icase", "es"], ["pt.CP1252@euro", "en.UTF-8@dict"], Some("pt_BR@dict")); + case(["en_US", "ru_RU", "mn_CN.UTF-8", "sn_ZW", "en", "ru", "mn_MN@dict", "sn"], ["mn.UTF-8@dict", "ru", "en", "sn"], Some("mn_CN.UTF-8")); // One available locale - assert_best_match(&["kk"], &["en", "en_US", "fr_FR", "fr", "it", "pt", "ru_RU", "es_ES", "kk_KZ"], Some("kk")); + case(["kk"], ["en", "en_US", "fr_FR", "fr", "it", "pt", "ru_RU", "es_ES", "kk_KZ"], Some("kk")); // One user locale - assert_best_match(&["en", "en_US", "fr_FR", "fr", "it", "pt", "ru_RU", "es_ES", "kk_KZ", "pt"], &["pt_PT"], Some("pt")); + case(["en", "en_US", "fr_FR", "fr", "it", "pt", "ru_RU", "es_ES", "kk_KZ", "pt"], ["pt_PT"], Some("pt")); // Not found - assert_best_match(&["en", "en_US", "fr_FR", "fr", "it", "pt", "es_ES", "kk_KZ", "pt"], &["ru"], None); - assert_best_match(&["en", "en_US", "fr_FR", "fr", "pt"], &["id"], None); - assert_best_match(&["ru", "be", "uk", "kk"], &["en"], None); + case(["en", "en_US", "fr_FR", "fr", "it", "pt", "es_ES", "kk_KZ", "pt"], ["ru"], None); + case(["en", "en_US", "fr_FR", "fr", "pt"], ["id"], None); + case(["ru", "be", "uk", "kk"], ["en"], None); // Empty available locales - assert_best_match(&[], &["en", "fr", "it", "pt"], None); + case(&[] as &[&str], ["en", "fr", "it", "pt"], None); // Empty user locales - assert_best_match(&["en", "fr", "it", "pt"], &[], None); + case(["en", "fr", "it", "pt"], &[] as &[&str], None); // Both lists empty - assert_best_match(&[], &[], None); + case(&[] as &[&str], &[] as &[&str], None); // Malformed - assert_best_match(&[" en"], &["en"], None); - assert_best_match(&["en\n"], &["en"], None); - assert_best_match(&["?ru"], &["ru"], None); - assert_best_match(&["ru!"], &["ru"], None); - assert_best_match(&["ruRU"], &["ru"], None); + case([" en"], ["en"], None); + case(["en\n"], ["en"], None); + case(["?ru"], ["ru"], None); + case(["ru!"], ["ru"], None); + case(["ruRU"], ["ru"], None); // Repeating - assert_best_match(&["en", "en", "en", "en"], &["ru_RU", "ru", "en_US", "en"], Some("en")); - assert_best_match(&["en_US", "en_GB", "ru_UA", "fr_FR", "it"], &["kk", "ru", "pt", "ru"], Some("ru_UA")); + case(["en", "en", "en", "en"], ["ru_RU", "ru", "en_US", "en"], Some("en")); + case(["en_US", "en_GB", "ru_UA", "fr_FR", "it"], ["kk", "ru", "pt", "ru"], Some("ru_UA")); // Littered - assert_best_match(&["!!!!!!", "qwydgn12i6i", "ЖЖяяЖяЬЬЬ", "en_US", "!*&^^&*", "qweqweqweqwe_qweqwe", "ru_RU", "@@", "@"], &["ru", "en"], Some("ru_RU")); - assert_best_match(&["", "", "", "zh", "", "", "", "", "", "he", "", ""], &["he", "", "", "zh"], Some("he")); + case(["!!!!!!", "qwydgn12i6i", "ЖЖяяЖяЬЬЬ", "en_US", "!*&^^&*", "qweqweqweqwe_qweqwe", "ru_RU", "@@", "@"], ["ru", "en"], Some("ru_RU")); + case(["", "", "", "zh", "", "", "", "", "", "he", "", ""], ["he", "", "", "zh"], Some("he")); // Special characters - assert_best_match(&["sq\0", "ru_RU", "sq_AL", "eu_ES"], &["en_US", "en", "sq_XK", "sq"], Some("sq_AL")); - assert_best_match(&["\0", "\x01\x02\x03\x04", "sq\0", "ru_RU", "sq_AL", "eu_ES"], &["en_US", "\x06", "en", "sq_XK", "sq", "\0"], Some("sq_AL")); + case(["sq\0", "ru_RU", "sq_AL", "eu_ES"], ["en_US", "en", "sq_XK", "sq"], Some("sq_AL")); + case(["\0", "\x01\x02\x03\x04", "sq\0", "ru_RU", "sq_AL", "eu_ES"], &["en_US", "\x06", "en", "sq_XK", "sq", "\0"], Some("sq_AL")); // Various letter cases - assert_best_match(&["EN"], &["en"], Some("EN")); - assert_best_match(&["En"], &["EN"], Some("En")); - assert_best_match(&["Ru_rU"], &["en", "ru"], Some("Ru_rU")); - assert_best_match(&["rU_rU"], &["en", "Ru"], Some("rU_rU")); - assert_best_match(&["EN.Utf-8"], &["en.UTF-8"], Some("EN.Utf-8")); - assert_best_match(&["En@dIcT"], &["EN_us"], Some("En@dIcT")); - assert_best_match(&["ru_ru.utf-8@icase"], &["en", "RU_RU.UTF-8@ICASE"], Some("ru_ru.utf-8@icase")); - assert_best_match(&["fr_FR.CP1252@euRO"], &["FR", "en"], Some("fr_FR.CP1252@euRO")); + case(["EN"], ["en"], Some("EN")); + case(["En"], ["EN"], Some("En")); + case(["Ru_rU"], ["en", "ru"], Some("Ru_rU")); + case(["rU_rU"], ["en", "Ru"], Some("rU_rU")); + case(["EN.Utf-8"], ["en.UTF-8"], Some("EN.Utf-8")); + case(["En@dIcT"], ["EN_us"], Some("En@dIcT")); + case(["ru_ru.utf-8@icase"], ["en", "RU_RU.UTF-8@ICASE"], Some("ru_ru.utf-8@icase")); + case(["fr_FR.CP1252@euRO"], ["FR", "en"], Some("fr_FR.CP1252@euRO")); + + // Various template parameter types + // &str and &&str + case(["en_US", "ru_RU"], ["ru", "en"], Some("ru_RU")); + case(&["en_US", "ru_RU"], ["ru", "en"], Some(&"ru_RU")); + case(["en_US", "ru_RU"], &["ru", "en"], Some("ru_RU")); + case(&["en_US", "ru_RU"], &["ru", "en"], Some(&"ru_RU")); + case([&"en_US", &"ru_RU"], ["ru", "en"], Some(&"ru_RU")); + // String and &String + case(["en_US".to_string(), "ru_RU".to_string()], ["ru", "en"], Some("ru_RU".to_string())); + case(&["en_US".to_string(), "ru_RU".to_string()], ["ru", "en"], Some(&"ru_RU".to_string())); + // Cow + use std::borrow::Cow; + case([Cow::Owned("en_US".to_string()), Cow::Borrowed("ru_RU")], ["ru", "en"], Some(Cow::Borrowed("ru_RU"))); + case([Cow::Borrowed("en_US"), Cow::Owned("ru_RU".to_string())], ["ru", "en"], Some(Cow::Owned("ru_RU".to_string()))); + // Rc and Arc + use std::rc::Rc; + use std::sync::Arc; + case([Rc::from("en_US"), Rc::from("ru_RU")], ["ru", "en"], Some(Rc::from("ru_RU"))); + case([Arc::from("en_US"), Arc::from("ru_RU")], ["ru", "en"], Some(Arc::from("ru_RU"))); + // Box + case([Box::from("en_US"), Box::from("ru_RU")], ["ru", "en"], Some(Box::from("ru_RU"))); } #[test] #[allow(non_snake_case)] fn test_PosixLocale() { - fn assert_parts(locale: &str, parts: (&str, Option<&str>, Option<&str>, Option<&str>)) { + fn case(locale: &str, parts: (&str, Option<&str>, Option<&str>, Option<&str>)) { let posix_locale = PosixLocale::parse(locale); assert_eq!(posix_locale.locale, locale); - assert_eq!(posix_locale.language, parts.0); - assert_eq!(posix_locale.territory, parts.1); - assert_eq!(posix_locale.codeset, parts.2); - assert_eq!(posix_locale.modifier, parts.3); + assert_eq!(posix_locale.language(), parts.0); + assert_eq!(posix_locale.territory(), parts.1); + assert_eq!(posix_locale.codeset(), parts.2); + assert_eq!(posix_locale.modifier(), parts.3); } // Language only - assert_parts("en", ("en", None, None, None)); - assert_parts("ru", ("ru", None, None, None)); - assert_parts("fr", ("fr", None, None, None)); + case("en", ("en", None, None, None)); + case("ru", ("ru", None, None, None)); + case("fr", ("fr", None, None, None)); // Language and territory - assert_parts("en_US", ("en", Some("US"), None, None)); - assert_parts("ru_RU", ("ru", Some("RU"), None, None)); - assert_parts("fr_FR", ("fr", Some("FR"), None, None)); + case("en_US", ("en", Some("US"), None, None)); + case("ru_RU", ("ru", Some("RU"), None, None)); + case("fr_FR", ("fr", Some("FR"), None, None)); // Language and codeset - assert_parts("en.UTF-8", ("en", None, Some("UTF-8"), None)); - assert_parts("ru.KOI8-R", ("ru", None, Some("KOI8-R"), None)); - assert_parts("fr.CP1252", ("fr", None, Some("CP1252"), None)); + case("en.UTF-8", ("en", None, Some("UTF-8"), None)); + case("ru.KOI8-R", ("ru", None, Some("KOI8-R"), None)); + case("fr.CP1252", ("fr", None, Some("CP1252"), None)); // Language and modifier - assert_parts("en@dict", ("en", None, None, Some("dict"))); - assert_parts("ru@icase", ("ru", None, None, Some("icase"))); - assert_parts("fr@euro", ("fr", None, None, Some("euro"))); + case("en@dict", ("en", None, None, Some("dict"))); + case("ru@icase", ("ru", None, None, Some("icase"))); + case("fr@euro", ("fr", None, None, Some("euro"))); // Language, territory and codeset - assert_parts("en_US.UTF-8", ("en", Some("US"), Some("UTF-8"), None)); - assert_parts("ru_RU.KOI8-R", ("ru", Some("RU"), Some("KOI8-R"), None)); - assert_parts("fr_FR.CP1252", ("fr", Some("FR"), Some("CP1252"), None)); + case("en_US.UTF-8", ("en", Some("US"), Some("UTF-8"), None)); + case("ru_RU.KOI8-R", ("ru", Some("RU"), Some("KOI8-R"), None)); + case("fr_FR.CP1252", ("fr", Some("FR"), Some("CP1252"), None)); // Language, territory and modifier - assert_parts("en_US@dict", ("en", Some("US"), None, Some("dict"))); - assert_parts("ru_RU@icase", ("ru", Some("RU"), None, Some("icase"))); - assert_parts("fr_FR@euro", ("fr", Some("FR"), None, Some("euro"))); + case("en_US@dict", ("en", Some("US"), None, Some("dict"))); + case("ru_RU@icase", ("ru", Some("RU"), None, Some("icase"))); + case("fr_FR@euro", ("fr", Some("FR"), None, Some("euro"))); // Language, codeset and modifier - assert_parts("en.UTF-8@dict", ("en", None, Some("UTF-8"), Some("dict"))); - assert_parts("ru.KOI8-R@icase", ("ru", None, Some("KOI8-R"), Some("icase"))); - assert_parts("fr.CP1252@euro", ("fr", None, Some("CP1252"), Some("euro"))); + case("en.UTF-8@dict", ("en", None, Some("UTF-8"), Some("dict"))); + case("ru.KOI8-R@icase", ("ru", None, Some("KOI8-R"), Some("icase"))); + case("fr.CP1252@euro", ("fr", None, Some("CP1252"), Some("euro"))); // Language, territory, codeset and modifier - assert_parts("en_US.UTF-8@dict", ("en", Some("US"), Some("UTF-8"), Some("dict"))); - assert_parts("ru_RU.KOI8-R@icase", ("ru", Some("RU"), Some("KOI8-R"), Some("icase"))); - assert_parts("fr_FR.CP1252@euro", ("fr", Some("FR"), Some("CP1252"), Some("euro"))); + case("en_US.UTF-8@dict", ("en", Some("US"), Some("UTF-8"), Some("dict"))); + case("ru_RU.KOI8-R@icase", ("ru", Some("RU"), Some("KOI8-R"), Some("icase"))); + case("fr_FR.CP1252@euro", ("fr", Some("FR"), Some("CP1252"), Some("euro"))); // Various letter cases - assert_parts("EN", ("EN", None, None, None)); - assert_parts("Ru", ("Ru", None, None, None)); - assert_parts("fR", ("fR", None, None, None)); - assert_parts("eN_us.Utf-8", ("eN", Some("us"), Some("Utf-8"), None)); - assert_parts("RU_ru.koi8-R", ("RU", Some("ru"), Some("koi8-R"), None)); - assert_parts("Fr_Fr.Cp1252", ("Fr", Some("Fr"), Some("Cp1252"), None)); - assert_parts("en_us.utf-8@DICT", ("en", Some("us"), Some("utf-8"), Some("DICT"))); - assert_parts("RU_RU.KOI8-R@Icase", ("RU", Some("RU"), Some("KOI8-R"), Some("Icase"))); - assert_parts("fR_fR.cP1252@eUrO", ("fR", Some("fR"), Some("cP1252"), Some("eUrO"))); + case("EN", ("EN", None, None, None)); + case("Ru", ("Ru", None, None, None)); + case("fR", ("fR", None, None, None)); + case("eN_us.Utf-8", ("eN", Some("us"), Some("Utf-8"), None)); + case("RU_ru.koi8-R", ("RU", Some("ru"), Some("koi8-R"), None)); + case("Fr_Fr.Cp1252", ("Fr", Some("Fr"), Some("Cp1252"), None)); + case("en_us.utf-8@DICT", ("en", Some("us"), Some("utf-8"), Some("DICT"))); + case("RU_RU.KOI8-R@Icase", ("RU", Some("RU"), Some("KOI8-R"), Some("Icase"))); + case("fR_fR.cP1252@eUrO", ("fR", Some("fR"), Some("cP1252"), Some("eUrO"))); // Empty - assert_parts("", ("", None, None, None)); + case("", ("", None, None, None)); // Whitespace - assert_parts(" ", (" ", None, None, None)); - assert_parts(" ", (" ", None, None, None)); - assert_parts("\t", ("\t", None, None, None)); - assert_parts("\n", ("\n", None, None, None)); - assert_parts("\n \t\t\n \n\t \t\t\n\n\t", ("\n \t\t\n \n\t \t\t\n\n\t", None, None, None)); + case(" ", (" ", None, None, None)); + case(" ", (" ", None, None, None)); + case("\t", ("\t", None, None, None)); + case("\n", ("\n", None, None, None)); + case("\n \t\t\n \n\t \t\t\n\n\t", ("\n \t\t\n \n\t \t\t\n\n\t", None, None, None)); // Litter - assert_parts("!!!", ("!!!", None, None, None)); - assert_parts("12345", ("12345", None, None, None)); - assert_parts("+-+-", ("+-+-", None, None, None)); + case("!!!", ("!!!", None, None, None)); + case("12345", ("12345", None, None, None)); + case("+-+-", ("+-+-", None, None, None)); // Malformed - assert_parts("!!!_9999.UUU@()()", ("!!!", Some("9999"), Some("UUU"), Some("()()"))); - assert_parts("12_123.1234@12345", ("12", Some("123"), Some("1234"), Some("12345"))); - assert_parts("+-+-@+-+-", ("+-+-", None, None, Some("+-+-"))); + case("!!!_9999.UUU@()()", ("!!!", Some("9999"), Some("UUU"), Some("()()"))); + case("12_123.1234@12345", ("12", Some("123"), Some("1234"), Some("12345"))); + case("+-+-@+-+-", ("+-+-", None, None, Some("+-+-"))); // Wrong order EXPECTED TO BE BROKEN - assert_parts("lang.codeset_region@modifier", ("lang.codeset", None, Some("codeset_region"), Some("modifier"))); - assert_parts("lang@modifier.codeset_region", ("lang@modifier.codeset", None, None, Some("modifier.codeset_region"))); - assert_parts("lang_region@modifier.codeset", ("lang", Some("region@modifier"), None, Some("modifier.codeset"))); - assert_parts("lang.codeset@modifier_region", ("lang.codeset@modifier", None, Some("codeset"), Some("modifier_region"))); - assert_parts("lang@modifier_region.codeset", ("lang@modifier", Some("region"), None, Some("modifier_region.codeset"))); + case("lang.codeset_region@modifier", ("lang.codeset", None, Some("codeset_region"), Some("modifier"))); + case("lang@modifier.codeset_region", ("lang@modifier.codeset", None, None, Some("modifier.codeset_region"))); + case("lang_region@modifier.codeset", ("lang", Some("region@modifier"), None, Some("modifier.codeset"))); + case("lang.codeset@modifier_region", ("lang.codeset@modifier", None, Some("codeset"), Some("modifier_region"))); + case("lang@modifier_region.codeset", ("lang@modifier", Some("region"), None, Some("modifier_region.codeset"))); // Parts missing - assert_parts("_.@", ("", Some(""), Some(""), Some(""))); - assert_parts("_US.UTF-8@dict", ("", Some("US"), Some("UTF-8"), Some("dict"))); - assert_parts("ru_.KOI8-R@icase", ("ru", Some(""), Some("KOI8-R"), Some("icase"))); - assert_parts("fr_FR.@euro", ("fr", Some("FR"), Some(""), Some("euro"))); - assert_parts("de_DE.ISO-8859-1@", ("de", Some("DE"), Some("ISO-8859-1"), Some(""))); + case("_.@", ("", Some(""), Some(""), Some(""))); + case("_US.UTF-8@dict", ("", Some("US"), Some("UTF-8"), Some("dict"))); + case("ru_.KOI8-R@icase", ("ru", Some(""), Some("KOI8-R"), Some("icase"))); + case("fr_FR.@euro", ("fr", Some("FR"), Some(""), Some("euro"))); + case("de_DE.ISO-8859-1@", ("de", Some("DE"), Some("ISO-8859-1"), Some(""))); // Special characters - assert_parts("\0", ("\0", None, None, None)); - assert_parts("\0_\0.\0@\0", ("\0", Some("\0"), Some("\0"), Some("\0"))); - assert_parts("\0\x01\x02\x03", ("\0\x01\x02\x03", None, None, None)); - assert_parts("\x03\x02\x01", ("\x03\x02\x01", None, None, None)); + case("\0", ("\0", None, None, None)); + case("\0_\0.\0@\0", ("\0", Some("\0"), Some("\0"), Some("\0"))); + case("\0\x01\x02\x03", ("\0\x01\x02\x03", None, None, None)); + case("\x03\x02\x01", ("\x03\x02\x01", None, None, None)); } } \ No newline at end of file