From 1d123c8b21c0aeb0e3da0df0d2025763157b1b31 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Tue, 30 Dec 2025 11:08:57 +0000 Subject: [PATCH] Add Database::user_general_messages() --- cpp/include/loot/database_interface.h | 13 +++ cpp/src/api/database.cpp | 10 ++ cpp/src/api/database.h | 3 + cpp/src/database.rs | 14 +++ cpp/src/lib.rs | 2 + .../api/interface/database_interface_test.h | 15 +++ nodejs/src/database.rs | 13 +++ python/src/database.rs | 12 ++ src/database/mod.rs | 105 +++++++++++++++--- 9 files changed, 170 insertions(+), 17 deletions(-) diff --git a/cpp/include/loot/database_interface.h b/cpp/include/loot/database_interface.h index 8ded7407..df97ff58 100644 --- a/cpp/include/loot/database_interface.h +++ b/cpp/include/loot/database_interface.h @@ -178,6 +178,19 @@ public: virtual std::vector GetGeneralMessages( bool evaluateConditions = false) const = 0; + /** + * @brief Get all general messages listed in the loaded userlist. + * @param evaluateConditions + * If true, any metadata conditions are evaluated before the metadata + * is returned, otherwise unevaluated metadata is returned. Evaluating + * general message conditions also clears the condition cache before + * evaluating conditions. + * @returns A vector of messages supplied in the userlist but not attached to + * any particular plugin. + */ + virtual std::vector GetUserGeneralMessages( + bool evaluateConditions = false) const = 0; + /** * @brief Gets the groups that are defined in the loaded metadata lists. * @param includeUserMetadata diff --git a/cpp/src/api/database.cpp b/cpp/src/api/database.cpp index 6ddf3b42..1114165e 100644 --- a/cpp/src/api/database.cpp +++ b/cpp/src/api/database.cpp @@ -95,6 +95,16 @@ std::vector Database::GetGeneralMessages( } } +std::vector Database::GetUserGeneralMessages( + bool evaluateConditions) const { + try { + return convert( + database_->user_general_messages(evaluateConditions)); + } catch (const ::rust::Error& e) { + std::rethrow_exception(mapError(e)); + } +} + std::vector Database::GetGroups(bool includeUserMetadata) const { try { return convert(database_->groups(includeUserMetadata)); diff --git a/cpp/src/api/database.h b/cpp/src/api/database.h index 6e90a923..2f06e7d3 100644 --- a/cpp/src/api/database.h +++ b/cpp/src/api/database.h @@ -38,6 +38,9 @@ public: std::vector GetGeneralMessages( bool evaluateConditions = false) const override; + std::vector GetUserGeneralMessages( + bool evaluateConditions = false) const override; + std::vector GetGroups(bool includeUserMetadata = true) const override; std::vector GetUserGroups() const override; void SetUserGroups(const std::vector& groups) override; diff --git a/cpp/src/database.rs b/cpp/src/database.rs index 61190071..bf2183bc 100644 --- a/cpp/src/database.rs +++ b/cpp/src/database.rs @@ -144,6 +144,20 @@ impl Database { .map_err(Into::into) } + pub fn user_general_messages( + &self, + evaluate_conditions: bool, + ) -> Result, VerboseError> { + Ok(self + .0 + .read() + .map_err(DatabaseLockPoisonError::from)? + .user_general_messages(to_eval_mode(evaluate_conditions))? + .into_iter() + .map(Into::into) + .collect()) + } + pub fn groups(&self, include_user_metadata: bool) -> Result, VerboseError> { Ok(self .0 diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index 87f30a07..d26ef2e7 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -276,6 +276,8 @@ mod ffi { pub fn general_messages(&self, evaluate_conditions: bool) -> Result>; + pub fn user_general_messages(&self, evaluate_conditions: bool) -> Result>; + pub fn groups(&self, include_user_metadata: bool) -> Result>; pub fn user_groups(&self) -> Result>; diff --git a/cpp/src/tests/api/interface/database_interface_test.h b/cpp/src/tests/api/interface/database_interface_test.h index 2de83be8..286c2b3c 100644 --- a/cpp/src/tests/api/interface/database_interface_test.h +++ b/cpp/src/tests/api/interface/database_interface_test.h @@ -566,6 +566,21 @@ TEST_P( EXPECT_TRUE(messages.empty()); } +TEST_P(DatabaseInterfaceTest, + getUserGeneralMessagesShouldGetGeneralMessagesFromTheUserlistOnly) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_NO_THROW(GenerateUserlist()); + ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath)); + ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_)); + + auto messages = handle_->GetDatabase().GetUserGeneralMessages(); + + std::vector expectedMessages({ + Message(MessageType::say, generalUserlistMessage), + }); + EXPECT_EQ(expectedMessages, messages); +} + TEST_P(DatabaseInterfaceTest, getPluginMetadataShouldReturnAnEmptyOptionalIfThePluginHasNoMetadata) { EXPECT_FALSE(handle_->GetDatabase().GetPluginMetadata(blankEsm)); diff --git a/nodejs/src/database.rs b/nodejs/src/database.rs index 90863fb5..8a71ae0a 100644 --- a/nodejs/src/database.rs +++ b/nodejs/src/database.rs @@ -183,6 +183,19 @@ impl Database { .map_err(Into::into) } + #[napi] + pub fn user_general_messages( + &self, + evaluate_conditions: EvalMode, + ) -> Result, VerboseError> { + self.0 + .read() + .map_err(DatabaseLockPoisonError::from)? + .user_general_messages(evaluate_conditions.into()) + .map(|v| v.into_iter().map(Into::into).collect()) + .map_err(Into::into) + } + #[napi] pub fn groups(&self, include_user_metadata: MergeMode) -> Result, VerboseError> { Ok(self diff --git a/python/src/database.rs b/python/src/database.rs index b1d18939..db83e6d0 100644 --- a/python/src/database.rs +++ b/python/src/database.rs @@ -148,6 +148,18 @@ impl Database { .map_err(Into::into) } + pub fn user_general_messages( + &self, + evaluate_conditions: bool, + ) -> Result, VerboseError> { + self.0 + .read() + .map_err(DatabaseLockPoisonError::from)? + .user_general_messages(to_eval_mode(evaluate_conditions)) + .map(|v| v.into_iter().map(Into::into).collect()) + .map_err(Into::into) + } + pub fn groups(&self, include_user_metadata: bool) -> Result, VerboseError> { Ok(self .0 diff --git a/src/database/mod.rs b/src/database/mod.rs index b5d65407..b0769c3c 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -194,23 +194,26 @@ impl Database { &self, evaluate_conditions: EvalMode, ) -> Result, ConditionEvaluationError> { - let messages_iter = self - .masterlist - .messages() - .iter() - .chain(self.userlist.messages()); + process_messages( + self.masterlist + .messages() + .iter() + .chain(self.userlist.messages()), + &self.condition_evaluator_state, + evaluate_conditions, + ) + } - if evaluate_conditions == EvalMode::Evaluate { - let messages = messages_iter - .filter_map(|m| { - filter_map_on_condition(m, m.condition(), &self.condition_evaluator_state) - }) - .collect::, _>>()?; - - Ok(messages) - } else { - Ok(messages_iter.cloned().collect()) - } + /// Get all general messages listed in the loaded userlist. + pub fn user_general_messages( + &self, + evaluate_conditions: EvalMode, + ) -> Result, ConditionEvaluationError> { + process_messages( + self.userlist.messages().iter(), + &self.condition_evaluator_state, + evaluate_conditions, + ) } /// Gets the groups that are defined in the loaded metadata lists. @@ -281,7 +284,7 @@ impl Database { } } - /// Get a plugin's metadata loaded from the given userlist. + /// Get a plugin's metadata loaded from the loaded userlist. pub fn plugin_user_metadata( &self, plugin_name: &str, @@ -379,6 +382,22 @@ fn merge_groups(lhs: &[Group], rhs: &[Group]) -> Vec { groups } +fn process_messages<'a, I: Iterator>( + messages_iter: I, + condition_evaluator_state: &loot_condition_interpreter::State, + evaluate_conditions: EvalMode, +) -> Result, ConditionEvaluationError> { + if evaluate_conditions == EvalMode::Evaluate { + let messages = messages_iter + .filter_map(|m| filter_map_on_condition(m, m.condition(), condition_evaluator_state)) + .collect::, _>>()?; + + Ok(messages) + } else { + Ok(messages_iter.cloned().collect()) + } +} + #[cfg(test)] mod tests { use std::path::PathBuf; @@ -1039,6 +1058,58 @@ plugins: } } + mod user_general_messages { + use super::*; + + #[test] + fn user_general_messages_should_return_only_general_messages_in_the_userlist() { + let fixture = Fixture::new(GameType::Oblivion); + let mut database = fixture.database(); + + database.load_masterlist(&fixture.metadata_path).unwrap(); + + let userlist_path = fixture.inner.local_path.join("userlist.yaml"); + std::fs::write( + &userlist_path, + "globals: [{type: say, content: 'A user message', condition: 'file(\"missing.esp\")'}]", + ) + .unwrap(); + + database.load_userlist(&userlist_path).unwrap(); + + assert_eq!( + &[Message::new(MessageType::Say, "A user message".into()) + .with_condition("file(\"missing.esp\")".into())], + database + .user_general_messages(EvalMode::DoNotEvaluate) + .unwrap() + .as_slice() + ); + } + + #[test] + fn should_filter_out_messages_with_false_conditions_when_evaluating_conditions() { + let fixture = Fixture::new(GameType::Oblivion); + let mut database = fixture.database(); + + let userlist_path = fixture.inner.local_path.join("userlist.yaml"); + std::fs::write( + &userlist_path, + "globals: [{type: say, content: 'A user message', condition: 'file(\"missing.esp\")'}]", + ) + .unwrap(); + + database.load_userlist(&userlist_path).unwrap(); + + assert!( + database + .user_general_messages(EvalMode::Evaluate) + .unwrap() + .is_empty() + ); + } + } + mod groups { use super::*;