icu_locale_core/extensions/transform/
value.rs1use crate::parser::ParseError;
6#[cfg(feature = "alloc")]
7use crate::parser::SubtagIterator;
8use crate::shortvec::ShortBoxSlice;
9use crate::subtags::{subtag, Subtag};
10use core::ops::RangeInclusive;
11#[cfg(feature = "alloc")]
12use core::str::FromStr;
13
14#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Value {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Value",
&&self.0)
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Value {
#[inline]
fn eq(&self, other: &Value) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Value {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) -> () {
let _: ::core::cmp::AssertParamIsEq<ShortBoxSlice<Subtag>>;
}
}Eq, #[automatically_derived]
impl ::core::clone::Clone for Value {
#[inline]
fn clone(&self) -> Value { Value(::core::clone::Clone::clone(&self.0)) }
}Clone, #[automatically_derived]
impl ::core::hash::Hash for Value {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
::core::hash::Hash::hash(&self.0, state)
}
}Hash, #[automatically_derived]
impl ::core::cmp::PartialOrd for Value {
#[inline]
fn partial_cmp(&self, other: &Value)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0)
}
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for Value {
#[inline]
fn cmp(&self, other: &Value) -> ::core::cmp::Ordering {
::core::cmp::Ord::cmp(&self.0, &other.0)
}
}Ord, #[automatically_derived]
impl ::core::default::Default for Value {
#[inline]
fn default() -> Value { Value(::core::default::Default::default()) }
}Default)]
33pub struct Value(ShortBoxSlice<Subtag>);
34
35#[allow(dead_code)]
36const TYPE_LENGTH: RangeInclusive<usize> = 3..=8;
37const TRUE_TVALUE: Subtag = const {
use crate::subtags::Subtag;
match Subtag::try_from_utf8("true".as_bytes()) {
Ok(r) => r,
_ => {
::core::panicking::panic_fmt(format_args!("Invalid subtags::Subtag: true"));
}
}
}subtag!("true");
38
39impl Value {
40 #[inline]
53 #[cfg(feature = "alloc")]
54 pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
55 Self::try_from_utf8(s.as_bytes())
56 }
57
58 #[cfg(feature = "alloc")]
62 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
63 let mut v = ShortBoxSlice::default();
64 let mut has_value = false;
65
66 for subtag in SubtagIterator::new(code_units) {
67 if !Self::is_type_subtag(subtag) {
68 return Err(ParseError::InvalidExtension);
69 }
70 has_value = true;
71 let val = Subtag::try_from_utf8(subtag).map_err(|_| ParseError::InvalidExtension)?;
72 if val != TRUE_TVALUE {
73 v.push(val);
74 }
75 }
76
77 if !has_value {
78 return Err(ParseError::InvalidExtension);
79 }
80 Ok(Self(v))
81 }
82
83 #[allow(dead_code)]
84 pub(crate) fn from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self {
85 Self(input)
86 }
87
88 #[allow(dead_code)]
89 pub(crate) fn is_type_subtag(t: &[u8]) -> bool {
90 TYPE_LENGTH.contains(&t.len()) && t.iter().all(u8::is_ascii_alphanumeric)
91 }
92
93 #[allow(dead_code)]
94 pub(crate) fn parse_subtag(t: &[u8]) -> Result<Option<Subtag>, ParseError> {
95 if !TYPE_LENGTH.contains(&t.len()) {
96 return Err(ParseError::InvalidExtension);
97 }
98 let s = Subtag::try_from_utf8(t).map_err(|_| ParseError::InvalidSubtag)?;
99
100 let s = s.to_ascii_lowercase();
101
102 if s == TRUE_TVALUE {
103 Ok(None)
104 } else {
105 Ok(Some(s))
106 }
107 }
108
109 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
110 where
111 F: FnMut(&str) -> Result<(), E>,
112 {
113 if self.0.is_empty() {
114 f(TRUE_TVALUE.as_str())?;
115 } else {
116 self.0.iter().map(Subtag::as_str).try_for_each(f)?;
117 }
118 Ok(())
119 }
120}
121
122#[cfg(feature = "alloc")]
124impl FromStr for Value {
125 type Err = ParseError;
126
127 #[inline]
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 Self::try_from_str(s)
130 }
131}
132
133impl writeable::Writeable for Value {
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W)
-> core::fmt::Result {
let mut initial = true;
self.for_each_subtag_str(&mut |subtag|
{
if initial {
initial = false;
} else { sink.write_char('-')?; }
sink.write_str(subtag)
})
}
#[inline]
fn writeable_length_hint(&self) -> writeable::LengthHint {
let mut result = writeable::LengthHint::exact(0);
let mut initial = true;
self.for_each_subtag_str::<core::convert::Infallible,
_>(&mut |subtag|
{
if initial { initial = false; } else { result += 1; }
result += subtag.len();
Ok(())
}).expect("infallible");
result
}
fn writeable_borrow(&self) -> Option<&str> {
let selff = self;
if selff.0.is_empty() { Some("true") } else { None }
}
}
impl core::fmt::Display for Value {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
::writeable::Writeable::write_to(&self, f)
}
}impl_writeable_for_each_subtag_str_no_test!(Value, selff, selff.0.is_empty() => Some("true"));
134
135#[test]
136fn test_writeable() {
137 use writeable::assert_writeable_eq;
138
139 let hybrid = "hybrid".parse().unwrap();
140 let foobar = "foobar".parse().unwrap();
141
142 assert_writeable_eq!(Value::default(), "true");
143 assert_writeable_eq!(
144 Value::from_short_slice_unchecked(vec![hybrid].into()),
145 "hybrid"
146 );
147 assert_writeable_eq!(
148 Value::from_short_slice_unchecked(vec![hybrid, foobar].into()),
149 "hybrid-foobar"
150 );
151}
152
153#[test]
154fn test_short_tvalue() {
155 let value = Value::try_from_str("foo-longstag");
156 assert!(value.is_ok());
157 let value = value.unwrap();
158 assert_eq!(value.0.len(), 2);
159 for (s, reference) in value.0.iter().zip(&[subtag!("foo"), subtag!("longstag")]) {
160 assert_eq!(s, reference);
161 }
162
163 let value = Value::try_from_str("foo-ba");
164 assert!(value.is_err());
165}