use core::cmp::Ordering;
#[cfg(feature = "alloc")]
use core::str::FromStr;
use crate::parser;
use crate::subtags;
use crate::ParseError;
#[cfg(feature = "alloc")]
use alloc::borrow::Cow;
#[derive(PartialEq, Eq, Clone, Hash)] #[allow(clippy::exhaustive_structs)] pub struct LanguageIdentifier {
pub language: subtags::Language,
pub script: Option<subtags::Script>,
pub region: Option<subtags::Region>,
pub variants: subtags::Variants,
}
impl LanguageIdentifier {
pub const UNKNOWN: Self = crate::langid!("und");
#[inline]
#[cfg(feature = "alloc")]
pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(s.as_bytes())
}
#[cfg(feature = "alloc")]
pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
crate::parser::parse_language_identifier(code_units, parser::ParserMode::LanguageIdentifier)
}
#[doc(hidden)] #[expect(clippy::type_complexity)]
pub const fn try_from_utf8_with_single_variant(
code_units: &[u8],
) -> Result<
(
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
Option<subtags::Variant>,
),
ParseError,
> {
crate::parser::parse_language_identifier_with_single_variant(
code_units,
parser::ParserMode::LanguageIdentifier,
)
}
#[cfg(feature = "alloc")]
pub fn try_from_locale_bytes(v: &[u8]) -> Result<Self, ParseError> {
parser::parse_language_identifier(v, parser::ParserMode::Locale)
}
pub const fn is_unknown(&self) -> bool {
self.language.is_unknown()
&& self.script.is_none()
&& self.region.is_none()
&& self.variants.is_empty()
}
#[cfg(feature = "alloc")]
pub fn normalize_utf8(input: &[u8]) -> Result<Cow<'_, str>, ParseError> {
let lang_id = Self::try_from_utf8(input)?;
Ok(writeable::to_string_or_borrow(&lang_id, input))
}
#[cfg(feature = "alloc")]
pub fn normalize(input: &str) -> Result<Cow<'_, str>, ParseError> {
Self::normalize_utf8(input.as_bytes())
}
pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
writeable::cmp_utf8(self, other)
}
pub(crate) fn as_tuple(
&self,
) -> (
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
&subtags::Variants,
) {
(self.language, self.script, self.region, &self.variants)
}
pub fn total_cmp(&self, other: &Self) -> Ordering {
self.as_tuple().cmp(&other.as_tuple())
}
pub fn normalizing_eq(&self, other: &str) -> bool {
macro_rules! subtag_matches {
($T:ty, $iter:ident, $expected:expr) => {
$iter
.next()
.map(|b| <$T>::try_from_utf8(b) == Ok($expected))
.unwrap_or(false)
};
}
let mut iter = parser::SubtagIterator::new(other.as_bytes());
if !subtag_matches!(subtags::Language, iter, self.language) {
return false;
}
if let Some(ref script) = self.script {
if !subtag_matches!(subtags::Script, iter, *script) {
return false;
}
}
if let Some(ref region) = self.region {
if !subtag_matches!(subtags::Region, iter, *region) {
return false;
}
}
for variant in self.variants.iter() {
if !subtag_matches!(subtags::Variant, iter, *variant) {
return false;
}
}
iter.next().is_none()
}
pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
where
F: FnMut(&str) -> Result<(), E>,
{
f(self.language.as_str())?;
if let Some(ref script) = self.script {
f(script.as_str())?;
}
if let Some(ref region) = self.region {
f(region.as_str())?;
}
for variant in self.variants.iter() {
f(variant.as_str())?;
}
Ok(())
}
pub(crate) fn for_each_subtag_str_lowercased<E, F>(&self, f: &mut F) -> Result<(), E>
where
F: FnMut(&str) -> Result<(), E>,
{
f(self.language.as_str())?;
if let Some(ref script) = self.script {
f(script.to_tinystr().to_ascii_lowercase().as_str())?;
}
if let Some(ref region) = self.region {
f(region.to_tinystr().to_ascii_lowercase().as_str())?;
}
for variant in self.variants.iter() {
f(variant.as_str())?;
}
Ok(())
}
pub(crate) fn write_lowercased_to<W: core::fmt::Write + ?Sized>(
&self,
sink: &mut W,
) -> core::fmt::Result {
let mut initial = true;
self.for_each_subtag_str_lowercased(&mut |subtag| {
if initial {
initial = false;
} else {
sink.write_char('-')?;
}
sink.write_str(subtag)
})
}
}
impl core::fmt::Debug for LanguageIdentifier {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(&self, f)
}
}
#[cfg(feature = "alloc")]
impl FromStr for LanguageIdentifier {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
impl_writeable_for_each_subtag_str_no_test!(LanguageIdentifier, selff, selff.script.is_none() && selff.region.is_none() && selff.variants.is_empty() => Some(selff.language.as_str()));
#[test]
fn test_writeable() {
use writeable::assert_writeable_eq;
assert_writeable_eq!(LanguageIdentifier::UNKNOWN, "und");
assert_writeable_eq!("und-001".parse::<LanguageIdentifier>().unwrap(), "und-001");
assert_writeable_eq!(
"und-Mymr".parse::<LanguageIdentifier>().unwrap(),
"und-Mymr",
);
assert_writeable_eq!(
"my-Mymr-MM".parse::<LanguageIdentifier>().unwrap(),
"my-Mymr-MM",
);
assert_writeable_eq!(
"my-Mymr-MM-posix".parse::<LanguageIdentifier>().unwrap(),
"my-Mymr-MM-posix",
);
assert_writeable_eq!(
"zh-macos-posix".parse::<LanguageIdentifier>().unwrap(),
"zh-macos-posix",
);
}
impl From<subtags::Language> for LanguageIdentifier {
fn from(language: subtags::Language) -> Self {
Self {
language,
script: None,
region: None,
variants: subtags::Variants::new(),
}
}
}
impl From<Option<subtags::Script>> for LanguageIdentifier {
fn from(script: Option<subtags::Script>) -> Self {
Self {
language: subtags::Language::UNKNOWN,
script,
region: None,
variants: subtags::Variants::new(),
}
}
}
impl From<Option<subtags::Region>> for LanguageIdentifier {
fn from(region: Option<subtags::Region>) -> Self {
Self {
language: subtags::Language::UNKNOWN,
script: None,
region,
variants: subtags::Variants::new(),
}
}
}
impl
From<(
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
)> for LanguageIdentifier
{
fn from(
lsr: (
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
),
) -> Self {
Self {
language: lsr.0,
script: lsr.1,
region: lsr.2,
variants: subtags::Variants::new(),
}
}
}
impl From<&LanguageIdentifier>
for (
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
)
{
fn from(langid: &LanguageIdentifier) -> Self {
(langid.language, langid.script, langid.region)
}
}