Skip to content

Commit 55f49d4

Browse files
committed
fix feature for iter
1 parent f375cff commit 55f49d4

File tree

8 files changed

+101
-89
lines changed

8 files changed

+101
-89
lines changed

.github/workflows/test.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ jobs:
2525
- "-F pointer"
2626
- "-F debug -F number"
2727
- "-F number -F pointer"
28+
- "-F iter"
29+
- "-F iter -F debug"
30+
- "-F iter -F number"
31+
- "-F iter -F pointer"
32+
- "-F iter -F debug -F number"
33+
- "-F iter -F number -F pointer"
2834
- --all-features
2935
include:
3036
- rust: nightly

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ documentation = "https://docs.rs/interpolator"
1212
include = ["src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"]
1313

1414
[features]
15-
default = ["debug", "number", "pointer", "iter"]
15+
# default = ["debug", "number", "pointer", "iter"]
1616
debug = []
1717
number = []
1818
pointer = []

src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub enum Error {
2424
UnsupportedOption(&'static str, &'static str, usize),
2525
}
2626

27+
#[cfg(feature = "iter")]
2728
impl Error {
2829
pub(crate) fn add_idx(self, idx: usize) -> Self {
2930
use Error::*;
@@ -119,6 +120,7 @@ pub enum ParseError {
119120
RangeBound(ParseIntError, usize),
120121
}
121122

123+
#[cfg(feature = "iter")]
122124
impl ParseError {
123125
pub(crate) fn add_idx(self, idx: usize) -> ParseError {
124126
use ParseError::*;

src/formattable.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ impl<'a> Formattable<'a> {
9292
}
9393
}
9494

95+
#[cfg(feature = "iter")]
9596
impl<'a> Formattable<'a> {
9697
/// Creates a [`Formattable`] from a list of values
9798
pub fn iter(value: &'a [Formattable<'a>]) -> Self {

src/hard_coded/iter.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,29 @@ pub(crate) fn iter(
7070
_ => Err(ParseError::Iter(0).into()),
7171
}
7272
}
73+
74+
#[cfg(test)]
75+
mod test {
76+
use collection_literals::hash;
77+
78+
use super::*;
79+
80+
#[test]
81+
fn iter() {
82+
let list = &[&1, &5].map(Formattable::display);
83+
let context = &hash!("h"=> Formattable::iter(list));
84+
assert_eq!(
85+
format("{h:i(`{it:+05}`)#() )#}", context).unwrap(),
86+
"`+0001`) `+0005`"
87+
);
88+
assert_eq!(format("{h:i(``)}", context).unwrap(), "````");
89+
assert_eq!(format("{h:i..({it})}", context).unwrap(), "15");
90+
assert_eq!(format("{h:i1..({it})}", context).unwrap(), "5");
91+
assert_eq!(format("{h:i1..1({it})}", context).unwrap(), "");
92+
assert_eq!(format("{h:i2..1({it})}", context).unwrap(), "");
93+
assert_eq!(format("{h:i-1..({it})}", context).unwrap(), "5");
94+
assert_eq!(format("{h:i..-1({it})}", context).unwrap(), "1");
95+
assert_eq!(format("{h:i..-2({it})}", context).unwrap(), "");
96+
assert_eq!(format("{h:i-5..-10({it})}", context).unwrap(), "");
97+
}
98+
}

src/hard_coded/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ macro_rules! call_format_value {
4949
}
5050
) => {
5151
match $trait_ {
52-
$($(#[cfg(feature = $feature)])? crate::TraitSpec::$Trait$(($($fields)*))? => {
52+
$($(#[cfg(feature = $feature)])? TraitSpec::$Trait$(($($fields)*))? => {
5353
let value = match $value.$getter() {
5454
Ok(v) => v,
55-
Err(e) => return Err(crate::Error::MissingTraitImpl(e, $idx))
55+
Err(e) => return Err(Error::MissingTraitImpl(e, $idx))
5656
};
5757
branch!(($idx $($ret)?),$fn::$fn($out, value, $width, $precision, $alignment, $sign, $hash, $zero, $($($fields)*)?))
5858
},)*
59+
TraitSpec::Phantom(_) => unreachable!()
5960
}
6061
};
6162
}

src/lib.rs

Lines changed: 39 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,45 @@
1919
//! assert_eq!(formatted, format!("{:+05}", 12));
2020
//! # return Ok::<(), interpolator::Error>(())
2121
//! ```
22-
//!
23-
//! # `i` iter format
24-
//!
25-
//! The feature `iter` enables an additional format trait `i`, it allows to
26-
//! format a list of values with a format string and an optional join
27-
//! expression.
28-
//!
29-
//! The syntax is `{list:i(the format string, '{it}' is the array element)(the
30-
//! optional join)}`, an empty join can also be omitted `{list:i({it})}`. Should
31-
//! you need to use `)` inside your format string or join, you can add `#`
32-
//! similar to rust's [raw string](https://doc.rust-lang.org/reference/tokens.html#raw-string-literals).
33-
//!
34-
//! It is also possible to only iterate a sub-slice specified through a range
35-
//! before the format string, i.e. `{list:i1..4({it})}`. For open ranges range
36-
//! bounds can also be omitted. To index from the end, you can use negative
37-
//! range bounds.
38-
//!
39-
//! A [`Formattable`] implementing iter is created using [`Formattable::iter`]:
40-
//!
41-
//! ```
42-
//! // HashMap macro
43-
//! use collection_literals::hash;
44-
//! use interpolator::{format, Formattable};
45-
//! // Needs to be a slice of references so because `Formattable::display` expects a
46-
//! // reference
47-
//! let items = [&"hello", &"hi", &"hey"].map(Formattable::display);
48-
//! let items = Formattable::iter(&items);
49-
//! let format_str = "Greetings: {items:i..-1(`{it}`)(, )} and {items:i-1..(`{it}`)}";
50-
//! assert_eq!(
51-
//! format(format_str, &hash!("items" => items))?,
52-
//! "Greetings: `hello`, `hi` and `hey`"
53-
//! );
54-
//! # return Ok::<(), interpolator::Error>(())
55-
//! ```
56-
//!
22+
23+
#![cfg_attr(
24+
feature = "iter",
25+
doc = r#"
26+
# `i` iter format
27+
28+
The feature `iter` enables an additional format trait `i`, it allows to
29+
format a list of values with a format string and an optional join
30+
expression.
31+
32+
The syntax is `{list:i(the format string, '{it}' is the array element)(the
33+
optional join)}`, an empty join can also be omitted `{list:i({it})}`. Should
34+
you need to use `)` inside your format string or join, you can add `#`
35+
similar to rust's [raw string](https://doc.rust-lang.org/reference/tokens.html#raw-string-literals).
36+
37+
It is also possible to only iterate a sub-slice specified through a range
38+
before the format string, i.e. `{list:i1..4({it})}`. For open ranges range
39+
bounds can also be omitted. To index from the end, you can use negative
40+
range bounds.
41+
42+
A [`Formattable`] implementing iter is created using [`Formattable::iter`]:
43+
44+
```
45+
// HashMap macro
46+
use collection_literals::hash;
47+
use interpolator::{format, Formattable};
48+
// Needs to be a slice of references so because `Formattable::display` expects a
49+
// reference
50+
let items = [&"hello", &"hi", &"hey"].map(Formattable::display);
51+
let items = Formattable::iter(&items);
52+
let format_str = "Greetings: {items:i..-1(`{it}`)(, )} and {items:i-1..(`{it}`)}";
53+
assert_eq!(
54+
format(format_str, &hash!("items" => items))?,
55+
"Greetings: `hello`, `hi` and `hey`"
56+
);
57+
# return Ok::<(), interpolator::Error>(())
58+
```"#
59+
)]
60+
5761
//! See [`format`](format()) and [`write`](write()) for details.
5862
//!
5963
//! # Features
@@ -209,29 +213,3 @@ pub fn write<'a, K: Borrow<str> + Eq + Hash, F: Borrow<Formattable<'a>>>(
209213
}
210214
Ok(())
211215
}
212-
213-
#[cfg(test)]
214-
mod test {
215-
use collection_literals::hash;
216-
217-
use super::*;
218-
219-
#[test]
220-
fn iter() {
221-
let list = &[&1, &5].map(Formattable::display);
222-
let context = &hash!("h"=> Formattable::iter(list));
223-
assert_eq!(
224-
format("{h:i(`{it:+05}`)#() )#}", context).unwrap(),
225-
"`+0001`) `+0005`"
226-
);
227-
assert_eq!(format("{h:i(``)}", context).unwrap(), "````");
228-
assert_eq!(format("{h:i..({it})}", context).unwrap(), "15");
229-
assert_eq!(format("{h:i1..({it})}", context).unwrap(), "5");
230-
assert_eq!(format("{h:i1..1({it})}", context).unwrap(), "");
231-
assert_eq!(format("{h:i2..1({it})}", context).unwrap(), "");
232-
assert_eq!(format("{h:i-1..({it})}", context).unwrap(), "5");
233-
assert_eq!(format("{h:i..-1({it})}", context).unwrap(), "1");
234-
assert_eq!(format("{h:i..-2({it})}", context).unwrap(), "");
235-
assert_eq!(format("{h:i-5..-10({it})}", context).unwrap(), "");
236-
}
237-
}

