Skip to content

Commit 862df16

Browse files
Allow passing a custom argument resolver to format
1 parent 40a3b65 commit 862df16

File tree

9 files changed

+90
-37
lines changed

9 files changed

+90
-37
lines changed

fluent-bundle/src/bundle.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::message::FluentMessage;
2424
use crate::resolver::{ResolveValue, Scope, WriteValue};
2525
use crate::resource::FluentResource;
2626
use crate::types::FluentValue;
27+
use crate::ArgumentResolver;
2728

2829
/// A collection of localization messages for a single locale, which are meant
2930
/// to be used together in a single view, widget or any other UI abstraction.
@@ -488,6 +489,21 @@ impl<R, M> FluentBundle<R, M> {
488489
args: Option<&'args FluentArgs>,
489490
errors: &mut Vec<FluentError>,
490491
) -> Cow<'bundle, str>
492+
where
493+
R: Borrow<FluentResource>,
494+
M: MemoizerKind,
495+
{
496+
self.format_pattern_with_argument_resolver(pattern, args, errors)
497+
}
498+
499+
/// Formats a pattern which comes from a `FluentMessage`.
500+
/// Works the same as [`FluentBundle::format_pattern`], but allows passing a custom argument resolver.
501+
pub fn format_pattern_with_argument_resolver<'bundle, 'args>(
502+
&'bundle self,
503+
pattern: &'bundle ast::Pattern<&'bundle str>,
504+
args: impl ArgumentResolver<'args>,
505+
errors: &mut Vec<FluentError>,
506+
) -> Cow<'bundle, str>
491507
where
492508
R: Borrow<FluentResource>,
493509
M: MemoizerKind,

fluent-bundle/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ pub use args::FluentArgs;
122122
pub type FluentBundle<R> = bundle::FluentBundle<R, intl_memoizer::IntlLangMemoizer>;
123123
pub use errors::FluentError;
124124
pub use message::{FluentAttribute, FluentMessage};
125+
pub use resolver::ArgumentResolver;
125126
pub use resource::FluentResource;
126127
#[doc(inline)]
127128
pub use types::FluentValue;

fluent-bundle/src/resolver/expression.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::scope::Scope;
1+
use super::scope::{ArgumentResolver, Scope};
22
use super::WriteValue;
33

44
use std::borrow::Borrow;
@@ -12,15 +12,16 @@ use crate::resource::FluentResource;
1212
use crate::types::FluentValue;
1313

