use std::cmp::Ordering;
use std::io::{BufRead, BufReader, Read};
use std::num::ParseIntError;
use std::time::Duration;
use Month::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
pub enum Month {
January = 0,
February = 1,
March = 2,
April = 3,
May = 4,
June = 5,
July = 6,
August = 7,
September = 8,
October = 9,
November = 10,
December = 11,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Date {
pub day: u8,
pub month: Month,
pub year: u16,
}
impl PartialOrd for Date {
fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Date {
fn cmp(&self, other: &Date) -> Ordering {
if self.year < other.year || (self.year == other.year && self.month < other.month || (self.month == other.month
&& self.day < other.day)) {
Ordering::Less
}
else if self == other {
Ordering::Equal
}
else {
Ordering::Greater
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Time {
pub hour: u8,
pub minute: u8,
}
impl PartialOrd for Time {
fn partial_cmp(&self, other: &Time) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Time {
fn cmp(&self, other: &Time) -> Ordering {
if self.hour < other.hour || (self.hour == other.hour && self.minute < other.minute) {
Ordering::Less
}
else if self == other {
Ordering::Equal
}
else {
Ordering::Greater
}
}
}
#[derive(Debug, PartialEq)]
pub struct Entry {
pub date: Date,
pub duration: Duration,
pub msg: String,
pub time: Time,
}
pub fn parse<R: Read>(reader: R) -> Result<Vec<Entry>, String> {
let mut entries = vec![];
let reader = BufReader::new(reader);
for line in reader.lines() {
let line = line.map_err(|error| error.to_string())?;
let mut parser = Parser::new(&line);
if let Ok(entry) = parser.entry() {
entries.push(entry);
}
}
Ok(entries)
}
struct Parser {
index: usize,
words: Vec<String>,
}
impl Parser {
fn new(line: &str) -> Self {
let words = line.split_whitespace()
.filter(|word| !word.trim().is_empty())
.map(ToString::to_string)
.collect();
Self {
index: 0,
words,
}
}
fn date(&mut self) -> Result<Date, String> {
let month =
match self.next_word().ok_or_else(|| "Expecting date, found end of line".to_string())?.to_lowercase().as_str() {
"jan" => January,
"feb" => February,
"mar" => March,
"apr" => April,
"may" => May,
"jun" => June,
"jul" => July,
"aug" => August,
"sep" => September,
"oct" => October,
"nov" => November,
"dec" => December,
month => return Err(format!("Invalid month {}", month)),
};
let day = self.num()? as u8;
let year = self.num()? as u16;
Ok(Date {
day,
month,
year,
})
}
fn duration(&mut self) -> Result<Duration, String> {
self.ident("DURATION")?;
let time = self.time_num()?;
Ok(Duration::from_secs(time.hour as u64 * 60 * 60 + time.minute as u64 * 60))
}
fn entry(&mut self) -> Result<Entry, String> {
self.ident("REM")?;
let date = self.date()?;
let time = self.time()?;
let duration = self.duration()?;
let msg = self.message()?;
Ok(Entry {
date,
duration,
msg,
time,
})
}
fn ident(&mut self, ident: &str) -> Result<(), String> {
if self.next_word().map(str::to_lowercase) != Some(ident.to_lowercase()) {
return Err("Expecting REM at beginning of line".to_string());
}
Ok(())
}
fn message(&mut self) -> Result<String, String> {
self.ident("MSG")?;
let message = self.words[self.index..].join(" ");
Ok(message)
}
fn next_word(&mut self) -> Option<&str> {
let index = self.index;
let result = self.words.get(index)
.map(|string| string.as_str());
if result.is_some() {
self.index += 1;
}
result
}
fn num(&mut self) -> Result<u32, String> {
self.next_word()
.ok_or_else(|| "Expecting day of month, found end of line".to_string())?
.parse()
.map_err(|error: ParseIntError| error.to_string())
}
fn time(&mut self) -> Result<Time, String> {
self.ident("AT")?;
let time = self.time_num()?;
Ok(time)
}
fn time_num(&mut self) -> Result<Time, String> {
let time = self.next_word().ok_or_else(|| "Expecting time, found end of line".to_string())?;
let mut parts = time.split(':');
let hour = parts.next()
.ok_or_else(|| "Expecting hour, found end of line".to_string())
.map_err(|error| error.to_string())?
.parse()
.map_err(|error: ParseIntError| error.to_string())?;
let minute = parts.next()
.ok_or_else(|| "Expecting hour, found end of line".to_string())
.map_err(|error| error.to_string())?
.parse()
.map_err(|error: ParseIntError| error.to_string())?;
Ok(Time {
hour,
minute,
})
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use {Date, Time, parse};
use Month::*;
#[test]
fn parse_rem() {
let data = "REM Mar 30 2018 AT 19:00 DURATION 1:15 MSG Event name";
let entries = parse(data.as_bytes()).expect("entries");
assert_eq!(entries[0].date, Date { day: 30, month: March, year: 2018 });
assert_eq!(entries[0].duration, Duration::from_secs(75 * 60));
assert_eq!(entries[0].msg, "Event name".to_string());
assert_eq!(entries[0].time, Time { hour: 19, minute: 0 });
let data = "REM Mar 30 2018 AT 19:00 DURATION 1:15 MSG Event name
REM Apr 9 2018 AT 12:50 DURATION 0:15 MSG Super Event";
let entries = parse(data.as_bytes()).expect("entries");
assert_eq!(entries[0].date, Date { day: 30, month: March, year: 2018 });
assert_eq!(entries[0].duration, Duration::from_secs(75 * 60));
assert_eq!(entries[0].msg, "Event name".to_string());
assert_eq!(entries[0].time, Time { hour: 19, minute: 0 });
assert_eq!(entries[1].date, Date { day: 9, month: April, year: 2018 });
let _month = entries[1].date.month;
assert!(entries[1].date.month == April);
assert_eq!(entries[1].duration, Duration::from_secs(15 * 60));
assert_eq!(entries[1].msg, "Super Event".to_string());
assert_eq!(entries[1].time, Time { hour: 12, minute: 50 });
}
}