Skip to content

Commit 9713a2e

Browse files
authored
iter improvements (#9)
* iter improvements -`i` without format -`i` with index -`{it}` -> `{}` * only test on main branch and pr * prepare new release
1 parent ae2ad1a commit 9713a2e

File tree

7 files changed

+119
-69
lines changed

7 files changed

+119
-69
lines changed

.github/workflows/test.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
name: Test
22

33
on:
4-
push: null
4+
push:
5+
branches:
6+
- main
57
pull_request: null
68
schedule:
79
- cron: '0 12 * * *'

CHANGELOG.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@ 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-
## [0.2.0] - 2023-02-28
7+
## [0.3.0] - 2023-03-01
8+
### Added
9+
- `i` gained implicit format `({})` when none is specified `{:i}`
10+
- `i` gained support for indexing single values without range `{:i1}`
811

12+
### Changed
13+
- **Breaking Change**: `i` uses `{}` instead of `{it}`
14+
15+
## [0.2.0] - 2023-02-28
916
### Added
10-
- Added `i` iter format
17+
- `i` iter format
1118

1219
### Changed
1320
- **Breaking Change**: Improved naming of error enums and variants
@@ -21,8 +28,9 @@ Fixed URLs in Cargo.toml
2128
## [0.1.0] - 2023-02-27
2229
Initial release
2330

24-
[unreleased]: https://github.com/ModProg/interpolator/compare/v0.2.0...HEAD
25-
[0.2.0]: https://github.com/ModProg/interpolator/v0.2.0
26-
[0.1.2]: https://github.com/ModProg/interpolator/v0.1.2
27-
[0.1.1]: https://github.com/ModProg/interpolator/v0.1.1
31+
[unreleased]: https://github.com/ModProg/interpolator/compare/v0.3.0...HEAD
32+
[0.3.0]: https://github.com/ModProg/interpolator/compare/v0.2.0...v0.3.0
33+
[0.2.0]: https://github.com/ModProg/interpolator/compare/v0.1.2...v0.2.0
34+
[0.1.2]: https://github.com/ModProg/interpolator/compare/v0.1.1...v0.1.2
35+
[0.1.1]: https://github.com/ModProg/interpolator/compare/v0.1.0...v0.1.1
2836
[0.1.0]: https://github.com/ModProg/interpolator/v0.1.0

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "interpolator"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2021"
55
categories = ["template-engine", "value-formatting", "text-processing"]
66
description = "runtime format strings, fully compatible with std's macros"

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,30 @@ The feature `iter` enables an additional format trait `i`, it allows to
5151
format a list of values with a format string and an optional join
5252
expression.
5353

54-
The syntax is `{list:i(the format string, '{it}' is the array element)(the
55-
optional join)}`, an empty join can also be omitted `{list:i({it})}`. Should
56-
you need to use `)` inside your format string or join, you can add `#`
57-
similar to rust's [raw string](https://doc.rust-lang.org/reference/tokens.html#raw-string-literals).
54+
The syntax is `{list:i(the format string, '{}' is the array element)(the
55+
join)}`, an empty join can also be omitted `{list:i({})}`. If join is omitted
56+
the format string `{}` can be omitted as well `{list:i}`.
57+
58+
Should you need to use `)` inside your format string or join, you can add `#`
59+
similar to rust's [raw string](https://doc.rust-lang.org/reference/tokens.html#raw-string-literals)
60+
(i.e. `#(({}))#`).
5861

5962
It is also possible to only iterate a sub-slice specified through a range
60-
before the format string, i.e. `{list:i1..4({it})}`. For open ranges range
63+
before the format string, i.e. `{list:i1..4}`. For open ranges range
6164
bounds can also be omitted. To index from the end, you can use negative
6265
range bounds.
6366

67+
It is also possible to index a single value by only specifying an `isize`
68+
`{list:i1}`.
69+
70+
6471
A `Formattable` implementing iter is created using `Formattable::iter`:
6572

6673
```rs
6774
// HashMap macro
6875
use collection_literals::hash;
6976
use interpolator::{format, Formattable};
70-
// Needs to be a slice of references so because `Formattable::display` expects a
77+
// Needs to be a slice of references because `Formattable::display` expects a
7178
// reference
7279
let items = [&"hello", &"hi", &"hey"].map(Formattable::display);
7380
let items = Formattable::iter(&items);

src/hard_coded/iter.rs

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ fn write_iter(
44
out: &mut impl Write,
55
value: &[Formattable<'_>],
66
range: Option<Range>,
7-
format: &str,
7+
format: Option<&str>,
88
join: Option<&str>,
99
) -> Result {
1010
if value.is_empty() {
@@ -21,18 +21,31 @@ fn write_iter(
2121
.min(rhs);
2222

2323
if rhs > lhs {
24-
for value in &value[lhs..rhs - 1] {
25-
let mut context = HashMap::new();
26-
context.insert("it", value);
27-
write(out, format, &context)?;
28-
if let Some(join) = join {
29-
write!(out, "{join}").map_err(|e| Error::Fmt(e, 0))?;
24+
if let Some(format) = format {
25+
for value in &value[lhs..rhs - 1] {
26+
let mut context = HashMap::new();
27+
context.insert("", value);
28+
write(out, format, &context)?;
29+
if let Some(join) = join {
30+
write!(out, "{join}").map_err(|e| Error::Fmt(e, 0))?;
31+
}
32+
}
33+
if let Some(value) = value[..rhs].last() {
34+
let mut context = HashMap::new();
35+
context.insert("", value);
36+
write(out, format, &context)?;
37+
}
38+
} else {
39+
for value in &value[lhs..rhs] {
40+
write!(
41+
out,
42+
"{}",
43+
value
44+
.get_display()
45+
.map_err(|e| Error::MissingTraitImpl(e, 0))?
46+
)
47+
.map_err(|e| Error::Fmt(e, 0))?;
3048
}
31-
}
32-
if let Some(value) = value[..rhs].last() {
33-
let mut context = HashMap::new();
34-
context.insert("it", value);
35-
write(out, format, &context)?;
3649
}
3750
}
3851
Ok(())
@@ -49,7 +62,7 @@ pub(crate) fn iter(
4962
hash: bool,
5063
zero: bool,
5164
range: Option<Range>,
52-
format: &str,
65+
format: Option<&str>,
5366
join: Option<&str>,
5467
) -> Result {
5568
match (precision, sign, hash, zero) {
@@ -82,17 +95,24 @@ mod test {
8295
let list = &[&1, &5].map(Formattable::display);
8396
let context = &hash!("h"=> Formattable::iter(list));
8497
assert_eq!(
85-
format("{h:i(`{it:+05}`)#() )#}", context).unwrap(),
98+
format("{h:i(`{:+05}`)#() )#}", context).unwrap(),
8699
"`+0001`) `+0005`"
87100
);
88101
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(), "");
102+
assert_eq!(format("{h:i..({})}", context).unwrap(), "15");
103+
assert_eq!(format("{h:i1..({})}", context).unwrap(), "5");
104+
assert_eq!(format("{h:i1..1({})}", context).unwrap(), "");
105+
assert_eq!(format("{h:i2..1({})}", context).unwrap(), "");
106+
assert_eq!(format("{h:i-1..({})}", context).unwrap(), "5");
107+
assert_eq!(format("{h:i..-1({})}", context).unwrap(), "1");
108+
assert_eq!(format("{h:i..-2({})}", context).unwrap(), "");
109+
assert_eq!(format("{h:i-5..-10({})}", context).unwrap(), "");
110+
assert_eq!(format("{h:i-1({})}", context).unwrap(), "5");
111+
assert_eq!(format("{h:i1}", context).unwrap(), "5");
112+
assert_eq!(format("{h:i..}", context).unwrap(), "15");
113+
assert_eq!(format("{h:i}", context).unwrap(), "15");
114+
assert_eq!(format("{h:i..1}", context).unwrap(), "1");
115+
assert_eq!(format("{h:i1..}", context).unwrap(), "5");
116+
assert_eq!(format("{h:i..=-1}", context).unwrap(), "15");
97117
}
98118
}

src/lib.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,33 @@ The feature `iter` enables an additional format trait `i`, it allows to
2929
format a list of values with a format string and an optional join
3030
expression.
3131
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})}`.
32+
The syntax is `{list:i(the format string, '{}' is the array element)(the
33+
join)}`, an empty join can also be omitted `{list:i({})}`. If join is omitted
34+
the format string `{}` can be omitted as well `{list:i}`.
3435
3536
Should you need to use `)` inside your format string or join, you can add `#`
3637
similar to rust's [raw string](https://doc.rust-lang.org/reference/tokens.html#raw-string-literals)
37-
(i.e. `#(({it}))#`).
38+
(i.e. `#(({}))#`).
3839
3940
It is also possible to only iterate a sub-slice specified through a range
40-
before the format string, i.e. `{list:i1..4({it})}`. For open ranges range
41+
before the format string, i.e. `{list:i1..4}`. For open ranges range
4142
bounds can also be omitted. To index from the end, you can use negative
4243
range bounds.
4344
45+
It is also possible to index a single value by only specifying an [`isize`]
46+
`{list:i1}`.
47+
4448
A [`Formattable`] implementing iter is created using [`Formattable::iter`]:
4549
4650
```
4751
// HashMap macro
4852
use collection_literals::hash;
4953
use interpolator::{format, Formattable};
50-
// Needs to be a slice of references so because `Formattable::display` expects a
54+
// Needs to be a slice of references because `Formattable::display` expects a
5155
// reference
5256
let items = [&"hello", &"hi", &"hey"].map(Formattable::display);
5357
let items = Formattable::iter(&items);
54-
let format_str = "Greetings: {items:i..-1(`{it}`)(, )} and {items:i-1..(`{it}`)}";
58+
let format_str = "Greetings: {items:i..-1(`{}`)(, )} and `{items:i-1}`";
5559
assert_eq!(
5660
format(format_str, &hash!("items" => items))?,
5761
"Greetings: `hello`, `hi` and `hey`"

src/parser.rs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub(crate) enum TraitSpec<'a> {
5555
#[cfg(feature = "pointer")]
5656
Pointer,
5757
#[cfg(feature = "iter")]
58-
Iter(Option<Range>, &'a str, Option<&'a str>),
58+
Iter(Option<Range>, Option<&'a str>, Option<&'a str>),
5959
#[allow(unused)]
6060
Phantom(&'a Infallible),
6161
}
@@ -142,36 +142,45 @@ impl<'a> TraitSpec<'a> {
142142
b'i' => {
143143
step(1, format, idx);
144144
return Ok(TraitSpec::Iter(
145-
if format.starts_with('(') {
146-
None
147-
} else {
148-
Some(Range(
149-
if format.starts_with("..") {
150-
step(2, format, idx);
151-
None
152-
} else {
153-
let lhs = parse_number(format, idx, start, ParseError::RangeBound)?;
154-
ensure!(format.starts_with(".."), ParseError::Expected("..", *idx));
155-
step(2, format, idx);
156-
Some(lhs)
157-
},
158-
if format.starts_with('=') {
145+
if format.starts_with('-')
146+
|| format.starts_with("..")
147+
|| format.starts_with(|c: char| c.is_ascii_digit())
148+
{
149+
let lhs = if format.starts_with("..") {
150+
None
151+
} else {
152+
Some(parse_number(format, idx, start, ParseError::RangeBound)?)
153+
};
154+
let inclusive;
155+
let rhs;
156+
if format.starts_with("..") {
157+
step(2, format, idx);
158+
inclusive = format.starts_with('=');
159+
if inclusive {
159160
step(1, format, idx);
160-
true
161+
}
162+
rhs = if format.starts_with('-')
163+
|| format.starts_with(|c: char| c.is_ascii_digit())
164+
{
165+
Some(parse_number(format, idx, start, ParseError::RangeBound)?)
161166
} else {
162-
false
163-
},
164-
if format.starts_with('(') {
165167
None
166-
} else {
167-
Some(parse_number(format, idx, start, ParseError::RangeBound)?)
168-
},
169-
))
168+
}
169+
} else {
170+
inclusive = true;
171+
rhs = lhs;
172+
}
173+
Some(Range(lhs, inclusive, rhs))
174+
} else {
175+
None
176+
},
177+
if format.starts_with('(') || format.starts_with('#') {
178+
Some(collect_parenthesized(format, idx, start)?)
179+
} else {
180+
None
170181
},
171-
collect_parenthesized(format, idx, start)?,
172182
if format.starts_with('(') || format.starts_with('#') {
173-
let join = collect_parenthesized(format, idx, start)?;
174-
Some(join)
183+
Some(collect_parenthesized(format, idx, start)?)
175184
} else {
176185
None
177186
},
@@ -327,7 +336,7 @@ impl<'a> FormatArgument<'a> {
327336
mod test {
328337
use super::*;
329338
#[test]
330-
fn collect_braced() {
339+
fn collect_parenthesized() {
331340
use super::collect_parenthesized;
332341
let mut idx = 0;
333342
let mut format = "()";

0 commit comments

Comments
 (0)