Hello ,
I'm currently writing a PDF commandline tool and for it I'd like to allow ranges patterns as
3..15
for pages from 3 to 14 (as well as3..
,..15
,..
)3.2.15
for pages from 3 to 14 with step 2 (as well as3.2.
,.2.15
,.2.
)
and furthermore3.2.15*3
for pages from 3 to 14 with step 2 with each 3 pages (as well as all the others).
This shall enable me to write stupdf example.pdf addpage 3.2.15*3
meaning
add 3 blank pages to each index of
3..15.step_by(2)
of the pdf example.pdf .
Now the problem I'm having is, I don't know how to exactly implement the parsing and the resulting Iterator.
use std::ops::{Range, RangeFrom, RangeTo, RangeFull};
enum Ranges {
One(u32),
RNormal(Range<u32>),
RFrom(RangeFrom<u32>),
RTo(RangeTo<u32>),
RFull(RangeFull),
}
struct PageIter {
range: Ranges,
step: u32,
amount: u32,
}
impl PageIter {
fn iter(&self) -> impl Iterator<Item = (u32, u32)> { //yields an error since the types are not the same
match self.range {
Ranges::One(i) => std::iter::once((i, self.amount)),
Ranges::RNormal(r) => r.step_by(self.step).map(|i| (i, self.amount)),
Ranges::RFrom(r) => r.step_by(self.step).map(|i| (i, self.amount)),
Ranges::RTo(r) => r.step_by(self.step).map(|i| (i, self.amount)),
Ranges::RFull(r) => r.step_by(self.step).map(|i| (i, self.amount)),
}
}
}
impl std::str::FromStr for PageIter {
type Err = Error; //TODO change Error Type
/// Parses &str in the following form: 'a.b.c*n' where it stands for '(a..c).step_by(b)' and amounts 'n'. Every value can be omitted, but '*' likewise
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match &s.split('*').collect::<Vec<&str>>()[..] {
[rangestr, amountstr] => {
let amount = match amountstr.parse::<u32>() {
Ok(i) if i > 0 => i,
_ => return Err(Error::Parse {offset: 0}),
};
let (range, step) = if let Some((p, s)) = parse_range_and_step(rangestr) { (p, s) } else { return Err(Error::Parse {offset: 0}) };
Ok(PageIter{ range, step, amount })
},
[rangestr] => {
let (range, step) = if let Some((p, s)) = parse_range_and_step(rangestr) { (p, s) } else { return Err(Error::Parse {offset: 0}) };
Ok(PageIter{ range, step, amount: 1 })
}
_ => Err(Error::Parse {offset: 0})
}
}
}
fn parse_range_and_step(s: &str) -> Option<(Ranges, u32)> {
match &s.split('.').collect::<Vec<&str>>()[..] {
["", stepstr, ""] => Some((
Ranges::RFull(..),
if stepstr == &"" { 1 } else if let Ok(i) = stepstr.parse::<u32>() { i } else { return None }
)),
[begstr, stepstr, ""] => {
let beg = if let Ok(i) = begstr.parse::<u32>() { i } else { return None };
Some((
Ranges::RFrom(beg..),
if stepstr == &"" { 1 } else if let Ok(i) = stepstr.parse::<u32>() { i } else { return None }
))
},
["", stepstr, endstr] => {
let end = if let Ok(i) = endstr.parse::<u32>() { i } else { return None };
Some((
Ranges::RTo(..end),
if stepstr == &"" { 1 } else if let Ok(i) = stepstr.parse::<u32>() { i } else { return None }
))
},
[begstr, stepstr, endstr] => {
let beg = if let Ok(i) = begstr.parse::<u32>() { i } else { return None };
let end = if let Ok(i) = endstr.parse::<u32>() { i } else { return None };
Some((
Ranges::RNormal(beg..end),
if stepstr == &"" { 1 } else if let Ok(i) = stepstr.parse::<u32>() { i } else { return None }
))
},
[numstr] => Some((Ranges::One(if let Ok(i) = numstr.parse::<u32>() { i } else { return None }), 1)),
_ => return None
}
}
I hope to reduce and simplify the parsing bit of the code, I don't know if the enum Ranges
is necessary and I'm not sure how to build the Iterator yielding (u32, u32)
.
Any help would really be appreciated!