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.
14mod number;
15mod plural;
16
17pub use number::*;
18use plural::PluralRules;
19
20use std::any::Any;
21use std::borrow::{Borrow, Cow};
22use std::fmt;
23use std::str::FromStr;
24
25use intl_pluralrules::{PluralCategory, PluralRuleType};
26
27use crate::memoizer::MemoizerKind;
28use crate::resolver::Scope;
29use crate::resource::FluentResource;
30
31pub 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
40impl PartialEq for dyn FluentType + Send {
41 fn eq(&self, other: &Self) -> bool {
42 self.equals(other.as_any())
43 }
44}
45
46pub trait AnyEq: Any + 'static {
47 fn equals(&self, other: &dyn Any) -> bool;
48 fn as_any(&self) -> &dyn Any;
49}
50
51impl<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)]
69pub enum FluentValue<'source> {
70 String(Cow<'source, str>),
71 Number(FluentNumber),
72 Custom(Box<dyn FluentType + Send>),
73 None,
74 Error,
75}
76
77impl<'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
88impl<'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
103impl<'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
186impl<'source> From<String> for FluentValue<'source> {
187 fn from(s: String) -> Self {
188 FluentValue::String(s.into())
189 }
190}
191
192impl<'source> From<&'source str> for FluentValue<'source> {
193 fn from(s: &'source str) -> Self {
194 FluentValue::String(s.into())
195 }
196}
197
198impl<'source> From<Cow<'source, str>> for FluentValue<'source> {
199 fn from(s: Cow<'source, str>) -> Self {
200 FluentValue::String(s)
201 }
202}
203