use super::{Item, Numeric, Fixed, Pad};
#[derive(Clone)]
pub struct StrftimeItems<'a> {
remainder: &'a str,
recons: &'static [Item<'static>],
}
impl<'a> StrftimeItems<'a> {
pub fn new(s: &'a str) -> StrftimeItems<'a> {
static FMT_NONE: [Item<'static>; 0] = [];
StrftimeItems { remainder: s, recons: &FMT_NONE }
}
}
impl<'a> Iterator for StrftimeItems<'a> {
type Item = Item<'a>;
fn next(&mut self) -> Option<Item<'a>> {
if !self.recons.is_empty() {
let item = self.recons[0];
self.recons = &self.recons[1..];
return Some(item);
}
match self.remainder.chars().next() {
None => return None,
Some('%') => {
self.remainder = &self.remainder[1..];
let spec = match self.remainder.chars().next() {
Some(x) => x,
None => return Some(Item::Error), };
self.remainder = &self.remainder[spec.len_utf8()..];
macro_rules! recons {
[$head:expr, $($tail:expr),+] => ({
const RECONS: &'static [Item<'static>] = &[$($tail),+];
self.recons = RECONS;
$head
})
}
match spec {
'A' => Some(fix!(LongWeekdayName)),
'B' => Some(fix!(LongMonthName)),
'C' => Some(num0!(YearDiv100)),
'D' => Some(recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
num0!(YearMod100)]),
'F' => Some(recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"),
num0!(Day)]),
'G' => Some(num0!(IsoYear)),
'H' => Some(num0!(Hour)),
'I' => Some(num0!(Hour12)),
'M' => Some(num0!(Minute)),
'P' => Some(fix!(LowerAmPm)),
'R' => Some(recons![num0!(Hour), lit!(":"), num0!(Minute)]),
'S' => Some(num0!(Second)),
'T' => Some(recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"),
num0!(Second)]),
'U' => Some(num0!(WeekFromSun)),
'V' => Some(num0!(IsoWeek)),
'W' => Some(num0!(WeekFromMon)),
'X' => Some(recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"),
num0!(Second)]),
'Y' => Some(num0!(Year)),
'Z' => Some(fix!(TimezoneName)),
'a' => Some(fix!(ShortWeekdayName)),
'b' => Some(fix!(ShortMonthName)),
'c' => Some(recons![fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName),
sp!(" "), nums!(Day), sp!(" "), num0!(Hour), lit!(":"),
num0!(Minute), lit!(":"), num0!(Second), sp!(" "),
num0!(Year)]),
'd' => Some(num0!(Day)),
'e' => Some(nums!(Day)),
'f' => Some(num0!(Nanosecond)),
'g' => Some(num0!(IsoYearMod100)),
'h' => Some(fix!(ShortMonthName)),
'j' => Some(num0!(Ordinal)),
'k' => Some(nums!(Hour)),
'l' => Some(nums!(Hour12)),
'm' => Some(num0!(Month)),
'n' => Some(sp!("\n")),
'p' => Some(fix!(UpperAmPm)),
'r' => Some(recons![num0!(Hour12), lit!(":"), num0!(Minute), lit!(":"),
num0!(Second), sp!(" "), fix!(UpperAmPm)]),
's' => Some(num!(Timestamp)),
't' => Some(sp!("\t")),
'u' => Some(num!(WeekdayFromMon)),
'v' => Some(recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"),
num0!(Year)]),
'w' => Some(num!(NumDaysFromSun)),
'x' => Some(recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
num0!(YearMod100)]),
'y' => Some(num0!(YearMod100)),
'z' => Some(fix!(TimezoneOffset)),
'+' => Some(fix!(RFC3339)),
'%' => Some(lit!("%")),
_ => Some(Item::Error), }
},
Some(c) if c.is_whitespace() => {
let nextspec = self.remainder.find(|c: char| !c.is_whitespace())
.unwrap_or(self.remainder.len());
assert!(nextspec > 0);
let item = sp!(&self.remainder[..nextspec]);
self.remainder = &self.remainder[nextspec..];
Some(item)
},
_ => {
let nextspec = self.remainder.find(|c: char| c.is_whitespace() || c == '%')
.unwrap_or(self.remainder.len());
assert!(nextspec > 0);
let item = lit!(&self.remainder[..nextspec]);
self.remainder = &self.remainder[nextspec..];
Some(item)
},
}
}
}
#[cfg(test)]
#[test]
fn test_strftime_items() {
fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> {
let items = StrftimeItems::new(s);
let items = items.map(|spec| if spec == Item::Error {None} else {Some(spec)});
items.collect::<Option<Vec<_>>>().unwrap_or(vec![Item::Error])
}
assert_eq!(parse_and_collect(""), []);
assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
assert_eq!(parse_and_collect("a b\t\nc"), [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"),
lit!("c")]);
assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
assert_eq!(parse_and_collect("%Y-%m-%d"), [num0!(Year), lit!("-"), num0!(Month), lit!("-"),
num0!(Day)]);
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
assert_eq!(parse_and_collect("%"), [Item::Error]);
assert_eq!(parse_and_collect("%%"), [lit!("%")]);
assert_eq!(parse_and_collect("%%%"), [Item::Error]);
assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
}