| 1 | //! `types` module contains types necessary for Fluent runtime | 
| 2 | //! value handling. | 
|---|
| 3 | //! The core struct is [`FluentValue`] which is a type that can be passed | 
|---|
| 4 | //! to the [`FluentBundle::format_pattern`](crate::bundle::FluentBundle) as an argument, it can be passed | 
|---|
| 5 | //! to any Fluent Function, and any function may return it. | 
|---|
| 6 | //! | 
|---|
| 7 | //! This part of functionality is not fully hashed out yet, since we're waiting | 
|---|
| 8 | //! for the internationalization APIs to mature, at which point all number | 
|---|
| 9 | //! formatting operations will be moved out of Fluent. | 
|---|
| 10 | //! | 
|---|
| 11 | //! For now, [`FluentValue`] can be a string, a number, or a custom [`FluentType`] | 
|---|
| 12 | //! which allows users of the library to implement their own types of values, | 
|---|
| 13 | //! such as dates, or more complex structures needed for their bindings. | 
|---|
| 14 | mod number; | 
|---|
| 15 | mod plural; | 
|---|
| 16 |  | 
|---|
| 17 | pub use number::*; | 
|---|
| 18 | use plural::PluralRules; | 
|---|
| 19 |  | 
|---|
| 20 | use std::any::Any; | 
|---|
| 21 | use std::borrow::{Borrow, Cow}; | 
|---|
| 22 | use std::fmt; | 
|---|
| 23 | use std::str::FromStr; | 
|---|
| 24 |  | 
|---|
| 25 | use intl_pluralrules::{PluralCategory, PluralRuleType}; | 
|---|
| 26 |  | 
|---|
| 27 | use crate::memoizer::MemoizerKind; | 
|---|
| 28 | use crate::resolver::Scope; | 
|---|
| 29 | use crate::resource::FluentResource; | 
|---|
| 30 |  | 
|---|
| 31 | /// Custom types can implement the [`FluentType`] trait in order to generate a string | 
|---|
| 32 | /// value for use in the message generation process. | 
|---|
| 33 | pub trait FluentType: fmt::Debug + AnyEq + 'static { | 
|---|
| 34 | /// Create a clone of the underlying type. | 
|---|
| 35 | fn duplicate(&self) -> Box<dyn FluentType + Send>; | 
|---|
| 36 |  | 
|---|
| 37 | /// Convert the custom type into a string value, for instance a custom DateTime | 
|---|
| 38 | /// type could return "Oct. 27, 2022". | 
|---|
| 39 | fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str>; | 
|---|
| 40 |  | 
|---|
| 41 | /// Convert the custom type into a string value, for instance a custom DateTime | 
|---|
| 42 | /// type could return "Oct. 27, 2022". This operation is provided the threadsafe | 
|---|
| 43 | /// [IntlLangMemoizer](intl_memoizer::concurrent::IntlLangMemoizer). | 
|---|
| 44 | fn as_string_threadsafe( | 
|---|
| 45 | &self, | 
|---|
| 46 | intls: &intl_memoizer::concurrent::IntlLangMemoizer, | 
|---|
| 47 | ) -> Cow<'static, str>; | 
|---|
| 48 | } | 
|---|
| 49 |  | 
|---|
| 50 | impl PartialEq for dyn FluentType + Send { | 
|---|
| 51 | fn eq(&self, other: &Self) -> bool { | 
|---|
| 52 | self.equals(other.as_any()) | 
|---|
| 53 | } | 
|---|
| 54 | } | 
|---|
| 55 |  | 
|---|
| 56 | pub trait AnyEq: Any + 'static { | 
|---|
| 57 | fn equals(&self, other: &dyn Any) -> bool; | 
|---|
| 58 | fn as_any(&self) -> &dyn Any; | 
|---|
| 59 | } | 
|---|
| 60 |  | 
|---|
| 61 | impl<T: Any + PartialEq> AnyEq for T { | 
|---|
| 62 | fn equals(&self, other: &dyn Any) -> bool { | 
|---|
| 63 | other | 
|---|
| 64 | .downcast_ref::<Self>() | 
|---|
| 65 | .map_or(default:false, |that: &T| self == that) | 
|---|
| 66 | } | 
|---|
| 67 | fn as_any(&self) -> &dyn Any { | 
|---|
| 68 | self | 
|---|
| 69 | } | 
|---|
| 70 | } | 
|---|
| 71 |  | 
|---|
| 72 | /// The `FluentValue` enum represents values which can be formatted to a String. | 
|---|
| 73 | /// | 
|---|
| 74 | /// Those values are either passed as arguments to [`FluentBundle::format_pattern`] or | 
|---|
| 75 | /// produced by functions, or generated in the process of pattern resolution. | 
|---|
| 76 | /// | 
|---|
| 77 | /// [`FluentBundle::format_pattern`]: crate::bundle::FluentBundle::format_pattern | 
|---|
| 78 | #[ derive(Debug)] | 
|---|
| 79 | pub enum FluentValue<'source> { | 
|---|
| 80 | String(Cow<'source, str>), | 
|---|
| 81 | Number(FluentNumber), | 
|---|
| 82 | Custom(Box<dyn FluentType + Send>), | 
|---|
| 83 | None, | 
|---|
| 84 | Error, | 
|---|
| 85 | } | 
|---|
| 86 |  | 
|---|
| 87 | impl<'s> PartialEq for FluentValue<'s> { | 
|---|
| 88 | fn eq(&self, other: &Self) -> bool { | 
|---|
| 89 | match (self, other) { | 
|---|
| 90 | (FluentValue::String(s: &Cow<'_, str>), FluentValue::String(s2: &Cow<'_, str>)) => s == s2, | 
|---|
| 91 | (FluentValue::Number(s: &FluentNumber), FluentValue::Number(s2: &FluentNumber)) => s == s2, | 
|---|
| 92 | (FluentValue::Custom(s: &Box), FluentValue::Custom(s2: &Box)) => s == s2, | 
|---|
| 93 | _ => false, | 
|---|
| 94 | } | 
|---|
| 95 | } | 
|---|
| 96 | } | 
|---|
| 97 |  | 
|---|
| 98 | impl<'s> Clone for FluentValue<'s> { | 
|---|
| 99 | fn clone(&self) -> Self { | 
|---|
| 100 | match self { | 
|---|
| 101 | FluentValue::String(s: &Cow<'_, str>) => FluentValue::String(s.clone()), | 
|---|
| 102 | FluentValue::Number(s: &FluentNumber) => FluentValue::Number(s.clone()), | 
|---|
| 103 | FluentValue::Custom(s: &Box) => { | 
|---|
| 104 | let new_value: Box<dyn FluentType + Send> = s.duplicate(); | 
|---|
| 105 | FluentValue::Custom(new_value) | 
|---|
| 106 | } | 
|---|
| 107 | FluentValue::Error => FluentValue::Error, | 
|---|
| 108 | FluentValue::None => FluentValue::None, | 
|---|
| 109 | } | 
|---|
| 110 | } | 
|---|
| 111 | } | 
|---|
| 112 |  | 
|---|
| 113 | impl<'source> FluentValue<'source> { | 
|---|
| 114 | /// Attempts to parse the string representation of a `value` that supports | 
|---|
| 115 | /// [`ToString`] into a [`FluentValue::Number`]. If it fails, it will instead | 
|---|
| 116 | /// convert it to a [`FluentValue::String`]. | 
|---|
| 117 | /// | 
|---|
| 118 | /// ``` | 
|---|
| 119 | /// use fluent_bundle::types::{FluentNumber, FluentNumberOptions, FluentValue}; | 
|---|
| 120 | /// | 
|---|
| 121 | /// // "2" parses into a `FluentNumber` | 
|---|
| 122 | /// assert_eq!( | 
|---|
| 123 | ///     FluentValue::try_number( "2"), | 
|---|
| 124 | ///     FluentValue::Number(FluentNumber::new(2.0, FluentNumberOptions::default())) | 
|---|
| 125 | /// ); | 
|---|
| 126 | /// | 
|---|
| 127 | /// // Floats can be parsed as well. | 
|---|
| 128 | /// assert_eq!( | 
|---|
| 129 | ///     FluentValue::try_number( "3.141569"), | 
|---|
| 130 | ///     FluentValue::Number(FluentNumber::new( | 
|---|
| 131 | ///         3.141569, | 
|---|
| 132 | ///         FluentNumberOptions { | 
|---|
| 133 | ///             minimum_fraction_digits: Some(6), | 
|---|
| 134 | ///             ..Default::default() | 
|---|
| 135 | ///         } | 
|---|
| 136 | ///     )) | 
|---|
| 137 | /// ); | 
|---|
| 138 | /// | 
|---|
| 139 | /// // When a value is not a valid number, it falls back to a `FluentValue::String` | 
|---|
| 140 | /// assert_eq!( | 
|---|
| 141 | ///     FluentValue::try_number( "A string"), | 
|---|
| 142 | ///     FluentValue::String( "A string".into()) | 
|---|
| 143 | /// ); | 
|---|
| 144 | /// ``` | 
|---|
| 145 | pub fn try_number(value: &'source str) -> Self { | 
|---|
| 146 | if let Ok(number) = FluentNumber::from_str(value) { | 
|---|
| 147 | number.into() | 
|---|
| 148 | } else { | 
|---|
| 149 | value.into() | 
|---|
| 150 | } | 
|---|
| 151 | } | 
|---|
| 152 |  | 
|---|
| 153 | /// Checks to see if two [`FluentValues`](FluentValue) match each other by having the | 
|---|
| 154 | /// same type and contents. The special exception is in the case of a string being | 
|---|
| 155 | /// compared to a number. Here attempt to check that the plural rule category matches. | 
|---|
| 156 | /// | 
|---|
| 157 | /// ``` | 
|---|
| 158 | /// use fluent_bundle::resolver::Scope; | 
|---|
| 159 | /// use fluent_bundle::{types::FluentValue, FluentBundle, FluentResource}; | 
|---|
| 160 | /// use unic_langid::langid; | 
|---|
| 161 | /// | 
|---|
| 162 | /// let langid_ars = langid!( "en"); | 
|---|
| 163 | /// let bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![langid_ars]); | 
|---|
| 164 | /// let scope = Scope::new(&bundle, None, None); | 
|---|
| 165 | /// | 
|---|
| 166 | /// // Matching examples: | 
|---|
| 167 | /// assert!(FluentValue::try_number( "2").matches(&FluentValue::try_number( "2"), &scope)); | 
|---|
| 168 | /// assert!(FluentValue::from( "fluent").matches(&FluentValue::from( "fluent"), &scope)); | 
|---|
| 169 | /// assert!( | 
|---|
| 170 | ///     FluentValue::from( "one").matches(&FluentValue::try_number( "1"), &scope), | 
|---|
| 171 | /// "Plural rules are matched." | 
|---|
| 172 | /// ); | 
|---|
| 173 | /// | 
|---|
| 174 | /// // Non-matching examples: | 
|---|
| 175 | /// assert!(!FluentValue::try_number( "2").matches(&FluentValue::try_number( "3"), &scope)); | 
|---|
| 176 | /// assert!(!FluentValue::from( "fluent").matches(&FluentValue::from( "not fluent"), &scope)); | 
|---|
| 177 | /// assert!(!FluentValue::from( "two").matches(&FluentValue::try_number( "100"), &scope),); | 
|---|
| 178 | /// ``` | 
|---|
| 179 | pub fn matches<R: Borrow<FluentResource>, M>( | 
|---|
| 180 | &self, | 
|---|
| 181 | other: &FluentValue, | 
|---|
| 182 | scope: &Scope<R, M>, | 
|---|
| 183 | ) -> bool | 
|---|
| 184 | where | 
|---|
| 185 | M: MemoizerKind, | 
|---|
| 186 | { | 
|---|
| 187 | match (self, other) { | 
|---|
| 188 | (&FluentValue::String(ref a), &FluentValue::String(ref b)) => a == b, | 
|---|
| 189 | (&FluentValue::Number(ref a), &FluentValue::Number(ref b)) => a == b, | 
|---|
| 190 | (&FluentValue::String(ref a), &FluentValue::Number(ref b)) => { | 
|---|
| 191 | let cat = match a.as_ref() { | 
|---|
| 192 | "zero"=> PluralCategory::ZERO, | 
|---|
| 193 | "one"=> PluralCategory::ONE, | 
|---|
| 194 | "two"=> PluralCategory::TWO, | 
|---|
| 195 | "few"=> PluralCategory::FEW, | 
|---|
| 196 | "many"=> PluralCategory::MANY, | 
|---|
| 197 | "other"=> PluralCategory::OTHER, | 
|---|
| 198 | _ => return false, | 
|---|
| 199 | }; | 
|---|
| 200 | // This string matches a plural rule keyword. Check if the number | 
|---|
| 201 | // matches the plural rule category. | 
|---|
| 202 | scope | 
|---|
| 203 | .bundle | 
|---|
| 204 | .intls | 
|---|
| 205 | .with_try_get_threadsafe::<PluralRules, _, _>( | 
|---|
| 206 | (PluralRuleType::CARDINAL,), | 
|---|
| 207 | |pr| pr.0.select(b) == Ok(cat), | 
|---|
| 208 | ) | 
|---|
| 209 | .unwrap() | 
|---|
| 210 | } | 
|---|
| 211 | _ => false, | 
|---|
| 212 | } | 
|---|
| 213 | } | 
|---|
| 214 |  | 
|---|
| 215 | /// Write out a string version of the [`FluentValue`] to `W`. | 
|---|
| 216 | pub fn write<W, R, M>(&self, w: &mut W, scope: &Scope<R, M>) -> fmt::Result | 
|---|
| 217 | where | 
|---|
| 218 | W: fmt::Write, | 
|---|
| 219 | R: Borrow<FluentResource>, | 
|---|
| 220 | M: MemoizerKind, | 
|---|
| 221 | { | 
|---|
| 222 | if let Some(formatter) = &scope.bundle.formatter { | 
|---|
| 223 | if let Some(val) = formatter(self, &scope.bundle.intls) { | 
|---|
| 224 | return w.write_str(&val); | 
|---|
| 225 | } | 
|---|
| 226 | } | 
|---|
| 227 | match self { | 
|---|
| 228 | FluentValue::String(s) => w.write_str(s), | 
|---|
| 229 | FluentValue::Number(n) => w.write_str(&n.as_string()), | 
|---|
| 230 | FluentValue::Custom(s) => w.write_str(&scope.bundle.intls.stringify_value(&**s)), | 
|---|
| 231 | FluentValue::Error => Ok(()), | 
|---|
| 232 | FluentValue::None => Ok(()), | 
|---|
| 233 | } | 
|---|
| 234 | } | 
|---|
| 235 |  | 
|---|
| 236 | /// Converts the [`FluentValue`] to a string. | 
|---|
| 237 | /// | 
|---|
| 238 | /// Clones inner values when owned, borrowed data is not cloned. | 
|---|
| 239 | /// Prefer using [`FluentValue::into_string()`] when possible. | 
|---|
| 240 | pub fn as_string<R: Borrow<FluentResource>, M>(&self, scope: &Scope<R, M>) -> Cow<'source, str> | 
|---|
| 241 | where | 
|---|
| 242 | M: MemoizerKind, | 
|---|
| 243 | { | 
|---|
| 244 | if let Some(formatter) = &scope.bundle.formatter { | 
|---|
| 245 | if let Some(val) = formatter(self, &scope.bundle.intls) { | 
|---|
| 246 | return val.into(); | 
|---|
| 247 | } | 
|---|
| 248 | } | 
|---|
| 249 | match self { | 
|---|
| 250 | FluentValue::String(s) => s.clone(), | 
|---|
| 251 | FluentValue::Number(n) => n.as_string(), | 
|---|
| 252 | FluentValue::Custom(s) => scope.bundle.intls.stringify_value(&**s), | 
|---|
| 253 | FluentValue::Error => "".into(), | 
|---|
| 254 | FluentValue::None => "".into(), | 
|---|
| 255 | } | 
|---|
| 256 | } | 
|---|
| 257 |  | 
|---|
| 258 | /// Converts the [`FluentValue`] to a string. | 
|---|
| 259 | /// | 
|---|
| 260 | /// Takes self by-value to be able to skip expensive clones. | 
|---|
| 261 | /// Prefer this method over [`FluentValue::as_string()`] when possible. | 
|---|
| 262 | pub fn into_string<R: Borrow<FluentResource>, M>(self, scope: &Scope<R, M>) -> Cow<'source, str> | 
|---|
| 263 | where | 
|---|
| 264 | M: MemoizerKind, | 
|---|
| 265 | { | 
|---|
| 266 | if let Some(formatter) = &scope.bundle.formatter { | 
|---|
| 267 | if let Some(val) = formatter(&self, &scope.bundle.intls) { | 
|---|
| 268 | return val.into(); | 
|---|
| 269 | } | 
|---|
| 270 | } | 
|---|
| 271 | match self { | 
|---|
| 272 | FluentValue::String(s) => s, | 
|---|
| 273 | FluentValue::Number(n) => n.as_string(), | 
|---|
| 274 | FluentValue::Custom(s) => scope.bundle.intls.stringify_value(s.as_ref()), | 
|---|
| 275 | FluentValue::Error => "".into(), | 
|---|
| 276 | FluentValue::None => "".into(), | 
|---|
| 277 | } | 
|---|
| 278 | } | 
|---|
| 279 |  | 
|---|
| 280 | pub fn into_owned<'a>(&self) -> FluentValue<'a> { | 
|---|
| 281 | match self { | 
|---|
| 282 | FluentValue::String(str) => FluentValue::String(Cow::from(str.to_string())), | 
|---|
| 283 | FluentValue::Number(s) => FluentValue::Number(s.clone()), | 
|---|
| 284 | FluentValue::Custom(s) => FluentValue::Custom(s.duplicate()), | 
|---|
| 285 | FluentValue::Error => FluentValue::Error, | 
|---|
| 286 | FluentValue::None => FluentValue::None, | 
|---|
| 287 | } | 
|---|
| 288 | } | 
|---|
| 289 | } | 
|---|
| 290 |  | 
|---|
| 291 | impl<'source> From<String> for FluentValue<'source> { | 
|---|
| 292 | fn from(s: String) -> Self { | 
|---|
| 293 | FluentValue::String(s.into()) | 
|---|
| 294 | } | 
|---|
| 295 | } | 
|---|
| 296 |  | 
|---|
| 297 | impl<'source> From<&'source String> for FluentValue<'source> { | 
|---|
| 298 | fn from(s: &'source String) -> Self { | 
|---|
| 299 | FluentValue::String(s.into()) | 
|---|
| 300 | } | 
|---|
| 301 | } | 
|---|
| 302 |  | 
|---|
| 303 | impl<'source> From<&'source str> for FluentValue<'source> { | 
|---|
| 304 | fn from(s: &'source str) -> Self { | 
|---|
| 305 | FluentValue::String(s.into()) | 
|---|
| 306 | } | 
|---|
| 307 | } | 
|---|
| 308 |  | 
|---|
| 309 | impl<'source> From<Cow<'source, str>> for FluentValue<'source> { | 
|---|
| 310 | fn from(s: Cow<'source, str>) -> Self { | 
|---|
| 311 | FluentValue::String(s) | 
|---|
| 312 | } | 
|---|
| 313 | } | 
|---|
| 314 |  | 
|---|
| 315 | impl<'source, T> From<Option<T>> for FluentValue<'source> | 
|---|
| 316 | where | 
|---|
| 317 | T: Into<FluentValue<'source>>, | 
|---|
| 318 | { | 
|---|
| 319 | fn from(v: Option<T>) -> Self { | 
|---|
| 320 | match v { | 
|---|
| 321 | Some(v: T) => v.into(), | 
|---|
| 322 | None => FluentValue::None, | 
|---|
| 323 | } | 
|---|
| 324 | } | 
|---|
| 325 | } | 
|---|
| 326 |  | 
|---|