1414
impl<'bundle> WriteValue<'bundle> for ast::Expression<&'bundle str> {
15-
fn write<'ast, 'args, 'errors, W, R, M>(
15+
fn write<'ast, 'args, 'errors, W, R, M, Args>(
1616
&'ast self,
1717
w: &mut W,
18-
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
18+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>,
1919
) -> fmt::Result
2020
where
2121
W: fmt::Write,
2222
R: Borrow<FluentResource>,
2323
M: MemoizerKind,
24+
Args: ArgumentResolver<'args>,
2425
{
2526
match self {
2627
Self::Inline(exp) => exp.write(w, scope),

fluent-bundle/src/resolver/inline_expression.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::scope::Scope;
1+
use super::scope::{ArgumentResolver, Scope};
22
use super::{ResolveValue, ResolverError, WriteValue};
33

44
use std::borrow::Borrow;
@@ -13,15 +13,16 @@ use crate::resource::FluentResource;
1313
use crate::types::FluentValue;
1414

1515
impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> {
16-
fn write<'ast, 'args, 'errors, W, R, M>(
16+
fn write<'ast, 'args, 'errors, W, R, M, Args>(
1717
&'ast self,
1818
w: &mut W,
19-
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
19+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>,
2020
) -> fmt::Result
2121
where
2222
W: fmt::Write,
2323
R: Borrow<FluentResource>,
2424
M: MemoizerKind,
25+
Args: ArgumentResolver<'args>,
2526
{
2627
match self {
2728
Self::StringLiteral { value } => unescape_unicode(w, value),
@@ -100,9 +101,15 @@ impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> {
100101
}
101102
}
102103
Self::VariableReference { id } => {
103-
let args = scope.local_args.as_ref().or(scope.args);
104+
let resolved_arg;
105+
let opt_arg = if let Some(args) = scope.local_args.as_ref() {
106+
args.get(id.name)
107+
} else {
108+
resolved_arg = scope.args.resolve(id.name);
109+
resolved_arg.as_ref().map(|it| it.as_ref())
110+
};
104111

105-
if let Some(arg) = args.and_then(|args| args.get(id.name)) {
112+
if let Some(arg) = opt_arg {
106113
arg.write(w, scope)
107114
} else {
108115
if scope.local_args.is_none() {
@@ -148,13 +155,14 @@ impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> {
148155
}
149156

150157
impl<'bundle> ResolveValue<'bundle> for ast::InlineExpression<&'bundle str> {
151-
fn resolve<'ast, 'args, 'errors, R, M>(
158+
fn resolve<'ast, 'args, 'errors, R, M, Args>(
152159
&'ast self,
153-
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
160+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>,
154161
) -> FluentValue<'bundle>
155162
where
156163
R: Borrow<FluentResource>,
157164
M: MemoizerKind,
165+
Args: ArgumentResolver<'args>,
158166
{
159167
match self {
160168
Self::StringLiteral { value } => unescape_unicode_to_string(value).into(),
@@ -164,8 +172,8 @@ impl<'bundle> ResolveValue<'bundle> for ast::InlineExpression<&'bundle str> {
164172
if let Some(arg) = local_args.get(id.name) {
165173
return arg.clone();
166174
}
167-
} else if let Some(arg) = scope.args.and_then(|args| args.get(id.name)) {
168-
return arg.into_owned();
175+
} else if let Some(arg) = scope.args.resolve(id.name) {
176+
return arg.as_ref().into_owned();
169177
}
170178

171179
if scope.local_args.is_none() {

fluent-bundle/src/resolver/mod.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod pattern;
1010
mod scope;
1111

1212
pub use errors::ResolverError;
13-
pub use scope::Scope;
13+
pub use scope::{ArgumentResolver, Scope};
1414

1515
use std::borrow::Borrow;
1616
use std::fmt;
@@ -22,27 +22,29 @@ use crate::types::FluentValue;
2222
/// Resolves an AST node to a [`FluentValue`].
2323
pub(crate) trait ResolveValue<'bundle> {
2424
/// Resolves an AST node to a [`FluentValue`].
25-
fn resolve<'ast, 'args, 'errors, R, M>(
25+
fn resolve<'ast, 'args, 'errors, R, M, Args>(
2626
&'ast self,
27-
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
27+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>,
2828
) -> FluentValue<'bundle>
2929
where
3030
R: Borrow<FluentResource>,
31-
M: MemoizerKind;
31+
M: MemoizerKind,
32+
Args: ArgumentResolver<'args>;
3233
}
3334

3435
/// Resolves an AST node to a string that is written to source `W`.
3536
pub(crate) trait WriteValue<'bundle> {
3637
/// Resolves an AST node to a string that is written to source `W`.
37-
fn write<'ast, 'args, 'errors, W, R, M>(
38+
fn write<'ast, 'args, 'errors, W, R, M, Args>(
3839
&'ast self,
3940
w: &mut W,
40-
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
41+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>,
4142
) -> fmt::Result
4243
where
4344
W: fmt::Write,
4445
R: Borrow<FluentResource>,
45-
M: MemoizerKind;
46+
M: MemoizerKind,
47+
Args: ArgumentResolver<'args>;
4648

4749
/// Writes error information to `W`. This can be used to add FTL errors inline
4850
/// to a message.

fluent-bundle/src/resolver/pattern.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::scope::Scope;
1+
use super::scope::{ArgumentResolver, Scope};
22
use super::{ResolverError, WriteValue};
33

44
use std::borrow::Borrow;
@@ -14,15 +14,16 @@ use crate::types::FluentValue;
1414
const MAX_PLACEABLES: u8 = 100;
1515

1616
impl<'bundle> WriteValue<'bundle> for ast::Pattern<&'bundle str> {
17-
fn write<'ast, 'args, 'errors, W, R, M>(
17+
fn write<'ast, 'args, 'errors, W, R, M, Args>(
1818
&'ast self,
1919
w: &mut W,
20-
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
20+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>,
2121
) -> fmt::Result
2222
where
2323
W: fmt::Write,
2424
R: Borrow<FluentResource>,
2525
M: MemoizerKind,
26+
Args: ArgumentResolver<'args>,
2627
{
2728
let len = self.elements.len();
2829

@@ -81,13 +82,14 @@ impl<'bundle> WriteValue<'bundle> for ast::Pattern<&'bundle str> {
8182
}
8283

8384
impl<'bundle> ResolveValue<'bundle> for ast::Pattern<&'bundle str> {
84-
fn resolve<'ast, 'args, 'errors, R, M>(
85+
fn resolve<'ast, 'args, 'errors, R, M, Args>(
8586
&'ast self,
86-
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
87+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>,
8788
) -> FluentValue<'bundle>
8889
where
8990
R: Borrow<FluentResource>,
9091
M: MemoizerKind,
92+
Args: ArgumentResolver<'args>,
9193
{
9294
let len = self.elements.len();
9395

fluent-bundle/src/resolver/scope.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ use crate::resolver::{ResolveValue, ResolverError, WriteValue};
44
use crate::types::FluentValue;
55
use crate::{FluentArgs, FluentError, FluentResource};
66
use fluent_syntax::ast;
7-
use std::borrow::Borrow;
7+
use std::borrow::{Borrow, Cow};
88
use std::fmt;
9+
use std::marker::PhantomData;
910

1011
/// State for a single `ResolveValue::to_value` call.
11-
pub struct Scope<'bundle, 'ast, 'args, 'errors, R, M> {
12+
pub struct Scope<'bundle, 'ast, 'args, 'errors, R, M, Args> {
13+
lt: PhantomData<&'args ()>,
1214
/// The current `FluentBundle` instance.
1315
pub bundle: &'bundle FluentBundle<R, M>,
1416
/// The current arguments passed by the developer.
15-
pub(super) args: Option<&'args FluentArgs<'args>>,
17+
pub(super) args: Args,
1618
/// Local args
1719
pub(super) local_args: Option<FluentArgs<'bundle>>,
1820
/// The running count of resolved placeables. Used to detect the Billion
@@ -26,15 +28,16 @@ pub struct Scope<'bundle, 'ast, 'args, 'errors, R, M> {
2628
pub dirty: bool,
2729
}
2830

29-
impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R, M> {
31+
impl<'bundle, 'ast, 'args, 'errors, R, M, Args> Scope<'bundle, 'ast, 'args, 'errors, R, M, Args> {
3032
pub fn new(
3133
bundle: &'bundle FluentBundle<R, M>,
32-
args: Option<&'args FluentArgs>,
34+
arg_resolver: Args,
3335
errors: Option<&'errors mut Vec<FluentError>>,
3436
) -> Self {
3537
Scope {
38+
lt: PhantomData,
3639
bundle,
37-
args,
40+
args: arg_resolver,
3841
local_args: None,
3942
placeables: 0,
4043
travelled: Default::default(),
@@ -48,7 +51,11 @@ impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R
4851
errors.push(error.into());
4952
}
5053
}
54+
}
5155

56+
impl<'bundle, 'ast, 'args, 'errors, R, M, Args: ArgumentResolver<'args>>
57+
Scope<'bundle, 'ast, 'args, 'errors, R, M, Args>
58+
{
5259
/// This method allows us to lazily add Pattern on the stack, only if the
5360
/// Pattern::resolve has been called on an empty stack.
5461
///
@@ -138,3 +145,14 @@ impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R
138145
}
139146
}
140147
}
148+
149+
pub trait ArgumentResolver<'a>: Copy {
150+
fn resolve(self, name: &str) -> Option<Cow<FluentValue<'a>>>;
151+
}
152+
153+
impl<'args> ArgumentResolver<'args> for Option<&'args FluentArgs<'args>> {
154+
fn resolve(self, name: &str) -> Option<Cow<FluentValue<'args>>> {
155+
let arg = self?.get(name)?;
156+
Some(Cow::Borrowed(arg))
157+
}
158+
}

fluent-bundle/src/types/mod.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,13 @@ impl<'source> FluentValue<'source> {
157157
///
158158
/// ```
159159
/// use fluent_bundle::resolver::Scope;
160-
/// use fluent_bundle::{types::FluentValue, FluentBundle, FluentResource};
160+
/// use fluent_bundle::{types::FluentValue, FluentArgs, FluentBundle, FluentResource};
161161
/// use unic_langid::langid;
162162
///
163163
/// let langid_ars = langid!("en");
164164
/// let bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![langid_ars]);
165-
/// let scope = Scope::new(&bundle, None, None);
165+
/// let argument_resolver: Option<&FluentArgs> = None;
166+
/// let scope = Scope::new(&bundle, argument_resolver, None);
166167
///
167168
/// // Matching examples:
168169
/// assert!(FluentValue::try_number("2").matches(&FluentValue::try_number("2"), &scope));
@@ -177,10 +178,10 @@ impl<'source> FluentValue<'source> {
177178
/// assert!(!FluentValue::from("fluent").matches(&FluentValue::from("not fluent"), &scope));
178179
/// assert!(!FluentValue::from("two").matches(&FluentValue::try_number("100"), &scope),);
179180
/// ```
180-
pub fn matches<R: Borrow<FluentResource>, M>(
181+
pub fn matches<R: Borrow<FluentResource>, M, Args>(
181182
&self,
182183
other: &FluentValue,
183-
scope: &Scope<R, M>,
184+
scope: &Scope<R, M, Args>,
184185
) -> bool
185186
where
186187
M: MemoizerKind,
@@ -214,7 +215,7 @@ impl<'source> FluentValue<'source> {
214215
}
215216

216217
/// Write out a string version of the [`FluentValue`] to `W`.
217-
pub fn write<W, R, M>(&self, w: &mut W, scope: &Scope<R, M>) -> fmt::Result
218+
pub fn write<W, R, M, Args>(&self, w: &mut W, scope: &Scope<R, M, Args>) -> fmt::Result
218219
where
219220
W: fmt::Write,
220221
R: Borrow<FluentResource>,
@@ -235,7 +236,10 @@ impl<'source> FluentValue<'source> {
235236
}
236237

237238
/// Converts the [`FluentValue`] to a string.
238-
pub fn as_string<R: Borrow<FluentResource>, M>(&self, scope: &Scope<R, M>) -> Cow<'source, str>
239+
pub fn as_string<R: Borrow<FluentResource>, M, Args>(
240+
&self,
241+
scope: &Scope<R, M, Args>,
242+
) -> Cow<'source, str>
239243
where
240244
M: MemoizerKind,
241245
{

fluent-bundle/tests/types_test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ fn fluent_value_matches() {
2121
// plural rules categories.
2222
let langid_ars = langid!("ars");
2323
let bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![langid_ars]);
24-
let scope = Scope::new(&bundle, None, None);
24+
let arg_resolver: Option<&FluentArgs> = None;
25+
let scope = Scope::new(&bundle, arg_resolver, None);
2526

2627
let string_val = FluentValue::from("string1");
2728
let string_val_copy = FluentValue::from("string1");

0 commit comments

Comments
 (0)