Skip to content

Commit f0b5f50

Browse files
committed
give i access to outside context
1 parent bdbf5b8 commit f0b5f50

File tree

6 files changed

+88
-32
lines changed

6 files changed

+88
-32
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
### Changed
9+
- **Breaking Changed**: `format` and `write` take `Context` trait, this breaks type inference
10+
- `i` allows access to outer variables
11+
712
## [0.4.0] - 2023-03-01
813
### Changed
914
- **Breaking Change**: Made error enums `non_exhaustive`

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ Takes a string and a context, containing `Formattable` values, returns a
1414
string.
1515

1616
```rs
17+
use std::collections::HashMap;
1718
use template::{format, Formattable};
1819

1920
let formatted = format(
2021
"{value:+05}", // could be dynamic
21-
&[("value", Formattable::display(&12))].into_iter().collect(),
22+
&[("value", Formattable::display(&12))].into_iter().collect::<HashMap<_,_>>(),
2223
)
2324
.unwrap();
2425

@@ -32,13 +33,14 @@ Takes a mutable `Write` e.g. `&mut String`, a format string and a context,
3233
containing `Formattable` values.
3334

3435
```rs
36+
use std::collections::HashMap;
3537
use template::{write, Formattable};
3638

3739
let mut buf = String::new();
3840
write(
3941
&mut buf,
4042
"{value:+05}", // could be dynamic
41-
&[("value", Formattable::display(&12))].into_iter().collect(),
43+
&[("value", Formattable::display(&12))].into_iter().collect::<HashMap<_,_>>(),
4244
)
4345
.unwrap();
4446

