upgrade winnow to 0.7.10

This commit is contained in:
yuankunzhang
2025-05-10 13:13:18 +08:00
parent 852e49e224
commit bea8ec2641
9 changed files with 141 additions and 120 deletions
Generated
+2 -12
View File
@@ -118,15 +118,6 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -147,7 +138,6 @@ name = "parse_datetime"
version = "0.9.0"
dependencies = [
"chrono",
"nom",
"num-traits",
"regex",
"winnow",
@@ -353,9 +343,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.5.40"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [
"memchr",
]
+1 -2
View File
@@ -10,6 +10,5 @@ readme = "README.md"
[dependencies]
regex = "1.10.4"
chrono = { version="0.4.38", default-features=false, features=["std", "alloc", "clock"] }
nom = "8.0.0"
winnow = "0.5.34"
winnow = "0.7.10"
num-traits = "0.2.19"
+5 -2
View File
@@ -13,7 +13,10 @@
//! > seconds are allowed, with either comma or period preceding the fraction.
//! > ISO 8601 fractional minutes and hours are not supported. Typically, hosts
//! > support nanosecond timestamp resolution; excess precision is silently discarded.
use winnow::{combinator::alt, seq, trace::trace, PResult, Parser};
use winnow::{
combinator::{alt, trace},
seq, ModalResult, Parser,
};
use crate::items::space;
@@ -29,7 +32,7 @@ pub struct DateTime {
pub(crate) time: Time,
}
pub fn parse(input: &mut &str) -> PResult<DateTime> {
pub fn parse(input: &mut &str) -> ModalResult<DateTime> {
seq!(DateTime {
date: trace("date iso", alt((date::iso1, date::iso2))),
// Note: the `T` is lowercased by the main parse function
+14 -15
View File
@@ -27,16 +27,15 @@
//! > September.
use winnow::{
ascii::{alpha1, dec_uint},
combinator::{alt, opt, preceded},
ascii::alpha1,
combinator::{alt, opt, preceded, trace},
seq,
stream::AsChar,
token::{take, take_while},
trace::trace,
PResult, Parser,
ModalResult, Parser,
};
use super::s;
use super::{dec_uint, s};
use crate::ParseDateTimeError;
#[derive(PartialEq, Eq, Clone, Debug, Default)]
@@ -46,14 +45,14 @@ pub struct Date {
pub year: Option<u32>,
}
pub fn parse(input: &mut &str) -> PResult<Date> {
pub fn parse(input: &mut &str) -> ModalResult<Date> {
alt((iso1, iso2, us, literal1, literal2)).parse_next(input)
}
/// Parse `YYYY-MM-DD` or `YY-MM-DD`
///
/// This is also used by [`combined`](super::combined).
pub fn iso1(input: &mut &str) -> PResult<Date> {
pub fn iso1(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
year: year.map(Some),
_: s('-'),
@@ -67,7 +66,7 @@ pub fn iso1(input: &mut &str) -> PResult<Date> {
/// Parse `YYYYMMDD`
///
/// This is also used by [`combined`](super::combined).
pub fn iso2(input: &mut &str) -> PResult<Date> {
pub fn iso2(input: &mut &str) -> ModalResult<Date> {
s((
take(4usize).try_map(|s: &str| s.parse::<u32>()),
take(2usize).try_map(|s: &str| s.parse::<u32>()),
@@ -82,7 +81,7 @@ pub fn iso2(input: &mut &str) -> PResult<Date> {
}
/// Parse `MM/DD/YYYY`, `MM/DD/YY` or `MM/DD`
fn us(input: &mut &str) -> PResult<Date> {
fn us(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
month: month,
_: s('/'),
@@ -93,7 +92,7 @@ fn us(input: &mut &str) -> PResult<Date> {
}
/// Parse `14 November 2022`, `14 Nov 2022`, "14nov2022", "14-nov-2022", "14-nov2022", "14nov-2022"
fn literal1(input: &mut &str) -> PResult<Date> {
fn literal1(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
day: day,
_: opt(s('-')),
@@ -104,7 +103,7 @@ fn literal1(input: &mut &str) -> PResult<Date> {
}
/// Parse `November 14, 2022` and `Nov 14, 2022`
fn literal2(input: &mut &str) -> PResult<Date> {
fn literal2(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
month: literal_month,
day: day,
@@ -115,7 +114,7 @@ fn literal2(input: &mut &str) -> PResult<Date> {
.parse_next(input)
}
pub fn year(input: &mut &str) -> PResult<u32> {
pub fn year(input: &mut &str) -> ModalResult<u32> {
// 2147485547 is the maximum value accepted
// by GNU, but chrono only behaves like GNU
// for years in the range: [0, 9999], so we
@@ -140,7 +139,7 @@ pub fn year(input: &mut &str) -> PResult<u32> {
.parse_next(input)
}
fn month(input: &mut &str) -> PResult<u32> {
fn month(input: &mut &str) -> ModalResult<u32> {
s(dec_uint)
.try_map(|x| {
(1..=12)
@@ -151,7 +150,7 @@ fn month(input: &mut &str) -> PResult<u32> {
.parse_next(input)
}
fn day(input: &mut &str) -> PResult<u32> {
fn day(input: &mut &str) -> ModalResult<u32> {
s(dec_uint)
.try_map(|x| {
(1..=31)
@@ -163,7 +162,7 @@ fn day(input: &mut &str) -> PResult<u32> {
}
/// Parse the name of a month (case-insensitive)
fn literal_month(input: &mut &str) -> PResult<u32> {
fn literal_month(input: &mut &str) -> ModalResult<u32> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s {
+75 -42
View File
@@ -34,18 +34,18 @@ mod relative;
mod time;
mod weekday;
mod epoch {
use winnow::{ascii::dec_int, combinator::preceded, PResult, Parser};
use winnow::{combinator::preceded, ModalResult, Parser};
use super::s;
pub fn parse(input: &mut &str) -> PResult<i32> {
use super::{dec_int, s};
pub fn parse(input: &mut &str) -> ModalResult<i32> {
s(preceded("@", dec_int)).parse_next(input)
}
}
mod timezone {
use super::time;
use winnow::PResult;
use winnow::ModalResult;
pub(crate) fn parse(input: &mut &str) -> PResult<time::Offset> {
pub(crate) fn parse(input: &mut &str) -> ModalResult<time::Offset> {
time::timezone(input)
}
}
@@ -53,15 +53,14 @@ mod timezone {
use chrono::NaiveDate;
use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike};
use winnow::error::{AddContext, ParserError, StrContext};
use winnow::error::{ContextError, ErrMode};
use winnow::trace::trace;
use winnow::error::{StrContext, StrContextValue};
use winnow::{
ascii::multispace0,
combinator::{alt, delimited, not, peek, preceded, repeat, separated},
ascii::{digit1, multispace0},
combinator::{alt, delimited, not, opt, peek, preceded, repeat, separated, trace},
error::{ContextError, ErrMode, ParserError},
stream::AsChar,
token::{none_of, take_while},
PResult, Parser,
token::{none_of, one_of, take_while},
ModalResult, Parser,
};
use crate::ParseDateTimeError;
@@ -93,7 +92,7 @@ where
/// Parse the space in-between tokens
///
/// You probably want to use the [`s`] combinator instead.
fn space<'a, E>(input: &mut &'a str) -> PResult<(), E>
fn space<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
where
E: ParserError<&'a str>,
{
@@ -110,7 +109,7 @@ where
/// The last comment should be ignored.
///
/// The plus is undocumented, but it seems to be ignored.
fn ignored_hyphen_or_plus<'a, E>(input: &mut &'a str) -> PResult<(), E>
fn ignored_hyphen_or_plus<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
where
E: ParserError<&'a str>,
{
@@ -127,7 +126,7 @@ where
///
/// A comment is given between parentheses, which must be balanced. Any other
/// tokens can be within the comment.
fn comment<'a, E>(input: &mut &'a str) -> PResult<(), E>
fn comment<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
where
E: ParserError<&'a str>,
{
@@ -139,8 +138,45 @@ where
.parse_next(input)
}
/// Parse a signed decimal integer.
///
/// Rationale for not using `winnow::ascii::dec_int`: When upgrading winnow from
/// 0.5 to 0.7, we discovered that `winnow::ascii::dec_int` now accepts only the
/// following two forms:
///
/// - 0
/// - [+-][1-9][0-9]*
///
/// Inputs like [+-]0[0-9]* (e.g., `+012`) are therefore rejected. We provide a
/// custom implementation to support such zero-prefixed integers.
fn dec_int<'a, E>(input: &mut &'a str) -> winnow::Result<i32, E>
where
E: ParserError<&'a str>,
{
(opt(one_of(['+', '-'])), digit1)
.void()
.take()
.verify_map(|s: &str| s.parse().ok())
.parse_next(input)
}
/// Parse an unsigned decimal integer.
///
/// See the rationale for `dec_int` for why we don't use
/// `winnow::ascii::dec_uint`.
fn dec_uint<'a, E>(input: &mut &'a str) -> winnow::Result<u32, E>
where
E: ParserError<&'a str>,
{
digit1
.void()
.take()
.verify_map(|s: &str| s.parse().ok())
.parse_next(input)
}
// Parse an item
pub fn parse_one(input: &mut &str) -> PResult<Item> {
pub fn parse_one(input: &mut &str) -> ModalResult<Item> {
trace(
"parse_one",
alt((
@@ -157,7 +193,7 @@ pub fn parse_one(input: &mut &str) -> PResult<Item> {
.parse_next(input)
}
pub fn parse(input: &mut &str) -> PResult<Vec<Item>> {
pub fn parse(input: &mut &str) -> ModalResult<Vec<Item>> {
let mut items = Vec::new();
let mut date_seen = false;
let mut time_seen = false;
@@ -170,12 +206,13 @@ pub fn parse(input: &mut &str) -> PResult<Vec<Item>> {
match item {
Item::DateTime(ref dt) => {
if date_seen || time_seen {
return Err(ErrMode::Backtrack(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(
winnow::error::StrContextValue::Description(
"date or time cannot appear more than once",
)),
)));
),
));
return Err(ErrMode::Backtrack(ctx_err));
}
date_seen = true;
@@ -186,12 +223,11 @@ pub fn parse(input: &mut &str) -> PResult<Vec<Item>> {
}
Item::Date(ref d) => {
if date_seen {
return Err(ErrMode::Backtrack(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
"date cannot appear more than once",
)),
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(StrContextValue::Description(
"date cannot appear more than once",
)));
return Err(ErrMode::Backtrack(ctx_err));
}
date_seen = true;
@@ -201,34 +237,31 @@ pub fn parse(input: &mut &str) -> PResult<Vec<Item>> {
}
Item::Time(_) => {
if time_seen {
return Err(ErrMode::Backtrack(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
"time cannot appear more than once",
)),
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(StrContextValue::Description(
"time cannot appear more than once",
)));
return Err(ErrMode::Backtrack(ctx_err));
}
time_seen = true;
}
Item::Year(_) => {
if year_seen {
return Err(ErrMode::Backtrack(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
"year cannot appear more than once",
)),
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(StrContextValue::Description(
"year cannot appear more than once",
)));
return Err(ErrMode::Backtrack(ctx_err));
}
year_seen = true;
}
Item::TimeZone(_) => {
if tz_seen {
return Err(ErrMode::Backtrack(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
"timezone cannot appear more than once",
)),
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(StrContextValue::Description(
"timezone cannot appear more than once",
)));
return Err(ErrMode::Backtrack(ctx_err));
}
tz_seen = true;
}
+4 -4
View File
@@ -5,14 +5,14 @@ use super::s;
use winnow::{
ascii::{alpha1, dec_uint},
combinator::{alt, opt},
PResult, Parser,
ModalResult, Parser,
};
pub fn ordinal(input: &mut &str) -> PResult<i32> {
pub fn ordinal(input: &mut &str) -> ModalResult<i32> {
alt((text_ordinal, number_ordinal)).parse_next(input)
}
fn number_ordinal(input: &mut &str) -> PResult<i32> {
fn number_ordinal(input: &mut &str) -> ModalResult<i32> {
let sign = opt(alt(('+'.value(1), '-'.value(-1)))).map(|s| s.unwrap_or(1));
(s(sign), s(dec_uint))
.verify_map(|(s, u): (i32, u32)| {
@@ -22,7 +22,7 @@ fn number_ordinal(input: &mut &str) -> PResult<i32> {
.parse_next(input)
}
fn text_ordinal(input: &mut &str) -> PResult<i32> {
fn text_ordinal(input: &mut &str) -> ModalResult<i32> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s {
+6 -6
View File
@@ -34,7 +34,7 @@
use winnow::{
ascii::{alpha1, float},
combinator::{alt, opt},
PResult, Parser,
ModalResult, Parser,
};
use super::{ordinal::ordinal, s};
@@ -63,7 +63,7 @@ impl Relative {
}
}
pub fn parse(input: &mut &str) -> PResult<Relative> {
pub fn parse(input: &mut &str) -> ModalResult<Relative> {
alt((
s("tomorrow").value(Relative::Days(1)),
s("yesterday").value(Relative::Days(-1)),
@@ -76,7 +76,7 @@ pub fn parse(input: &mut &str) -> PResult<Relative> {
.parse_next(input)
}
fn seconds(input: &mut &str) -> PResult<Relative> {
fn seconds(input: &mut &str) -> ModalResult<Relative> {
(
opt(alt((s(float), ordinal.map(|x| x as f64)))),
s(alpha1).verify(|s: &str| matches!(s, "seconds" | "second" | "sec" | "secs")),
@@ -86,17 +86,17 @@ fn seconds(input: &mut &str) -> PResult<Relative> {
.parse_next(input)
}
fn other(input: &mut &str) -> PResult<Relative> {
fn other(input: &mut &str) -> ModalResult<Relative> {
(opt(ordinal), integer_unit, ago)
.map(|(n, unit, ago)| unit.mul(n.unwrap_or(1) * if ago { -1 } else { 1 }))
.parse_next(input)
}
fn ago(input: &mut &str) -> PResult<bool> {
fn ago(input: &mut &str) -> ModalResult<bool> {
opt(s("ago")).map(|o| o.is_some()).parse_next(input)
}
fn integer_unit(input: &mut &str) -> PResult<Relative> {
fn integer_unit(input: &mut &str) -> ModalResult<Relative> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s.strip_suffix('s').unwrap_or(s) {
+31 -34
View File
@@ -41,16 +41,16 @@ use std::fmt::Display;
use chrono::FixedOffset;
use winnow::{
ascii::{dec_uint, digit1, float},
ascii::{digit1, float},
combinator::{alt, opt, peek, preceded},
error::{AddContext, ContextError, ErrMode, StrContext},
error::{ContextError, ErrMode, StrContext, StrContextValue},
seq,
stream::AsChar,
token::take_while,
PResult, Parser,
ModalResult, Parser,
};
use super::{relative, s};
use super::{dec_uint, relative, s};
#[derive(PartialEq, Debug, Clone, Default)]
pub struct Offset {
@@ -132,14 +132,14 @@ enum Suffix {
Pm,
}
pub fn parse(input: &mut &str) -> PResult<Time> {
pub fn parse(input: &mut &str) -> ModalResult<Time> {
alt((am_pm_time, iso)).parse_next(input)
}
/// Parse an ISO 8601 time string
///
/// Also used by the [`combined`](super::combined) module
pub fn iso(input: &mut &str) -> PResult<Time> {
pub fn iso(input: &mut &str) -> ModalResult<Time> {
alt((
(hour24, timezone).map(|(hour, offset)| Time {
hour,
@@ -161,7 +161,7 @@ pub fn iso(input: &mut &str) -> PResult<Time> {
/// Parse a time ending with AM or PM
///
/// The hours are restricted to 12 or lower in this format
fn am_pm_time(input: &mut &str) -> PResult<Time> {
fn am_pm_time(input: &mut &str) -> ModalResult<Time> {
seq!(
hour12,
opt(preceded(colon, minute)),
@@ -189,36 +189,36 @@ fn am_pm_time(input: &mut &str) -> PResult<Time> {
}
/// Parse a colon preceded by whitespace
fn colon(input: &mut &str) -> PResult<()> {
fn colon(input: &mut &str) -> ModalResult<()> {
s(':').void().parse_next(input)
}
/// Parse a number of hours in `0..24`(preceded by whitespace)
fn hour24(input: &mut &str) -> PResult<u32> {
fn hour24(input: &mut &str) -> ModalResult<u32> {
s(dec_uint).verify(|x| *x < 24).parse_next(input)
}
/// Parse a number of hours in `0..=12` (preceded by whitespace)
fn hour12(input: &mut &str) -> PResult<u32> {
fn hour12(input: &mut &str) -> ModalResult<u32> {
s(dec_uint).verify(|x| *x <= 12).parse_next(input)
}
/// Parse a number of minutes (preceded by whitespace)
fn minute(input: &mut &str) -> PResult<u32> {
fn minute(input: &mut &str) -> ModalResult<u32> {
s(dec_uint).verify(|x| *x < 60).parse_next(input)
}
/// Parse a number of seconds (preceded by whitespace)
fn second(input: &mut &str) -> PResult<f64> {
fn second(input: &mut &str) -> ModalResult<f64> {
s(float).verify(|x| *x < 60.0).parse_next(input)
}
pub(crate) fn timezone(input: &mut &str) -> PResult<Offset> {
pub(crate) fn timezone(input: &mut &str) -> ModalResult<Offset> {
alt((timezone_num, timezone_name_offset)).parse_next(input)
}
/// Parse a timezone starting with `+` or `-`
fn timezone_num(input: &mut &str) -> PResult<Offset> {
fn timezone_num(input: &mut &str) -> ModalResult<Offset> {
// Strings like "+8 years" are ambiguous, they can either be parsed as a
// timezone offset "+8" and a relative time "years", or just a relative time
// "+8 years". GNU date parses them the second way, so we do the same here.
@@ -232,21 +232,19 @@ fn timezone_num(input: &mut &str) -> PResult<Offset> {
.parse_next(input)
.and_then(|(negative, (hours, minutes))| {
if !(0..=12).contains(&hours) {
return Err(ErrMode::Cut(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
"timezone hour between 0 and 12",
)),
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(StrContextValue::Description(
"timezone hour between 0 and 12",
)));
return Err(ErrMode::Cut(ctx_err));
}
if !(0..=60).contains(&minutes) {
return Err(ErrMode::Cut(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
"timezone minute between 0 and 60",
)),
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(StrContextValue::Description(
"timezone minute between 0 and 60",
)));
return Err(ErrMode::Cut(ctx_err));
}
Ok(Offset {
@@ -258,7 +256,7 @@ fn timezone_num(input: &mut &str) -> PResult<Offset> {
}
/// Parse a timezone offset with a colon separating hours and minutes
fn timezone_colon(input: &mut &str) -> PResult<(u32, u32)> {
fn timezone_colon(input: &mut &str) -> ModalResult<(u32, u32)> {
seq!(
s(take_while(1..=2, AsChar::is_dec_digit)).try_map(|x: &str| {
// parse will fail on empty input
@@ -275,15 +273,14 @@ fn timezone_colon(input: &mut &str) -> PResult<(u32, u32)> {
}
/// Parse a timezone offset without colon
fn timezone_colonless(input: &mut &str) -> PResult<(u32, u32)> {
fn timezone_colonless(input: &mut &str) -> ModalResult<(u32, u32)> {
if let Ok(x) = peek(s(digit1::<&str, ContextError>)).parse_next(input) {
if x.len() > 4 {
return Err(ErrMode::Cut(ContextError::new().add_context(
&input,
StrContext::Expected(winnow::error::StrContextValue::Description(
"timezone offset cannot be more than 4 digits",
)),
let mut ctx_err = ContextError::new();
ctx_err.push(StrContext::Expected(StrContextValue::Description(
"timezone offset cannot be more than 4 digits",
)));
return Err(ErrMode::Cut(ctx_err));
}
}
@@ -304,7 +301,7 @@ fn timezone_colonless(input: &mut &str) -> PResult<(u32, u32)> {
}
/// Parse a timezone by name
fn timezone_name_offset(input: &mut &str) -> PResult<Offset> {
fn timezone_name_offset(input: &mut &str) -> ModalResult<Offset> {
/// I'm assuming there are no timezone abbreviations with more
/// than 6 charactres
const MAX_TZ_SIZE: usize = 6;
@@ -331,7 +328,7 @@ fn timezone_name_offset(input: &mut &str) -> PResult<Offset> {
/// Timezone list extracted from:
/// https://www.timeanddate.com/time/zones/
fn tzname_to_offset(input: &str) -> PResult<Offset> {
fn tzname_to_offset(input: &str) -> ModalResult<Offset> {
let mut offset_str = match input {
"z" => Ok("+0"),
"yekt" => Ok("+5"),
@@ -568,7 +565,7 @@ fn tzname_to_offset(input: &str) -> PResult<Offset> {
}
/// Parse the plus or minus character and return whether it was negative
fn plus_or_minus(input: &mut &str) -> PResult<bool> {
fn plus_or_minus(input: &mut &str) -> ModalResult<bool> {
s(alt(("+".value(false), "-".value(true)))).parse_next(input)
}
+3 -3
View File
@@ -21,7 +21,7 @@
//! >
//! > A comma following a day of the week item is ignored.
use winnow::{ascii::alpha1, combinator::opt, seq, PResult, Parser};
use winnow::{ascii::alpha1, combinator::opt, seq, ModalResult, Parser};
use super::{ordinal::ordinal, s};
@@ -55,7 +55,7 @@ impl From<Day> for chrono::Weekday {
}
}
}
pub fn parse(input: &mut &str) -> PResult<Weekday> {
pub fn parse(input: &mut &str) -> ModalResult<Weekday> {
seq!(Weekday {
offset: opt(ordinal).map(|o| o.unwrap_or_default()),
day: day,
@@ -63,7 +63,7 @@ pub fn parse(input: &mut &str) -> PResult<Weekday> {
.parse_next(input)
}
fn day(input: &mut &str) -> PResult<Day> {
fn day(input: &mut &str) -> ModalResult<Day> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s {