1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
45use core::str::FromStr;
67use crate::parser::ParseError;
8use crate::subtags::{Region, Subtag};
910#[repr(transparent)]
#[doc = r" A subdivision suffix used in [`SubdivisionId`]."]
#[doc = r""]
#[doc =
r" This suffix represents a specific subdivision code under a given [`Region`]."]
#[doc =
r" For example the value of [`SubdivisionId`] may be `gbsct`, where the [`SubdivisionSuffix`]"]
#[doc = r" is `sct` for Scotland."]
#[doc = r""]
#[doc =
r" Such a value associated with a key `rg` means that the locale should use Unit Preferences"]
#[doc =
r" (default calendar, currency, week data, time cycle, measurement system) for Scotland, even if the"]
#[doc = r" [`LanguageIdentifier`](crate::LanguageIdentifier) is `en-US`."]
#[doc = r""]
#[doc =
r" A subdivision suffix has to be a sequence of alphanumerical characters no"]
#[doc = r" shorter than one and no longer than four characters."]
#[doc = r""]
#[doc = r""]
#[doc = r" # Examples"]
#[doc = r""]
#[doc = r" ```"]
#[doc =
r" use icu::locale::extensions::unicode::{subdivision_suffix, SubdivisionSuffix};"]
#[doc = r""]
#[doc = r" let ss: SubdivisionSuffix ="]
#[doc =
r#" "sct".parse().expect("Failed to parse a SubdivisionSuffix.");"#]
#[doc = r""]
#[doc = r#" assert_eq!(ss, subdivision_suffix!("sct"));"#]
#[doc = r" ```"]
pub struct SubdivisionSuffix(tinystr::TinyAsciiStr<4>);
#[automatically_derived]
impl ::core::fmt::Debug for SubdivisionSuffix {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"SubdivisionSuffix", &&self.0)
}
}
#[automatically_derived]
impl ::core::marker::StructuralPartialEq for SubdivisionSuffix { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SubdivisionSuffix {
#[inline]
fn eq(&self, other: &SubdivisionSuffix) -> bool { self.0 == other.0 }
}
#[automatically_derived]
impl ::core::cmp::Eq for SubdivisionSuffix {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) -> () {
let _: ::core::cmp::AssertParamIsEq<tinystr::TinyAsciiStr<4>>;
}
}
#[automatically_derived]
impl ::core::clone::Clone for SubdivisionSuffix {
#[inline]
fn clone(&self) -> SubdivisionSuffix {
let _: ::core::clone::AssertParamIsClone<tinystr::TinyAsciiStr<4>>;
*self
}
}
#[automatically_derived]
impl ::core::hash::Hash for SubdivisionSuffix {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
::core::hash::Hash::hash(&self.0, state)
}
}
#[automatically_derived]
impl ::core::cmp::PartialOrd for SubdivisionSuffix {
#[inline]
fn partial_cmp(&self, other: &SubdivisionSuffix)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0)
}
}
#[automatically_derived]
impl ::core::cmp::Ord for SubdivisionSuffix {
#[inline]
fn cmp(&self, other: &SubdivisionSuffix) -> ::core::cmp::Ordering {
::core::cmp::Ord::cmp(&self.0, &other.0)
}
}
#[automatically_derived]
impl ::core::marker::Copy for SubdivisionSuffix { }
impl SubdivisionSuffix {
/// A constructor which takes a str slice, parses it and
#[doc = "produces a well-formed [`SubdivisionSuffix`]."]
///
/// # Examples
///
/// ```
#[doc = "use icu_locale_core::extensions :: unicode ::SubdivisionSuffix;"]
///
#[doc = "assert!(SubdivisionSuffix::try_from_str(\"sct\").is_ok());"]
#[doc =
"assert!(SubdivisionSuffix::try_from_str(\"toolooong\").is_err());"]
/// ```
#[inline]
pub const fn try_from_str(s: &str)
-> Result<Self, crate::parser::errors::ParseError> {
Self::try_from_utf8(s.as_bytes())
}
/// See [`Self::try_from_str`]
pub const fn try_from_utf8(code_units: &[u8])
-> Result<Self, crate::parser::errors::ParseError> {
#[allow(clippy :: double_comparisons)]
if code_units.len() < 1 || code_units.len() > 4 {
return Err(crate::parser::errors::ParseError::InvalidExtension);
}
match tinystr::TinyAsciiStr::try_from_utf8(code_units) {
Ok(s) if s.is_ascii_alphanumeric() =>
Ok(Self(s.to_ascii_lowercase())),
_ => Err(crate::parser::errors::ParseError::InvalidExtension),
}
}
#[doc = "Safely creates a [`SubdivisionSuffix`] from its raw format"]
/// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
/// this constructor only takes normalized values.
pub const fn try_from_raw(raw: [u8; 4])
-> Result<Self, crate::parser::errors::ParseError> {
if let Ok(s) = tinystr::TinyAsciiStr::<4>::try_from_raw(raw) {
if s.len() >= 1 &&
(s.is_ascii_alphanumeric() && s.is_ascii_lowercase()) {
Ok(Self(s))
} else {
Err(crate::parser::errors::ParseError::InvalidExtension)
}
} else { Err(crate::parser::errors::ParseError::InvalidExtension) }
}
#[doc = "Unsafely creates a [`SubdivisionSuffix`] from its raw format"]
/// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
/// this constructor only takes normalized values.
///
/// # Safety
///
/// This function is safe iff [`Self::try_from_raw`] returns an `Ok`. This is the case
/// for inputs that are correctly normalized.
pub const unsafe fn from_raw_unchecked(v: [u8; 4]) -> Self {
Self(tinystr::TinyAsciiStr::from_utf8_unchecked(v))
}
/// Deconstructs into a raw format to be consumed by
/// [`from_raw_unchecked`](Self::from_raw_unchecked()) or
/// [`try_from_raw`](Self::try_from_raw()).
pub const fn into_raw(self) -> [u8; 4] { *self.0.all_bytes() }
#[inline]
/// A helper function for displaying as a `&str`.
pub const fn as_str(&self) -> &str { self.0.as_str() }
#[doc(hidden)]
pub const fn to_tinystr(&self) -> tinystr::TinyAsciiStr<4> { self.0 }
/// Compare with BCP-47 bytes.
///
/// The return value is equivalent to what would happen if you first converted
/// `self` to a BCP-47 string and then performed a byte comparison.
///
/// This function is case-sensitive and results in a *total order*, so it is appropriate for
/// binary search. The only argument producing [`Ordering::Equal`](core::cmp::Ordering::Equal)
/// is `self.as_str().as_bytes()`.
#[inline]
pub fn strict_cmp(self, other: &[u8]) -> core::cmp::Ordering {
self.as_str().as_bytes().cmp(other)
}
/// Compare with a potentially unnormalized BCP-47 string.
///
/// The return value is equivalent to what would happen if you first parsed the
/// BCP-47 string and then performed a structural comparison.
///
#[inline]
pub fn normalizing_eq(self, other: &str) -> bool {
self.as_str().eq_ignore_ascii_case(other)
}
}
impl core::str::FromStr for SubdivisionSuffix {
type Err = crate::parser::errors::ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::try_from_str(s) }
}
impl<'l> From<&'l SubdivisionSuffix> for &'l str {
fn from(input: &'l SubdivisionSuffix) -> Self { input.as_str() }
}
impl From<SubdivisionSuffix> for tinystr::TinyAsciiStr<4> {
fn from(input: SubdivisionSuffix) -> Self { input.to_tinystr() }
}
impl writeable::Writeable for SubdivisionSuffix {
#[inline]
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W)
-> core::fmt::Result {
sink.write_str(self.as_str())
}
#[inline]
fn writeable_length_hint(&self) -> writeable::LengthHint {
writeable::LengthHint::exact(self.0.len())
}
}
/// This trait is implemented for compatibility with [`fmt!`](alloc::fmt).
/// To create a string, [`Writeable::write_to_string`] is usually more efficient.
impl core::fmt::Display for SubdivisionSuffix {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
::writeable::Writeable::write_to(&self, f)
}
}
impl SubdivisionSuffix {
/// Converts the given value to a `String`.
///
/// Under the hood, this uses an efficient [`Writeable`] implementation.
/// However, in order to avoid allocating a string, it is more efficient
/// to use [`Writeable`] directly.
pub fn to_string(&self) -> ::writeable::_internal::String {
::writeable::Writeable::write_to_string(self).into_owned()
}
}
#[doc =
"A macro allowing for compile-time construction of valid [`SubdivisionSuffix`] subtags."]
///
/// # Examples
///
/// Parsing errors don't have to be handled at runtime:
/// ```
/// assert_eq!(
#[doc =
" icu_locale_core::extensions::unicode::subdivision_suffix!(\"sct\"),"]
#[doc =
" \"sct\".parse::<icu_locale_core::extensions::unicode::SubdivisionSuffix>().unwrap()"]
/// );
/// ```
///
/// Invalid input is a compile failure:
/// ```compile_fail,E0080
#[doc =
"icu_locale_core::extensions::unicode::subdivision_suffix!(\"toolooong\");"]
/// ```
///
#[doc =
"[`SubdivisionSuffix`]: crate::extensions::unicode::SubdivisionSuffix"]
#[macro_export]
#[doc(hidden)]
macro_rules! extensions_unicode_subdivision_suffix {
($string : literal) =>
{
const
{
use crate :: extensions :: unicode :: SubdivisionSuffix; match
SubdivisionSuffix :: try_from_utf8($string.as_bytes())
{
Ok(r) => r, #[allow(clippy :: panic)] _ => panic!
(concat!
("Invalid ", stringify! (extensions), "::", stringify!
(unicode), "::", stringify! (SubdivisionSuffix), ": ",
$string)),
}
}
};
}
#[doc(inline)]
pub use extensions_unicode_subdivision_suffix as subdivision_suffix;
unsafe impl zerovec::ule::ULE for SubdivisionSuffix {
fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> {
let it = bytes.chunks_exact(core::mem::size_of::<Self>());
if !it.remainder().is_empty() {
return Err(zerovec::ule::UleError::length::<Self>(bytes.len()));
}
for v in it {
let mut a = [0; core::mem::size_of::<Self>()];
a.copy_from_slice(v);
if Self::try_from_raw(a).is_err() {
return Err(zerovec::ule::UleError::parse::<Self>());
}
}
Ok(())
}
}
impl zerovec::ule::NicheBytes<4> for SubdivisionSuffix {
const NICHE_BIT_PATTERN: [u8; 4] =
<tinystr::TinyAsciiStr<4>>::NICHE_BIT_PATTERN;
}
impl zerovec::ule::AsULE for SubdivisionSuffix {
type ULE = Self;
fn to_unaligned(self) -> Self::ULE { self }
fn from_unaligned(unaligned: Self::ULE) -> Self { unaligned }
}
impl<'a> zerovec::maps::ZeroMapKV<'a> for SubdivisionSuffix {
type Container = zerovec::ZeroVec<'a, SubdivisionSuffix>;
type Slice = zerovec::ZeroSlice<SubdivisionSuffix>;
type GetType = SubdivisionSuffix;
type OwnedType = SubdivisionSuffix;
}impl_tinystr_subtag!(
11/// A subdivision suffix used in [`SubdivisionId`].
12 ///
13 /// This suffix represents a specific subdivision code under a given [`Region`].
14 /// For example the value of [`SubdivisionId`] may be `gbsct`, where the [`SubdivisionSuffix`]
15 /// is `sct` for Scotland.
16 ///
17 /// Such a value associated with a key `rg` means that the locale should use Unit Preferences
18 /// (default calendar, currency, week data, time cycle, measurement system) for Scotland, even if the
19 /// [`LanguageIdentifier`](crate::LanguageIdentifier) is `en-US`.
20 ///
21 /// A subdivision suffix has to be a sequence of alphanumerical characters no
22 /// shorter than one and no longer than four characters.
23 ///
24 ///
25 /// # Examples
26 ///
27 /// ```
28 /// use icu::locale::extensions::unicode::{subdivision_suffix, SubdivisionSuffix};
29 ///
30 /// let ss: SubdivisionSuffix =
31 /// "sct".parse().expect("Failed to parse a SubdivisionSuffix.");
32 ///
33 /// assert_eq!(ss, subdivision_suffix!("sct"));
34 /// ```
35SubdivisionSuffix,
36 extensions::unicode,
37 subdivision_suffix,
38extensions_unicode_subdivision_suffix,
391..=4,
40 s,
41 s.is_ascii_alphanumeric(),
42 s.to_ascii_lowercase(),
43 s.is_ascii_alphanumeric() && s.is_ascii_lowercase(),
44 InvalidExtension,
45 ["sct"],
46 ["toolooong"],
47);
4849/// A Subivision Id as defined in [`Unicode Locale Identifier`].
50///
51/// Subdivision Id is used in [`Unicode`] extensions:
52/// * `rg` - Regional Override
53/// * `sd` - Regional Subdivision
54///
55/// In both cases the subdivision is composed of a [`Region`] and a [`SubdivisionSuffix`] which represents
56/// different meaning depending on the key.
57///
58/// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/tr35.html#unicode_subdivision_id
59/// [`Unicode`]: crate::extensions::unicode::Unicode
60///
61/// # Examples
62///
63/// ```
64/// use icu::locale::{
65/// extensions::unicode::{subdivision_suffix, SubdivisionId},
66/// subtags::region,
67/// };
68///
69/// let ss = subdivision_suffix!("zzzz");
70/// let region = region!("gb");
71///
72/// let si = SubdivisionId::new(region, ss);
73///
74/// assert_eq!(si.to_string(), "gbzzzz");
75/// ```
76#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SubdivisionId {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "SubdivisionId",
"region", &self.region, "suffix", &&self.suffix)
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for SubdivisionId {
#[inline]
fn eq(&self, other: &SubdivisionId) -> bool {
self.region == other.region && self.suffix == other.suffix
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SubdivisionId {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) -> () {
let _: ::core::cmp::AssertParamIsEq<Region>;
let _: ::core::cmp::AssertParamIsEq<SubdivisionSuffix>;
}
}Eq, #[automatically_derived]
impl ::core::clone::Clone for SubdivisionId {
#[inline]
fn clone(&self) -> SubdivisionId {
let _: ::core::clone::AssertParamIsClone<Region>;
let _: ::core::clone::AssertParamIsClone<SubdivisionSuffix>;
*self
}
}Clone, #[automatically_derived]
impl ::core::hash::Hash for SubdivisionId {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
::core::hash::Hash::hash(&self.region, state);
::core::hash::Hash::hash(&self.suffix, state)
}
}Hash, #[automatically_derived]
impl ::core::cmp::PartialOrd for SubdivisionId {
#[inline]
fn partial_cmp(&self, other: &SubdivisionId)
-> ::core::option::Option<::core::cmp::Ordering> {
match ::core::cmp::PartialOrd::partial_cmp(&self.region,
&other.region) {
::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
::core::cmp::PartialOrd::partial_cmp(&self.suffix,
&other.suffix),
cmp => cmp,
}
}
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for SubdivisionId {
#[inline]
fn cmp(&self, other: &SubdivisionId) -> ::core::cmp::Ordering {
match ::core::cmp::Ord::cmp(&self.region, &other.region) {
::core::cmp::Ordering::Equal =>
::core::cmp::Ord::cmp(&self.suffix, &other.suffix),
cmp => cmp,
}
}
}Ord, #[automatically_derived]
impl ::core::marker::Copy for SubdivisionId { }Copy)]
77#[non_exhaustive]
78pub struct SubdivisionId {
79/// A region field of a Subdivision Id.
80pub region: Region,
81/// A subdivision suffix field of a Subdivision Id.
82pub suffix: SubdivisionSuffix,
83}
8485impl SubdivisionId {
86/// Returns a new [`SubdivisionId`].
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// use icu::locale::{
92 /// extensions::unicode::{subdivision_suffix, SubdivisionId},
93 /// subtags::region,
94 /// };
95 ///
96 /// let ss = subdivision_suffix!("zzzz");
97 /// let region = region!("gb");
98 ///
99 /// let si = SubdivisionId::new(region, ss);
100 ///
101 /// assert_eq!(si.to_string(), "gbzzzz");
102 /// ```
103pub const fn new(region: Region, suffix: SubdivisionSuffix) -> Self {
104Self { region, suffix }
105 }
106107/// A constructor which takes a str slice, parses it and
108 /// produces a well-formed [`SubdivisionId`].
109#[inline]
110pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
111Self::try_from_utf8(s.as_bytes())
112 }
113114/// See [`Self::try_from_str`]
115pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
116let is_alpha = code_units117 .first()
118 .and_then(|b| {
119b.is_ascii_alphabetic()
120 .then_some(true)
121 .or_else(|| b.is_ascii_digit().then_some(false))
122 })
123 .ok_or(ParseError::InvalidExtension)?;
124let region_len = if is_alpha { 2 } else { 3 };
125let (region_code_units, suffix_code_units) = code_units126 .split_at_checked(region_len)
127 .ok_or(ParseError::InvalidExtension)?;
128let region =
129Region::try_from_utf8(region_code_units).map_err(|_| ParseError::InvalidExtension)?;
130let suffix = SubdivisionSuffix::try_from_utf8(suffix_code_units)?;
131Ok(Self { region, suffix })
132 }
133134/// Convert to [`Subtag`]
135pub fn into_subtag(self) -> Subtag {
136let result = self.region.to_tinystr().concat(self.suffix.to_tinystr());
137Subtag::from_tinystr_unvalidated(result)
138 }
139}
140141impl writeable::Writeablefor SubdivisionId {
142#[inline]
143fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
144sink.write_str(self.region.to_tinystr().to_ascii_lowercase().as_str())?;
145sink.write_str(self.suffix.as_str())
146 }
147148#[inline]
149fn writeable_length_hint(&self) -> writeable::LengthHint {
150self.region.writeable_length_hint() + self.suffix.writeable_length_hint()
151 }
152}
153154/// This trait is implemented for compatibility with [`fmt!`](alloc::fmt).
/// To create a string, [`Writeable::write_to_string`] is usually more efficient.
impl core::fmt::Display for SubdivisionId {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
::writeable::Writeable::write_to(&self, f)
}
}
impl SubdivisionId {
/// Converts the given value to a `String`.
///
/// Under the hood, this uses an efficient [`Writeable`] implementation.
/// However, in order to avoid allocating a string, it is more efficient
/// to use [`Writeable`] directly.
pub fn to_string(&self) -> ::writeable::_internal::String {
::writeable::Writeable::write_to_string(self).into_owned()
}
}writeable::impl_display_with_writeable!(SubdivisionId);
155156impl FromStrfor SubdivisionId {
157type Err = ParseError;
158159#[inline]
160fn from_str(s: &str) -> Result<Self, Self::Err> {
161Self::try_from_str(s)
162 }
163}
164165#[cfg(test)]
166mod tests {
167use super::*;
168169#[test]
170fn test_subdivisionid_fromstr() {
171let si: SubdivisionId = "gbzzzz".parse().expect("Failed to parse SubdivisionId");
172assert_eq!(si.region.to_string(), "GB");
173assert_eq!(si.suffix.to_string(), "zzzz");
174assert_eq!(si.to_string(), "gbzzzz");
175176for sample in ["", "gb", "o"] {
177let oe: Result<SubdivisionId, _> = sample.parse();
178assert!(oe.is_err(), "Should fail: {}", sample);
179 }
180 }
181}