Compare commits

..

No commits in common. "741f96360651410ad56ad1ab774af20f6610b355" and "4b30bf288ad228f4edf7434dc06ee3d510b6e9f2" have entirely different histories.

3 changed files with 9 additions and 196 deletions

View File

@ -1,147 +0,0 @@
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()),
])
);
}
}

View File

@ -68,10 +68,8 @@
// PM // PM
// VW // VW
use crate::{ use crate::tree::{parse_collection, parse_size, ParseSizeError, Size};
date::{self, parse_date_field, Date}, use nom::IResult;
tree::{parse_collection, ParseSizeError, Size},
};
#[derive(Debug)] #[derive(Debug)]
pub enum Error<'a> { pub enum Error<'a> {
@ -135,7 +133,7 @@ pub struct GameInfo {
pub event: Option<String>, pub event: Option<String>,
// Games can be played across multiple days, even multiple years. The format specifies // Games can be played across multiple days, even multiple years. The format specifies
// shortcuts. // shortcuts.
pub date: Vec<Date>, pub date: Vec<chrono::NaiveDate>,
pub location: Option<String>, pub location: Option<String>,
// special rules for the round-number and type // special rules for the round-number and type
pub round: Option<String>, pub round: Option<String>,
@ -172,6 +170,7 @@ pub enum GameResult {
impl TryFrom<&str> for GameResult { impl TryFrom<&str> for GameResult {
type Error = String; type Error = String;
fn try_from(s: &str) -> Result<GameResult, Self::Error> { fn try_from(s: &str) -> Result<GameResult, Self::Error> {
println!("Result try_from: {:?}", s);
if s == "0" { if s == "0" {
Ok(GameResult::Draw) Ok(GameResult::Draw)
} else if s == "Void" { } else if s == "Void" {
@ -210,7 +209,6 @@ pub enum GameType {
Unsupported, Unsupported,
} }
/*
enum PropType { enum PropType {
Move, Move,
Setup, Setup,
@ -230,10 +228,9 @@ enum PropValue {
Move, Move,
Stone, Stone,
} }
*/
pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> { pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> {
let (_, trees) = parse_collection::<nom::error::VerboseError<&'a str>>(input)?; let (input, trees) = parse_collection::<nom::error::VerboseError<&'a str>>(input)?;
let games = trees let games = trees
.into_iter() .into_iter()
@ -278,42 +275,6 @@ pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> {
.and_then(|prop| prop.values[0].parse::<u64>().ok()) .and_then(|prop| prop.values[0].parse::<u64>().ok())
.and_then(|seconds| Some(std::time::Duration::from_secs(seconds))); .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 { Ok(GameTree {
file_format, file_format,
app_name, app_name,
@ -329,7 +290,7 @@ pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{date::Date, tree::Size}; use crate::tree::Size;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
@ -401,12 +362,12 @@ mod tests {
assert_eq!( assert_eq!(
tree.info.date, tree.info.date,
vec![ vec![
Date::Date(chrono::NaiveDate::from_ymd_opt(1996, 10, 18).unwrap()), chrono::NaiveDate::from_ymd_opt(1996, 10, 18).unwrap(),
Date::Date(chrono::NaiveDate::from_ymd_opt(1996, 10, 19).unwrap()), 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("21st Meijin".to_owned()));
assert_eq!(tree.info.round, Some("2 (final)".to_owned())); assert_eq!(tree.info.event, Some("2 (final)".to_owned()));
assert_eq!(tree.info.source, Some("Go World #78".to_owned())); assert_eq!(tree.info.source, Some("Go World #78".to_owned()));
assert_eq!(tree.info.game_keeper, Some("Arno Hollosi".to_owned())); assert_eq!(tree.info.game_keeper, Some("Arno Hollosi".to_owned()));
}); });

View File

@ -1,4 +1,3 @@
pub mod date;
pub mod go; pub mod go;
pub mod tree; pub mod tree;