Compare commits
2 Commits
4b30bf288a
...
741f963606
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 741f963606 | |
Savanni D'Gerinel | 82deabce48 |
|
@ -0,0 +1,147 @@
|
|||
use chrono::{Datelike, NaiveDate};
|
||||
use std::num::ParseIntError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum Error {
|
||||
#[error("Failed to parse integer {0}")]
|
||||
ParseNumberError(ParseIntError),
|
||||
|
||||
#[error("Invalid date")]
|
||||
InvalidDate,
|
||||
|
||||
#[error("unsupported date format")]
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub enum Date {
|
||||
Year(i32),
|
||||
YearMonth(i32, u32),
|
||||
Date(chrono::NaiveDate),
|
||||
}
|
||||
|
||||
/*
|
||||
impl TryFrom<&str> for Date {
|
||||
type Error = String;
|
||||
fn try_from(s: &str) -> Result<Date, Self::Error> {
|
||||
let date_parts = s.split("-").collect::<Vec<&str>>();
|
||||
|
||||
if date_parts.len() >= 1 {
|
||||
let year = date_parts[0]
|
||||
.parse::<i32>()
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
if date_parts.len() >= 2 {
|
||||
let month = date_parts[1]
|
||||
.parse::<u32>()
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
if date_parts.len() >= 3 {
|
||||
let day = date_parts[2]
|
||||
.parse::<u32>()
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
let date =
|
||||
chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or("invalid date")?;
|
||||
Ok(Date::Date(date))
|
||||
} else {
|
||||
Ok(Date::YearMonth(year, month))
|
||||
}
|
||||
} else {
|
||||
Ok(Date::Year(year))
|
||||
}
|
||||
} else {
|
||||
return Err("no elements".to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fn parse_numbers(s: &str) -> Result<Vec<i32>, Error> {
|
||||
s.split("-")
|
||||
.map(|s| s.parse::<i32>().map_err(|err| Error::ParseNumberError(err)))
|
||||
.collect::<Result<Vec<i32>, Error>>()
|
||||
}
|
||||
|
||||
pub fn parse_date_field(s: &str) -> Result<Vec<Date>, Error> {
|
||||
let date_elements = s.split(",");
|
||||
let mut dates = Vec::new();
|
||||
|
||||
let mut most_recent: Option<Date> = None;
|
||||
for element in date_elements {
|
||||
let fields = parse_numbers(element)?;
|
||||
|
||||
let new_date = match fields.as_slice() {
|
||||
[] => panic!("all segments must have a field"),
|
||||
[v1] => match most_recent {
|
||||
Some(Date::Year(_)) => Date::Year(*v1),
|
||||
Some(Date::YearMonth(y, _)) => Date::YearMonth(y, *v1 as u32),
|
||||
Some(Date::Date(d)) => {
|
||||
Date::Date(d.clone().with_day(*v1 as u32).ok_or(Error::InvalidDate)?)
|
||||
}
|
||||
|
||||
None => Date::Year(*v1),
|
||||
},
|
||||
[v1, v2] => Date::YearMonth(*v1, *v2 as u32),
|
||||
[v1, v2, v3, ..] => Date::Date(
|
||||
NaiveDate::from_ymd_opt(v1.clone(), v2.clone() as u32, v3.clone() as u32).unwrap(),
|
||||
),
|
||||
};
|
||||
dates.push(new_date.clone());
|
||||
most_recent = Some(new_date);
|
||||
}
|
||||
|
||||
Ok(dates)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_parses_a_year() {
|
||||
assert_eq!(parse_date_field("1996"), Ok(vec![Date::Year(1996)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_a_month() {
|
||||
assert_eq!(
|
||||
parse_date_field("1996-12"),
|
||||
Ok(vec![Date::YearMonth(1996, 12)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_a_date() {
|
||||
assert_eq!(
|
||||
parse_date_field("1996-12-27"),
|
||||
Ok(vec![Date::Date(
|
||||
NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()
|
||||
)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_date_continuation() {
|
||||
assert_eq!(
|
||||
parse_date_field("1996-12-27,28"),
|
||||
Ok(vec![
|
||||
Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()),
|
||||
Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap())
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_date_crossing_year_boundary() {
|
||||
assert_eq!(
|
||||
parse_date_field("1996-12-27,28,1997-01-03,04"),
|
||||
Ok(vec![
|
||||
Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()),
|
||||
Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap()),
|
||||
Date::Date(NaiveDate::from_ymd_opt(1997, 1, 3).unwrap()),
|
||||
Date::Date(NaiveDate::from_ymd_opt(1997, 1, 4).unwrap()),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
|
@ -68,8 +68,10 @@
|
|||
// PM
|
||||
// VW
|
||||
|
||||
use crate::tree::{parse_collection, parse_size, ParseSizeError, Size};
|
||||
use nom::IResult;
|
||||
use crate::{
|
||||
date::{self, parse_date_field, Date},
|
||||
tree::{parse_collection, ParseSizeError, Size},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error<'a> {
|
||||
|
@ -133,7 +135,7 @@ pub struct GameInfo {
|
|||
pub event: Option<String>,
|
||||
// Games can be played across multiple days, even multiple years. The format specifies
|
||||
// shortcuts.
|
||||
pub date: Vec<chrono::NaiveDate>,
|
||||
pub date: Vec<Date>,
|
||||
pub location: Option<String>,
|
||||
// special rules for the round-number and type
|
||||
pub round: Option<String>,
|
||||
|
@ -170,7 +172,6 @@ pub enum GameResult {
|
|||
impl TryFrom<&str> for GameResult {
|
||||
type Error = String;
|
||||
fn try_from(s: &str) -> Result<GameResult, Self::Error> {
|
||||
println!("Result try_from: {:?}", s);
|
||||
if s == "0" {
|
||||
Ok(GameResult::Draw)
|
||||
} else if s == "Void" {
|
||||
|
@ -209,6 +210,7 @@ pub enum GameType {
|
|||
Unsupported,
|
||||
}
|
||||
|
||||
/*
|
||||
enum PropType {
|
||||
Move,
|
||||
Setup,
|
||||
|
@ -228,9 +230,10 @@ enum PropValue {
|
|||
Move,
|
||||
Stone,
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> {
|
||||
let (input, trees) = parse_collection::<nom::error::VerboseError<&'a str>>(input)?;
|
||||
let (_, trees) = parse_collection::<nom::error::VerboseError<&'a str>>(input)?;
|
||||
|
||||
let games = trees
|
||||
.into_iter()
|
||||
|
@ -275,6 +278,42 @@ pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> {
|
|||
.and_then(|prop| prop.values[0].parse::<u64>().ok())
|
||||
.and_then(|seconds| Some(std::time::Duration::from_secs(seconds)));
|
||||
|
||||
info.date = tree.sequence[0]
|
||||
.find_prop("DT")
|
||||
.and_then(|prop| {
|
||||
let v = prop
|
||||
.values
|
||||
.iter()
|
||||
.map(|val| parse_date_field(val))
|
||||
.fold(Ok(vec![]), |acc, v| match (acc, v) {
|
||||
(Ok(mut acc), Ok(mut values)) => {
|
||||
acc.append(&mut values);
|
||||
Ok(acc)
|
||||
}
|
||||
(Ok(_), Err(err)) => Err(err),
|
||||
(Err(err), _) => Err(err),
|
||||
})
|
||||
.ok()?;
|
||||
Some(v)
|
||||
})
|
||||
.unwrap_or(vec![]);
|
||||
|
||||
info.event = tree.sequence[0]
|
||||
.find_prop("EV")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.round = tree.sequence[0]
|
||||
.find_prop("RO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.source = tree.sequence[0]
|
||||
.find_prop("SO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.game_keeper = tree.sequence[0]
|
||||
.find_prop("US")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
Ok(GameTree {
|
||||
file_format,
|
||||
app_name,
|
||||
|
@ -290,7 +329,7 @@ pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tree::Size;
|
||||
use crate::{date::Date, tree::Size};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
|
@ -362,12 +401,12 @@ mod tests {
|
|||
assert_eq!(
|
||||
tree.info.date,
|
||||
vec![
|
||||
chrono::NaiveDate::from_ymd_opt(1996, 10, 18).unwrap(),
|
||||
chrono::NaiveDate::from_ymd_opt(1996, 10, 19).unwrap(),
|
||||
Date::Date(chrono::NaiveDate::from_ymd_opt(1996, 10, 18).unwrap()),
|
||||
Date::Date(chrono::NaiveDate::from_ymd_opt(1996, 10, 19).unwrap()),
|
||||
]
|
||||
);
|
||||
assert_eq!(tree.info.event, Some("21st Meijin".to_owned()));
|
||||
assert_eq!(tree.info.event, Some("2 (final)".to_owned()));
|
||||
assert_eq!(tree.info.round, Some("2 (final)".to_owned()));
|
||||
assert_eq!(tree.info.source, Some("Go World #78".to_owned()));
|
||||
assert_eq!(tree.info.game_keeper, Some("Arno Hollosi".to_owned()));
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod date;
|
||||
pub mod go;
|
||||
pub mod tree;
|
||||
|
||||
|
|
Loading…
Reference in New Issue