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 | pub trait FluentType: fmt::Debug + AnyEq + 'static { |
32 | fn duplicate(&self) -> Box<dyn FluentType + Send>; |
33 | fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str>; |
34 | fn as_string_threadsafe( |
35 | &self, |
36 | intls: &intl_memoizer::concurrent::IntlLangMemoizer, |
37 | ) -> Cow<'static, str>; |
38 | } |
39 | |
40 | impl PartialEq for dyn FluentType + Send { |
41 | fn eq(&self, other: &Self) -> bool { |
42 | self.equals(other.as_any()) |
43 | } |
44 | } |
45 | |
46 | pub trait AnyEq: Any + 'static { |
47 | fn equals(&self, other: &dyn Any) -> bool; |
48 | fn as_any(&self) -> &dyn Any; |
49 | } |
50 | |
51 | impl<T: Any + PartialEq> AnyEq for T { |
52 | fn equals(&self, other: &dyn Any) -> bool { |
53 | other |
54 | .downcast_ref::<Self>() |
55 | .map_or(default:false, |that: &T| self == that) |
56 | } |
57 | fn as_any(&self) -> &dyn Any { |
58 | self |
59 | } |
60 | } |
61 | |
62 | /// The `FluentValue` enum represents values which can be formatted to a String. |
63 | /// |
64 | /// Those values are either passed as arguments to [`FluentBundle::format_pattern`][] or |
65 | /// produced by functions, or generated in the process of pattern resolution. |
66 | /// |
67 | /// [`FluentBundle::format_pattern`]: ../bundle/struct.FluentBundle.html#method.format_pattern |
68 | #[derive (Debug)] |
69 | pub enum FluentValue<'source> { |
70 | String(Cow<'source, str>), |
71 | Number(FluentNumber), |
72 | Custom(Box<dyn FluentType + Send>), |
73 | None, |
74 | Error, |
75 | } |
76 | |
77 | impl<'s> PartialEq for FluentValue<'s> { |
78 | fn eq(&self, other: &Self) -> bool { |
79 | match (self, other) { |
80 | (FluentValue::String(s: &Cow<'_, str>), FluentValue::String(s2: &Cow<'_, str>)) => s == s2, |
81 | (FluentValue::Number(s: &FluentNumber), FluentValue::Number(s2: &FluentNumber)) => s == s2, |
82 | (FluentValue::Custom(s: &Box), FluentValue::Custom(s2: &Box)) => s == s2, |
83 | _ => false, |
84 | } |
85 | } |
86 | } |
87 | |
88 | impl<'s> Clone for FluentValue<'s> { |
89 | fn clone(&self) -> Self { |
90 | match self { |
91 | FluentValue::String(s: &Cow<'_, str>) => FluentValue::String(s.clone()), |
92 | FluentValue::Number(s: &FluentNumber) => FluentValue::Number(s.clone()), |
93 | FluentValue::Custom(s: &Box) => { |
94 | let new_value: Box<dyn FluentType + Send> = s.duplicate(); |
95 | FluentValue::Custom(new_value) |
96 | } |
97 | FluentValue::Error => FluentValue::Error, |
98 | FluentValue::None => FluentValue::None, |
99 | } |
100 | } |
101 | } |
102 | |
103 | impl<'source> FluentValue<'source> { |
104 | pub fn try_number<S: ToString>(v: S) -> Self { |
105 | let s = v.to_string(); |
106 | if let Ok(num) = FluentNumber::from_str(&s) { |
107 | num.into() |
108 | } else { |
109 | s.into() |
110 | } |
111 | } |
112 | |
113 | pub fn matches<R: Borrow<FluentResource>, M>( |
114 | &self, |
115 | other: &FluentValue, |
116 | scope: &Scope<R, M>, |
117 | ) -> bool |
118 | where |
119 | M: MemoizerKind, |
120 | { |
121 | match (self, other) { |
122 | (&FluentValue::String(ref a), &FluentValue::String(ref b)) => a == b, |
123 | (&FluentValue::Number(ref a), &FluentValue::Number(ref b)) => a == b, |
124 | (&FluentValue::String(ref a), &FluentValue::Number(ref b)) => { |
125 | let cat = match a.as_ref() { |
126 | "zero" => PluralCategory::ZERO, |
127 | "one" => PluralCategory::ONE, |
128 | "two" => PluralCategory::TWO, |
129 | "few" => PluralCategory::FEW, |
130 | "many" => PluralCategory::MANY, |
131 | "other" => PluralCategory::OTHER, |
132 | _ => return false, |
133 | }; |
134 | scope |
135 | .bundle |
136 | .intls |
137 | .with_try_get_threadsafe::<PluralRules, _, _>( |
138 | (PluralRuleType::CARDINAL,), |
139 | |pr| pr.0.select(b) == Ok(cat), |
140 | ) |
141 | .unwrap() |
142 | } |
143 | _ => false, |
144 | } |
145 | } |
146 | |
147 | pub fn write<W, R, M>(&self, w: &mut W, scope: &Scope<R, M>) -> fmt::Result |
148 | where |
149 | W: fmt::Write, |
150 | R: Borrow<FluentResource>, |
151 | M: MemoizerKind, |
152 | { |
153 | if let Some(formatter) = &scope.bundle.formatter { |
154 | if let Some(val) = formatter(self, &scope.bundle.intls) { |
155 | return w.write_str(&val); |
156 | } |
157 | } |
158 | match self { |
159 | FluentValue::String(s) => w.write_str(s), |
160 | FluentValue::Number(n) => w.write_str(&n.as_string()), |
161 | FluentValue::Custom(s) => w.write_str(&scope.bundle.intls.stringify_value(&**s)), |
162 | FluentValue::Error => Ok(()), |
163 | FluentValue::None => Ok(()), |
164 | } |
165 | } |
166 | |
167 | pub fn as_string<R: Borrow<FluentResource>, M>(&self, scope: &Scope<R, M>) -> Cow<'source, str> |
168 | where |
169 | M: MemoizerKind, |
170 | { |
171 | if let Some(formatter) = &scope.bundle.formatter { |
172 | if let Some(val) = formatter(self, &scope.bundle.intls) { |
173 | return val.into(); |
174 | } |
175 | } |
176 | match self { |
177 | FluentValue::String(s) => s.clone(), |
178 | FluentValue::Number(n) => n.as_string(), |
179 | FluentValue::Custom(s) => scope.bundle.intls.stringify_value(&**s), |
180 | FluentValue::Error => "" .into(), |
181 | FluentValue::None => "" .into(), |
182 | } |
183 | } |
184 | } |
185 | |
186 | impl<'source> From<String> for FluentValue<'source> { |
187 | fn from(s: String) -> Self { |
188 | FluentValue::String(s.into()) |
189 | } |
190 | } |
191 | |
192 | impl<'source> From<&'source str> for FluentValue<'source> { |
193 | fn from(s: &'source str) -> Self { |
194 | FluentValue::String(s.into()) |
195 | } |
196 | } |
197 | |
198 | impl<'source> From<Cow<'source, str>> for FluentValue<'source> { |
199 | fn from(s: Cow<'source, str>) -> Self { |
200 | FluentValue::String(s) |
201 | } |
202 | } |
203 | |