icu_locale_core/extensions/other/
mod.rs

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 ).
4
5//! Other Use Extensions is a list of extensions other than unicode,
6//! transform or private.
7//!
8//! Those extensions are treated as a pass-through, and no Unicode related
9//! behavior depends on them.
10//!
11//! The main struct for this extension is [`Other`] which is a list of [`Subtag`]s.
12//!
13//! # Examples
14//!
15//! ```
16//! use icu::locale::extensions::other::Other;
17//! use icu::locale::Locale;
18//!
19//! let mut loc: Locale = "en-US-a-foo-faa".parse().expect("Parsing failed.");
20//! ```
21
22#[cfg(feature = "alloc")]
23use core::str::FromStr;
24
25#[cfg(feature = "alloc")]
26use super::ExtensionType;
27#[cfg(feature = "alloc")]
28use crate::parser::ParseError;
29#[cfg(feature = "alloc")]
30use crate::parser::SubtagIterator;
31use crate::shortvec::ShortBoxSlice;
32use crate::subtags::Subtag;
33#[cfg(feature = "alloc")]
34use alloc::vec::Vec;
35
36/// A list of [`Other Use Extensions`] as defined in [`Unicode Locale
37/// Identifier`] specification.
38///
39/// Those extensions are treated as a pass-through, and no Unicode related
40/// behavior depends on them.
41///
42/// # Examples
43///
44/// ```
45/// use icu::locale::extensions::other::Other;
46/// use icu::locale::subtags::Subtag;
47///
48/// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
49/// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
50///
51/// let other = Other::from_vec_unchecked(b'a', vec![subtag1, subtag2]);
52/// assert_eq!(&other.to_string(), "a-foo-bar");
53/// ```
54///
55/// [`Other Use Extensions`]: https://unicode.org/reports/tr35/#other_extensions
56/// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/#Unicode_locale_identifier
57#[derive(#[automatically_derived]
impl ::core::clone::Clone for Other {
    #[inline]
    fn clone(&self) -> Other {
        Other {
            ext: ::core::clone::Clone::clone(&self.ext),
            keys: ::core::clone::Clone::clone(&self.keys),
        }
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for Other {
    #[inline]
    fn eq(&self, other: &Other) -> bool {
        self.ext == other.ext && self.keys == other.keys
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Other {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) -> () {
        let _: ::core::cmp::AssertParamIsEq<u8>;
        let _: ::core::cmp::AssertParamIsEq<ShortBoxSlice<Subtag>>;
    }
}Eq, #[automatically_derived]
impl ::core::fmt::Debug for Other {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "Other", "ext",
            &self.ext, "keys", &&self.keys)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for Other {
    #[inline]
    fn default() -> Other {
        Other {
            ext: ::core::default::Default::default(),
            keys: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl ::core::hash::Hash for Other {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
        ::core::hash::Hash::hash(&self.ext, state);
        ::core::hash::Hash::hash(&self.keys, state)
    }
}Hash, #[automatically_derived]
impl ::core::cmp::PartialOrd for Other {
    #[inline]
    fn partial_cmp(&self, other: &Other)
        -> ::core::option::Option<::core::cmp::Ordering> {
        match ::core::cmp::PartialOrd::partial_cmp(&self.ext, &other.ext) {
            ::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
                ::core::cmp::PartialOrd::partial_cmp(&self.keys, &other.keys),
            cmp => cmp,
        }
    }
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for Other {
    #[inline]
    fn cmp(&self, other: &Other) -> ::core::cmp::Ordering {
        match ::core::cmp::Ord::cmp(&self.ext, &other.ext) {
            ::core::cmp::Ordering::Equal =>
                ::core::cmp::Ord::cmp(&self.keys, &other.keys),
            cmp => cmp,
        }
    }
}Ord)]
58pub struct Other {
59    // Safety invariant: must be ASCII
60    ext: u8,
61    keys: ShortBoxSlice<Subtag>,
62}
63
64impl Other {
65    /// A constructor which takes a str slice, parses it and
66    /// produces a well-formed [`Other`].
67    ///
68    /// ✨ *Enabled with the `alloc` Cargo feature.*
69    #[inline]
70    #[cfg(feature = "alloc")]
71    pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
72        Self::try_from_utf8(s.as_bytes())
73    }
74
75    /// See [`Self::try_from_str`]
76    ///
77    /// ✨ *Enabled with the `alloc` Cargo feature.*
78    #[cfg(feature = "alloc")]
79    pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
80        let mut iter = SubtagIterator::new(code_units);
81
82        let ext = iter.next().ok_or(ParseError::InvalidExtension)?;
83        if let ExtensionType::Other(b) = ExtensionType::try_from_byte_slice(ext)? {
84            return Self::try_from_iter(b, &mut iter);
85        }
86
87        Err(ParseError::InvalidExtension)
88    }
89
90    /// A constructor which takes a pre-sorted list of [`Subtag`].
91    ///
92    /// ✨ *Enabled with the `alloc` Cargo feature.*
93    ///
94    /// # Panics
95    ///
96    /// Panics if `ext` is not ASCII alphabetic.
97    ///
98    /// # Examples
99    ///
100    /// ```
101    /// use icu::locale::extensions::other::Other;
102    /// use icu::locale::subtags::Subtag;
103    ///
104    /// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
105    /// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
106    ///
107    /// let other = Other::from_vec_unchecked(b'a', vec![subtag1, subtag2]);
108    /// assert_eq!(&other.to_string(), "a-foo-bar");
109    /// ```
110    #[cfg(feature = "alloc")]
111    pub fn from_vec_unchecked(ext: u8, keys: Vec<Subtag>) -> Self {
112        Self::from_short_slice_unchecked(ext, keys.into())
113    }
114
115    #[allow(dead_code)]
116    pub(crate) fn from_short_slice_unchecked(ext: u8, keys: ShortBoxSlice<Subtag>) -> Self {
117        if !ext.is_ascii_alphabetic() {
    ::core::panicking::panic("assertion failed: ext.is_ascii_alphabetic()")
};assert!(ext.is_ascii_alphabetic());
118        // Safety invariant upheld here: ext checked as ASCII above
119        Self { ext, keys }
120    }
121
122    #[cfg(feature = "alloc")]
123    pub(crate) fn try_from_iter(ext: u8, iter: &mut SubtagIterator) -> Result<Self, ParseError> {
124        debug_assert!(matches!(
125            ExtensionType::try_from_byte(ext),
126            Ok(ExtensionType::Other(_)),
127        ));
128
129        let mut keys = ShortBoxSlice::new();
130        while let Some(subtag) = iter.peek() {
131            if !Subtag::valid_key(subtag) {
132                break;
133            }
134            if let Ok(key) = Subtag::try_from_utf8(subtag) {
135                keys.push(key);
136            }
137            iter.next();
138        }
139
140        if keys.is_empty() {
141            Err(ParseError::InvalidExtension)
142        } else {
143            Ok(Self::from_short_slice_unchecked(ext, keys))
144        }
145    }
146
147    /// Gets the tag character for this extension as a &str.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use icu::locale::Locale;
153    ///
154    /// let loc: Locale = "und-a-hello-world".parse().unwrap();
155    /// let other_ext = &loc.extensions.other[0];
156    /// assert_eq!(other_ext.get_ext_str(), "a");
157    /// ```
158    pub fn get_ext_str(&self) -> &str {
159        if true {
    if !self.ext.is_ascii_alphabetic() {
        ::core::panicking::panic("assertion failed: self.ext.is_ascii_alphabetic()")
    };
};debug_assert!(self.ext.is_ascii_alphabetic());
160        // Safety: from safety invariant on self.ext (that it is ASCII)
161        unsafe { core::str::from_utf8_unchecked(core::slice::from_ref(&self.ext)) }
162    }
163
164    /// Gets the tag character for this extension as a char.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use icu::locale::Locale;
170    ///
171    /// let loc: Locale = "und-a-hello-world".parse().unwrap();
172    /// let other_ext = &loc.extensions.other[0];
173    /// assert_eq!(other_ext.get_ext(), 'a');
174    /// ```
175    pub fn get_ext(&self) -> char {
176        self.ext as char
177    }
178
179    /// Gets the tag character for this extension as a byte.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use icu::locale::Locale;
185    ///
186    /// let loc: Locale = "und-a-hello-world".parse().unwrap();
187    /// let other_ext = &loc.extensions.other[0];
188    /// assert_eq!(other_ext.get_ext_byte(), b'a');
189    /// ```
190    pub fn get_ext_byte(&self) -> u8 {
191        self.ext
192    }
193
194    pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F, with_ext: bool) -> Result<(), E>
195    where
196        F: FnMut(&str) -> Result<(), E>,
197    {
198        if self.keys.is_empty() {
199            return Ok(());
200        }
201
202        if with_ext {
203            f(self.get_ext_str())?;
204        }
205        self.keys.iter().map(|t| t.as_str()).try_for_each(f)
206    }
207}
208
209/// ✨ *Enabled with the `alloc` Cargo feature.*
210#[cfg(feature = "alloc")]
211impl FromStr for Other {
212    type Err = ParseError;
213
214    #[inline]
215    fn from_str(s: &str) -> Result<Self, Self::Err> {
216        Self::try_from_str(s)
217    }
218}
219
220/// 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 Other {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        ::writeable::Writeable::write_to(&self, f)
    }
}writeable::impl_display_with_writeable!(Other, #[cfg(feature = "alloc")]);
221
222impl writeable::Writeable for Other {
223    fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
224        if self.keys.is_empty() {
225            return Ok(());
226        }
227        sink.write_str(self.get_ext_str())?;
228        for key in self.keys.iter() {
229            sink.write_char('-')?;
230            writeable::Writeable::write_to(key, sink)?;
231        }
232
233        Ok(())
234    }
235
236    fn writeable_length_hint(&self) -> writeable::LengthHint {
237        if self.keys.is_empty() {
238            return writeable::LengthHint::exact(0);
239        };
240        let mut result = writeable::LengthHint::exact(1);
241        for key in self.keys.iter() {
242            result += writeable::Writeable::writeable_length_hint(key) + 1;
243        }
244        result
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_other_extension_fromstr() {
254        let oe: Other = "o-foo-bar".parse().expect("Failed to parse Other");
255        assert_eq!(oe.to_string(), "o-foo-bar");
256
257        let oe: Result<Other, _> = "o".parse();
258        assert!(oe.is_err());
259    }
260}