diff --git a/include/loot/metadata/plugin_metadata.h b/include/loot/metadata/plugin_metadata.h index 6f7f18ff..c39e6a16 100644 --- a/include/loot/metadata/plugin_metadata.h +++ b/include/loot/metadata/plugin_metadata.h @@ -60,26 +60,19 @@ public: */ LOOT_API PluginMetadata(const std::string& name); - // Merges from the given plugin into this one, unless there is already equal - // metadata present. For 'enabled' and 'priority' metadata, use the given - // plugin's values, but if the 'priority' user value is zero, ignore it. /** * Merge metadata from the given PluginMetadata object into this object. * * If an equal metadata object already exists in this PluginMetadata object, * it is not duplicated. This object's priorities are replaced if the given * PluginMetadata object's priorities are explicit. This object's enabled - * state is replaced by the given object's state. + * state is replaced by the given object's state. This object's group is + * replaced by the given object's group if the latter is explicit. * @param plugin * The plugin metadata to merge. */ LOOT_API void MergeMetadata(const PluginMetadata& plugin); - // Returns metadata in this plugin not in the given plugin. - // For 'enabled', use this plugin's value. - // For 'priority', use 0 if the two plugin priorities are equal, and make it - // not explicit. Otherwise use this plugin's value. - /** * Get metadata in this object that isn't present in the given PluginMetadata * object. @@ -87,8 +80,8 @@ public: * The PluginMetadata object to compare against. * @return A PluginMetadata object containing the metadata in this object that * is not in the given object. The returned object inherits this - * object's enabled state. The returned object also inherits this - * plugin's priorities, unless a priority is equal to the given + * object's enabled state and group. The returned object also inherits + * this plugin's priorities, unless a priority is equal to the given * object's priority, in which case the returned object is given * an implicit zero priority instead. */ @@ -112,6 +105,19 @@ public: */ LOOT_API bool IsEnabled() const; + /** + * Get the plugin's group. + * @return The name of the group this plugin belongs to. + */ + LOOT_API std::string GetGroup() const; + + /** + * Check if the plugin's group was set explicitly or if the default value was + * implied. + * @return True if the plugin's group was set explicitly, false otherwise. + */ + LOOT_API bool IsGroupExplicit() const; + /** * Get the plugin's local priority metadata. * @return The plugin's local priority metadata. @@ -188,6 +194,13 @@ public: */ LOOT_API void SetEnabled(const bool enabled); + /** + * Set the plugin's group. + * @param group + * The name of the group this plugin belongs to. + */ + LOOT_API void SetGroup(const std::string& group); + /** * Set the plugin's local priority. * @param priority @@ -260,8 +273,8 @@ public: /** * Check if no plugin metadata is set. - * @return True if the local and global priorities are implicit and the - * metadata containers are all empty, false otherwise. + * @return True if the group and local and global priorities are implicit and + * the metadata containers are all empty, false otherwise. */ LOOT_API bool HasNameOnly() const; @@ -305,6 +318,8 @@ public: private: std::string name_; bool enabled_; + std::string group_; + bool isGroupExplicit_; Priority localPriority_; Priority globalPriority_; std::set loadAfter_; diff --git a/src/api/metadata/condition_evaluator.cpp b/src/api/metadata/condition_evaluator.cpp index 32b582af..ab68fc2e 100644 --- a/src/api/metadata/condition_evaluator.cpp +++ b/src/api/metadata/condition_evaluator.cpp @@ -112,6 +112,10 @@ PluginMetadata ConditionEvaluator::evaluateAll( evaluatedMetadata.SetGlobalPriority(pluginMetadata.GetGlobalPriority()); evaluatedMetadata.SetLocations(pluginMetadata.GetLocations()); + if (pluginMetadata.IsGroupExplicit()) { + evaluatedMetadata.SetGroup(pluginMetadata.GetGroup()); + } + std::set fileSet; for (const auto& file : pluginMetadata.GetLoadAfterFiles()) { if (evaluate(file.GetCondition())) diff --git a/src/api/metadata/plugin_metadata.cpp b/src/api/metadata/plugin_metadata.cpp index 2242d8a9..280aef9c 100644 --- a/src/api/metadata/plugin_metadata.cpp +++ b/src/api/metadata/plugin_metadata.cpp @@ -40,11 +40,16 @@ using std::set; using std::vector; namespace loot { -PluginMetadata::PluginMetadata() : enabled_(true) {} +PluginMetadata::PluginMetadata() : + enabled_(true), + group_("default"), + isGroupExplicit_(false) {} PluginMetadata::PluginMetadata(const std::string& n) : name_(n), - enabled_(true) { + enabled_(true), + group_("default"), + isGroupExplicit_(false) { // If the name passed ends in '.ghost', that should be trimmed. if (boost::iends_with(name_, ".ghost")) name_ = name_.substr(0, name_.length() - 6); @@ -63,6 +68,11 @@ void PluginMetadata::MergeMetadata(const PluginMetadata& plugin) { // but if the 'priority' user value is not explicit, ignore it. enabled_ = plugin.IsEnabled(); + if (plugin.IsGroupExplicit()) { + group_ = plugin.GetGroup(); + isGroupExplicit_ = true; + } + if (plugin.localPriority_.IsExplicit()) { SetLocalPriority(plugin.localPriority_); } @@ -186,6 +196,12 @@ std::string PluginMetadata::GetLowercasedName() const { bool PluginMetadata::IsEnabled() const { return enabled_; } +std::string PluginMetadata::GetGroup() const { return group_; } + +bool PluginMetadata::IsGroupExplicit() const { + return isGroupExplicit_; +} + Priority PluginMetadata::GetLocalPriority() const { return localPriority_; } Priority PluginMetadata::GetGlobalPriority() const { return globalPriority_; } @@ -227,6 +243,11 @@ std::vector PluginMetadata::GetSimpleMessages( void PluginMetadata::SetEnabled(const bool e) { enabled_ = e; } +void PluginMetadata::SetGroup(const std::string& group) { + group_ = group; + isGroupExplicit_ = true; +} + void PluginMetadata::SetLocalPriority(const Priority& priority) { localPriority_ = priority; } @@ -267,7 +288,8 @@ void PluginMetadata::SetLocations(const std::set& locations) { } bool PluginMetadata::HasNameOnly() const { - return !localPriority_.IsExplicit() && !globalPriority_.IsExplicit() && + return !IsGroupExplicit() && !localPriority_.IsExplicit() && + !globalPriority_.IsExplicit() && loadAfter_.empty() && requirements_.empty() && incompatibilities_.empty() && messages_.empty() && tags_.empty() && dirtyInfo_.empty() && cleanInfo_.empty() && locations_.empty(); diff --git a/src/api/metadata/yaml/plugin_metadata.h b/src/api/metadata/yaml/plugin_metadata.h index 8848286e..821d650e 100644 --- a/src/api/metadata/yaml/plugin_metadata.h +++ b/src/api/metadata/yaml/plugin_metadata.h @@ -53,6 +53,9 @@ struct convert { if (!rhs.IsEnabled()) node["enabled"] = rhs.IsEnabled(); + if (rhs.IsGroupExplicit()) + node["group"] = rhs.GetGroup(); + if (rhs.GetLocalPriority().IsExplicit()) node["priority"] = rhs.GetLocalPriority().GetValue(); @@ -106,6 +109,9 @@ struct convert { if (node["enabled"]) rhs.SetEnabled(node["enabled"].as()); + if (node["group"]) + rhs.SetGroup(node["group"].as()); + // Read priority values as int to prevent values that are too large from // being converted to -128. if (node["priority"]) { @@ -161,6 +167,9 @@ inline Emitter& operator<<(Emitter& out, const loot::PluginMetadata& rhs) { if (!rhs.IsEnabled()) out << Key << "enabled" << Value << rhs.IsEnabled(); + if (rhs.IsGroupExplicit()) + out << Key << "group" << Value << YAML::SingleQuoted << rhs.GetGroup(); + if (rhs.GetLocalPriority().IsExplicit()) { out << Key << "priority" << Value << rhs.GetLocalPriority().GetValue(); } diff --git a/src/tests/api/internals/metadata/condition_evaluator_test.h b/src/tests/api/internals/metadata/condition_evaluator_test.h index f42c8571..01a81b98 100644 --- a/src/tests/api/internals/metadata/condition_evaluator_test.h +++ b/src/tests/api/internals/metadata/condition_evaluator_test.h @@ -105,6 +105,7 @@ TEST_P(ConditionEvaluatorTest, TEST_P(ConditionEvaluatorTest, evaluateAllShouldEvaluateAllMetadataConditions) { PluginMetadata plugin(blankEsm); + plugin.SetGroup("group1"); File file1(blankEsp); File file2(blankDifferentEsm, "", "file(\"" + missingEsp + "\")"); @@ -128,6 +129,8 @@ TEST_P(ConditionEvaluatorTest, evaluateAllShouldEvaluateAllMetadataConditions) { EXPECT_NO_THROW(plugin = evaluator_.evaluateAll(plugin)); std::set expectedFiles({file1}); + EXPECT_EQ("group1", plugin.GetGroup()); + EXPECT_TRUE(plugin.IsGroupExplicit()); EXPECT_EQ(expectedFiles, plugin.GetLoadAfterFiles()); EXPECT_EQ(expectedFiles, plugin.GetRequirements()); EXPECT_EQ(expectedFiles, plugin.GetIncompatibilities()); @@ -136,6 +139,14 @@ TEST_P(ConditionEvaluatorTest, evaluateAllShouldEvaluateAllMetadataConditions) { EXPECT_EQ(std::set({info1}), plugin.GetDirtyInfo()); EXPECT_EQ(std::set({info1}), plugin.GetCleanInfo()); } + +TEST_P(ConditionEvaluatorTest, evaluateAllShouldPreserveGroupExplicitness) { + PluginMetadata plugin(blankEsm); + + EXPECT_NO_THROW(plugin = evaluator_.evaluateAll(plugin)); + EXPECT_EQ("default", plugin.GetGroup()); + EXPECT_FALSE(plugin.IsGroupExplicit()); +} } } diff --git a/src/tests/api/internals/metadata/plugin_metadata_test.h b/src/tests/api/internals/metadata/plugin_metadata_test.h index f9ef07d7..c7413391 100644 --- a/src/tests/api/internals/metadata/plugin_metadata_test.h +++ b/src/tests/api/internals/metadata/plugin_metadata_test.h @@ -51,20 +51,22 @@ INSTANTIATE_TEST_CASE_P(, TEST_P( PluginMetadataTest, - defaultConstructorShouldLeaveNameEmptyAndEnableMetadataAndLeaveAllOtherFieldsAtTheirDefaults) { + defaultConstructorShouldLeaveNameEmptyAndEnableMetadataAndSetGroupToDefault) { PluginMetadata plugin; EXPECT_TRUE(plugin.GetName().empty()); EXPECT_TRUE(plugin.IsEnabled()); + EXPECT_EQ("default", plugin.GetGroup()); } TEST_P( PluginMetadataTest, - stringConstructorShouldSetNameToGivenStringAndEnableMetadataAndLeaveAllOtherFieldsAtTheirDefaults) { + stringConstructorShouldSetNameToGivenStringAndEnableMetadataAndSetGroupToDefault) { PluginMetadata plugin(blankEsm); EXPECT_EQ(blankEsm, plugin.GetName()); EXPECT_TRUE(plugin.IsEnabled()); + EXPECT_EQ("default", plugin.GetGroup()); } TEST_P(PluginMetadataTest, @@ -138,6 +140,27 @@ TEST_P(PluginMetadataTest, EXPECT_FALSE(plugin1.IsEnabled()); } +TEST_P(PluginMetadataTest, mergeMetadataShouldUseMergedGroupIfItIsExplicit) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin1.SetGroup("group1"); + plugin2.SetGroup("group2"); + plugin1.MergeMetadata(plugin2); + + EXPECT_EQ("group2", plugin1.GetGroup()); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldNotUseMergedGroupIfItIsImplicit) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin1.SetGroup("group1"); + plugin1.MergeMetadata(plugin2); + + EXPECT_EQ("group1", plugin1.GetGroup()); +} + TEST_P(PluginMetadataTest, mergeMetadataShouldUseMergedNonZeroLocalPriorityValue) { PluginMetadata plugin1; @@ -529,6 +552,14 @@ TEST_P(PluginMetadataTest, EXPECT_TRUE(plugin.HasNameOnly()); } +TEST_P(PluginMetadataTest, + hasNameOnlyShouldBeFalseIfTheGroupIsExplicit) { + PluginMetadata plugin; + plugin.SetGroup("group"); + + EXPECT_FALSE(plugin.HasNameOnly()); +} + TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfTheLocalPriorityIsExplicit) { PluginMetadata plugin(blankEsp); @@ -659,6 +690,35 @@ TEST_P(PluginMetadataTest, EXPECT_STREQ("", emitter.c_str()); } +TEST_P(PluginMetadataTest, + emittingAsYamlShouldOutputAPluginOmittingAnImplicitGroup) { + PluginMetadata plugin(blankEsm); + plugin.SetLoadAfterFiles({ File(blankEsm) }); + + YAML::Emitter emitter; + emitter << plugin; + + EXPECT_STREQ( + "name: 'Blank.esm'\n" + "after:\n" + " - 'Blank.esm'", + emitter.c_str()); +} + +TEST_P(PluginMetadataTest, + emittingAsYamlShouldOutputAPluginWithAnExplicitGroup) { + PluginMetadata plugin(blankEsm); + plugin.SetGroup("group1"); + + YAML::Emitter emitter; + emitter << plugin; + + EXPECT_STREQ( + "name: 'Blank.esm'\n" + "group: 'group1'", + emitter.c_str()); +} + TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithAnExplicitLocalPriorityCorrectly) { PluginMetadata plugin(blankEsm);