src/parser.rs

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::convert::Infallible;
12
use std::str::FromStr;
23

34
use super::*;
@@ -55,19 +56,20 @@ pub(crate) enum TraitSpec<'a> {
5556
Pointer,
5657
#[cfg(feature = "iter")]
5758
Iter(Option<Range>, &'a str, Option<&'a str>),
59+
#[allow(unused)]
60+
Phantom(&'a Infallible),
5861
}
5962

60-
fn parse_number<T: FromStr<Err = ParseIntError>>(
63+
fn parse_number<T: FromStr, IE: Into<Error>>(
6164
format: &mut &str,
6265
idx: &mut usize,
6366
start: usize,
67+
error: fn(<T as FromStr>::Err, usize) -> IE,
6468
) -> Result<T> {
6569
let len = format
6670
.find(|c: char| c != '-' && !c.is_ascii_digit())
6771
.ok_or(ParseError::FormatSpecUnclosed(start))?;
68-
let i = format[..len]
69-
.parse()
70-
.map_err(|e| ParseError::RangeBound(e, *idx))?;
72+
let i = format[..len].parse().map_err(|e| error(e, *idx).into())?;
7173
step(len, format, idx);
7274
Ok(i)
7375
}
@@ -80,7 +82,7 @@ pub(crate) struct Range(
8082
);
8183

8284
impl<'a> TraitSpec<'a> {
83-
#[allow(clippy::too_many_lines)]
85+
#[allow(clippy::too_many_lines, unused_variables)]
8486
fn from_str(format: &mut &'a str, idx: &mut usize, start: usize) -> Result<Self> {
8587
if format.starts_with('}') {
8688
return Ok(Self::Display);
@@ -148,7 +150,7 @@ impl<'a> TraitSpec<'a> {
148150
step(2, format, idx);
149151
None
150152
} else {
151-
let lhs = parse_number(format, idx, start)?;
153+
let lhs = parse_number(format, idx, start, ParseError::RangeBound)?;
152154
ensure!(format.starts_with(".."), ParseError::Expected("..", *idx));
153155
step(2, format, idx);
154156
Some(lhs)
@@ -162,7 +164,7 @@ impl<'a> TraitSpec<'a> {
162164
if format.starts_with('(') {
163165
None
164166
} else {
165-
Some(parse_number(format, idx, start)?)
167+
Some(parse_number(format, idx, start, ParseError::RangeBound)?)
166168
},
167169
))
168170
},
@@ -186,6 +188,7 @@ impl<'a> TraitSpec<'a> {
186188
}
187189
}
188190

191+
#[cfg(feature = "iter")]
189192
fn collect_parenthesized<'a>(
190193
format: &mut &'a str,
191194
idx: &mut usize,
@@ -297,27 +300,21 @@ impl<'a> FormatArgument<'a> {
297300
step(1, format, idx);
298301
}
299302
if format.starts_with(|c: char| c.is_ascii_digit()) {
300-
let width = format
301-
.find(|c: char| !c.is_ascii_digit())
302-
.ok_or(ParseError::FormatSpecUnclosed(start_index))?;
303-
it.width = Some(
304-
format[..width]
305-
.parse()
306-
.map_err(|e| ParseError::InvalidWidth(e, *idx))?,
307-
);
308-
step(width, format, idx);
303+
it.width = Some(parse_number(
304+
format,
305+
idx,
306+
start_index,
307+
ParseError::InvalidWidth,
308+
)?);
309309
}
310310
if format.starts_with('.') {
311311
step(1, format, idx);
312-
let precision = format
313-
.find(|c: char| !c.is_ascii_digit())
314-
.ok_or(ParseError::FormatSpecUnclosed(start_index))?;
315-
it.precision = Some(
316-
format[..precision]
317-
.parse()
318-
.map_err(|e| ParseError::InvalidPrecision(e, *idx))?,
319-
);
320-
step(precision, format, idx);
312+
it.precision = Some(parse_number(
313+
format,
314+
idx,
315+
start_index,
316+
ParseError::InvalidPrecision,
317+
)?);
321318
}
322319
it.trait_ = TraitSpec::from_str(format, idx, start_index)?;
323320
}
@@ -326,6 +323,7 @@ impl<'a> FormatArgument<'a> {
326323
}
327324

328325
#[cfg(test)]
326+
#[cfg(feature = "iter")]
329327
mod test {
330328
use super::*;
331329
#[test]

0 commit comments

Comments
 (0)