src/hard_coded/iter.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ fn write_iter(
66
range: Option<Range>,
77
format: Option<&str>,
88
join: Option<&str>,
9+
context: &impl Context,
910
) -> Result {
1011
if value.is_empty() {
1112
return Ok(());
@@ -23,17 +24,13 @@ fn write_iter(
2324
if rhs > lhs {
2425
if let Some(format) = format {
2526
for value in &value[lhs..rhs - 1] {
26-
let mut context = HashMap::new();
27-
context.insert("", value);
28-
write(out, format, &context)?;
27+
write(out, format, &IterContext::new(context, *value))?;
2928
if let Some(join) = join {
30-
write!(out, "{join}").map_err(|e| Error::Fmt(e, 0))?;
29+
write(out, join, context)?;
3130
}
3231
}
3332
if let Some(value) = value[..rhs].last() {
34-
let mut context = HashMap::new();
35-
context.insert("", value);
36-
write(out, format, &context)?;
33+
write(out, format, &IterContext::new(context, *value))?;
3734
}
3835
} else {
3936
for value in &value[lhs..rhs] {
@@ -64,20 +61,21 @@ pub(crate) fn iter(
6461
range: Option<Range>,
6562
format: Option<&str>,
6663
join: Option<&str>,
64+
context: &impl Context,
6765
) -> Result {
6866
match (precision, sign, hash, zero) {
6967
(None, Sign::None, false, false) => {
7068
if let Some(width) = width {
7169
let mut buf = String::new();
72-
write_iter(&mut buf, value, range, format, join)?;
70+
write_iter(&mut buf, value, range, format, join, context)?;
7371
match alignment {
7472
Alignment::Left | Alignment::None => write!(out, "{buf:<width$}"),
7573
Alignment::Center => write!(out, "{buf:^width$}"),
7674
Alignment::Right => write!(out, "{buf:>width$}"),
7775
}
7876
.map_err(|e| Error::Fmt(e, 0))
7977
} else {
80-
write_iter(out, value, range, format, join)
78+
write_iter(out, value, range, format, join, context)
8179
}
8280
}
8381
_ => Err(ParseError::Iter(0).into()),
@@ -115,4 +113,11 @@ mod test {
115113
assert_eq!(format("{h:i1..}", context).unwrap(), "5");
116114
assert_eq!(format("{h:i..=-1}", context).unwrap(), "15");
117115
}
116+
#[test]
117+
fn outside_context() {
118+
let list = &[&"hi", &"ho"].map(Formattable::display);
119+
let context = &hash!("list" => Formattable::iter(list), "x" => Formattable::display(&"?!"));
120+
assert_eq!(format("{list:i({}{x})}", context).unwrap(), "hi?!ho?!");
121+
assert_eq!(format("{list:i({})({x})}", context).unwrap(), "hi?!ho");
122+
}
118123
}

src/hard_coded/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ macro_rules! call_format_value {
4545
$zero:ident,
4646
$trait_:ident,
4747
$idx:ident {
48-
$($Trait:ident$(($($fields:tt)*))? => $fn:ident($getter:ident $(, $feature:literal)?) $($ret:ident)?,)*
48+
$($Trait:ident$(($($fields:tt)*))? => $fn:ident($getter:ident $(, $feature:literal)? $(, $args:ident)*) $($ret:ident)?,)*
4949
}
5050
) => {
5151
match $trait_ {
@@ -54,7 +54,7 @@ macro_rules! call_format_value {
5454
Ok(v) => v,
5555
Err(e) => return Err(Error::MissingTraitImpl(e, $idx))
5656
};
57-
branch!(($idx $($ret)?),$fn::$fn($out, value, $width, $precision, $alignment, $sign, $hash, $zero, $($($fields)*)?))
57+
branch!(($idx $($ret)?),$fn::$fn($out, value, $width, $precision, $alignment, $sign, $hash, $zero, $($($fields)*)? $(, $args)*))
5858
},)*
5959
TraitSpec::Phantom(_) => unreachable!()
6060
}
@@ -97,6 +97,8 @@ pub(crate) fn format_value(
9797
zero: bool,
9898
trait_: TraitSpec,
9999
idx: usize,
100+
#[allow(unused)]
101+
context: &impl Context,
100102
) -> Result<()> {
101103
call_format_value! {
102104
match out, value, width, precision, alignment, sign, hash, zero, trait_, idx {
@@ -111,7 +113,7 @@ pub(crate) fn format_value(
111113
LowerExp => lower_exp(get_lower_exp, "number"),
112114
UpperExp => upper_exp(get_upper_exp, "number"),
113115
Pointer => pointer(get_pointer, "pointer"),
114-
Iter(range, format, join) => iter(get_iter, "iter") return,
116+
Iter(range, format, join) => iter(get_iter, "iter", context) return,
115117
}
116118
}
117119
.map_err(|e| Error::Fmt(e, idx))

src/lib.rs

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
//! [features](#features).
1010
//!
1111
//! ```
12+
//! # use std::collections::HashMap;
1213
//! use interpolator::{format, Formattable};
1314
//!
1415
//! let formatted = format(
1516
//! "{value:+05}", // could be dynamic
16-
//! &[("value", Formattable::display(&12))].into_iter().collect(),
17+
//! &[("value", Formattable::display(&12))]
18+
//! .into_iter()
19+
//! .collect::<HashMap<_, _>>(),
1720
//! )?;
1821
//!
1922
//! assert_eq!(formatted, format!("{:+05}", 12));
@@ -55,10 +58,10 @@ use interpolator::{format, Formattable};
5558
// reference
5659
let items = [&"hello", &"hi", &"hey"].map(Formattable::display);
5760
let items = Formattable::iter(&items);
58-
let format_str = "Greetings: {items:i..-1(`{}`)(, )} and `{items:i-1}`";
61+
let format_str = "Greetings: {items:i..-1(`{}{outside}`)(, )} and `{items:i-1}{outside}`";
5962
assert_eq!(
60-
format(format_str, &hash!("items" => items))?,
61-
"Greetings: `hello`, `hi` and `hey`"
63+
format(format_str, &hash!("items" => items, "outside" => Formattable::display(&'!')))?,
64+
"Greetings: `hello!`, `hi!` and `hey!`"
6265
);
6366
# return Ok::<(), interpolator::Error>(())
6467
```"#
@@ -112,11 +115,14 @@ type Result<T = (), E = Error> = std::result::Result<T, E>;
112115
/// string.
113116
///
114117
/// ```
118+
/// # use std::collections::HashMap;
115119
/// use interpolator::{format, Formattable};
116120
///
117121
/// let formatted = format(
118122
/// "{value:+05}", // could be dynamic
119-
/// &[("value", Formattable::display(&12))].into_iter().collect(),
123+
/// &[("value", Formattable::display(&12))]
124+
/// .into_iter()
125+
/// .collect::<HashMap<_, _>>(),
120126
/// )
121127
/// .unwrap();
122128
///
@@ -130,28 +136,64 @@ type Result<T = (), E = Error> = std::result::Result<T, E>;
130136
/// failed.
131137
///
132138
/// For more details have a look at [`Error`] and [`ParseError`].
133-
pub fn format<K: Borrow<str> + Eq + Hash>(
134-
format: &str,
135-
context: &HashMap<K, Formattable>,
136-
) -> Result<String> {
139+
pub fn format(format: &str, context: &impl Context) -> Result<String> {
137140
let mut out = String::with_capacity(format.len());
138141
write(&mut out, format, context)?;
139142
Ok(out)
140143
}
141144

145+
/// Context for `format` and `write`
146+
pub trait Context {
147+
/// Returns the [`Formattable`] for the requested key
148+
fn get(&self, key: &str) -> Option<Formattable>;
149+
}
150+
151+
impl<K: Borrow<str> + Eq + Hash> Context for HashMap<K, Formattable<'_>> {
152+
fn get(&self, key: &str) -> Option<Formattable> {
153+
HashMap::get(self, key).copied()
154+
}
155+
}
156+
157+
#[cfg(feature = "iter")]
158+
struct IterContext<'a> {
159+
outer: &'a dyn Context,
160+
inner: Formattable<'a>,
161+
}
162+
163+
#[cfg(feature = "iter")]
164+
impl<'a> IterContext<'a> {
165+
fn new(outer: &'a impl Context, inner: Formattable<'a>) -> Self {
166+
Self { outer, inner }
167+
}
168+
}
169+
170+
#[cfg(feature = "iter")]
171+
impl<'a> Context for IterContext<'a> {
172+
fn get(&self, key: &str) -> Option<Formattable> {
173+
if key.is_empty() {
174+
Some(self.inner)
175+
} else {
176+
self.outer.get(key)
177+
}
178+
}
179+
}
180+
142181
/// Runtime version of [`write!`].
143182
///
144183
/// Takes a mutable [`Write`] e.g. `&mut String`, a format string and a context,
145184
/// containing [`Formattable`] values.
146185
///
147186
/// ```
187+
/// # use std::collections::HashMap;
148188
/// use interpolator::{write, Formattable};
149189
///
150190
/// let mut buf = String::new();
151191
/// write(
152192
/// &mut buf,
153193
/// "{value:+05}", // could be dynamic
154-
/// &[("value", Formattable::display(&12))].into_iter().collect(),
194+
/// &[("value", Formattable::display(&12))]
195+
/// .into_iter()
196+
/// .collect::<HashMap<_, _>>(),
155197
/// )
156198
/// .unwrap();
157199
///
@@ -165,11 +207,7 @@ pub fn format<K: Borrow<str> + Eq + Hash>(
165207
/// failed.
166208
///
167209
/// For more details have a look at [`Error`] and [`ParseError`].
168-
pub fn write<'a, K: Borrow<str> + Eq + Hash, F: Borrow<Formattable<'a>>>(
169-
out: &mut impl Write,
170-
mut format: &str,
171-
context: &'a HashMap<K, F>,
172-
) -> Result {
210+
pub fn write(out: &mut impl Write, mut format: &str, context: &impl Context) -> Result {
173211
let format = &mut format;
174212
let idx = &mut 0;
175213
while !format.is_empty() {
@@ -206,6 +244,7 @@ pub fn write<'a, K: Borrow<str> + Eq + Hash, F: Borrow<Formattable<'a>>>(
206244
zero,
207245
trait_,
208246
*idx,
247+
context,
209248
)?;
210249
ensure!(format.starts_with('}'), ParseError::Expected("}", *idx));
211250
step(1, format, idx);

tests/test.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,16 @@ fn test(
120120
t.pass_inline(
121121
&converter.to_token_stream().to_string(),
122122
&quote! {
123-
use interpolator::{{format, Formattable, write}};
124-
use std::thread;
125123
use std::fmt::Write;
124+
use std::thread;
125+
126+
use collection_literals::hash;
127+
use interpolator::{{format, Formattable, write}};
128+
126129
fn main() {
127130
// This stack overflows without the thread on windows + nightly
128131
thread::spawn(move ||{
129-
let value = &[("ident", Formattable::#converter(&#value))].into_iter().collect();
132+
let value = &hash!("ident" => Formattable::#converter(&#value));
130133
#(
131134
assert_eq!(
132135
format(#format_args, value).unwrap(),

0 commit comments

Comments
 (0)