From 150e71ebe869b5acec789803a3a32ddfcd71ffb6 Mon Sep 17 00:00:00 2001 From: yzoug Date: Sat, 17 Feb 2024 14:32:49 +0100 Subject: [PATCH 1/2] Implement export_one_chapter Studies endpoint --- src/api/mod.rs | 1 + src/api/studies.rs | 60 ++++++++++++++ src/model/mod.rs | 1 + src/model/studies/export_one_chapter.rs | 13 +++ src/model/studies/mod.rs | 100 ++++++++++++++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 src/api/studies.rs create mode 100644 src/model/studies/export_one_chapter.rs create mode 100644 src/model/studies/mod.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index 6306a11..fa69401 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -8,6 +8,7 @@ pub mod games; pub mod messaging; pub mod openings; pub mod puzzles; +pub mod studies; pub mod tablebase; pub mod users; diff --git a/src/api/studies.rs b/src/api/studies.rs new file mode 100644 index 0000000..c842641 --- /dev/null +++ b/src/api/studies.rs @@ -0,0 +1,60 @@ +use std::str::FromStr; + +use async_std::stream::StreamExt; +use tracing::error; + +use crate::client::LichessApi; +use crate::error::Result; +use crate::model::studies::*; + +impl LichessApi { + pub async fn export_one_chapter( + &self, + request: GetRequest, + ) -> Result { + let mut stream = self.get_pgn(request).await?; + let mut study_chapter = StudyChapter { + headers: vec![], + sections: vec![], + }; + + let mut current_section = StudySection::default(); + let mut inside_comment = false; + let mut switch_section_next = false; + + while let Some(s) = stream.next().await { + let line = s?; + println!("Got line: {}", line); + if line.starts_with('[') { + match StudyHeader::from_str(&line) { + Ok(h) => study_chapter.headers.push(h), + Err(e) => error!("Can't parse study header: {e}. Ignoring line '{line}'."), + } + } else { + for c in line.chars() { + if c == '{' || c == '}' { + inside_comment = if c == '{' { true } else { false }; + if switch_section_next { + study_chapter.sections.push(current_section); + current_section = StudySection::default(); + switch_section_next = false; + } else { + switch_section_next = true; + } + } else if inside_comment { + current_section.comment.push(c); + } else { + current_section.pgn.push(c); + } + } + if inside_comment { + current_section.comment.push('\n'); + } else { + current_section.pgn.push('\n'); + } + } + } + + Ok(study_chapter) + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 6ba06da..78af6c6 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -8,6 +8,7 @@ pub mod games; pub mod messaging; pub mod openings; pub mod puzzles; +pub mod studies; pub mod tablebase; pub mod users; diff --git a/src/model/studies/export_one_chapter.rs b/src/model/studies/export_one_chapter.rs new file mode 100644 index 0000000..b75bb79 --- /dev/null +++ b/src/model/studies/export_one_chapter.rs @@ -0,0 +1,13 @@ +use super::GetStudyQuery; + +pub type GetRequest = crate::model::Request; + +impl GetRequest { + pub fn new(study_id: String, chapter_id: String, query: GetStudyQuery) -> Self { + Self { + path: format!("/api/study/{}/{}.pgn", study_id, chapter_id), + query: Some(query), + ..Default::default() + } + } +} diff --git a/src/model/studies/mod.rs b/src/model/studies/mod.rs new file mode 100644 index 0000000..986a826 --- /dev/null +++ b/src/model/studies/mod.rs @@ -0,0 +1,100 @@ +pub mod export_one_chapter; + +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +pub type GetRequest = crate::model::Request; + +#[derive(Default, Clone, Debug, Serialize)] +#[skip_serializing_none] +pub struct GetStudyQuery { + pub clocks: Option, + pub comments: Option, + pub variations: Option, + pub source: Option, + pub orientation: Option +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct Study { + pub chapters: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StudyHeader { + pub header_type: StudyHeaderType, + pub header_value: String, +} + +impl FromStr for StudyHeader { + type Err = &'static str; + fn from_str(value: &str) -> Result { + let s = match value.strip_prefix('[') { + Some(s) => s, + None => return Err("Header does not begin with '[' character."), + }; + let mut s_split = s.split_whitespace(); + let header_type = match s_split.next().unwrap() { + "Event" => StudyHeaderType::Event, + "Site" => StudyHeaderType::Site, + "Result" => StudyHeaderType::Result, + "Variant" => StudyHeaderType::Variant, + "ECO" => StudyHeaderType::ECO, + "Opening" => StudyHeaderType::Opening, + "Annotator" => StudyHeaderType::Annotator, + "UTCDate" => StudyHeaderType::UTCDate, + "UTCTime" => StudyHeaderType::UTCTime, + "ChapterMode" => StudyHeaderType::ChapterMode, + _ => StudyHeaderType::Unknown + }; + + let mut header_value = String::new(); + while let Some(next_s) = s_split.next() { + let mut next_string = next_s.to_string(); + if next_string.starts_with('"') { + next_string.remove(0); + } + if next_string.ends_with(']') { + next_string.pop(); + } + if next_string.ends_with('"') { + next_string.pop(); + } + header_value += &next_string; + } + + Ok(StudyHeader { + header_type: header_type, + header_value: header_value, + }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum StudyHeaderType { + Event, + Site, + Result, + Variant, + ECO, + Opening, + Annotator, + UTCDate, + UTCTime, + ChapterMode, + Unknown +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct StudyChapter { + pub headers: Vec, + pub sections: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct StudySection { + pub comment: String, + pub pgn: String, +} \ No newline at end of file From 0632f7cedbb1c46759e88e1a5ec21e38630b495e Mon Sep 17 00:00:00 2001 From: yzoug Date: Sat, 17 Feb 2024 17:16:05 +0100 Subject: [PATCH 2/2] Implement import_pgn_into_study Studies endpoint --- src/api/studies.rs | 7 ++++ src/model/studies/import_pgn_into_study.rs | 41 ++++++++++++++++++++++ src/model/studies/mod.rs | 2 ++ 3 files changed, 50 insertions(+) create mode 100644 src/model/studies/import_pgn_into_study.rs diff --git a/src/api/studies.rs b/src/api/studies.rs index c842641..ae21151 100644 --- a/src/api/studies.rs +++ b/src/api/studies.rs @@ -5,8 +5,11 @@ use tracing::error; use crate::client::LichessApi; use crate::error::Result; +use crate::model::studies::import_pgn_into_study::PostRequest; use crate::model::studies::*; +use self::import_pgn_into_study::StudyImportPgnChapters; + impl LichessApi { pub async fn export_one_chapter( &self, @@ -57,4 +60,8 @@ impl LichessApi { Ok(study_chapter) } + + pub async fn import_pgn_into_study(&self, request: PostRequest) -> Result { + self.get_single_model(request).await + } } diff --git a/src/model/studies/import_pgn_into_study.rs b/src/model/studies/import_pgn_into_study.rs new file mode 100644 index 0000000..0c7b6b3 --- /dev/null +++ b/src/model/studies/import_pgn_into_study.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use crate::model::{Body, Request}; + +use crate::model::VariantKey; + +#[derive(Default, Clone, Debug, Serialize)] +#[skip_serializing_none] +pub struct ImportPgnBody { + pub name: String, + pub pgn: String, + pub variant: Option, + pub orientation: Option, +} + +#[derive(Default, Clone, Debug, Deserialize)] +pub struct StudyImportPgnChapters { + pub chapters: Vec, +} + +#[derive(Default, Clone, Debug, Deserialize)] +pub struct StudyChapterListItem { + pub id: String, + pub name: String, +} + +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery; + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(study_id: String, import_pgn_body: ImportPgnBody) -> Self { + Self { + method: http::Method::POST, + path: format!("/api/study/{}/import-pgn", study_id), + body: Body::Form(import_pgn_body), + ..Default::default() + } + } +} diff --git a/src/model/studies/mod.rs b/src/model/studies/mod.rs index 986a826..7a630b0 100644 --- a/src/model/studies/mod.rs +++ b/src/model/studies/mod.rs @@ -1,4 +1,5 @@ pub mod export_one_chapter; +pub mod import_pgn_into_study; use std::str::FromStr; @@ -93,6 +94,7 @@ pub struct StudyChapter { pub sections: Vec, } + #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct StudySection { pub comment: String,