use std::str::FromStr;
use chrono::{Local, NaiveDate, NaiveDateTime, TimeZone};
use crate::{
car::{Car, Event, Notify},
errors::{
WardError, ERROR_PARSING_CAR_NUMBER, ERROR_PARSING_DATETIME, ERROR_PARSING_EVENT_STATUS,
},
};
const DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
pub(crate) fn raw_numbers(raw_list: &str) -> Vec<Car> {
let lines = raw_list.lines();
let mut cars: Vec<Car> = vec![];
for line in lines {
let number_count: usize;
let (line_title, line_data) = line.split_once("=").unwrap();
if line_title.contains(&"Number") {
number_count = find_sequence(line_title);
let car_number = line_data;
let car = Car {
number: car_number.to_string(),
begin: None,
end: None,
notify: Notify::Off,
note: None,
};
cars.insert(number_count, car);
}
if line_title.contains(&"Begin") {
let car = &mut cars[find_sequence(line_title)];
car.begin = date_from_str(line_data);
}
if line_title.contains(&"End") {
let car = &mut cars[find_sequence(line_title)];
car.end = date_from_str(line_data);
}
if line_title.contains(&"Notify") {
let car = &mut cars[find_sequence(line_title)];
car.notify = Notify::from_str(line_data).unwrap();
}
if line_title.contains(&"Note") {
let car = &mut cars[find_sequence(line_title)];
car.note = match line_data {
"" => None,
data => Some(data.to_string()),
};
}
}
cars
}
fn find_sequence(line: &str) -> usize {
let mut result = String::new();
line.chars().into_iter().for_each(|c| {
if c.is_numeric() {
result.push(c)
};
});
result.parse::<usize>().unwrap()
}
fn date_from_str(date: &str) -> Option<NaiveDate> {
match NaiveDate::from_str(date) {
Ok(date) => Some(date),
Err(_) => None,
}
}
pub fn str_from_date(date: Option<NaiveDate>) -> String {
match date {
Some(date) => date.to_string(),
None => "".to_string(),
}
}
pub(crate) fn events(raw_events: &str, status: Option<bool>) -> Result<Vec<Event>, WardError> {
let mut events = vec![];
let lines = raw_events.lines();
for line in lines {
let mut event = Event {
time: None,
car_number: "".to_string(),
status: false,
};
let mut splitter = line.split(",");
let naive_datetime = match splitter.next() {
Some(date) => match NaiveDateTime::parse_from_str(date, DATE_TIME_FORMAT) {
Ok(date_time) => date_time,
Err(err) => return Err(WardError::ParseError(err.to_string())),
},
None => return Err(WardError::ParseError(ERROR_PARSING_DATETIME.to_string())),
};
let datetime = Local.from_local_datetime(&naive_datetime).unwrap();
event.time = Some(datetime);
event.car_number = match splitter.next() {
Some(number) => number.to_string(),
None => return Err(WardError::ParseError(ERROR_PARSING_CAR_NUMBER.to_string())),
};
event.status = match splitter.next() {
Some(status) => match status {
"+" => true,
"-" => false,
_ => false,
},
None => {
return Err(WardError::ParseError(
ERROR_PARSING_EVENT_STATUS.to_string(),
))
}
};
match status {
Some(status) => {
if event.status == status {
events.push(event)
}
}
None => events.push(event),
}
}
Ok(events)
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use chrono::{DateTime, Datelike, Local, NaiveDate};
use crate::{car::Notify, parse::{self, date_from_str}};
#[test]
fn finding_sequence() {
let number = parse::find_sequence("any_s438tring");
assert_eq!(number, 438);
}
#[test]
fn str_date() {
let date_str = "2024-11-22";
let date = parse::date_from_str(date_str).unwrap();
assert_eq!(date.year(), 2024);
assert_eq!(date.month(), 11);
assert_eq!(date.day(), 22);
}
#[test]
fn wrong_date_from_str() {
let date_str = "not_data_str";
let date = parse::date_from_str(date_str);
assert!(date.is_none());
let date_str = "";
let date = parse::date_from_str(date_str);
assert!(date.is_none());
}
#[test]
fn string_from_date() {
let date = NaiveDate::default();
assert_eq!(parse::str_from_date(Some(date)), "1970-01-01");
assert_eq!(parse::str_from_date(None), "");
}
#[test]
fn parse_from_str() {
let raw_data = r#"Number0=X111XX777
Begin0=2020-11-13
End0=2020-12-22
Notify0=on
Note0="#;
let cars = parse::raw_numbers(raw_data);
assert_eq!(cars[0].number, "X111XX777");
assert_eq!(cars[0].begin, date_from_str("2020-11-13"));
assert_eq!(cars[0].end, date_from_str("2020-12-22"));
assert_eq!(cars[0].notify, Notify::On);
assert_eq!(cars[0].note, None);
}
#[test]
fn parse_from_str_with_comment() {
let raw_data = r#"Number0=X111XX777
Begin0=2020-11-13
End0=2020-12-22
Notify0=on
Note0=comment"#;
let cars = parse::raw_numbers(raw_data);
assert_eq!(cars[0].number, "X111XX777");
assert_eq!(cars[0].begin, date_from_str("2020-11-13"));
assert_eq!(cars[0].end, date_from_str("2020-12-22"));
assert_eq!(cars[0].notify, Notify::On);
assert_eq!(cars[0].note, Some("comment".to_string()));
}
#[test]
fn parse_from_str_with_long_comment() {
let raw_data = r#"Number0=X111XX777
Begin0=2020-11-13
End0=2020-12-22
Notify0=on
Note0=Long string COMMENT"#;
let cars = parse::raw_numbers(raw_data);
assert_eq!(cars[0].number, "X111XX777");
assert_eq!(cars[0].begin, date_from_str("2020-11-13"));
assert_eq!(cars[0].end, date_from_str("2020-12-22"));
assert_eq!(cars[0].notify, Notify::On);
assert_eq!(cars[0].note, Some("Long string COMMENT".to_string()));
}
#[test]
fn events() {
let raw_data = r#"2024-01-18 09:03:11,K147CP,-
2024-01-18 09:03:48,P829EX,+"#;
let events = parse::events(raw_data, None).unwrap();
assert_eq!(events.len(), 2);
assert_eq!(
events[0].time.unwrap(),
DateTime::<Local>::from_str("2024-01-18T09:03:11+03:00").unwrap()
);
assert_eq!(events[0].car_number, "K147CP");
assert_eq!(events[0].status, false);
assert_eq!(
events[1].time.unwrap(),
DateTime::<Local>::from_str("2024-01-18T09:03:48+03:00").unwrap()
);
assert_eq!(events[1].car_number, "P829EX");
assert_eq!(events[1].status, true);
let events = parse::events(raw_data, Some(false)).unwrap();
assert_eq!(events.len(), 1);
assert_eq!(
events[0].time.unwrap(),
DateTime::<Local>::from_str("2024-01-18T09:03:11+03:00").unwrap()
);
assert_eq!(events[0].car_number, "K147CP");
assert_eq!(events[0].status, false);
let events = parse::events(raw_data, Some(true)).unwrap();
assert_eq!(events.len(), 1);
assert_eq!(
events[0].time.unwrap(),
DateTime::<Local>::from_str("2024-01-18T09:03:48+03:00").unwrap()
);
assert_eq!(events[0].car_number, "P829EX");
assert_eq!(events[0].status, true);
}
}