From ba98e000cb12d45ef849ec401781eda5de4c967a Mon Sep 17 00:00:00 2001 From: SARDONYX-sard <68905624+SARDONYX-sard@users.noreply.github.com> Date: Wed, 31 Jan 2024 22:17:41 +0900 Subject: [PATCH] refactor(core): apply lint fix - add docs - remove unused dependencies - remove `unwrap` from test code --- Cargo.lock | 1 - cspell.jsonc | 7 + dar2oar_core/Cargo.toml | 1 - dar2oar_core/src/condition_parser/actor.rs | 4 + dar2oar_core/src/condition_parser/compare.rs | 7 +- .../src/condition_parser/conditions.rs | 7 + .../src/condition_parser/dar_interface.rs | 7 +- dar2oar_core/src/condition_parser/equip.rs | 7 +- dar2oar_core/src/condition_parser/faction.rs | 5 + dar2oar_core/src/condition_parser/has.rs | 5 + dar2oar_core/src/condition_parser/macros.rs | 7 +- dar2oar_core/src/condition_parser/mod.rs | 9 +- dar2oar_core/src/conditions/and.rs | 18 +- dar2oar_core/src/conditions/compare_values.rs | 29 ++- dar2oar_core/src/conditions/condition.rs | 25 ++- .../src/conditions/condition_config.rs | 15 +- .../src/conditions/current_weather.rs | 18 +- dar2oar_core/src/conditions/faction_rank.rs | 21 +- dar2oar_core/src/conditions/has_keyword.rs | 18 +- .../src/conditions/has_magic_effect.rs | 19 +- .../has_magic_effect_with_keyword.rs | 25 ++- dar2oar_core/src/conditions/has_perk.rs | 18 +- dar2oar_core/src/conditions/has_ref_type.rs | 18 +- dar2oar_core/src/conditions/is_equipped.rs | 20 +- .../src/conditions/is_equipped_has_keyword.rs | 19 +- .../src/conditions/is_equipped_type.rs | 19 +- .../src/conditions/is_movement_direction.rs | 18 +- .../src/conditions/is_worn_has_keyword.rs | 18 +- dar2oar_core/src/conditions/mod.rs | 76 ++++++- .../src/conditions/namespace_config.rs | 8 +- dar2oar_core/src/conditions/or.rs | 10 +- dar2oar_core/src/conditions/random.rs | 12 +- dar2oar_core/src/dar_syntax/error.rs | 15 +- dar2oar_core/src/dar_syntax/mod.rs | 1 + dar2oar_core/src/dar_syntax/syntax.rs | 48 +++- dar2oar_core/src/error.rs | 56 ++++- dar2oar_core/src/fs/converter/common.rs | 25 ++- dar2oar_core/src/fs/converter/mod.rs | 47 ++-- dar2oar_core/src/fs/converter/parallel.rs | 19 +- dar2oar_core/src/fs/converter/sequential.rs | 33 +-- dar2oar_core/src/fs/converter/support_cmd.rs | 25 ++- dar2oar_core/src/fs/mapping_table.rs | 58 ++--- dar2oar_core/src/fs/mod.rs | 1 + dar2oar_core/src/fs/path_changer.rs | 45 ++-- dar2oar_core/src/fs/section_writer.rs | 4 +- dar2oar_core/src/lib.rs | 207 ++++++++++++++---- dar2oar_core/src/test_helper.rs | 4 +- dar2oar_core/src/values/actor_value.rs | 19 +- dar2oar_core/src/values/comparison.rs | 1 + dar2oar_core/src/values/direction_value.rs | 62 ++++-- dar2oar_core/src/values/form_value.rs | 4 +- dar2oar_core/src/values/graph_value.rs | 17 +- dar2oar_core/src/values/keyword_value.rs | 29 ++- dar2oar_core/src/values/literal_value.rs | 3 + dar2oar_core/src/values/mod.rs | 16 +- dar2oar_core/src/values/numeric_literal.rs | 45 ++-- dar2oar_core/src/values/numeric_value.rs | 39 +++- dar2oar_core/src/values/plugin_value.rs | 15 +- dar2oar_core/src/values/random_value.rs | 9 + dar2oar_core/src/values/static_value.rs | 10 +- dar2oar_core/src/values/type_value.rs | 80 ++++--- 61 files changed, 1012 insertions(+), 416 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca97019..603249e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -821,7 +821,6 @@ dependencies = [ "criterion", "jwalk", "nom", - "once_cell", "pretty_assertions", "serde", "serde_json", diff --git a/cspell.jsonc b/cspell.jsonc index 68e44bd..ba7acc3 100644 --- a/cspell.jsonc +++ b/cspell.jsonc @@ -1,14 +1,19 @@ { "words": [ "appender", + "bools", "chrono", "Couprie", + "ctypes", "ebnf", + "expl", "fullscreen", "Geoffroy", "Greatsword", "icns", + "idents", "jwalk", + "linkedlist", "Mincho", "mohidden", "mpsc", @@ -30,7 +35,9 @@ "thiserror", "Unhide", "unlisten", + "unnested", "unoptimized", + "unseparated", "Usize", "walkdir", "Warhammer", diff --git a/dar2oar_core/Cargo.toml b/dar2oar_core/Cargo.toml index 2d3e124..b72983b 100644 --- a/dar2oar_core/Cargo.toml +++ b/dar2oar_core/Cargo.toml @@ -35,7 +35,6 @@ criterion = { version = "0.5.1", features = [ "async_tokio", "html_reports", ] } -once_cell = "1.18.0" pretty_assertions = "1.4.0" temp-dir = "0.1.11" tracing-appender = "0.2" diff --git a/dar2oar_core/src/condition_parser/actor.rs b/dar2oar_core/src/condition_parser/actor.rs index 0c7150e..991a9d9 100644 --- a/dar2oar_core/src/condition_parser/actor.rs +++ b/dar2oar_core/src/condition_parser/actor.rs @@ -1,3 +1,4 @@ +//! Parses an actor-related condition based on the provided arguments and condition name. use super::dar_interface::ParseError; use super::macros::get_try_into; use crate::{ @@ -6,6 +7,9 @@ use crate::{ values::{ActorValue, ActorValueType, Cmp, NumericValue}, }; +/// Parses an actor-related condition based on the provided arguments and condition name. +/// # Errors +/// If parsing fails. pub(super) fn parse_actor( condition_name: &str, args: Vec>, diff --git a/dar2oar_core/src/condition_parser/compare.rs b/dar2oar_core/src/condition_parser/compare.rs index be014ba..27bbc2b 100644 --- a/dar2oar_core/src/condition_parser/compare.rs +++ b/dar2oar_core/src/condition_parser/compare.rs @@ -1,3 +1,4 @@ +//! Parses a comparison-based condition for plugin values. use super::dar_interface::ParseError; use super::macros::get_try_into; use crate::{ @@ -6,7 +7,11 @@ use crate::{ values::{Cmp, NumericValue, PluginValue}, }; -/// condition_name: "ValueEqualTo" | "ValueLessThan" +/// Parses a comparison-based condition for plugin values. +/// `ValueEqualTo` | `ValueLessThan` +/// +/// # Errors +/// Parsing failed. pub(super) fn parse_compare( condition_name: &str, args: Vec>, diff --git a/dar2oar_core/src/condition_parser/conditions.rs b/dar2oar_core/src/condition_parser/conditions.rs index 960efb6..fc487ca 100644 --- a/dar2oar_core/src/condition_parser/conditions.rs +++ b/dar2oar_core/src/condition_parser/conditions.rs @@ -1,3 +1,4 @@ +//! Parses a high-level condition set based on the provided syntax. use super::actor::parse_actor; use super::compare::parse_compare; use super::dar_interface::ParseError; @@ -13,6 +14,9 @@ use crate::conditions::{ use crate::dar_syntax::syntax::{self, Expression}; use crate::values::{Cmp, DirectionValue}; +/// Parses a high-level condition set based on the provided syntax. +/// # Errors +/// Parsing failed. pub fn parse_conditions(input: syntax::Condition) -> Result { Ok(match input { syntax::Condition::And(conditions) => { @@ -39,6 +43,9 @@ pub fn parse_conditions(input: syntax::Condition) -> Result) -> Result { let Expression { negated, diff --git a/dar2oar_core/src/condition_parser/dar_interface.rs b/dar2oar_core/src/condition_parser/dar_interface.rs index 29ee908..190eca0 100644 --- a/dar2oar_core/src/condition_parser/dar_interface.rs +++ b/dar2oar_core/src/condition_parser/dar_interface.rs @@ -8,6 +8,7 @@ use crate::{ }, }; +/// Couldn't parse in DAR to OAR processing Errors #[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)] pub enum ParseError { /// - 1st arg: Expected value @@ -51,7 +52,7 @@ impl TryFrom<&FnArg<'_>> for NumericLiteral { fn try_from(value: &FnArg) -> Result { match value { FnArg::Number(num) => Ok(num.into()), - other => Err(ParseError::UnexpectedValue( + other @ FnArg::PluginValue { .. } => Err(ParseError::UnexpectedValue( "Number(e.g. 3.0)".into(), format!("{other:?}",), )), @@ -109,7 +110,7 @@ impl TryFrom<&FnArg<'_>> for StaticValue { fn try_from(value: &FnArg) -> Result { match value { FnArg::Number(num) => Ok(num.into()), - other => Err(ParseError::UnexpectedValue( + other @ FnArg::PluginValue { .. } => Err(ParseError::UnexpectedValue( "StaticValue(e.g. 3.0)".to_string(), format!("{other:?}",), )), @@ -192,7 +193,7 @@ impl TryFrom<&FnArg<'_>> for Direction { .try_into() .map_err(|e: &str| ParseError::UnexpectedValue(e.into(), "0..=4".into()))?, }), - other => Err(ParseError::UnexpectedValue( + other @ FnArg::PluginValue { .. } => Err(ParseError::UnexpectedValue( "1..=4(in Cast &FnArg to Direction)".into(), format!("{other:?}"), )), diff --git a/dar2oar_core/src/condition_parser/equip.rs b/dar2oar_core/src/condition_parser/equip.rs index 41966c1..a1dbd51 100644 --- a/dar2oar_core/src/condition_parser/equip.rs +++ b/dar2oar_core/src/condition_parser/equip.rs @@ -1,3 +1,4 @@ +//! Parses equipment-related conditions based on the provided arguments and condition name. use super::macros::{gen_cond, get_try_into}; use super::{dar_interface::ParseError, macros::GetArg as _}; use crate::{ @@ -6,6 +7,10 @@ use crate::{ values::{NumericLiteral, TypeValue}, }; +/// Parses equipment-related conditions based on the provided arguments and condition name. +/// +/// # Errors +/// If parsing fails. pub(super) fn parse_equip( condition_name: &str, args: Vec>, @@ -21,7 +26,7 @@ pub(super) fn parse_equip( "IsEquippedRightType" | "IsEquippedLeftType" => { let numeric_value: NumericLiteral = get_try_into!(args[0], "WeaponType -1..18")?; let type_value = TypeValue { - value: numeric_value.try_into().map_err(|_: &str| { + value: numeric_value.try_into().map_err(|_err| { ParseError::UnexpectedValue("-1..18".into(), "Unknown value".into()) })?, }; diff --git a/dar2oar_core/src/condition_parser/faction.rs b/dar2oar_core/src/condition_parser/faction.rs index e45a98d..7df2bb2 100644 --- a/dar2oar_core/src/condition_parser/faction.rs +++ b/dar2oar_core/src/condition_parser/faction.rs @@ -1,3 +1,4 @@ +//! Parses faction-related conditions based on the provided arguments and condition name. use super::dar_interface::ParseError; use super::macros::{get_try_into, GetArg as _}; use crate::{ @@ -6,6 +7,10 @@ use crate::{ values::Cmp, }; +/// Parses faction-related conditions based on the provided arguments and condition name. +/// +/// # Errors +/// If parsing fails. pub(super) fn parse_faction( condition_name: &str, args: Vec>, diff --git a/dar2oar_core/src/condition_parser/has.rs b/dar2oar_core/src/condition_parser/has.rs index bb59c4c..93a62bf 100644 --- a/dar2oar_core/src/condition_parser/has.rs +++ b/dar2oar_core/src/condition_parser/has.rs @@ -1,3 +1,4 @@ +//! Parses has-prefix conditions based on the provided arguments and condition name. use super::dar_interface::ParseError; use super::macros::{gen_cond, get_try_into, GetArg as _}; use crate::conditions::{ @@ -6,6 +7,10 @@ use crate::conditions::{ }; use crate::dar_syntax::syntax::FnArg; +/// Parses has-prefix conditions based on the provided arguments and condition name. +/// +/// # Errors +/// If parsing fails. pub(super) fn parse_has( condition_name: &str, args: Vec>, diff --git a/dar2oar_core/src/condition_parser/macros.rs b/dar2oar_core/src/condition_parser/macros.rs index 8ef5ce7..68cbaab 100644 --- a/dar2oar_core/src/condition_parser/macros.rs +++ b/dar2oar_core/src/condition_parser/macros.rs @@ -1,3 +1,4 @@ +//! Macros for type conversion of parsed DAR structures into easily serializable OAR structures use super::ParseError; use crate::dar_syntax::syntax::FnArg; @@ -53,7 +54,7 @@ impl GetArg for Vec> { } } -/// Access [Vec::get](https://doc.rust-lang.org/stable/alloc/vec/struct.Vec.html#method.get)(index) & try_into() +/// [`Vec::get(index)`](https://doc.rust-lang.org/stable/alloc/vec/struct.Vec.html#method.get) & [`TryInto`] macro_rules! get_try_into { ($args:ident[$index:literal], $expected:literal) => { > as $crate::condition_parser::macros::GetArg>::try_get( @@ -70,7 +71,9 @@ macro_rules! get_try_into { } pub(super) use get_try_into; -/// Generate `ConditionSet` & [Vec]::get(index) & try_into() (can use `into` if you need) +/// Generate `ConditionSet` & +/// [`Vec::get`](https://doc.rust-lang.org/stable/alloc/vec/struct.Vec.html#method.get)(index) & +/// [`TryInto`] (can use `into` if you need) macro_rules! gen_cond { ($id:ident($field_name:ident, $negated:ident), $args:ident, $expected:literal) => { ConditionSet::$id($id { diff --git a/dar2oar_core/src/condition_parser/mod.rs b/dar2oar_core/src/condition_parser/mod.rs index 2a011ee..711feb6 100644 --- a/dar2oar_core/src/condition_parser/mod.rs +++ b/dar2oar_core/src/condition_parser/mod.rs @@ -1,3 +1,4 @@ +//! Module to convert a parsed DAR into a serializable OAR structure. mod actor; mod compare; mod conditions; @@ -13,6 +14,11 @@ use crate::conditions::ConditionSet; use crate::dar_syntax::{convert_error, syntax::parse_condition}; use crate::error::{ConvertError, Result}; +/// Parse a DAR string and convert it into a vector of [`ConditionSets`] representing an OAR structure. +/// +/// This function takes a DAR string as input and parses it into a serializable OAR structure. +/// It returns a [`Result`] containing a vector of [`ConditionSet`] if successful, +/// or a [`ConvertError`] if any parsing or conversion error occurs. pub fn parse_dar2oar(input: &str) -> Result> { let (remain, dar_syn) = match parse_condition(input) { Ok(syn) => { @@ -22,8 +28,7 @@ pub fn parse_dar2oar(input: &str) -> Result> { Err(err) => { let err = match err { nom::Err::Incomplete(_) => return Err(ConvertError::IncompleteConversion), - nom::Err::Error(err) => err, - nom::Err::Failure(err) => err, + nom::Err::Error(err) | nom::Err::Failure(err) => err, }; tracing::trace!("Entered ConvertError::InvalidDarSyntax"); diff --git a/dar2oar_core/src/conditions/and.rs b/dar2oar_core/src/conditions/and.rs index 8d113f2..a7da916 100644 --- a/dar2oar_core/src/conditions/and.rs +++ b/dar2oar_core/src/conditions/and.rs @@ -1,25 +1,31 @@ +//! Represents a logical AND condition set. use super::{condition::default_required_version, is_false, ConditionSet}; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a logical AND condition set. +/// /// - OAR: AND -/// - DAR: fn_name() AND +/// - DAR: `fn_name() AND` /// -/// - NOTE: -/// Fields other than Fields other than conditions are never used in DAR to OAR. -/// In DAR, AND is pushed up to the root conditions. -/// The non-conditions definitions exist in anticipation of future OAR parsing. +/// # NOTE +/// Fields other than conditions are never used in DAR to OAR. +/// In DAR, AND is pushed up to the root conditions. +/// The non-conditions definitions exist in anticipation of future OAR parsing. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct And { - /// Condition name "AND" + /// The name of the condition, which is "AND". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The list of conditions forming the logical AND. #[serde(rename = "Conditions")] pub conditions: Vec, } diff --git a/dar2oar_core/src/conditions/compare_values.rs b/dar2oar_core/src/conditions/compare_values.rs index 276f650..5580f7d 100644 --- a/dar2oar_core/src/conditions/compare_values.rs +++ b/dar2oar_core/src/conditions/compare_values.rs @@ -1,19 +1,24 @@ +//! Structure comparing two A and two B use super::{condition::default_required_version, is_false}; use crate::values::{Cmp, NumericValue}; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Structure comparing A and B #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct CompareValues { /// Condition name "CompareValues" pub condition: CompactString, + /// The required version for compatibility with this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// Comparison object A #[serde(default)] #[serde(rename = "Value A")] pub value_a: NumericValue, @@ -21,6 +26,7 @@ pub struct CompareValues { #[serde(default)] /// == | != | > | >= | < | <= pub comparison: Cmp, + /// Comparison object B #[serde(default)] #[serde(rename = "Value B")] pub value_b: NumericValue, @@ -46,16 +52,17 @@ mod tests { ActorValue, ActorValueType, GraphValue, GraphVariableType, NumericLiteral, NumericValue, PluginValue, StaticValue, }; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_stringify_compare_values() { + fn should_stringify_compare_values() -> Result<()> { let compare_values = CompareValues { value_a: NumericValue::StaticValue(StaticValue { value: 42.0 }), value_b: NumericValue::StaticValue(StaticValue { value: 42.0 }), ..Default::default() }; - let serialized = serde_json::to_string_pretty(&compare_values).unwrap(); + let serialized = serde_json::to_string_pretty(&compare_values)?; let expected = r#"{ "condition": "CompareValues", @@ -70,10 +77,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_stringify_compare_values_with_actor_value() { + fn should_stringify_compare_values_with_actor_value() -> Result<()> { let compare_values = CompareValues { value_a: NumericValue::ActorValue(ActorValue { actor_value: NumericLiteral::Decimal(123), @@ -86,7 +94,7 @@ mod tests { comparison: Cmp::Ge, ..Default::default() }; - let serialized = serde_json::to_string_pretty(&compare_values).unwrap(); + let serialized = serde_json::to_string_pretty(&compare_values)?; let expected = r#"{ "condition": "CompareValues", @@ -103,10 +111,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_stringify_compare_values_with_graph_variable() { + fn should_stringify_compare_values_with_graph_variable() -> Result<()> { let compare_values = CompareValues { value_a: NumericValue::GraphVariable(GraphValue { graph_variable: "true".to_string(), @@ -120,7 +129,7 @@ mod tests { comparison: Cmp::Ne, ..Default::default() }; - let serialized = serde_json::to_string_pretty(&compare_values).unwrap(); + let serialized = serde_json::to_string_pretty(&compare_values)?; let expected = r#"{ "condition": "CompareValues", @@ -137,15 +146,16 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_stringify_compare_values_with_global_variable() { + fn should_stringify_compare_values_with_global_variable() -> Result<()> { let compare_values = CompareValues { value_a: NumericValue::GlobalVariable( PluginValue { plugin_name: "my_plugin.esm".into(), - form_id: 1usize.into(), + form_id: 1_usize.into(), } .into(), ), @@ -159,7 +169,7 @@ mod tests { comparison: Cmp::Gt, ..Default::default() }; - let serialized = serde_json::to_string_pretty(&compare_values).unwrap(); + let serialized = serde_json::to_string_pretty(&compare_values)?; let expected = r#"{ "condition": "CompareValues", @@ -180,5 +190,6 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/condition.rs b/dar2oar_core/src/conditions/condition.rs index 0c0dcaa..d2d9526 100644 --- a/dar2oar_core/src/conditions/condition.rs +++ b/dar2oar_core/src/conditions/condition.rs @@ -1,25 +1,31 @@ +//! Represents a generic condition. use super::is_false; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Representing the default required version. pub const REQUIRED_VERSION: &str = "1.0.0.0"; +/// Create a default required version as a [`CompactString`]. pub fn default_required_version() -> CompactString { REQUIRED_VERSION.into() } +/// Represents a generic condition. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Condition { - /// Condition name (e.g. IsWornHasKeyword) + /// The name of the condition (e.g., IsWornHasKeyword). #[serde(default)] pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] - /// The required version for this condition. pub required_version: CompactString, - /// condition to **Not** (default is `false`). + /// Indicates whether the condition is negated or not (default: `false`). + /// + /// # NOTE + /// There is code written under the assumption that it is skipped when false (e.g., `IsEquipped`). #[serde(default)] - // NOTE: There is code written under the assumption that it is skipped when false (e.g. IsEquipped). #[serde(skip_serializing_if = "is_false")] pub negated: bool, } @@ -47,6 +53,7 @@ impl Condition { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] @@ -67,13 +74,13 @@ mod tests { } #[test] - fn serialize_condition() { + fn serialize_condition() -> Result<()> { let condition = Condition { condition: "SomeCondition".into(), required_version: REQUIRED_VERSION.into(), negated: true, }; - let serialized_json = serde_json::to_string_pretty(&condition).unwrap(); + let serialized_json = serde_json::to_string_pretty(&condition)?; let expected_json = r#"{ "condition": "SomeCondition", @@ -82,16 +89,17 @@ mod tests { }"#; assert_eq!(serialized_json, expected_json); + Ok(()) } #[test] - fn deserialize_condition() { + fn deserialize_condition() -> Result<()> { let json_str = r#"{ "condition": "AnotherCondition", "requiredVersion": "1.0.0.0", "negated": false }"#; - let deserialized: Condition = serde_json::from_str(json_str).unwrap(); + let deserialized: Condition = serde_json::from_str(json_str)?; let expected = Condition { condition: "AnotherCondition".into(), @@ -100,5 +108,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/condition_config.rs b/dar2oar_core/src/conditions/condition_config.rs index eff7b8d..8958201 100644 --- a/dar2oar_core/src/conditions/condition_config.rs +++ b/dar2oar_core/src/conditions/condition_config.rs @@ -1,22 +1,27 @@ +//! Represents the configuration for each animation root specified in a `config.json` file. use super::ConditionSet; use compact_str::CompactString; use serde::{Deserialize, Serialize}; -/// Each animation root config.json +/// Represents the configuration for each animation root specified in a `config.json` file. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct ConditionsConfig { - /// # NOTE - /// An arbitrary name given by the user (value in the mapping table) will probably exceed 24bytes. - /// Therefore, it should not be a [CompactString]. + /// An arbitrary name given by the user (value in the mapping table). + /// + /// # Note + /// The name will probably exceed 24 bytes, so it should not be a [CompactString]. #[serde(default)] pub name: String, + /// The description associated with the animation root configuration. #[serde(default)] pub description: CompactString, + /// The priority of the animation root. #[serde(default)] pub priority: i32, + /// An optional override for the animations folder. #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "overrideAnimationsFolder")] pub override_animations_folder: Option, - #[serde(default)] + /// A vector containing the conditions associated with the animation root. pub conditions: Vec, } diff --git a/dar2oar_core/src/conditions/current_weather.rs b/dar2oar_core/src/conditions/current_weather.rs index 638ec7d..a8c25c8 100644 --- a/dar2oar_core/src/conditions/current_weather.rs +++ b/dar2oar_core/src/conditions/current_weather.rs @@ -1,19 +1,24 @@ +//! Represents a condition to check if the current weather matches a specified weather. use super::{condition::default_required_version, is_false}; use crate::values::PluginValue; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if the current weather matches a specified weather. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct CurrentWeather { - /// Condition name "CurrentWeather" + /// The name of the condition, which is "CurrentWeather". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The specific weather condition to check for. #[serde(default)] #[serde(rename = "Weather")] pub weather: PluginValue, @@ -33,12 +38,13 @@ impl Default for CurrentWeather { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_current_weather() { + fn should_serialize_current_weather() -> Result<()> { let current_weather = CurrentWeather::default(); - let serialized = serde_json::to_string_pretty(¤t_weather).unwrap(); + let serialized = serde_json::to_string_pretty(¤t_weather)?; let expected = r#"{ "condition": "CurrentWeather", @@ -50,10 +56,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_current_weather() { + fn should_deserialize_current_weather() -> Result<()> { let json_str = r#"{ "condition": "CurrentWeather", "requiredVersion": "1.0.0.0", @@ -62,10 +69,11 @@ mod tests { "formID": "" } }"#; - let deserialized: CurrentWeather = serde_json::from_str(json_str).unwrap(); + let deserialized: CurrentWeather = serde_json::from_str(json_str)?; let expected = CurrentWeather::default(); assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/faction_rank.rs b/dar2oar_core/src/conditions/faction_rank.rs index 5adf9ef..cf7c951 100644 --- a/dar2oar_core/src/conditions/faction_rank.rs +++ b/dar2oar_core/src/conditions/faction_rank.rs @@ -1,26 +1,32 @@ +//! Represents a condition to test the reference's faction rank against a specified rank. use super::{condition::default_required_version, is_false}; use crate::values::{Cmp, NumericValue, PluginValue}; use compact_str::CompactString; use serde::{Deserialize, Serialize}; -/// Tests the ref's faction rank against the specified rank. +/// Represents a condition to test the reference's faction rank against a specified rank. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FactionRank { - /// Condition name "FactionRank" + /// The name of the condition, which is "FactionRank". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The faction to test against the reference's faction rank. #[serde(default)] #[serde(rename = "Faction")] pub faction: PluginValue, + /// The comparison operator to use in the faction rank comparison. #[serde(default)] #[serde(rename = "Comparison")] pub comparison: Cmp, + /// The numeric value to compare the faction rank against. #[serde(default)] #[serde(rename = "Numeric value")] pub numeric_value: NumericValue, @@ -43,10 +49,11 @@ impl Default for FactionRank { mod tests { use super::*; use crate::values::StaticValue; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_faction_rank_with_custom_values() { + fn should_serialize_faction_rank_with_custom_values() -> Result<()> { let faction_rank = FactionRank { faction: PluginValue { plugin_name: "CustomPlugin".into(), @@ -56,7 +63,7 @@ mod tests { numeric_value: NumericValue::StaticValue(StaticValue { value: 75.0 }), ..Default::default() }; - let serialized = serde_json::to_string_pretty(&faction_rank).unwrap(); + let serialized = serde_json::to_string_pretty(&faction_rank)?; let expected = r#"{ "condition": "FactionRank", @@ -72,10 +79,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_faction_rank() { + fn should_deserialize_faction_rank() -> Result<()> { let json_str = r#" { "condition": "FactionRank", @@ -90,7 +98,7 @@ mod tests { } } "#; - let deserialized: FactionRank = serde_json::from_str(json_str).unwrap(); + let deserialized: FactionRank = serde_json::from_str(json_str)?; let expected = FactionRank { faction: PluginValue { @@ -103,5 +111,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/has_keyword.rs b/dar2oar_core/src/conditions/has_keyword.rs index f23ecde..8311f6a 100644 --- a/dar2oar_core/src/conditions/has_keyword.rs +++ b/dar2oar_core/src/conditions/has_keyword.rs @@ -1,19 +1,24 @@ +//! Represents a condition to check if an entity has a specific keyword. use super::{condition::default_required_version, is_false}; use crate::values::Keyword; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if an entity has a specific keyword. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct HasKeyword { - /// Condition name "HasKeyword" + /// The name of the condition, which is "HasKeyword". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The keyword to check for in the entity. #[serde(default)] #[serde(rename = "Keyword")] pub keyword: Keyword, @@ -34,12 +39,13 @@ impl Default for HasKeyword { mod tests { use super::*; use crate::values::LiteralValue; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_has_keyword() { + fn should_serialize_has_keyword() -> Result<()> { let has_keyword = HasKeyword::default(); - let serialized = serde_json::to_string_pretty(&has_keyword).unwrap(); + let serialized = serde_json::to_string_pretty(&has_keyword)?; let expected = r#"{ "condition": "HasKeyword", @@ -50,10 +56,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_has_keyword() { + fn should_deserialize_has_keyword() -> Result<()> { let json_str = r#"{ "condition": "HasKeyword", "requiredVersion": "1.0.0.0", @@ -61,7 +68,7 @@ mod tests { "editorID": "SomeKeyword" } }"#; - let deserialized: HasKeyword = serde_json::from_str(json_str).unwrap(); + let deserialized: HasKeyword = serde_json::from_str(json_str)?; let expected = HasKeyword { keyword: Keyword::Literal(LiteralValue { @@ -71,5 +78,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/has_magic_effect.rs b/dar2oar_core/src/conditions/has_magic_effect.rs index 898e85c..1ad1882 100644 --- a/dar2oar_core/src/conditions/has_magic_effect.rs +++ b/dar2oar_core/src/conditions/has_magic_effect.rs @@ -1,22 +1,28 @@ +//! Represents a condition to check if an entity has a specific magic effect. use super::{condition::default_required_version, is_false}; use crate::values::PluginValue; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if an entity has a specific magic effect. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct HasMagicEffect { - /// Condition name "HasMagicEffect" + /// The name of the condition, which is "HasMagicEffect". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The magic effect to check for in the entity. #[serde(default)] #[serde(rename = "Magic effect")] pub magic_effect: PluginValue, + /// Indicates whether to consider only active effects. #[serde(default)] #[serde(rename = "Active effects only")] pub active_effects_only: bool, @@ -37,12 +43,13 @@ impl Default for HasMagicEffect { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_has_magic_effect() { + fn should_serialize_has_magic_effect() -> Result<()> { let has_magic_effect = HasMagicEffect::default(); - let serialized = serde_json::to_string_pretty(&has_magic_effect).unwrap(); + let serialized = serde_json::to_string_pretty(&has_magic_effect)?; let expected = r#"{ "condition": "HasMagicEffect", @@ -55,10 +62,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_has_magic_effect() { + fn should_deserialize_has_magic_effect() -> Result<()> { let json_str = r#"{ "condition": "HasMagicEffect", "requiredVersion": "1.0.0.0", @@ -68,7 +76,7 @@ mod tests { }, "Active effects only": true }"#; - let deserialized: HasMagicEffect = serde_json::from_str(json_str).unwrap(); + let deserialized: HasMagicEffect = serde_json::from_str(json_str)?; let expected = HasMagicEffect { magic_effect: PluginValue { @@ -80,5 +88,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/has_magic_effect_with_keyword.rs b/dar2oar_core/src/conditions/has_magic_effect_with_keyword.rs index 846c21e..023dc2b 100644 --- a/dar2oar_core/src/conditions/has_magic_effect_with_keyword.rs +++ b/dar2oar_core/src/conditions/has_magic_effect_with_keyword.rs @@ -1,27 +1,33 @@ +//! Represents a condition to check if an entity has a magic effect with a specific keyword. use super::{condition::default_required_version, is_false}; use crate::values::{FormValue, Keyword}; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if an entity has a magic effect with a specific keyword. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct HasMagicEffectWithKeyword { - /// Condition name "HasMagicEffectWithKeyword" + /// The name of the condition, which is "HasMagicEffectWithKeyword". /// - /// # NOTE - /// This condition name is 25bytes. - /// Optimization by [CompactString] is limited to 24bytes, the size of a [String] structure. + /// # Note + /// This condition name is 25 bytes. + /// Optimization by [`CompactString`] is limited to 24 bytes, the size of a [`String`] structure. /// Therefore, this field cannot be SSO (Small String Optimization). pub condition: String, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The keyword to check for in the magic effect. #[serde(default)] #[serde(rename = "Keyword")] pub keyword: Keyword, + /// Indicates whether to consider only active effects. #[serde(default)] #[serde(rename = "Active effects only")] pub active_effects_only: bool, @@ -43,12 +49,13 @@ impl Default for HasMagicEffectWithKeyword { mod tests { use super::*; use crate::values::PluginValue; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_has_magic_effect() { + fn should_serialize_has_magic_effect() -> Result<()> { let has_magic_effect = HasMagicEffectWithKeyword::default(); - let serialized = serde_json::to_string_pretty(&has_magic_effect).unwrap(); + let serialized = serde_json::to_string_pretty(&has_magic_effect)?; let expected = r#"{ "condition": "HasMagicEffectWithKeyword", @@ -63,10 +70,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_has_magic_effect() { + fn should_deserialize_has_magic_effect() -> Result<()> { let json_str = r#"{ "condition": "HasMagicEffectWithKeyword", "requiredVersion": "1.0.0.0", @@ -78,7 +86,7 @@ mod tests { }, "Active effects only": true }"#; - let deserialized: HasMagicEffectWithKeyword = serde_json::from_str(json_str).unwrap(); + let deserialized: HasMagicEffectWithKeyword = serde_json::from_str(json_str)?; let expected = HasMagicEffectWithKeyword { keyword: Keyword::Form(FormValue { @@ -92,5 +100,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/has_perk.rs b/dar2oar_core/src/conditions/has_perk.rs index 44ec93f..265335b 100644 --- a/dar2oar_core/src/conditions/has_perk.rs +++ b/dar2oar_core/src/conditions/has_perk.rs @@ -1,19 +1,24 @@ +//! Represents a condition to check if an entity has a specific perk. use super::{condition::default_required_version, is_false}; use crate::values::PluginValue; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if an entity has a specific perk. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct HasPerk { - /// Condition name "HasPerk" + /// The name of the condition, which is "HasPerk". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The perk to check for in the entity. #[serde(default)] #[serde(rename = "Perk")] pub perk: PluginValue, @@ -33,12 +38,13 @@ impl Default for HasPerk { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_has_perk() { + fn should_serialize_has_perk() -> Result<()> { let has_perk = HasPerk::default(); - let serialized = serde_json::to_string_pretty(&has_perk).unwrap(); + let serialized = serde_json::to_string_pretty(&has_perk)?; let expected = r#"{ "condition": "HasPerk", @@ -50,10 +56,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_has_perk() { + fn should_deserialize_has_perk() -> Result<()> { let json_str = r#"{ "condition": "HasPerk", "requiredVersion": "1.0.0.0", @@ -63,7 +70,7 @@ mod tests { "formID": "12345" } }"#; - let deserialized: HasPerk = serde_json::from_str(json_str).unwrap(); + let deserialized: HasPerk = serde_json::from_str(json_str)?; let expected = HasPerk { negated: true, @@ -75,5 +82,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/has_ref_type.rs b/dar2oar_core/src/conditions/has_ref_type.rs index 5a10d17..fc6a245 100644 --- a/dar2oar_core/src/conditions/has_ref_type.rs +++ b/dar2oar_core/src/conditions/has_ref_type.rs @@ -1,19 +1,24 @@ +//! Represents a condition to check if a reference has a specific type. use super::{condition::default_required_version, is_false}; use crate::values::Keyword; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if a reference has a specific type. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct HasRefType { - /// Condition name "HasRefType" + /// The name of the condition, which is "HasRefType". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The reference type to check for. #[serde(default)] #[serde(rename = "Location ref type")] pub location_ref_type: Keyword, @@ -34,10 +39,11 @@ impl Default for HasRefType { mod tests { use super::*; use crate::values::{FormValue, LiteralValue, PluginValue}; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_has_ref_type() { + fn should_serialize_has_ref_type() -> Result<()> { let has_ref_type = HasRefType { location_ref_type: Keyword::Form(FormValue { form: PluginValue { @@ -47,7 +53,7 @@ mod tests { }), ..Default::default() }; - let serialized = serde_json::to_string_pretty(&has_ref_type).unwrap(); + let serialized = serde_json::to_string_pretty(&has_ref_type)?; let expected = r#"{ "condition": "HasRefType", @@ -61,10 +67,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_has_ref_type() { + fn should_deserialize_has_ref_type() -> Result<()> { let json_str = r#"{ "condition": "HasRefType", "requiredVersion": "1.0.0.0", @@ -73,7 +80,7 @@ mod tests { "editorID": "ExampleLocationTyp" } }"#; - let deserialized: HasRefType = serde_json::from_str(json_str).unwrap(); + let deserialized: HasRefType = serde_json::from_str(json_str)?; let expected = HasRefType { negated: true, @@ -84,5 +91,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/is_equipped.rs b/dar2oar_core/src/conditions/is_equipped.rs index 8c2dafd..adf8a64 100644 --- a/dar2oar_core/src/conditions/is_equipped.rs +++ b/dar2oar_core/src/conditions/is_equipped.rs @@ -1,23 +1,28 @@ +//! Represents a condition based on whether an entity is equipped with a specific form. use super::{condition::default_required_version, is_false}; use crate::values::PluginValue; use compact_str::CompactString; use serde::{Deserialize, Serialize}; -/// - OAR: IsEquipped +/// Represents a condition based on whether an entity is equipped with a specific form. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IsEquipped { - /// Condition name "IsEquipped" + /// The name of the condition, which is "IsEquipped". pub condition: CompactString, + /// The required version for compatibility with this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The form associated with the condition. #[serde(default)] #[serde(rename = "Form")] pub form: PluginValue, + /// Indicates whether the entity is equipped in the left hand. #[serde(default)] #[serde(rename = "Left hand")] pub left_hand: bool, @@ -38,10 +43,11 @@ impl Default for IsEquipped { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_is_equipped() { + fn should_serialize_is_equipped() -> Result<()> { let is_equipped = IsEquipped { form: PluginValue { plugin_name: "MyPlugin".into(), @@ -50,7 +56,7 @@ mod tests { left_hand: true, ..Default::default() }; - let serialized = serde_json::to_string_pretty(&is_equipped).unwrap(); + let serialized = serde_json::to_string_pretty(&is_equipped)?; let expected = r#"{ "condition": "IsEquipped", @@ -63,10 +69,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_is_equipped() { + fn should_deserialize_is_equipped() -> Result<()> { // This is the actual json output by OAR. let json_str = r#"{ "condition": "IsEquipped", @@ -78,7 +85,7 @@ mod tests { }, "Left hand": false }"#; - let deserialized: IsEquipped = serde_json::from_str(json_str).unwrap(); + let deserialized: IsEquipped = serde_json::from_str(json_str)?; let expected = IsEquipped { condition: "IsEquipped".into(), @@ -92,5 +99,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/is_equipped_has_keyword.rs b/dar2oar_core/src/conditions/is_equipped_has_keyword.rs index 3132f57..7428e0b 100644 --- a/dar2oar_core/src/conditions/is_equipped_has_keyword.rs +++ b/dar2oar_core/src/conditions/is_equipped_has_keyword.rs @@ -1,22 +1,28 @@ +//! Represents a condition to check if an equipped item has a specific keyword. use super::{condition::default_required_version, is_false}; use crate::values::Keyword; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if an equipped item has a specific keyword. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IsEquippedHasKeyword { - /// Condition name "IsEquippedHasKeyword" + /// The name of the condition, which is "IsEquippedHasKeyword". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The keyword to check for in the equipped item. #[serde(default)] #[serde(rename = "Keyword")] pub keyword: Keyword, + /// Indicates whether the equipped item should be in the left hand. #[serde(default)] #[serde(rename = "Left hand")] pub left_hand: bool, @@ -38,12 +44,13 @@ impl Default for IsEquippedHasKeyword { mod tests { use super::*; use crate::values::{FormValue, PluginValue}; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_is_equipped_has_keyword() { + fn should_serialize_is_equipped_has_keyword() -> Result<()> { let is_equipped_has_keyword = IsEquippedHasKeyword::default(); - let serialized = serde_json::to_string_pretty(&is_equipped_has_keyword).unwrap(); + let serialized = serde_json::to_string_pretty(&is_equipped_has_keyword)?; let expected = r#"{ "condition": "IsEquippedHasKeyword", @@ -55,10 +62,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_is_equipped_has_keyword() { + fn should_deserialize_is_equipped_has_keyword() -> Result<()> { let json_str = r#"{ "condition": "IsEquippedHasKeyword", "requiredVersion": "1.0.0.0", @@ -70,7 +78,7 @@ mod tests { }, "Left hand": true }"#; - let deserialized: IsEquippedHasKeyword = serde_json::from_str(json_str).unwrap(); + let deserialized: IsEquippedHasKeyword = serde_json::from_str(json_str)?; let expected = IsEquippedHasKeyword { keyword: Keyword::Form(FormValue { @@ -84,5 +92,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/is_equipped_type.rs b/dar2oar_core/src/conditions/is_equipped_type.rs index ec27ca2..7cd1a66 100644 --- a/dar2oar_core/src/conditions/is_equipped_type.rs +++ b/dar2oar_core/src/conditions/is_equipped_type.rs @@ -1,22 +1,28 @@ +//! Represents a condition to check if a specific type is equipped. use super::{condition::default_required_version, is_false}; use crate::values::TypeValue; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition to check if a specific type is equipped. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IsEquippedType { - /// Condition name "IsEquippedType" + /// The name of the condition, which is "IsEquippedType". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The type value to check for equipment. #[serde(default)] #[serde(rename = "Type")] pub type_value: TypeValue, + /// Indicates whether the equipment should be in the left hand. #[serde(default)] #[serde(rename = "Left hand")] pub left_hand: bool, @@ -38,10 +44,11 @@ impl Default for IsEquippedType { mod tests { use super::*; use crate::values::WeaponType; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_is_equipped_type() { + fn should_serialize_is_equipped_type() -> Result<()> { let is_equipped_type = IsEquippedType { negated: true, type_value: TypeValue { @@ -49,7 +56,7 @@ mod tests { }, ..Default::default() }; - let serialized = serde_json::to_string_pretty(&is_equipped_type).unwrap(); + let serialized = serde_json::to_string_pretty(&is_equipped_type)?; let expected = r#"{ "condition": "IsEquippedType", @@ -62,10 +69,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_is_equipped_type() { + fn should_deserialize_is_equipped_type() -> Result<()> { let json_str = r#"{ "condition": "IsEquippedType", "requiredVersion": "1.0.0.0", @@ -74,7 +82,7 @@ mod tests { }, "Left hand": false }"#; - let deserialized: IsEquippedType = serde_json::from_str(json_str).unwrap(); + let deserialized: IsEquippedType = serde_json::from_str(json_str)?; let expected = IsEquippedType { type_value: TypeValue { @@ -85,6 +93,7 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } #[test] diff --git a/dar2oar_core/src/conditions/is_movement_direction.rs b/dar2oar_core/src/conditions/is_movement_direction.rs index 2f0b0d7..e29f244 100644 --- a/dar2oar_core/src/conditions/is_movement_direction.rs +++ b/dar2oar_core/src/conditions/is_movement_direction.rs @@ -1,19 +1,24 @@ +//! Represents a condition based on the movement direction of an entity. use super::{condition::default_required_version, is_false}; use crate::values::DirectionValue; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition based on the movement direction of an entity. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IsMovementDirection { - /// Condition name "IsMovementDirection" + /// The name of the condition, which is "IsMovementDirection". pub condition: CompactString, + /// The required version for compatibility with this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The movement direction associated with the condition. #[serde(default)] #[serde(rename = "Direction")] pub direction: DirectionValue, @@ -34,17 +39,18 @@ impl Default for IsMovementDirection { mod tests { use super::*; use crate::values::Direction; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_is_movement_direction() { + fn should_serialize_is_movement_direction() -> Result<()> { let is_movement_direction = IsMovementDirection { direction: DirectionValue { value: Direction::Left, }, ..Default::default() }; - let serialized = serde_json::to_string_pretty(&is_movement_direction).unwrap(); + let serialized = serde_json::to_string_pretty(&is_movement_direction)?; let expected = r#"{ "condition": "IsMovementDirection", @@ -55,10 +61,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_is_movement_direction() { + fn should_deserialize_is_movement_direction() -> Result<()> { // This is the actual json output by OAR. let json_str = r#"{ "condition": "IsMovementDirection", @@ -68,7 +75,7 @@ mod tests { "value": 2.0 } }"#; - let deserialized: IsMovementDirection = serde_json::from_str(json_str).unwrap(); + let deserialized: IsMovementDirection = serde_json::from_str(json_str)?; let expected = IsMovementDirection { negated: true, @@ -79,5 +86,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/is_worn_has_keyword.rs b/dar2oar_core/src/conditions/is_worn_has_keyword.rs index e1b1487..df92c3c 100644 --- a/dar2oar_core/src/conditions/is_worn_has_keyword.rs +++ b/dar2oar_core/src/conditions/is_worn_has_keyword.rs @@ -1,19 +1,24 @@ +//! Represents a condition based on whether an entity is worn and has a specific keyword. use super::{condition::default_required_version, is_false}; use crate::values::Keyword; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents a condition based on whether an entity is worn and has a specific keyword. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IsWornHasKeyword { - /// Condition name "IsWornHasKeyword" + /// The name of the condition, which is "IsWornHasKeyword". pub condition: CompactString, + /// The required version for compatibility with this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The keyword associated with the condition. #[serde(default)] #[serde(rename = "Keyword")] pub keyword: Keyword, @@ -34,12 +39,13 @@ impl Default for IsWornHasKeyword { mod tests { use super::*; use crate::values::{FormValue, PluginValue}; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_is_worn_has_keyword() { + fn should_serialize_is_worn_has_keyword() -> Result<()> { let is_worn_has_keyword = IsWornHasKeyword::default(); - let serialized = serde_json::to_string_pretty(&is_worn_has_keyword).unwrap(); + let serialized = serde_json::to_string_pretty(&is_worn_has_keyword)?; let expected = r#"{ "condition": "IsWornHasKeyword", @@ -50,10 +56,11 @@ mod tests { }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_is_worn_has_keyword() { + fn should_deserialize_is_worn_has_keyword() -> Result<()> { let json_str = r#"{ "condition": "IsWornHasKeyword", "requiredVersion": "1.0.0.0", @@ -64,7 +71,7 @@ mod tests { } } }"#; - let deserialized: IsWornHasKeyword = serde_json::from_str(json_str).unwrap(); + let deserialized: IsWornHasKeyword = serde_json::from_str(json_str)?; let expected = IsWornHasKeyword { keyword: Keyword::Form(FormValue { @@ -77,5 +84,6 @@ mod tests { }; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/conditions/mod.rs b/dar2oar_core/src/conditions/mod.rs index 10bf650..02b8063 100644 --- a/dar2oar_core/src/conditions/mod.rs +++ b/dar2oar_core/src/conditions/mod.rs @@ -1,3 +1,4 @@ +//! Module for representing conditions used in DAR files. mod and; mod compare_values; mod condition; @@ -34,12 +35,16 @@ use crate::values::{Cmp, NumericValue, PluginValue}; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Returns `true` if the provided boolean value is `false`, otherwise `false`. +/// +/// This function is used as a predicate for serialization purposes to skip fields +/// that have a default value of `false`. #[inline] -pub(super) fn is_false(t: &bool) -> bool { +pub(super) const fn is_false(t: &bool) -> bool { !(*t) } -/// Generate structures that have only condition, Comparison and NumericValue +/// Generate structures that have only condition, Comparison and [`NumericValue`] macro_rules! gen_cmp_num_struct { ($($(#[$attr:meta])* $name:ident),+ $(,)?) => { $( @@ -87,7 +92,7 @@ gen_cmp_num_struct!( CurrentGameTime ); -/// generate structures that have only condition and PluginValue +/// generate structures that have only condition and [`PluginValue`] #[macro_export] macro_rules! gen_one_plugin_struct { ($($(#[$attr:meta])* $name:ident, $field:ident => $rename_field:literal),+ $(,)?) => { @@ -137,39 +142,101 @@ gen_one_plugin_struct!( IsWorn, form => "Form", ); +/// Represents a set of conditions that can be serialized to the OAR of functions present in the DAR. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum ConditionSet { + /// Represents a logical AND operation between conditions. And(And), + + /// Represents a single condition. Condition(Condition), + + /// Represents a comparison between values. CompareValues(CompareValues), + + /// Represents a condition based on the current game time. CurrentGameTime(CurrentGameTime), + + /// Represents a condition based on the current weather in the game. CurrentWeather(CurrentWeather), + + /// Represents a condition based on the faction rank of an entity. FactionRank(FactionRank), + + /// Represents a condition based on whether an entity has a certain keyword. HasKeyword(HasKeyword), + + /// Represents a condition based on whether an entity has a specific magic effect. HasMagicEffect(HasMagicEffect), + + /// Represents a condition based on whether an entity has a magic effect with a certain keyword. HasMagicEffectWithKeyword(HasMagicEffectWithKeyword), + + /// Represents a condition based on whether an entity has a specific perk. HasPerk(HasPerk), + + /// Represents a condition based on the reference type of an entity. HasRefType(HasRefType), + + /// Represents a condition based on whether an entity has a specific spell. HasSpell(HasSpell), + + /// Represents a condition based on the actor base of an entity. IsActorBase(IsActorBase), + + /// Represents a condition based on the class of an entity. IsClass(IsClass), + + /// Represents a condition based on the combat style of an entity. IsCombatStyle(IsCombatStyle), + + /// Represents a condition based on whether an entity is equipped with something. IsEquipped(IsEquipped), + + /// Represents a condition based on whether an equipped item has a certain keyword. IsEquippedHasKeyword(IsEquippedHasKeyword), + + /// Represents a condition based on whether a shout is equipped. IsEquippedShout(IsEquippedShout), + + /// Represents a condition based on the equipped type of an entity. IsEquippedType(IsEquippedType), + + /// Represents a condition based on whether an entity is in a faction. IsInFaction(IsInFaction), + + /// Represents a condition based on whether an entity is in a specific location. IsInLocation(IsInLocation), + + /// Represents a condition based on the parent cell of an entity. IsParentCell(IsParentCell), + + /// Represents a condition based on the race of an entity. IsRace(IsRace), + + /// Represents a condition based on the voice type of an entity. IsVoiceType(IsVoiceType), + + /// Represents a condition based on the world space of an entity. IsWorldSpace(IsWorldSpace), + + /// Represents a condition based on whether an entity is worn. IsWorn(IsWorn), + + /// Represents a condition based on whether a worn item has a certain keyword. IsWornHasKeyword(IsWornHasKeyword), + + /// Represents a condition based on the movement direction of an entity. IsDirectionMovement(IsMovementDirection), + + /// Represents a condition based on the level of an entity. Level(Level), + + /// Represents a logical OR operation between conditions. Or(Or), + + /// Represents a random condition. RandomCondition(RandomCondition), } @@ -185,9 +252,10 @@ impl TryFrom for Vec { } } +/// Represents an error that can occur while working with conditions. #[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)] pub enum ConditionError { - // Couldn't cast + /// Error indicating failure to cast to Vec. #[error("Only And or Or can be converted to Vec.")] CastError, } diff --git a/dar2oar_core/src/conditions/namespace_config.rs b/dar2oar_core/src/conditions/namespace_config.rs index d4d88aa..342ef2f 100644 --- a/dar2oar_core/src/conditions/namespace_config.rs +++ b/dar2oar_core/src/conditions/namespace_config.rs @@ -1,12 +1,18 @@ +//! Specifically for the 'config.json' namespace. use serde::{Deserialize, Serialize}; -/// name space config.json +/// Represents the configuration structure for the 'config.json' namespace. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct MainConfig<'a> { + /// The name associated with the configuration. #[serde(default)] pub name: &'a str, + + /// The description associated with the configuration. #[serde(default)] pub description: &'a str, + + /// The author associated with the configuration. #[serde(default)] pub author: &'a str, } diff --git a/dar2oar_core/src/conditions/or.rs b/dar2oar_core/src/conditions/or.rs index 00ed230..a378032 100644 --- a/dar2oar_core/src/conditions/or.rs +++ b/dar2oar_core/src/conditions/or.rs @@ -1,20 +1,26 @@ +//! OR condition use super::{condition::default_required_version, is_false, ConditionSet}; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// Represents the "OR" condition in the OAR of functions in the DAR. +/// /// - OAR: OR -/// - DAR: fn_name() OR +/// - DAR: `fn_name() OR` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Or { - /// Condition name "OR" + /// The name of the condition, which is "OR". pub condition: CompactString, + /// The required version for compatibility with this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// A vector containing the sub-conditions for the "OR" condition. #[serde(rename = "Conditions")] pub conditions: Vec, } diff --git a/dar2oar_core/src/conditions/random.rs b/dar2oar_core/src/conditions/random.rs index 522ac9b..a8b8df6 100644 --- a/dar2oar_core/src/conditions/random.rs +++ b/dar2oar_core/src/conditions/random.rs @@ -1,26 +1,34 @@ +//! Represents a condition involving randomness. use super::{condition::default_required_version, is_false}; use crate::values::{Cmp, NumericValue, RandomValue}; use compact_str::CompactString; use serde::{Deserialize, Serialize}; -/// - OAR: Random +/// Represents a condition involving randomness. +/// +/// - OAR (Object Arithmetic Representation): Random #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RandomCondition { - /// Condition name "Random" + /// The name of the condition, which is "Random". pub condition: CompactString, + /// The required version for this condition. #[serde(default = "default_required_version")] #[serde(rename = "requiredVersion")] pub required_version: CompactString, + /// Indicates whether the condition is negated or not. #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub negated: bool, + /// The random value used in the condition. #[serde(default)] #[serde(rename = "Random value")] pub random_value: RandomValue, + /// The comparison operator to use in the condition. #[serde(default)] #[serde(rename = "Comparison")] pub comparison: Cmp, + /// The numeric value to compare against in the condition. #[serde(default)] #[serde(rename = "Numeric value")] pub numeric_value: NumericValue, diff --git a/dar2oar_core/src/dar_syntax/error.rs b/dar2oar_core/src/dar_syntax/error.rs index 9edb8af..3b56a86 100644 --- a/dar2oar_core/src/dar_syntax/error.rs +++ b/dar2oar_core/src/dar_syntax/error.rs @@ -1,6 +1,7 @@ -/// Copyright (c) 2014-2019 Geoffroy Couprie -/// MIT License -/// https://opensource.org/license/mit/ +//! Copyright (c) 2014-2019 Geoffroy Couprie +//! MIT License +//! +//! use nom::{ error::{VerboseError, VerboseErrorKind}, Offset, @@ -39,8 +40,7 @@ pub fn convert_error>(input: I, e: VerboseErro .iter() .rev() .position(|&b| b == b'\n') - .map(|pos| offset - pos) - .unwrap_or(0); + .map_or(0, |pos| offset - pos); // Find the full line after that newline let line = input[line_begin..] @@ -111,8 +111,9 @@ pub fn convert_error>(input: I, e: VerboseErro ), } } - // Because `write!` to a `String` is infallible, this `unwrap` is fine. - .unwrap(); + .expect( + "Unreachable, Because `write!` to a `String` is infallible, this `unwrap` is fine.", + ); } result diff --git a/dar2oar_core/src/dar_syntax/mod.rs b/dar2oar_core/src/dar_syntax/mod.rs index bcab47a..62bf180 100644 --- a/dar2oar_core/src/dar_syntax/mod.rs +++ b/dar2oar_core/src/dar_syntax/mod.rs @@ -1,3 +1,4 @@ +//! DAR syntax parser & error handling mod error; pub mod syntax; pub use error::convert_error; diff --git a/dar2oar_core/src/dar_syntax/syntax.rs b/dar2oar_core/src/dar_syntax/syntax.rs index b0b65bc..6886d38 100644 --- a/dar2oar_core/src/dar_syntax/syntax.rs +++ b/dar2oar_core/src/dar_syntax/syntax.rs @@ -49,12 +49,19 @@ use nom::{ }; use std::fmt; +/// DAR Function arguments +/// - Plugin e.g. Skyrim.esm | 0x007 +/// - Literal e.g. 1.0 #[derive(Debug, Clone, PartialEq)] pub enum FnArg<'a> { + /// e.g. "Skyrim.esm" | 0x007 PluginValue { + /// e.g. "Skyrim.esm" plugin_name: &'a str, + /// e.g. 1 form_id: NumberLiteral, }, + /// Just number. (e.g. 1) Number(NumberLiteral), } @@ -73,8 +80,11 @@ impl fmt::Display for FnArg<'_> { /// Hex | Decimal | Float #[derive(Debug, Clone, PartialEq)] pub enum NumberLiteral { + /// e.g. 0x007 Hex(usize), + /// e.g. 1 Decimal(isize), + /// e.g. 1.0 Float(f32), } @@ -88,26 +98,34 @@ impl fmt::Display for NumberLiteral { } } +/// DAR One line representation #[derive(Debug, Clone, PartialEq)] pub struct Expression<'a> { /// not condition pub negated: bool, /// function name == condition name pub fn_name: &'a str, + /// arguments pub args: Vec>, } /// AND | OR #[derive(Debug, Clone, PartialEq)] pub enum Operator { + /// AND And, + /// OR Or, } +/// Represents a high-level condition, which can be an AND combination, OR combination, or a leaf expression. #[derive(Debug, Clone, PartialEq)] pub enum Condition<'a> { + /// Represents an AND combination of multiple conditions. And(Vec>), + /// Represents an OR combination of multiple conditions. Or(Vec>), + /// Represents a leaf expression within the condition hierarchy. Exp(Expression<'a>), } @@ -115,20 +133,20 @@ impl<'a> Condition<'a> { /// push to inner vec /// /// # panics - /// If push to Self::Exp + /// If push to [`Self::Exp`] fn push(&mut self, expression: Condition<'a>) { match self { - Condition::And(inner) => inner.push(expression), - Condition::Or(inner) => inner.push(expression), + Condition::And(inner) | Condition::Or(inner) => inner.push(expression), Condition::Exp(_) => panic!("Expression cannot push"), } } } -/// IResult wrapped for VerboseError -type IResult<'a, I, O> = nom::IResult>; +/// Type alias for a result type using [`nom::IResult`], wrapping potential errors with [`nom::error::VerboseError`] +type IResult<'a, I, O, E = nom::error::VerboseError<&'a str>> = nom::IResult; use nom::error::ParseError; // To use from_error_kind +/// A macro for returning an error with a specific error kind in the `nom::error::VerboseError` variant. macro_rules! bail_kind { ($input:ident, $kind:ident) => { return Err(nom::Err::Error(nom::error::VerboseError::from_error_kind( @@ -138,6 +156,7 @@ macro_rules! bail_kind { }; } +/// Parses a string literal enclosed in single or double quotes with support for escaping characters. fn parse_string(input: &str) -> IResult<&str, &str> { alt(( delimited( @@ -181,6 +200,11 @@ fn parse_radix_number(input: &str) -> IResult<&str, NumberLiteral> { } } +/// Parse decimal number(e.g. "123") +/// +/// ```EBNF +/// decimal = ["-"] digit { digit } ; +/// ``` fn parse_decimal(input: &str) -> IResult<&str, NumberLiteral> { let (input, _) = multispace0(input)?; let (input, is_negative) = opt(char('-'))(input)?; @@ -200,6 +224,7 @@ fn parse_decimal(input: &str) -> IResult<&str, NumberLiteral> { } } +/// Parse float number(e.g. "12.3") fn parse_float(input: &str) -> IResult<&str, NumberLiteral> { let (input, _) = multispace0(input)?; let (input, is_negative) = opt(char('-'))(input)?; @@ -223,10 +248,12 @@ fn parse_float(input: &str) -> IResult<&str, NumberLiteral> { } } +/// Parse a number(e.g. "0x123", "123", "12.3") fn parse_number(input: &str) -> IResult<&str, NumberLiteral> { alt((parse_radix_number, parse_float, parse_decimal))(input) } +/// Parse plugin value(e.g. `"Skyrim.esm" | 0x007`) fn parse_plugin(input: &str) -> IResult<&str, FnArg<'_>> { let (input, (plugin_name, form_id)) = separated_pair( preceded(space0, parse_string), @@ -243,10 +270,12 @@ fn parse_plugin(input: &str) -> IResult<&str, FnArg<'_>> { )) } +/// Parse function arguments. fn parse_argument(input: &str) -> IResult<&str, FnArg<'_>> { alt((parse_plugin, map(parse_number, FnArg::Number)))(input) } +/// Prase identifier fn parse_ident(input: &str) -> IResult<&str, &str> { context( "Expected ident. (Example: IsActorBase)", @@ -254,7 +283,10 @@ fn parse_ident(input: &str) -> IResult<&str, &str> { )(input) } +/// Parse function call(with arguments) fn parse_fn_call(input: &str) -> IResult<&str, (&str, Vec>)> { + /// Parse function call with arguments + #[inline] fn with_args(input: &str) -> IResult<&str, Option>>> { let (input, _) = tag("(")(input)?; let (input, args) = @@ -268,9 +300,7 @@ fn parse_fn_call(input: &str) -> IResult<&str, (&str, Vec>)> { let (input, _) = multispace0(input)?; let (input, args) = opt(with_args)(input)?; - let args = args - .map(|args| args.unwrap_or_default()) - .unwrap_or(Vec::new()); + let args = args.map_or(Vec::new(), Option::unwrap_or_default); Ok((input, (fn_name, args))) } @@ -285,6 +315,7 @@ fn parse_operator(input: &str) -> IResult<&str, Operator> { Ok((input, operator)) } +/// Parse one line DAR Syntax fn parse_expression(input: &str) -> IResult<&str, Expression> { let (input, _) = multispace0(input)?; let (input, negate) = opt(tag("NOT"))(input)?; @@ -311,6 +342,7 @@ fn comment(input: &str) -> IResult<&str, &str> { Ok((input, comment)) } +/// Parse DAR syntax pub fn parse_condition(input: &str) -> IResult<&str, Condition<'_>> { let mut top_conditions = Condition::And(Vec::new()); let mut or_vec = Vec::new(); diff --git a/dar2oar_core/src/error.rs b/dar2oar_core/src/error.rs index 0b67b85..2b3f2e8 100644 --- a/dar2oar_core/src/error.rs +++ b/dar2oar_core/src/error.rs @@ -1,43 +1,77 @@ +//! Error types for Converter + +/// It is used to represent different types of errors that can occur during the conversion process. +/// Each variant of the enum represents a specific type of error, +/// and it can contain additional data associated with the error, +/// such as error messages or other relevant information. #[derive(Debug, thiserror::Error)] pub enum ConvertError { + /// Failed to write section config target. #[error("Failed to write section config target: {0}")] FailedWriteSectionConfig(String), + + /// Never converted. #[error("Never converted.")] NeverConverted, + + /// No such paths exist. #[error("No such paths exist: \"{0}\"")] NonExistPath(String), + + /// Nothing in the specified path. #[error("Nothing in the specified path")] NotFoundEntry, + /// Could not find files with ".mohidden" extension. #[error("Could not find files with \".mohidden\" extension")] NotFoundUnhideTarget, + /// Not found "DynamicAnimationReplacer" directory. #[error("Not found \"DynamicAnimationReplacer\" directory")] NotFoundDarDir, + /// Not found file name. #[error("Not found file name")] NotFoundFileName, + /// Not found "OpenAnimationReplacer" directory. #[error("Not found \"OpenAnimationReplacer\" directory")] NotFoundOarDir, + /// Not found DAR priority(Number) directory. #[error("Not found DAR priority(Number) directory")] NotFoundPriorityDir, + + /// Incomplete conversion. #[error("Incomplete conversion")] IncompleteConversion, + /// Incomplete parse DAR. Remain: #[error("Incomplete parse DAR. Remain:\n{0}")] IncompleteParseDar(String), + + /// DAR syntax error. #[error("DAR syntax error.:\n{0}")] InvalidDarSyntax(String), + + /// This is not valid utf8. #[error("This is not valid utf8")] InvalidUtf8, + + /// Condition error. #[error(transparent)] ConditionError(#[from] crate::conditions::ConditionError), + + /// Parse error. #[error(transparent)] ParseError(#[from] crate::condition_parser::ParseError), + /// Convert json error. #[error(transparent)] JsonError(#[from] serde_json::Error), + + /// Parse integer error. #[error(transparent)] ParseIntError(#[from] core::num::ParseIntError), + /// Represents all other cases of `std::io::Error`. #[error(transparent)] IOError(#[from] std::io::Error), + /// Thread join error. #[error(transparent)] JoinError(#[from] tokio::task::JoinError), @@ -47,8 +81,8 @@ pub enum ConvertError { impl PartialEq for ConvertError { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::FailedWriteSectionConfig(l0), Self::FailedWriteSectionConfig(r0)) => l0 == r0, - (Self::InvalidDarSyntax(l0), Self::InvalidDarSyntax(r0)) => l0 == r0, + (Self::FailedWriteSectionConfig(l0), Self::FailedWriteSectionConfig(r0)) + | (Self::InvalidDarSyntax(l0), Self::InvalidDarSyntax(r0)) => l0 == r0, (Self::ConditionError(l0), Self::ConditionError(r0)) => l0 == r0, (Self::ParseError(l0), Self::ParseError(r0)) => l0 == r0, (Self::JsonError(l0), Self::JsonError(r0)) => l0.to_string() == r0.to_string(), @@ -59,4 +93,22 @@ impl PartialEq for ConvertError { } } +/// A specialized [Result] type for the conversion process. +/// +/// It is a shorthand for [`core::result::Result`] where the error type is defaulted +/// to [`ConvertError`]. This allows functions and methods in the conversion process +/// to conveniently use this type without explicitly specifying the error type. +/// +/// # Examples +/// +/// ``` +/// use dar2oar_core::error::Result; +/// +/// fn convert_something() -> Result<()> { +/// // Some conversion logic here +/// // ... +/// +/// Ok(()) +/// } +/// ``` pub type Result = core::result::Result; diff --git a/dar2oar_core/src/fs/converter/common.rs b/dar2oar_core/src/fs/converter/common.rs index a2a6ec0..41c6822 100644 --- a/dar2oar_core/src/fs/converter/common.rs +++ b/dar2oar_core/src/fs/converter/common.rs @@ -1,3 +1,4 @@ +//! Common parts for sequential and parallel conversions use crate::condition_parser::parse_dar2oar; use crate::conditions::ConditionsConfig; use crate::error::{ConvertError, Result}; @@ -11,7 +12,7 @@ use tokio::fs; // NOTE: Variables in fields do not appear in the log if Option::None. #[tracing::instrument(level = "debug", skip(options, is_converted_once), fields(specified_output = &options.oar_dir))] /// Common parts of parallel & sequential loop processing. -pub async fn convert_inner

( +pub(super) async fn convert_inner

( options: &ConvertOptions, path: P, parsed_path: &ParsedPath, @@ -73,11 +74,11 @@ where tracing::debug!("Copy with Nest Dir: {:?}", remain.join(file_name)); let non_leaf_dir = $section_root.join(remain); // e.g. mesh/[...]/male/ fs::create_dir_all(&non_leaf_dir).await?; - fs::copy(path, &non_leaf_dir.join(file_name)).await?; + let _ = fs::copy(path, &non_leaf_dir.join(file_name)).await?; } else { tracing::debug!("Copy: {file_name}"); fs::create_dir_all(&$section_root).await?; - fs::copy(path, $section_root.join(file_name)).await?; + let _ = fs::copy(path, $section_root.join(file_name)).await?; } }; } @@ -122,7 +123,7 @@ where write_name_space_config(&oar_name_space, &parsed_mod_name, author.as_deref()) .await?; } else { - copy_other_file!(section_root) + copy_other_file!(section_root); }; } Err(invalid_priority) => { @@ -137,16 +138,17 @@ where true => oar_name_space, false => section_root, }; - copy_other_file!(section_root) + copy_other_file!(section_root); } }; if *hide_dar && is_contain_oar(path).is_none() { - hide_path(path).await? + hide_path(path).await?; }; Ok(()) } +/// Asynchronously hide a path by renaming it with a ".mohidden" extension. async fn hide_path(path: impl AsRef) -> Result<()> { let path = path.as_ref(); // NOTE: Do not use `set_extension` as it overwrites rather than adds. @@ -164,10 +166,19 @@ async fn hide_path(path: impl AsRef) -> Result<()> { Ok(()) } +/// Handle conversion results based on whether any conversions occurred. #[inline] -pub(super) fn handle_conversion_results(is_converted_once: bool) -> Result<()> { +pub(super) const fn handle_conversion_results(is_converted_once: bool) -> Result<()> { match is_converted_once { true => Ok(()), false => Err(ConvertError::NeverConverted), } } + +/// Find `DynamicAnimationReplacer` string in a argument +#[inline] +pub(super) fn is_contain_dar(path: impl AsRef) -> Option { + path.as_ref() + .iter() + .position(|os_str| os_str == std::ffi::OsStr::new("DynamicAnimationReplacer")) +} diff --git a/dar2oar_core/src/fs/converter/mod.rs b/dar2oar_core/src/fs/converter/mod.rs index c01a272..c5f3ed5 100644 --- a/dar2oar_core/src/fs/converter/mod.rs +++ b/dar2oar_core/src/fs/converter/mod.rs @@ -1,3 +1,4 @@ +//! Converter system mod common; pub mod parallel; @@ -8,27 +9,10 @@ use crate::error::Result; use compact_str::CompactString; use std::collections::HashMap; -/// # Convert DAR to OAR +/// Converts Dynamic Animation Replacer (DAR) files to Overwrite Animation Replacer (OAR) files. /// -/// Asynchronously converts Dynamic Animation Replacer (DAR) files to Overwrite Animation Replacer (OAR) files. -/// -/// ## Parameters -/// -/// - `options`: A structure (`ConvertOptions`) containing various configuration options for the conversion process. -/// - `dar_dir`: Path to the DAR source directory. -/// - `oar_dir`: Optional path to the OAR destination directory. If not provided, it is inferred from the source directory. -/// - `mod_name`: Optional module name in `config.json` and directory name. If not provided, it is inferred from the source directory. -/// - `author`: Optional mod author in `config.json`. -/// - `section_table`: Optional path to the section name table. -/// - `section_1person_table`: Optional path to the section name table for the first person. -/// - `run_parallel`: A boolean flag indicating whether to use multi-threading for the conversion process. -/// - `hide_dar`: A boolean flag indicating whether to add `mohidden` to the DAR directory before conversion, treating it as a hidden directory (for MO2 users). -/// -/// - `progress_fn`: A closure that takes a `usize` parameter, representing the progress of the conversion. -/// -/// ## Returns -/// -/// - `Result`: A result indicating the success or failure of the conversion process, along with a `ConvertedReport` enum providing details on the completed actions. +/// # Errors +/// Failed conversion /// /// ## Examples /// @@ -91,11 +75,16 @@ pub async fn convert_dar_to_oar( } } +/// A structure for creating dummy functions to facilitate refactoring. +#[derive(Debug)] pub struct Closure; impl Closure { - pub fn default(_: usize) {} + /// No operation function pointer + #[inline] + pub const fn default(_: usize) {} } +/// The options for converting a DAR directory to an OAR directory. #[derive(Debug, Clone, Default)] pub struct ConvertOptions { /// DAR source dir path @@ -118,9 +107,8 @@ pub struct ConvertOptions { #[cfg(test)] mod test { - use crate::test_helper::init_tracing; - use super::*; + use crate::test_helper::init_tracing; use anyhow::Result; // const DAR_DIR: &str = "../test/data/UNDERDOG - Animations"; @@ -157,18 +145,23 @@ mod test { let sender = move |idx: usize| { let tx = tx.clone(); - tokio::spawn(async move { - tx.send(idx).await.unwrap(); + let handle = tokio::spawn(async move { + match tx.send(idx).await { + Ok(ok) => ok, + Err(err) => tracing::error!("{}", err), + }; }); + + drop(handle); }; let handle = tokio::spawn(convert_dar_to_oar(create_options().await?, sender)); - let mut walk_len = 0usize; + let mut walk_len = 0; while let Some(idx) = rx.recv().await { match walk_len == 0 { true => walk_len = idx, // NOTE: 1st received index is length. - false => println!("[recv] Converted: {}/{}", idx + 1, walk_len), + false => tracing::info!("[recv] Converted: {}/{}", idx + 1, walk_len), } } diff --git a/dar2oar_core/src/fs/converter/parallel.rs b/dar2oar_core/src/fs/converter/parallel.rs index 356a31d..322d8a3 100644 --- a/dar2oar_core/src/fs/converter/parallel.rs +++ b/dar2oar_core/src/fs/converter/parallel.rs @@ -1,3 +1,5 @@ +//! Multi thread converter +use super::common::is_contain_dar; use crate::error::{ConvertError, Result}; use crate::fs::converter::common::{convert_inner, handle_conversion_results}; use crate::fs::converter::ConvertOptions; @@ -13,8 +15,8 @@ use std::sync::Arc; /// - `options`: Convert options /// - `progress_fn`: For progress callback(1st time: max contents count, 2nd~: index) /// -/// # Return -/// Complete info +/// # Errors +/// Failed to convert /// /// # NOTE /// For library reasons, you get the number of DAR dirs and files, not the number of DAR files only @@ -35,7 +37,7 @@ pub async fn convert_dar_to_oar( let mut task_handles: Vec>> = Vec::new(); for entry in entires { - let path = entry.map_err(|_| ConvertError::NotFoundEntry)?.path(); + let path = entry.map_err(|_err| ConvertError::NotFoundEntry)?.path(); if !path.is_file() { continue; } @@ -74,10 +76,10 @@ pub async fn convert_dar_to_oar( handle_conversion_results(is_converted_once.load(Ordering::Relaxed)) } +/// Get DAR files using a custom filter. pub(crate) fn get_dar_files(root: impl AsRef) -> WalkDirGeneric<(usize, bool)> { WalkDirGeneric::<(usize, bool)>::new(root).process_read_dir( |_depth, _path, _read_dir_state, children| { - // Custom filter children.retain(|dir_entry_result| { dir_entry_result .as_ref() @@ -93,6 +95,7 @@ pub(crate) fn get_dar_files(root: impl AsRef) -> WalkDirGeneric<(usize, bo ) } +/// Check if a path contains the directory `OpenAnimationReplacer`. #[inline] pub(super) fn is_contain_oar(path: impl AsRef) -> Option { path.as_ref() @@ -100,14 +103,8 @@ pub(super) fn is_contain_oar(path: impl AsRef) -> Option { .position(|os_str| os_str == std::ffi::OsStr::new("OpenAnimationReplacer")) } +/// Get OAR files using a custom filter. pub(crate) fn get_oar(root: impl AsRef) -> WalkDirGeneric<(usize, bool)> { - #[inline] - fn is_contain_dar(path: impl AsRef) -> Option { - path.as_ref() - .iter() - .position(|os_str| os_str == std::ffi::OsStr::new("DynamicAnimationReplacer")) - } - WalkDirGeneric::<(usize, bool)>::new(root).process_read_dir( |_depth, _path, _read_dir_state, children| { // Custom filter diff --git a/dar2oar_core/src/fs/converter/sequential.rs b/dar2oar_core/src/fs/converter/sequential.rs index aa9dd00..2eb525c 100644 --- a/dar2oar_core/src/fs/converter/sequential.rs +++ b/dar2oar_core/src/fs/converter/sequential.rs @@ -1,5 +1,6 @@ +//! Single thread converter use crate::error::Result; -use crate::fs::converter::common::{convert_inner, handle_conversion_results}; +use crate::fs::converter::common::{convert_inner, handle_conversion_results, is_contain_dar}; use crate::fs::converter::ConvertOptions; use crate::fs::path_changer::parse_dar_path; use async_walkdir::{Filtering, WalkDir}; @@ -13,8 +14,8 @@ use tokio_stream::StreamExt; /// - `options`: Convert options /// - `progress_fn`: For progress callback(1st time: max contents count, 2nd~: index) /// -/// # Return -/// Complete info +/// # Errors +/// Failed to convert pub async fn convert_dar_to_oar( options: ConvertOptions, mut progress_fn: impl FnMut(usize), @@ -27,7 +28,7 @@ pub async fn convert_dar_to_oar( let is_converted_once = AtomicBool::new(false); let mut entries = get_dar_files(dar_dir).await; - let mut idx = 0usize; + let mut idx = 0; while let Some(entry) = entries.next().await { let path = entry?.path(); let path = path.as_path(); @@ -52,33 +53,19 @@ pub async fn convert_dar_to_oar( handle_conversion_results(is_converted_once.load(Ordering::Relaxed)) } +/// Get files in `DynamicAnimationReplacer` directly. async fn get_dar_files(root: impl AsRef) -> WalkDir { WalkDir::new(root).filter(move |entry| async move { - if let Ok(file_type) = entry.file_type().await { - match file_type.is_dir() { - true => Filtering::Ignore, - false => Filtering::Continue, - } - } else { - // NOTE: Non-existent, non-authoritative, and I/O errors will ignore. - // Reason - // - Because if there is no entry in a higher-level function, it will cause an error. - // - In async closure, Result and ? operators cannot be used. - Filtering::Ignore - } + (entry.file_type().await).map_or(Filtering::Ignore, |file_type| match file_type.is_dir() { + true => Filtering::Ignore, + false => Filtering::Continue, + }) }) } /// # NOTE /// I thought this would make performance very bad, but it only gets worse by a few tens of milliseconds. async fn get_dar_file_count(root: impl AsRef) -> Result { - #[inline] - pub fn is_contain_dar(path: impl AsRef) -> Option { - path.as_ref() - .iter() - .position(|os_str| os_str == std::ffi::OsStr::new("DynamicAnimationReplacer")) - } - let mut walk_len = 0; let mut entries = get_dar_files(root).await; while let Some(entry) = entries.next().await { diff --git a/dar2oar_core/src/fs/converter/support_cmd.rs b/dar2oar_core/src/fs/converter/support_cmd.rs index 40fcb81..93e550c 100644 --- a/dar2oar_core/src/fs/converter/support_cmd.rs +++ b/dar2oar_core/src/fs/converter/support_cmd.rs @@ -1,3 +1,4 @@ +//! Auxiliary commands for smooth use of the converter use crate::error::{ConvertError, Result}; use crate::fs::converter::parallel::{get_dar_files, get_oar, is_contain_oar}; use std::ffi::{OsStr, OsString}; @@ -8,6 +9,9 @@ use tokio::fs; /// A parallel search will find the `DynamicAnimationReplacer` directory in the path passed as the argument /// and remove only the `mohidden` extension names from the files in that directory. +/// +/// # Errors +/// - Failed to find the `DynamicAnimationReplacer` directory pub async fn unhide_dar( dar_dir: impl AsRef, mut progress_fn: impl FnMut(usize), @@ -21,7 +25,7 @@ pub async fn unhide_dar( let entires = get_dar_files(dar_dir).into_iter(); for (idx, entry) in entires.enumerate() { - let path = Arc::new(entry.map_err(|_| ConvertError::NotFoundEntry)?.path()); + let path = Arc::new(entry.map_err(|_err| ConvertError::NotFoundEntry)?.path()); if path.extension() != Some(OsStr::new("mohidden")) { continue; @@ -33,7 +37,7 @@ pub async fn unhide_dar( let path = Arc::clone(&path); async move { let mut no_hidden_path = path.as_path().to_owned(); - no_hidden_path.set_extension(""); // Remove .mohidden extension + let _ = no_hidden_path.set_extension(""); // Remove .mohidden extension tracing::debug!("Rename {idx}th:\n- From: {path:?}\n- To: {no_hidden_path:?}\n"); fs::rename(path.as_path(), no_hidden_path).await?; @@ -49,7 +53,7 @@ pub async fn unhide_dar( progress_fn(idx); } - for task_handle in task_handles.into_iter() { + for task_handle in task_handles { task_handle.await??; } @@ -60,6 +64,9 @@ pub async fn unhide_dar( } /// A parallel search will find and remove the `OpenAnimationReplacer` directory from the path passed as the argument. +/// +/// # Errors +/// - Failed to find the `OpenAnimationReplacer` directory pub async fn remove_oar( search_dir: impl AsRef, mut progress_fn: impl FnMut(usize), @@ -73,12 +80,14 @@ pub async fn remove_oar( let mut prev_dir = OsString::new(); for (idx, entry) in get_oar(search_dir).into_iter().enumerate() { - let path = Arc::new(entry.map_err(|_| ConvertError::NotFoundEntry)?.path()); + let path = Arc::new(entry.map_err(|_err| ConvertError::NotFoundEntry)?.path()); if path.is_dir() { - if let Some(idx) = is_contain_oar(path.as_ref()) { + if let Some(oar_start_idx) = is_contain_oar(path.as_ref()) { let paths: Vec<&OsStr> = path.iter().collect(); - if let Some(oar_dir) = paths.get(0..idx + 1).map(|path| path.join(OsStr::new("/"))) + if let Some(oar_dir) = paths + .get(0..oar_start_idx + 1) + .map(|str_paths| str_paths.join(OsStr::new("/"))) { if prev_dir == oar_dir { continue; @@ -110,7 +119,7 @@ pub async fn remove_oar( progress_fn(idx); } - for task_handle in task_handles.into_iter() { + for task_handle in task_handles { task_handle.await??; } @@ -143,7 +152,7 @@ mod test { .path() .join("TestMod/meshes/actors/character/animations/DynamicAnimationReplacer/100"); create_dir_all(test_dir.as_path()).await?; - File::create(test_dir.join("_condition.txt.mohidden")).await?; + let _ = File::create(test_dir.join("_condition.txt.mohidden")).await?; assert!(unhide_dar(temp_dir.path(), sender!()).await.is_ok()); Ok(()) diff --git a/dar2oar_core/src/fs/mapping_table.rs b/dar2oar_core/src/fs/mapping_table.rs index d1cdc1c..2d79716 100644 --- a/dar2oar_core/src/fs/mapping_table.rs +++ b/dar2oar_core/src/fs/mapping_table.rs @@ -1,8 +1,11 @@ +//! Module for handling mapping tables in the conversion process. +//! +//! This module provides functions to read and parse mapping tables from files asynchronously. use crate::error::{ConvertError, Result}; use compact_str::CompactString; use std::collections::HashMap; use std::path::Path; -use tokio::{fs::File, io::AsyncReadExt}; +use tokio::fs::read_to_string; /// Get mapping table from path pub async fn get_mapping_table( @@ -15,6 +18,9 @@ pub async fn get_mapping_table( } /// Try to read mapping table from path +/// +/// # Errors +/// Path is not exist. pub async fn read_mapping_table( table_path: impl AsRef, ) -> Result> { @@ -23,16 +29,18 @@ pub async fn read_mapping_table( return Err(ConvertError::NonExistPath(format!("{table_path:?}"))); }; - let mut file_contents = String::new(); - File::open(table_path) - .await? - .read_to_string(&mut file_contents) - .await?; - Ok(parse_mapping_table(&file_contents)) + let contents = read_to_string(table_path).await?; + Ok(parse_mapping_table(&contents)) } -/// The key can only be up to [f32]::MAX due to DAR specifications. -/// Therefore, [CompactString] is used to fit into 24 bytes. +/// Parse the mapping table from a string. +/// +/// This function takes a string representing the mapping table and parses it into a [`HashMap`]. +/// It handles sequential numbering of duplicate keys when no key is available. +/// +/// # Information +/// The key can only be up to [`f32`]`::MAX` due to DAR specifications. +/// Therefore, [`CompactString`] is used to fit into 24 bytes. fn parse_mapping_table(table: &str) -> HashMap { let mut map = HashMap::new(); @@ -44,13 +52,10 @@ fn parse_mapping_table(table: &str) -> HashMap { continue; }; - let mapping = match line.find(' ') { - Some(idx) => { - let (key, val) = line.split_at(idx); - (key, Some(val)) - } - None => (line, None), - }; + let mapping = line.find(' ').map_or((line, None), |sep_idx| { + let (key, val) = line.split_at(sep_idx); + (key, Some(val)) + }); let section_name = match mapping.1 { None => { idx += 1; @@ -63,7 +68,7 @@ fn parse_mapping_table(table: &str) -> HashMap { } }; - match mapping.0 { + let _ = match mapping.0 { "" | "\r\n" | "\n" => continue, // Skip blank lines. key => map.insert(key.into(), section_name), }; @@ -96,15 +101,16 @@ mod tests { let result = parse_mapping_table(input); - let mut expected = HashMap::new(); - expected.insert("8000000".into(), "Combat".into()); - expected.insert("8000001".into(), "Base".into()); - expected.insert("8000002".into(), "Base_1".into()); - expected.insert("8000005".into(), "Female".into()); - expected.insert("8001000".into(), "Unarmed".into()); - expected.insert("8001010".into(), "Sword".into()); - expected.insert("8001020".into(), "Sword+Shield".into()); - + let expected = [ + ("8000000".into(), "Combat".into()), + ("8000001".into(), "Base".into()), + ("8000002".into(), "Base_1".into()), + ("8000005".into(), "Female".into()), + ("8001000".into(), "Unarmed".into()), + ("8001010".into(), "Sword".into()), + ("8001020".into(), "Sword+Shield".into()), + ] + .into(); assert_eq!(result, expected); } } diff --git a/dar2oar_core/src/fs/mod.rs b/dar2oar_core/src/fs/mod.rs index 09b822c..8e772bd 100644 --- a/dar2oar_core/src/fs/mod.rs +++ b/dar2oar_core/src/fs/mod.rs @@ -1,3 +1,4 @@ +//! A group of modules that rely on asynchronous file I/O. mod path_changer; mod section_writer; diff --git a/dar2oar_core/src/fs/path_changer.rs b/dar2oar_core/src/fs/path_changer.rs index ce3b266..30d1262 100644 --- a/dar2oar_core/src/fs/path_changer.rs +++ b/dar2oar_core/src/fs/path_changer.rs @@ -1,20 +1,28 @@ +//! The path of the DAR is analyzed to obtain the information necessary for the conversion. +//! +//! # Format +//! - Common: "\/\<`ModName`\>/meshes/actors/character/\[_1stperson\]/animations/" +//! +//! ### OAR:([]: optional, <>: variable) +//! - Common + "`OptionAnimationReplacer`/\<`NameSpace`\>/\<`EachSectionName`\>" +//! +//! ### DAR: (Only priority order assignment is taken into account. In other words, actor-based allocation is not considered.) +//! - Common + "`DynamicAnimationReplacer`/`_CustomConditions`/\/_conditions.txt" + use crate::error::{ConvertError, Result}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; -/// # Format -/// ### OAR:([]: optional, <>: variable) -/// - "\/\/meshes/actors/character/\[_1stperson\]/animations/OptionAnimationReplacer/\/\" -/// -/// ### DAR: (Only priority order assignment is taken into account. In other words, actor-based allocation is not considered.) -/// - "\/\/_1stperson/character/animations/DynamicAnimationReplacer/_CustomConditions/\/_conditions.txt" +/// The information necessary for the conversion #[derive(Debug, Clone)] pub struct ParsedPath { /// ModName/meshes/actors/character/animations/DynamicAnimationReplacer pub dar_root: PathBuf, /// ModName/meshes/actors/character/animations/OpenAnimationReplacer pub oar_root: PathBuf, + /// A path that contains a directory named `_1stperson`? pub is_1st_person: bool, + /// ModName/meshes/actors/character pub mod_name: Option, /// Number is the expected priority dir name of the formal DAR, /// but returns Err for the Mod creator who leaves a note. @@ -42,8 +50,8 @@ pub fn parse_dar_path(path: impl AsRef) -> Result { .iter() .position(|os_str| os_str == OsStr::new("DynamicAnimationReplacer")) .and_then(|idx| { - paths.get(0..idx).map(|path| { - let mut dar = Path::new(&path.join(OsStr::new("/"))).to_path_buf(); + paths.get(0..idx).map(|str_paths| { + let mut dar = Path::new(&str_paths.join(OsStr::new("/"))).to_path_buf(); let mut oar = dar.clone(); dar.push("DynamicAnimationReplacer"); oar.push("OpenAnimationReplacer"); @@ -58,23 +66,23 @@ pub fn parse_dar_path(path: impl AsRef) -> Result { .and_then(|idx| { paths .get(idx - 1) - .and_then(|path| path.to_str().map(|path| path.to_owned())) + .and_then(|mod_name| mod_name.to_str().map(str::to_owned)) }); let priority = path .iter() .position(|os_str| os_str == OsStr::new("_CustomConditions")) - .and_then(|idx| paths.get(idx + 1).and_then(|path| path.to_str())) + .and_then(|idx| paths.get(idx + 1).and_then(|priority| priority.to_str())) .ok_or(ConvertError::NotFoundPriorityDir)?; - let priority = priority.parse::().map_err(|_| priority.into()); + let priority = priority.parse::().map_err(|_err| priority.into()); let remain_dir = path .iter() .position(|os_str| os_str == OsStr::new("_CustomConditions")) .and_then(|idx| { - paths.get(idx + 2..paths.len() - 1).and_then(|path| { - let string = path.join(OsStr::new("/")); + paths.get(idx + 2..paths.len() - 1).and_then(|str_paths| { + let string = str_paths.join(OsStr::new("/")); match string.is_empty() { true => None, false => Some(PathBuf::from(string)), @@ -95,10 +103,11 @@ pub fn parse_dar_path(path: impl AsRef) -> Result { #[cfg(test)] mod test { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn test_parse_dar_path_1st_person() { + fn test_parse_dar_path_1st_person() -> Result<()> { let path = Path::new("../ModName/Meshes/actors/character/_1stperson/animations/DynamicAnimationReplacer/_CustomConditions/8107000/_conditions.txt"); let result = parse_dar_path(path); @@ -110,7 +119,7 @@ mod test { mod_name, priority, remain_dir, - } = result.unwrap(); + } = result?; assert_eq!( dar_root, @@ -128,10 +137,11 @@ mod test { assert_eq!(mod_name, Some("ModName".to_string())); assert_eq!(priority, Ok(8107000)); assert_eq!(remain_dir, None); + Ok(()) } #[test] - fn test_parse_dar_path_3rd_person() { + fn test_parse_dar_path_3rd_person() -> Result<()> { let path = Path::new("../ModName/meshes/actors/character/animations/DynamicAnimationReplacer/_CustomConditions/8107000/InnerDir/_conditions.txt"); let result = parse_dar_path(path); @@ -143,7 +153,7 @@ mod test { mod_name, priority, remain_dir, - } = result.unwrap(); + } = result?; assert_eq!( dar_root, @@ -157,6 +167,7 @@ mod test { assert_eq!(mod_name, Some("ModName".to_string())); assert_eq!(priority, Ok(8107000)); assert_eq!(remain_dir, Some(Path::new("InnerDir").to_path_buf())); + Ok(()) } #[test] diff --git a/dar2oar_core/src/fs/section_writer.rs b/dar2oar_core/src/fs/section_writer.rs index 3d0b673..4c3a563 100644 --- a/dar2oar_core/src/fs/section_writer.rs +++ b/dar2oar_core/src/fs/section_writer.rs @@ -1,8 +1,10 @@ +//! Functions for writing `config.json` use crate::conditions::{ConditionsConfig, MainConfig}; use crate::error::Result; use std::path::Path; use tokio::{fs, io::AsyncWriteExt}; +/// Write json to a file. async fn write_json_to(target_path: impl AsRef, value: &T) -> Result<()> where T: ?Sized + serde::Serialize, @@ -24,7 +26,7 @@ where /// Write root config.json /// /// If it exists, do nothing. (This behavior is intended to facilitate the creation of config files -/// for 1st_person and 3rd_person.) +/// for `1st_person` an`3rd_person`on.) pub(crate) async fn write_name_space_config

( oar_name_space_path: P, mod_name: &str, diff --git a/dar2oar_core/src/lib.rs b/dar2oar_core/src/lib.rs index d08a625..c9ebb26 100644 --- a/dar2oar_core/src/lib.rs +++ b/dar2oar_core/src/lib.rs @@ -1,30 +1,130 @@ -//! # dar2oar_core +#![deny( + clippy::all, + clippy::await_holding_lock, + clippy::char_lit_as_u8, + clippy::checked_conversions, + clippy::cognitive_complexity, + clippy::dbg_macro, + clippy::debug_assert_with_mut_call, + clippy::doc_link_with_quotes, + clippy::doc_markdown, + clippy::empty_enum, + clippy::empty_line_after_outer_attr, + clippy::empty_structs_with_brackets, + clippy::enum_glob_use, + clippy::exit, + clippy::expl_impl_clone_on_copy, + clippy::explicit_deref_methods, + clippy::explicit_into_iter_loop, + clippy::fallible_impl_from, + clippy::filter_map_next, + clippy::flat_map_option, + clippy::float_cmp, + clippy::float_cmp_const, + clippy::float_equality_without_abs, + clippy::fn_params_excessive_bools, + clippy::from_iter_instead_of_collect, + clippy::if_let_mutex, + clippy::implicit_clone, + clippy::imprecise_flops, + clippy::inefficient_to_string, + clippy::invalid_upcast_comparisons, + clippy::items_after_test_module, + clippy::large_digit_groups, + clippy::large_stack_arrays, + clippy::large_types_passed_by_value, + clippy::let_unit_value, + clippy::linkedlist, + clippy::lossy_float_literal, + clippy::macro_use_imports, + clippy::manual_ok_or, + clippy::map_err_ignore, + clippy::map_flatten, + clippy::map_unwrap_or, + clippy::match_on_vec_items, + clippy::match_same_arms, + clippy::match_wild_err_arm, + clippy::match_wildcard_for_single_variants, + clippy::mem_forget, + clippy::mismatched_target_os, + clippy::missing_const_for_fn, + clippy::missing_docs_in_private_items, + clippy::missing_enforced_import_renames, + clippy::missing_errors_doc, + clippy::missing_panics_doc, + clippy::mut_mut, + clippy::mutex_integer, + clippy::needless_borrow, + clippy::needless_continue, + clippy::needless_for_each, + clippy::option_if_let_else, + clippy::option_option, + clippy::path_buf_push_overwrite, + clippy::print_stderr, + clippy::print_stdout, + clippy::ptr_as_ptr, + clippy::rc_mutex, + clippy::ref_option_ref, + clippy::rest_pat_in_fully_bound_structs, + clippy::same_functions_in_if_condition, + clippy::semicolon_if_nothing_returned, + clippy::single_match_else, + clippy::string_add, + clippy::string_add_assign, + clippy::string_lit_as_bytes, + clippy::string_to_string, + clippy::suspicious_operation_groupings, + clippy::todo, + clippy::trait_duplication_in_bounds, + clippy::unimplemented, + clippy::unnested_or_patterns, + clippy::unseparated_literal_suffix, + clippy::unused_self, + clippy::unwrap_used, + clippy::used_underscore_binding, + clippy::useless_let_if_seq, + clippy::useless_transmute, + clippy::verbose_file_reads, + clippy::wildcard_dependencies, + clippy::wildcard_imports, + clippy::zero_sized_map_values +)] +// See: https://rust-unofficial.github.io/patterns/anti_patterns/deny-warnings.html#alternatives +#![deny( + bad_style, + dead_code, + future_incompatible, + improper_ctypes, + keyword_idents, + missing_debug_implementations, + missing_docs, + no_mangle_generic_items, + non_ascii_idents, + non_shorthand_field_patterns, + nonstandard_style, + noop_method_call, + overflowing_literals, + path_statements, + patterns_in_fns_without_body, + renamed_and_removed_lints, + trivial_casts, + trivial_numeric_casts, + unconditional_recursion, + unsafe_code, + unused, + unused_allocation, + unused_comparisons, + unused_crate_dependencies, + unused_extern_crates, + unused_import_braces, + unused_parens, + unused_results, + while_true +)] +//! # `dar2oar_core` //! //! `dar2oar_core` is a Rust crate that provides functionality for converting Dynamic Animation Replacer (DAR) files to Overwrite Animation Replacer (OAR) files. The crate includes modules for parsing conditions, handling DAR syntax, managing values, and dealing with file systems. //! -//! ## Modules -//! -//! - [condition_parser](condition_parser): Module to convert a parsed DAR into a serializable OAR structure. -//! - [conditions](conditions): Module for representing conditions used in DAR files. -//! - [dar_syntax](dar_syntax): Module for handling DAR syntax and conversions. -//! - [values](values): Module for managing values used in OAR files. -//! -//! ## Submodules -//! -//! - [error](error): Submodule for defining error types related to DAR to OAR conversion. -//! - [fs](fs): Submodule containing file system-related functionalities, including the main conversion function. -//! -//! ## Public Functions and Types -//! -//! - `convert_dar_to_oar`: The main function for converting DAR files to OAR files. It accepts configuration options and a progress callback. -//! - `Closure`: A struct that provides a default closure for progress reporting. -//! - `ConvertOptions`: A struct containing various configuration options for the conversion process. -//! - `ConvertedReport`: An enum representing different outcomes of the conversion process. -//! - `remove_oar`: Function for removing OAR files from a directory. -//! - `unhide_dar`: Function to unhide DAR files after conversion. -//! - `get_mapping_table`: Function for obtaining a mapping table. -//! - `read_mapping_table`: Function for reading a mapping table from a specified path. -//! //! ## Examples //! //! ### Async with non Progress report. @@ -32,22 +132,48 @@ //! ```no_run //! use anyhow::Result; //! use dar2oar_core::{convert_dar_to_oar, ConvertOptions, get_mapping_table}; +//! use tracing::{level_filters::LevelFilter, subscriber::DefaultGuard}; +//! use tracing_appender::non_blocking::WorkerGuard; //! //! const DAR_DIR: &str = "../test/data/UNDERDOG Animations"; //! const TABLE_PATH: &str = "../test/settings/UnderDog Animations_v1.9.6_mapping_table.txt"; //! const LOG_PATH: &str = "../convert.log"; //! -//! /// Initialization macro for setting up logging. -//! macro_rules! logger_init { -//! () => { -//! let (non_blocking, _guard) = -//! tracing_appender::non_blocking(std::fs::File::create(LOG_PATH).unwrap()); -//! tracing_subscriber::fmt() -//! .with_writer(non_blocking) -//! .with_ansi(false) -//! .with_max_level(tracing::Level::DEBUG) -//! .init(); -//! }; +//! /// Multithread init logger. +//! /// +//! /// File I/O is No ANSI color, output to stdout has ANSI color. +//! /// +//! /// # Returns +//! /// Guards +//! /// - If this variable is dropped, the logger stops. +//! pub(crate) fn init_tracing( +//! test_name: &str, +//! filter: impl Into, +//! ) -> Result<(WorkerGuard, DefaultGuard)> { +//! use tracing_subscriber::{fmt, layer::SubscriberExt}; +//! std::fs::create_dir_all("../logs")?; +//! let (file_writer, guard) = +//! tracing_appender::non_blocking(std::fs::File::create(format!("../logs/{test_name}.log"))?); +//! let thread_guard = tracing::subscriber::set_default( +//! fmt::Subscriber::builder() +//! .compact() +//! .pretty() +//! .with_file(true) +//! .with_line_number(true) +//! .with_max_level(filter) +//! .with_target(false) +//! .finish() +//! .with( +//! fmt::Layer::default() +//! .compact() +//! .with_ansi(false) +//! .with_file(true) +//! .with_line_number(true) +//! .with_target(false) +//! .with_writer(file_writer), +//! ), +//! ); +//! Ok((guard, thread_guard)) //! } //! //! /// Asynchronous function to create conversion options. @@ -61,7 +187,7 @@ //! //! #[tokio::main] //! async fn main() -> Result<()> { -//! logger_init!(); +//! let _guard = init_tracing("unhide_dar", tracing::Level::DEBUG)?; //! convert_dar_to_oar(create_options().await?, |_| {}).await?; //! Ok(()) //! } @@ -128,18 +254,19 @@ //! Ok(()) //! } //! ``` -//! mod condition_parser; mod conditions; mod dar_syntax; mod values; -#[cfg(test)] -mod test_helper; - pub mod error; pub mod fs; pub use crate::fs::converter::support_cmd::{remove_oar, unhide_dar}; pub use crate::fs::converter::{convert_dar_to_oar, Closure, ConvertOptions}; pub use crate::fs::mapping_table::{get_mapping_table, read_mapping_table}; + +#[cfg(test)] +mod test_helper; +#[cfg(test)] +extern crate criterion as _; // Needed for cargo bench. diff --git a/dar2oar_core/src/test_helper.rs b/dar2oar_core/src/test_helper.rs index e0d62a3..dc5f657 100644 --- a/dar2oar_core/src/test_helper.rs +++ b/dar2oar_core/src/test_helper.rs @@ -2,7 +2,9 @@ use anyhow::Result; use tracing::{level_filters::LevelFilter, subscriber::DefaultGuard}; use tracing_appender::non_blocking::WorkerGuard; -/// multithread init logger. +/// Multithread init logger. +/// +/// File I/O is No ANSI color, output to stdout has ANSI color. /// /// # Returns /// Guards diff --git a/dar2oar_core/src/values/actor_value.rs b/dar2oar_core/src/values/actor_value.rs index 6f4d8a2..f1364fd 100644 --- a/dar2oar_core/src/values/actor_value.rs +++ b/dar2oar_core/src/values/actor_value.rs @@ -1,6 +1,8 @@ +//! Person and its internal value use super::numeric_literal::NumericLiteral; use serde::{Deserialize, Serialize}; +/// Person and its internal value #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct ActorValue { /// default: 0 @@ -22,13 +24,17 @@ pub struct ActorValue { /// - Max Actor Value => "Max" /// - Actor Value Percentage (0-1) => "Percentage" /// -/// default: ActorValue +/// default: `ActorValue` #[derive(Debug, Clone, Default, PartialEq)] pub enum ActorValueType { + /// Value #[default] ActorValue, + /// Base Base, + /// Max value Max, + /// % Percentage, } @@ -82,6 +88,7 @@ impl<'de> Deserialize<'de> for ActorValueType { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] @@ -106,17 +113,19 @@ mod tests { } #[test] - fn test_serialize() { + fn test_serialize() -> Result<()> { let value = ActorValueType::Max; - let result = serde_json::to_string(&value).unwrap(); + let result = serde_json::to_string(&value)?; assert_eq!(result, "\"Max\""); + Ok(()) } #[test] - fn test_deserialize_valid() { + fn test_deserialize_valid() -> Result<()> { let json = "\"Percentage\""; - let result: ActorValueType = serde_json::from_str(json).unwrap(); + let result: ActorValueType = serde_json::from_str(json)?; assert_eq!(result, ActorValueType::Percentage); + Ok(()) } #[test] diff --git a/dar2oar_core/src/values/comparison.rs b/dar2oar_core/src/values/comparison.rs index d83cfe4..8997614 100644 --- a/dar2oar_core/src/values/comparison.rs +++ b/dar2oar_core/src/values/comparison.rs @@ -1,3 +1,4 @@ +//! Comparison use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; diff --git a/dar2oar_core/src/values/direction_value.rs b/dar2oar_core/src/values/direction_value.rs index 53b7393..3dd4f5d 100644 --- a/dar2oar_core/src/values/direction_value.rs +++ b/dar2oar_core/src/values/direction_value.rs @@ -1,12 +1,16 @@ +//! Actor's Direction use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; +/// Actor's Direction #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct DirectionValue { + /// Actor's Direction value pub(crate) value: Direction, } +/// Actor's Direction #[derive(Debug, Clone, Default, PartialEq)] pub enum Direction { /// 0.0 @@ -67,6 +71,7 @@ impl<'de> Deserialize<'de> for Direction { where D: Deserializer<'de>, { + /// Inner struct for deserialization struct DirectionVisitor; impl<'de> Visitor<'de> for DirectionVisitor { @@ -80,13 +85,15 @@ impl<'de> Deserialize<'de> for Direction { where E: Error, { - match Direction::try_from(value) { - Ok(value) => Ok(value), - Err(_) => Err(Error::unknown_variant( - &value.to_string(), - &["0", "1", "2", "3", "4"], - )), - } + Direction::try_from(value).map_or_else( + |_err| { + Err(Error::unknown_variant( + &value.to_string(), + &["0", "1", "2", "3", "4"], + )) + }, + |value| Ok(value), + ) } } @@ -94,18 +101,19 @@ impl<'de> Deserialize<'de> for Direction { let value: Value = Deserialize::deserialize(deserializer)?; match value { Value::Number(num) => { - let direction = - Direction::try_from(num.as_u64().unwrap_or(num.as_f64().unwrap() as u64)) - .map_err(|err| { - Error::invalid_type( - serde::de::Unexpected::Other(err), - &"a valid i64 or f64", - ) - })?; + let direction = num + .as_u64() + .unwrap_or(num.as_f64().ok_or(Error::invalid_type( + serde::de::Unexpected::Other("WeaponType parse f64"), + &"a valid f64", + ))? as u64); + let direction = Direction::try_from(direction).map_err(|err| { + Error::invalid_type(serde::de::Unexpected::Other(err), &"a valid u64 or f64") + })?; Ok(direction) } Value::String(s) => { - let t = s.parse::().map_err(|_| { + let t = s.parse::().map_err(|_err| { Error::invalid_type( serde::de::Unexpected::Other("Couldn't parse float value"), &"a valid Direction value", @@ -124,10 +132,11 @@ impl<'de> Deserialize<'de> for Direction { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_direction_value() { + fn should_serialize_direction_value() -> Result<()> { let direction_value = DirectionValue { value: Direction::Back, }; @@ -135,22 +144,24 @@ mod tests { let expected = r#"{ "value": 3.0 }"#; - let serialized = serde_json::to_string_pretty(&direction_value).unwrap(); + let serialized = serde_json::to_string_pretty(&direction_value)?; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_direction_value() { + fn should_deserialize_direction_value() -> Result<()> { let json_str = r#"{ "value": 1.0 }"#; - let deserialized: DirectionValue = serde_json::from_str(json_str).unwrap(); + let deserialized: DirectionValue = serde_json::from_str(json_str)?; let expected = DirectionValue { value: Direction::Forward, }; assert_eq!(deserialized, expected); + Ok(()) } #[test] @@ -165,21 +176,24 @@ mod tests { } #[test] - fn should_serialize_direction() { + fn should_serialize_direction() -> Result<()> { let direction = Direction::Right; let expected = "2.0"; - let serialized = serde_json::to_string(&direction).unwrap(); + let serialized = serde_json::to_string(&direction)?; + assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_direction() { + fn should_deserialize_direction() -> Result<()> { let json_str = "4.0"; - let deserialized: Direction = serde_json::from_str(json_str).unwrap(); + let deserialized: Direction = serde_json::from_str(json_str)?; let expected = Direction::Left; assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/values/form_value.rs b/dar2oar_core/src/values/form_value.rs index 873cabb..878fd46 100644 --- a/dar2oar_core/src/values/form_value.rs +++ b/dar2oar_core/src/values/form_value.rs @@ -1,9 +1,11 @@ +//! Wrapper for wrapping pluginValue with a key called `form` use super::PluginValue; use serde::{Deserialize, Serialize}; -/// Wrapper for wrapping pluginValue with a key called "form" +/// Wrapper for wrapping pluginValue with a key called `form` #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct FormValue { + /// A combination of the plugin name and the ID in it. pub form: PluginValue, } diff --git a/dar2oar_core/src/values/graph_value.rs b/dar2oar_core/src/values/graph_value.rs index 4680b92..27e086e 100644 --- a/dar2oar_core/src/values/graph_value.rs +++ b/dar2oar_core/src/values/graph_value.rs @@ -1,19 +1,28 @@ +//! Pair str & Int | Float | Bool use serde::{Deserialize, Deserializer, Serialize, Serializer}; +/// Pair str & Int | Float | Bool #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct GraphValue { + /// string + /// + /// TODO: Unknown variable #[serde(rename = "graphVariable")] pub graph_variable: String, + /// Float | Int | Bool #[serde(rename = "graphVariableType")] - pub graph_variable_type: GraphVariableType, // Use the enum type here + pub graph_variable_type: GraphVariableType, } /// Float | Int | Bool #[derive(Debug, Clone, Default, PartialEq)] pub enum GraphVariableType { + /// Floating point number #[default] Float, + /// Integer Int, + /// Boolean Bool, } @@ -49,17 +58,19 @@ impl<'de> Deserialize<'de> for GraphVariableType { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_current_weather() { + fn should_serialize_current_weather() -> Result<()> { let graph_value = GraphValue::default(); let expected = r#"{ "graphVariable": "", "graphVariableType": "Float" }"#; - let serialized = serde_json::to_string_pretty(&graph_value).unwrap(); + let serialized = serde_json::to_string_pretty(&graph_value)?; assert_eq!(serialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/values/keyword_value.rs b/dar2oar_core/src/values/keyword_value.rs index bfd5a9c..aae438c 100644 --- a/dar2oar_core/src/values/keyword_value.rs +++ b/dar2oar_core/src/values/keyword_value.rs @@ -1,13 +1,18 @@ +//! Trigger keywords use super::{FormValue, LiteralValue}; use serde::{Deserialize, Serialize}; use serde_json::Value; +/// Trigger keywords #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum Keyword { + /// Single numeric type Literal(LiteralValue), + /// plugin value Form(FormValue), } + impl Default for Keyword { fn default() -> Self { Self::Literal(LiteralValue::default()) @@ -52,36 +57,38 @@ impl<'de> Deserialize<'de> for Keyword { #[cfg(test)] mod tests { - use crate::values::PluginValue; - use super::*; + use crate::values::PluginValue; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_keyword_default() { + fn should_serialize_keyword_default() -> Result<()> { let keyword_enum = Keyword::default(); - let serialized = serde_json::to_string_pretty(&keyword_enum).unwrap(); + let serialized = serde_json::to_string_pretty(&keyword_enum)?; let expected = r#"{ "editorID": "" }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_keyword_enum_literal() { + fn should_deserialize_keyword_enum_literal() -> Result<()> { let input = r#"{ "editorID": "SomeKeyword" }"#; - let deserialized: Keyword = serde_json::from_str(input).unwrap(); + let deserialized: Keyword = serde_json::from_str(input)?; let expected = Keyword::Literal(LiteralValue { editor_id: "SomeKeyword".to_string(), }); assert_eq!(deserialized, expected); + Ok(()) } #[test] - fn should_serialize_keyword_enum_form() { + fn should_serialize_keyword_enum_form() -> Result<()> { let keyword_enum = Keyword::Form(FormValue::default()); let expected = r#"{ @@ -90,12 +97,13 @@ mod tests { "formID": "" } }"#; - let serialized = serde_json::to_string_pretty(&keyword_enum).unwrap(); + let serialized = serde_json::to_string_pretty(&keyword_enum)?; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_keyword_enum_form() { + fn should_deserialize_keyword_enum_form() -> Result<()> { let input = r#"{ "form": { "pluginName": "MyPlugin", @@ -103,7 +111,7 @@ mod tests { } }"#; - let deserialized: Keyword = serde_json::from_str(input).unwrap(); + let deserialized: Keyword = serde_json::from_str(input)?; let expected = Keyword::Form(FormValue { form: PluginValue { plugin_name: "MyPlugin".into(), @@ -112,5 +120,6 @@ mod tests { }); assert_eq!(deserialized, expected); + Ok(()) } } diff --git a/dar2oar_core/src/values/literal_value.rs b/dar2oar_core/src/values/literal_value.rs index aeaa339..da5f1f4 100644 --- a/dar2oar_core/src/values/literal_value.rs +++ b/dar2oar_core/src/values/literal_value.rs @@ -1,7 +1,10 @@ +//! String Literal use serde::{Deserialize, Serialize}; +/// Wrapper `editor_id` #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct LiteralValue { + /// Editor ID #[serde(rename = "editorID")] #[serde(default)] pub editor_id: String, diff --git a/dar2oar_core/src/values/mod.rs b/dar2oar_core/src/values/mod.rs index 606421e..3664751 100644 --- a/dar2oar_core/src/values/mod.rs +++ b/dar2oar_core/src/values/mod.rs @@ -1,3 +1,4 @@ +//! DAR Condition values mod actor_value; mod comparison; mod direction_value; @@ -29,22 +30,23 @@ pub use self::static_value::StaticValue; pub use self::type_value::{TypeValue, WeaponType}; use serde::{Deserialize, Serialize}; +/// DAR variable set #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub(crate) enum ValueSet { + /// Person and its internal value ActorValue(ActorValue), + /// Keyword ID KeywordValue(LiteralValue), + /// Just f32 value NumericValue(StaticValue), + /// Pair plugin name & ID PluginValue(PluginValue), + /// A value with a range, used for randomization. RandomValue(RandomValue), + /// Weapon type TypeValue(TypeValue), + /// Unknown value #[default] Unknown, } - -#[macro_export] -macro_rules! deserialize_json { - ($value:expr) => { - serde_json::from_value($value).map_err(|e| serde::de::Error::custom(e.to_string())) - }; -} diff --git a/dar2oar_core/src/values/numeric_literal.rs b/dar2oar_core/src/values/numeric_literal.rs index 311be31..2069691 100644 --- a/dar2oar_core/src/values/numeric_literal.rs +++ b/dar2oar_core/src/values/numeric_literal.rs @@ -1,3 +1,4 @@ +//! Numeric type tied to DAR use core::fmt; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; @@ -11,8 +12,11 @@ use serde_json::Value; /// default: 0.0 #[derive(Debug, Clone, PartialEq)] pub enum NumericLiteral { + /// e.g. 0x007 Hex(usize), + /// e.g. 1 Decimal(isize), + /// e.g. 1.0 Float(f32), } @@ -69,6 +73,7 @@ impl<'de> Deserialize<'de> for NumericLiteral { where D: Deserializer<'de>, { + /// Inner deserialization struct struct NumericLiteralVisitor; impl<'de> serde::de::Visitor<'de> for NumericLiteralVisitor { @@ -85,11 +90,10 @@ impl<'de> Deserialize<'de> for NumericLiteral { if value.starts_with("0x") { // Parse hexadecimal value let hex_value = value.trim_start_matches("0x"); - if let Ok(hex) = usize::from_str_radix(hex_value, 16) { - Ok(NumericLiteral::Hex(hex)) - } else { - Err(E::custom(format!("Invalid hexadecimal value: {}", value))) - } + usize::from_str_radix(hex_value, 16).map_or_else( + |_err| Err(E::custom(format!("Invalid hexadecimal value: {}", value))), + |hex| Ok(NumericLiteral::Hex(hex)), + ) } else if let Ok(decimal) = value.parse::() { Ok(NumericLiteral::Decimal(decimal)) } else if let Ok(float) = value.parse::() { @@ -115,48 +119,55 @@ impl<'de> Deserialize<'de> for NumericLiteral { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn test_serialize_non_prefix_hex() { + fn test_serialize_non_prefix_hex() -> Result<()> { let value = NumericLiteral::Hex(0x2a); - let serialized = serde_json::to_string(&value).unwrap(); + let serialized = serde_json::to_string(&value)?; assert_eq!(serialized, r#""2a""#); + Ok(()) } #[test] - fn test_serialize_decimal() { + fn test_serialize_decimal() -> Result<()> { let value = NumericLiteral::Decimal(123); - let serialized = serde_json::to_string(&value).unwrap(); + let serialized = serde_json::to_string(&value)?; assert_eq!(serialized, "123"); + Ok(()) } #[test] - fn test_serialize_float() { + fn test_serialize_float() -> Result<()> { let value = NumericLiteral::Float(3.12); - let serialized = serde_json::to_string(&value).unwrap(); + let serialized = serde_json::to_string(&value)?; assert_eq!(serialized, "3.12"); + Ok(()) } #[test] - fn test_deserialize_hex() { + fn test_deserialize_hex() -> Result<()> { let json = r#""0x2a""#; - let deserialized: NumericLiteral = serde_json::from_str(json).unwrap(); + let deserialized: NumericLiteral = serde_json::from_str(json)?; assert_eq!(deserialized, NumericLiteral::Hex(0x2a)); + Ok(()) } #[test] - fn test_deserialize_decimal() { + fn test_deserialize_decimal() -> Result<()> { let json = "123"; - let deserialized: NumericLiteral = serde_json::from_str(json).unwrap(); + let deserialized: NumericLiteral = serde_json::from_str(json)?; assert_eq!(deserialized, NumericLiteral::Decimal(123)); + Ok(()) } #[test] - fn test_deserialize_float() { + fn test_deserialize_float() -> Result<()> { let json = "3.12"; - let deserialized: NumericLiteral = serde_json::from_str(json).unwrap(); + let deserialized: NumericLiteral = serde_json::from_str(json)?; assert_eq!(deserialized, NumericLiteral::Float(3.12)); + Ok(()) } #[test] diff --git a/dar2oar_core/src/values/numeric_value.rs b/dar2oar_core/src/values/numeric_value.rs index ff238e4..821f0f3 100644 --- a/dar2oar_core/src/values/numeric_value.rs +++ b/dar2oar_core/src/values/numeric_value.rs @@ -1,16 +1,22 @@ +//! A set of f32 | `PluginValue` | Form | Pair str, number use super::{actor_value::ActorValue, static_value::StaticValue, FormValue, GraphValue}; -use crate::deserialize_json; use serde::{Deserialize, Serialize}; use serde_json::Value; +/// f32 | `PluginValue` | Form | Pair str, number +/// /// In fact, it can be variously accepted rather than Numeric, /// but the GUI description of OAR says Numeric Value, so we follow it. #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum NumericValue { + /// Just f32 value StaticValue(StaticValue), + /// Pair plugin name & ID GlobalVariable(FormValue), + /// Person and its internal value ActorValue(ActorValue), + /// Pair str & Int | Float | Bool GraphVariable(GraphValue), } @@ -25,6 +31,13 @@ impl<'de> Deserialize<'de> for NumericValue { where D: serde::Deserializer<'de>, { + /// Macro to change from [`serde_json`] error to [`serde`] custom error + macro_rules! deserialize_json { + ($value:expr) => { + serde_json::from_value($value).map_err(|e| serde::de::Error::custom(e)) + }; + } + let value: Value = Deserialize::deserialize(deserializer)?; if let Value::Object(map) = &value { @@ -56,24 +69,25 @@ impl<'de> Deserialize<'de> for NumericValue { #[cfg(test)] mod tests { - use crate::values::PluginValue; - use super::*; + use crate::values::PluginValue; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_numeric_value_static() { + fn should_serialize_numeric_value_static() -> Result<()> { let numeric_value = NumericValue::StaticValue(StaticValue::default()); - let serialized = serde_json::to_string_pretty(&numeric_value).unwrap(); + let serialized = serde_json::to_string_pretty(&numeric_value)?; let expected = r#"{ "value": 0.0 }"#; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_serialize_numeric_value_global_variable() { + fn should_serialize_numeric_value_global_variable() -> Result<()> { let numeric_value = NumericValue::GlobalVariable(FormValue::default()); let expected = r#"{ @@ -82,24 +96,26 @@ mod tests { "formID": "" } }"#; - let serialized = serde_json::to_string_pretty(&numeric_value).unwrap(); + let serialized = serde_json::to_string_pretty(&numeric_value)?; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_numeric_value_static() { + fn should_deserialize_numeric_value_static() -> Result<()> { let json_str = r#"{ "value": 42.0 }"#; - let deserialized: NumericValue = serde_json::from_str(json_str).unwrap(); + let deserialized: NumericValue = serde_json::from_str(json_str)?; let expected = NumericValue::StaticValue(StaticValue { value: 42.0 }); assert_eq!(deserialized, expected); + Ok(()) } #[test] - fn should_deserialize_numeric_value_global_variable() { + fn should_deserialize_numeric_value_global_variable() -> Result<()> { let json_str = r#"{ "form": { "pluginName": "MyPlugin", @@ -107,7 +123,7 @@ mod tests { } }"#; - let deserialized: NumericValue = serde_json::from_str(json_str).unwrap(); + let deserialized: NumericValue = serde_json::from_str(json_str)?; let expected = NumericValue::GlobalVariable( PluginValue { plugin_name: "MyPlugin".into(), @@ -117,6 +133,7 @@ mod tests { ); assert_eq!(deserialized, expected); + Ok(()) } #[test] diff --git a/dar2oar_core/src/values/plugin_value.rs b/dar2oar_core/src/values/plugin_value.rs index 60172f9..3ea6f08 100644 --- a/dar2oar_core/src/values/plugin_value.rs +++ b/dar2oar_core/src/values/plugin_value.rs @@ -1,9 +1,12 @@ +//! A combination of the plugin name and the ID in it. use super::NumericLiteral; use compact_str::CompactString; use serde::{Deserialize, Serialize}; +/// A combination of the plugin name and the ID in it. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct PluginValue { + /// e.g. `Skyrim.esm` #[serde(rename = "pluginName")] #[serde(default)] pub plugin_name: String, @@ -38,6 +41,7 @@ impl From<&str> for FormID { } } +/// Macro for type conversion of [`NumericLiteral`] and its internal numeric value to [`FormID`]. macro_rules! from { ($($_type:ident),+ $(,)?) => { $( @@ -71,10 +75,11 @@ impl From for FormID { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_plugin_value() { + fn should_serialize_plugin_value() -> Result<()> { let plugin_value = PluginValue { plugin_name: "MyPlugin".into(), form_id: NumericLiteral::Decimal(12345).into(), @@ -85,24 +90,26 @@ mod tests { "pluginName": "MyPlugin", "formID": "3039" }"#; - let serialized = serde_json::to_string_pretty(&plugin_value).unwrap(); + let serialized = serde_json::to_string_pretty(&plugin_value)?; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_plugin_value() { + fn should_deserialize_plugin_value() -> Result<()> { let json_str = r#"{ "pluginName": "AnotherPlugin", "formID": "fff" }"#; - let deserialized: PluginValue = serde_json::from_str(json_str).unwrap(); + let deserialized: PluginValue = serde_json::from_str(json_str)?; let expected = PluginValue { plugin_name: "AnotherPlugin".into(), form_id: "fff".into(), }; assert_eq!(deserialized, expected); + Ok(()) } #[test] diff --git a/dar2oar_core/src/values/random_value.rs b/dar2oar_core/src/values/random_value.rs index 1ca0613..8f67628 100644 --- a/dar2oar_core/src/values/random_value.rs +++ b/dar2oar_core/src/values/random_value.rs @@ -1,9 +1,15 @@ +//! A value with a range, used for randomization. use serde::{Deserialize, Serialize}; +/// A value with a range, used for randomization. +/// +/// This struct has `min` and `max` fields to define the range of the random value. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RandomValue { + /// The minimum value of the range. #[serde(default)] pub min: f32, + /// The maximum value of the range, with a default value of 1.0 (100%). #[serde(default = "max_percent")] pub max: f32, } @@ -17,6 +23,9 @@ impl Default for RandomValue { } } +/// Returns the maximum percentage value (1.0) used as the default for `RandomValue::max`. +/// +/// This function is used as the default value for the `max` field in `RandomValue`. const fn max_percent() -> f32 { 1.0 } diff --git a/dar2oar_core/src/values/static_value.rs b/dar2oar_core/src/values/static_value.rs index 5857d82..c96aea0 100644 --- a/dar2oar_core/src/values/static_value.rs +++ b/dar2oar_core/src/values/static_value.rs @@ -1,8 +1,13 @@ +//! A static value within a certain range. use serde::{Deserialize, Serialize}; -/// -99999996802856924650656260769173209088.000 <= value <= 9.999999680285692e37 +/// A static value within a certain range. +/// # NOTE +/// The value is expected to be within the range -99999996802856924650656260769173209088.000 +/// to 9.999999680285692e37. +/// +/// If the value is out of this range (i.e., -inf or inf), it may cause issues with `config.json` serialization. /// -/// when out of range(i.e. -inf or inf), break config.json. Example is here. /// ```json:config.json /// { /// "condition": "CompareValues", @@ -12,6 +17,7 @@ use serde::{Deserialize, Serialize}; /// ``` #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct StaticValue { + /// The value of the static value. pub value: f32, } diff --git a/dar2oar_core/src/values/type_value.rs b/dar2oar_core/src/values/type_value.rs index eccefd1..289a47d 100644 --- a/dar2oar_core/src/values/type_value.rs +++ b/dar2oar_core/src/values/type_value.rs @@ -1,14 +1,18 @@ +//! Wrapper for [`WeaponType`] use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use super::NumericLiteral; +/// Wrapper for [`WeaponType`] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct TypeValue { + /// Weapon type value pub value: WeaponType, } +/// Weapon type enumeration #[derive(Debug, Clone, Default, PartialEq)] pub enum WeaponType { /// -1.0 @@ -159,20 +163,13 @@ impl From for f64 { } } -impl WeaponType { - #[inline] - fn as_f64(&self) -> f64 { - self.clone().into() - } -} - impl Serialize for WeaponType { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // Serialize the variant as a floating-point number. - let value = self.as_f64(); + let value: f64 = self.clone().into(); value.serialize(serializer) } } @@ -184,6 +181,7 @@ impl<'de> Deserialize<'de> for WeaponType { where D: Deserializer<'de>, { + /// Inner struct to deserialization. struct WeaponTypeVisitor; impl<'de> Visitor<'de> for WeaponTypeVisitor { @@ -197,17 +195,14 @@ impl<'de> Deserialize<'de> for WeaponType { where E: Error, { - match WeaponType::try_from(value as i64) { - Ok(value) => Ok(value), - Err(_) => Err(Error::unknown_variant( - &value.to_string(), - &[ - "-1.0", "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", - "9.0", "10.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", - "18.0", - ], - )), - } + WeaponType::try_from(value as i64).or(Err(Error::unknown_variant( + &value.to_string(), + &[ + "-1.0", "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", + "9.0", "10.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", + "18.0", + ], + ))) } } @@ -215,25 +210,24 @@ impl<'de> Deserialize<'de> for WeaponType { let value: Value = Deserialize::deserialize(deserializer)?; match value { Value::Number(num) => { - let t = WeaponType::try_from(num.as_i64().unwrap_or(num.as_f64().unwrap() as i64)) - .map_err(|err| { - Error::invalid_type( - serde::de::Unexpected::Other(err), - &"a valid i64 or f64", - ) - })?; - Ok(t) - } - Value::String(s) => { - let value: WeaponType = s.as_str().try_into().map_err(|_| { - println!("Yes"); + let weapon = WeaponType::try_from(num.as_i64().unwrap_or(num.as_f64().ok_or( Error::invalid_type( - serde::de::Unexpected::Other("Couldn't parse float value"), - &"a valid WeaponType float string", - ) + serde::de::Unexpected::Other("WeaponType parse f64"), + &"a valid f64", + ), + )? + as i64)) + .map_err(|err| { + Error::invalid_type(serde::de::Unexpected::Other(err), &"a valid i64 or f64") })?; - Ok(value) + Ok(weapon) } + Value::String(s) => Ok(s.as_str().try_into().map_err(|_err| { + Error::invalid_type( + serde::de::Unexpected::Other("Couldn't parse float value"), + &"a valid WeaponType float string", + ) + })?), _ => Err(Error::invalid_type( serde::de::Unexpected::Other("not a valid value for WeaponType"), &"a valid WeaponType value", @@ -245,10 +239,11 @@ impl<'de> Deserialize<'de> for WeaponType { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use pretty_assertions::assert_eq; #[test] - fn should_serialize_type_value() { + fn should_serialize_type_value() -> Result<()> { let type_value = TypeValue { value: WeaponType::Other, }; @@ -256,35 +251,38 @@ mod tests { let expected = r#"{ "value": -1.0 }"#; - let serialized = serde_json::to_string_pretty(&type_value).unwrap(); + let serialized = serde_json::to_string_pretty(&type_value)?; assert_eq!(serialized, expected); + Ok(()) } #[test] - fn should_deserialize_type_value() { + fn should_deserialize_type_value() -> Result<()> { let json_str = r#"{ "value": 18.0 }"#; - let deserialized: TypeValue = serde_json::from_str(json_str).unwrap(); + let deserialized: TypeValue = serde_json::from_str(json_str)?; let expected = TypeValue { value: WeaponType::Torch, }; assert_eq!(deserialized, expected); + Ok(()) } #[test] - fn should_deserialize_type_value_as_string() { + fn should_deserialize_type_value_as_string() -> Result<()> { let json_str = r#"{ "value": "5.0" }"#; - let deserialized: TypeValue = serde_json::from_str(json_str).unwrap(); + let deserialized: TypeValue = serde_json::from_str(json_str)?; let expected = TypeValue { value: WeaponType::Greatsword, }; assert_eq!(deserialized, expected); + Ok(()) } }