From 5ab3acd354a2a6c60c94535678e97b39fd020080 Mon Sep 17 00:00:00 2001 From: barsoosayque Date: Sun, 24 Nov 2024 14:04:10 +0700 Subject: [PATCH] split package into different files, structurize package impl --- crates/opensi-core/src/components.rs | 119 ++++++++++++++++++ crates/opensi-core/src/lib.rs | 2 + crates/opensi-core/src/node.rs | 6 +- crates/opensi-core/src/package.rs | 172 ++++++--------------------- crates/opensi-core/src/serde_impl.rs | 10 +- 5 files changed, 166 insertions(+), 143 deletions(-) create mode 100644 crates/opensi-core/src/components.rs diff --git a/crates/opensi-core/src/components.rs b/crates/opensi-core/src/components.rs new file mode 100644 index 0000000..4638aea --- /dev/null +++ b/crates/opensi-core/src/components.rs @@ -0,0 +1,119 @@ +use serde::{Deserialize, Serialize}; + +use crate::serde_impl; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub struct Info { + #[serde(skip_serializing_if = "String::is_empty")] + pub comments: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub extension: String, + #[serde(with = "serde_impl::authors", skip_serializing_if = "Vec::is_empty")] + pub authors: Vec, + #[serde(with = "serde_impl::sources", skip_serializing_if = "Vec::is_empty")] + pub sources: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Authors { + #[serde(rename = "author")] + pub authors: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub struct Round { + #[serde(rename = "@name")] + pub name: String, + #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] + pub variant: Option, + #[serde(rename = "@info", skip_serializing_if = "Option::is_none")] + pub info: Option, + #[serde(with = "serde_impl::themes")] + pub themes: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Theme { + #[serde(rename = "@name")] + pub name: String, + #[serde(with = "serde_impl::questions")] + pub questions: Vec, + #[serde(rename = "@info", skip_serializing_if = "Option::is_none")] + pub info: Option, +} + +impl Theme { + /// Try to guess a price for the next question: + /// - Either a difference between the last two question prices; + /// - Or the last question's price plus 100; + /// + /// In case of no questions, the default price is 100. + pub fn guess_next_question_price(&self) -> usize { + let mut iter = self.questions.iter().rev(); + match (iter.next(), iter.next()) { + (Some(last), Some(prev)) => { + let diff = last.price.abs_diff(prev.price); + last.price + diff + }, + (Some(last), None) => last.price + 100, + _ => 100, + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Questions { + #[serde(rename = "question")] + pub questions: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub struct Question { + #[serde(rename = "@price")] + pub price: usize, + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub question_type: Option, + #[serde(with = "serde_impl::atoms")] + pub scenario: Vec, + #[serde(with = "serde_impl::answers")] + pub right: Vec, + #[serde(with = "serde_impl::answers", skip_serializing_if = "Vec::is_empty")] + pub wrong: Vec, + #[serde(rename = "@info", skip_serializing_if = "Option::is_none")] + pub info: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct QuestionType { + #[serde(rename = "@name")] + pub name: String, + #[serde(rename = "param", skip_serializing_if = "Option::is_none")] + pub params: Option>, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Answer { + #[serde(rename = "$value", skip_serializing_if = "Option::is_none")] + pub body: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Param { + #[serde(rename = "@name")] + pub name: String, + #[serde(rename = "$value")] + pub body: String, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct Atom { + #[serde(rename = "@time", skip_serializing_if = "Option::is_none")] + pub time: Option, + #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] + pub variant: Option, + #[serde(rename = "$value", skip_serializing_if = "Option::is_none")] + pub body: Option, +} diff --git a/crates/opensi-core/src/lib.rs b/crates/opensi-core/src/lib.rs index df635cd..8523ab0 100644 --- a/crates/opensi-core/src/lib.rs +++ b/crates/opensi-core/src/lib.rs @@ -1,10 +1,12 @@ #![allow(dead_code)] +pub mod components; pub mod node; pub mod package; mod serde_impl; pub mod prelude { + pub use crate::components::*; pub use crate::node::*; pub use crate::package::*; } diff --git a/crates/opensi-core/src/node.rs b/crates/opensi-core/src/node.rs index 7c87c3d..eec04d3 100644 --- a/crates/opensi-core/src/node.rs +++ b/crates/opensi-core/src/node.rs @@ -61,7 +61,7 @@ impl From<(usize, usize, usize)> for PackageNode { } } -/// Typed [`Round`](crate::package::Round) index. +/// Typed [`Round`](crate::components::Round) index. #[derive( serde::Deserialize, serde::Serialize, @@ -104,7 +104,7 @@ impl Display for RoundIdx { } } -/// Typed [`Theme`](crate::package::Theme) indices. +/// Typed [`Theme`](crate::components::Theme) indices. #[derive( serde::Deserialize, serde::Serialize, @@ -155,7 +155,7 @@ impl Display for ThemeIdx { } } -/// Typed [`Question`](crate::package::Question) indices. +/// Typed [`Question`](crate::components::Question) indices. #[derive( serde::Deserialize, serde::Serialize, diff --git a/crates/opensi-core/src/package.rs b/crates/opensi-core/src/package.rs index 82c579e..971332c 100644 --- a/crates/opensi-core/src/package.rs +++ b/crates/opensi-core/src/package.rs @@ -9,9 +9,12 @@ use std::{fs::File, io, io::Read}; use zip::write::FileOptions; use zip::{CompressionMethod, ZipArchive, ZipWriter}; +use crate::components::{Atom, Info, Question, Round, Theme}; use crate::node::{PackageNode, QuestionIdx, RoundIdx, ThemeIdx}; use crate::serde_impl; +/// Complete package structure with meta information about +/// the package and its tree of [`Question`]. #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[serde(default, rename = "package")] pub struct Package { @@ -49,7 +52,9 @@ pub struct Package { pub resource: HashMap>, } +/// # [`PackageNode`]-based methods impl Package { + /// Clone a node and push it afterwards. pub fn duplicate_node(&mut self, node: PackageNode) { match node { PackageNode::Round(idx) => { @@ -64,6 +69,7 @@ impl Package { }; } + /// Create a new default node and push it. pub fn allocate_node(&mut self, node: PackageNode) { match node { PackageNode::Round(_) => { @@ -78,6 +84,7 @@ impl Package { }; } + /// Remove a single node. pub fn remove_node(&mut self, node: PackageNode) { match node { PackageNode::Round(idx) => { @@ -91,7 +98,10 @@ impl Package { }, }; } +} +/// # Methods around [`Round`] +impl Package { /// Get [`Round`] by index. pub fn get_round(&self, idx: impl Into) -> Option<&Round> { let idx = idx.into(); @@ -144,7 +154,10 @@ impl Package { let round = Round { name: "Новый раунд".to_string(), ..Default::default() }; self.push_round(round) } +} +/// # Methods around [`Theme`] +impl Package { /// Get [`Theme`] in [`Round`] by indices. pub fn get_theme(&self, idx: impl Into) -> Option<&Theme> { let idx = idx.into(); @@ -202,7 +215,10 @@ impl Package { let theme = Theme { name: "Новая тема".to_string(), ..Default::default() }; self.push_theme(idx, theme) } +} +/// # Methods around [`Theme`]. +impl Package { /// Get [`Question`] in [`Theme`] in [`Round`] by indices. pub fn get_question(&self, idx: impl Into) -> Option<&Question> { let idx = idx.into(); @@ -273,141 +289,7 @@ impl Package { } } -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -#[serde(default)] -pub struct Info { - #[serde(skip_serializing_if = "String::is_empty")] - pub comments: String, - #[serde(skip_serializing_if = "String::is_empty")] - pub extension: String, - #[serde(with = "serde_impl::authors", skip_serializing_if = "Vec::is_empty")] - pub authors: Vec, - #[serde(with = "serde_impl::sources", skip_serializing_if = "Vec::is_empty")] - pub sources: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Authors { - #[serde(rename = "author")] - pub authors: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -#[serde(default)] -pub struct Round { - #[serde(rename = "@name")] - pub name: String, - #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] - pub variant: Option, - #[serde(rename = "@info", skip_serializing_if = "Option::is_none")] - pub info: Option, - #[serde(with = "serde_impl::themes")] - pub themes: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Theme { - #[serde(rename = "@name")] - pub name: String, - #[serde(with = "serde_impl::questions")] - pub questions: Vec, - #[serde(rename = "@info", skip_serializing_if = "Option::is_none")] - pub info: Option, -} - -impl Theme { - /// Try to guess a price for the next question: - /// - Either a difference between the last two question prices; - /// - Or the last question's price plus 100; - /// - /// In case of no questions, the default price is 100. - pub fn guess_next_question_price(&self) -> usize { - let mut iter = self.questions.iter().rev(); - match (iter.next(), iter.next()) { - (Some(last), Some(prev)) => { - let diff = last.price.abs_diff(prev.price); - last.price + diff - }, - (Some(last), None) => last.price + 100, - _ => 100, - } - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Questions { - #[serde(rename = "question")] - pub questions: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -#[serde(default)] -pub struct Question { - #[serde(rename = "@price")] - pub price: usize, - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub question_type: Option, - #[serde(with = "serde_impl::atoms")] - pub scenario: Vec, - #[serde(with = "serde_impl::answers")] - pub right: Vec, - #[serde(with = "serde_impl::answers", skip_serializing_if = "Vec::is_empty")] - pub wrong: Vec, - #[serde(rename = "@info", skip_serializing_if = "Option::is_none")] - pub info: Option, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct QuestionType { - #[serde(rename = "@name")] - pub name: String, - #[serde(rename = "param", skip_serializing_if = "Option::is_none")] - pub params: Option>, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Answer { - #[serde(rename = "$value", skip_serializing_if = "Option::is_none")] - pub body: Option, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Param { - #[serde(rename = "@name")] - pub name: String, - #[serde(rename = "$value")] - pub body: String, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Atom { - #[serde(rename = "@time", skip_serializing_if = "Option::is_none")] - pub time: Option, - #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] - pub variant: Option, - #[serde(rename = "$value", skip_serializing_if = "Option::is_none")] - pub body: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Resource { - Audio(String), - Video(String), - Image(String), - Texts(String), -} - -impl Resource { - fn extract_key(&self) -> &str { - match self { - Resource::Audio(key) - | Resource::Video(key) - | Resource::Image(key) - | Resource::Texts(key) => key, - } - } -} - +/// # IO and resource methods impl Package { const CONTENT_TYPE_FILE_CONTENT: &'static str = r#""""#; const XML_VERSION_ENCODING: &'static str = r#""#; @@ -533,3 +415,23 @@ impl Package { Ok(result.into_inner()) } } + +/// Single resource handle inside [`Package`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Resource { + Audio(String), + Video(String), + Image(String), + Texts(String), +} + +impl Resource { + fn extract_key(&self) -> &str { + match self { + Resource::Audio(key) + | Resource::Video(key) + | Resource::Image(key) + | Resource::Texts(key) => key, + } + } +} diff --git a/crates/opensi-core/src/serde_impl.rs b/crates/opensi-core/src/serde_impl.rs index 13ea609..944e7c6 100644 --- a/crates/opensi-core/src/serde_impl.rs +++ b/crates/opensi-core/src/serde_impl.rs @@ -33,10 +33,10 @@ macro_rules! generate_serde_mod { }; } -generate_serde_mod!(rounds: crate::package::Round as round); -generate_serde_mod!(themes: crate::package::Theme as theme); -generate_serde_mod!(questions: crate::package::Question as question); -generate_serde_mod!(atoms: crate::package::Atom as atom); -generate_serde_mod!(answers: crate::package::Answer as answer); +generate_serde_mod!(rounds: crate::components::Round as round); +generate_serde_mod!(themes: crate::components::Theme as theme); +generate_serde_mod!(questions: crate::components::Question as question); +generate_serde_mod!(atoms: crate::components::Atom as atom); +generate_serde_mod!(answers: crate::components::Answer as answer); generate_serde_mod!(authors: String as author); generate_serde_mod!(sources: String as source);