From 01836966b4b3b1cd21d5b0bdfd55fd33ebe4789f Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 6 Aug 2016 16:58:21 +0100 Subject: [PATCH] Tweak cleaning data metadata structure Rename `util` to `utility`, and add an `info` key that acts like the message structure's `content` key. Also change the generated message structures slightly to reflect the changes. Closes #608. --- CMakeLists.txt | 2 +- docs/LOOT Metadata Syntax.html | 3 +- resources/l10n/template.pot | 175 ++++++++---------- src/backend/metadata/message.cpp | 15 +- src/backend/metadata/message_content.cpp | 17 ++ src/backend/metadata/message_content.h | 3 + src/backend/metadata/plugin_cleaning_data.cpp | 70 +++++-- src/backend/metadata/plugin_cleaning_data.h | 39 +++- src/backend/plugin/plugin.cpp | 8 +- src/backend/plugin/plugin.h | 5 +- src/gui/query_handler.cpp | 4 +- src/tests/api/api_game_operations_test.h | 2 +- .../api/loot_get_masterlist_revision_test.h | 8 +- src/tests/api/loot_update_masterlist_test.h | 24 +-- src/tests/api/loot_write_minimal_list_test.h | 2 +- src/tests/backend/masterlist_test.h | 2 +- .../metadata/plugin_cleaning_data_test.h | 124 +++++++++---- .../backend/metadata/plugin_metadata_test.h | 52 +++--- src/tests/backend/plugin/plugin_test.h | 26 +-- 19 files changed, 349 insertions(+), 232 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f71a54a..be29afa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ set(PSEUDOSEM_INCLUDE_DIRS "${SOURCE_DIR}/include") ExternalProject_Add(testing-metadata PREFIX "external" - URL "https://github.com/loot/testing-metadata/archive/1.1.0.tar.gz" + URL "https://github.com/loot/testing-metadata/archive/2.0.0.tar.gz" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "") diff --git a/docs/LOOT Metadata Syntax.html b/docs/LOOT Metadata Syntax.html index 82fbfefe..5ed180b1 100644 --- a/docs/LOOT Metadata Syntax.html +++ b/docs/LOOT Metadata Syntax.html @@ -433,7 +433,8 @@ name: 'Unofficial Skyrim Patch on Steam Workshop' Key NameValue TypeRequiredNotes crchexadecimal integer✓The CRC-32 checksum of the plugin. If the plugin is dirty, this needs to be the CRC of the plugin before before cleaning. LOOT displays the CRCs of installed plugins in its report. The 8-character CRC should be preceded by 0x so that it is correctly interpreted. - utilstring✓The utility that should be or that was used to clean the plugin. + utilitystring✓The utility that was used to check the plugin for dirty edits. If available, the version of the utility used should also be included (e.g. TES5Edit v3.11). + infostring or localised content list✗A message that will be displayed to the user. If a localised content list is provided, one of the structures must be for English. This is only used if the plugin is dirty, and is intended for providing cleaning instructions to the user. itminteger✗The number of identical-to-master records reported for the dirty plugin. If the number is unknown or zero, this field should not be supplied. udrinteger✗The number of undeleted records reported for the dirty plugin. If the number is unknown or zero, this field should not be supplied. navinteger✗The number of deleted navmeshes reported for the dirty plugin. If the number is unknown or zero, this field should not be supplied. diff --git a/resources/l10n/template.pot b/resources/l10n/template.pot index 499d2b74..ee0f57b0 100644 --- a/resources/l10n/template.pot +++ b/resources/l10n/template.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: LOOT 0.9.2\n" "Report-Msgid-Bugs-To: https://github.com/loot/loot/issues\n" -"POT-Creation-Date: 2016-08-06 16:32+0100\n" +"POT-Creation-Date: 2016-08-06 16:58+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -131,132 +131,127 @@ msgstr "" #: src/gui/html/js/translateStaticText.js:33 #: src/gui/html/js/translateStaticText.js:88 -msgid "Global Priority" +msgid "Priority" msgstr "" #: src/gui/html/js/translateStaticText.js:34 -msgid "" -"Global priorities are compared against all other plugins. Normal priorities " -"are compared against only conflicting plugins." +#: src/gui/html/js/translateStaticText.js:87 +msgid "Global Priority" msgstr "" -#: src/gui/html/js/translateStaticText.js:35 -msgid "Priority Value" -msgstr "" - -#: src/gui/html/js/translateStaticText.js:37 +#: src/gui/html/js/translateStaticText.js:36 msgid "Main" msgstr "" -#: src/gui/html/js/translateStaticText.js:38 +#: src/gui/html/js/translateStaticText.js:37 msgid "Load After" msgstr "" -#: src/gui/html/js/translateStaticText.js:39 +#: src/gui/html/js/translateStaticText.js:38 msgid "Requirements" msgstr "" -#: src/gui/html/js/translateStaticText.js:40 +#: src/gui/html/js/translateStaticText.js:39 msgid "Incompatibilities" msgstr "" -#: src/gui/html/js/translateStaticText.js:41 +#: src/gui/html/js/translateStaticText.js:40 msgid "Messages" msgstr "" -#: src/gui/html/js/translateStaticText.js:42 +#: src/gui/html/js/translateStaticText.js:41 msgid "Bash Tags" msgstr "" -#: src/gui/html/js/translateStaticText.js:43 +#: src/gui/html/js/translateStaticText.js:42 msgid "Dirty Plugin Info" msgstr "" -#: src/gui/html/js/translateStaticText.js:44 +#: src/gui/html/js/translateStaticText.js:43 msgid "Clean Plugin Info" msgstr "" -#: src/gui/html/js/translateStaticText.js:45 +#: src/gui/html/js/translateStaticText.js:44 msgid "Locations" msgstr "" +#: src/gui/html/js/translateStaticText.js:46 +#: src/gui/html/js/translateStaticText.js:50 +#: src/gui/html/js/translateStaticText.js:54 +msgid "Filename" +msgstr "" + #: src/gui/html/js/translateStaticText.js:47 #: src/gui/html/js/translateStaticText.js:51 #: src/gui/html/js/translateStaticText.js:55 -msgid "Filename" +msgid "Display Name" msgstr "" #: src/gui/html/js/translateStaticText.js:48 #: src/gui/html/js/translateStaticText.js:52 #: src/gui/html/js/translateStaticText.js:56 -msgid "Display Name" -msgstr "" - -#: src/gui/html/js/translateStaticText.js:49 -#: src/gui/html/js/translateStaticText.js:53 -#: src/gui/html/js/translateStaticText.js:57 -#: src/gui/html/js/translateStaticText.js:61 -#: src/gui/html/js/translateStaticText.js:66 +#: src/gui/html/js/translateStaticText.js:60 +#: src/gui/html/js/translateStaticText.js:65 msgid "Condition" msgstr "" -#: src/gui/html/js/translateStaticText.js:59 +#: src/gui/html/js/translateStaticText.js:58 msgid "Type" msgstr "" -#: src/gui/html/js/translateStaticText.js:60 +#: src/gui/html/js/translateStaticText.js:59 msgid "Content" msgstr "" -#: src/gui/html/js/translateStaticText.js:62 +#: src/gui/html/js/translateStaticText.js:61 #: src/gui/html/js/translateStaticText.js:252 msgid "Language" msgstr "" -#: src/gui/html/js/translateStaticText.js:64 +#: src/gui/html/js/translateStaticText.js:63 msgid "Add/Remove" msgstr "" -#: src/gui/html/js/translateStaticText.js:65 +#: src/gui/html/js/translateStaticText.js:64 msgid "Bash Tag" msgstr "" -#: src/gui/html/js/translateStaticText.js:68 -#: src/gui/html/js/translateStaticText.js:74 +#: src/gui/html/js/translateStaticText.js:67 +#: src/gui/html/js/translateStaticText.js:73 msgid "CRC" msgstr "" -#: src/gui/html/js/translateStaticText.js:69 +#: src/gui/html/js/translateStaticText.js:68 msgid "ITM Count" msgstr "" -#: src/gui/html/js/translateStaticText.js:70 +#: src/gui/html/js/translateStaticText.js:69 msgid "Deleted References" msgstr "" -#: src/gui/html/js/translateStaticText.js:71 +#: src/gui/html/js/translateStaticText.js:70 msgid "Deleted Navmeshes" msgstr "" -#: src/gui/html/js/translateStaticText.js:72 -#: src/gui/html/js/translateStaticText.js:75 +#: src/gui/html/js/translateStaticText.js:71 +#: src/gui/html/js/translateStaticText.js:74 msgid "Cleaning Utility" msgstr "" -#: src/gui/html/js/translateStaticText.js:77 +#: src/gui/html/js/translateStaticText.js:76 msgid "URL" msgstr "" -#: src/gui/html/js/translateStaticText.js:78 +#: src/gui/html/js/translateStaticText.js:77 #: src/gui/html/js/translateStaticText.js:261 msgid "Name" msgstr "" -#: src/gui/html/js/translateStaticText.js:80 +#: src/gui/html/js/translateStaticText.js:79 msgid "Save Metadata" msgstr "" -#: src/gui/html/js/translateStaticText.js:81 +#: src/gui/html/js/translateStaticText.js:80 #: src/gui/html/js/translateStaticText.js:98 #: src/gui/html/js/translateStaticText.js:182 #: src/gui/html/js/translateStaticText.js:275 @@ -607,14 +602,14 @@ msgstr "" msgid "Failed to apply plugin metadata. Details: %1%" msgstr "" -#: src/gui/query_handler.cpp:598 +#: src/gui/query_handler.cpp:601 msgid "" "An error occurred while parsing the masterlist: %1%. This probably happened " "because an update to LOOT changed its metadata syntax support. Try updating " "your masterlist to resolve the error." msgstr "" -#: src/gui/query_handler.cpp:613 +#: src/gui/query_handler.cpp:616 msgid "" "An error occurred while parsing the userlist: %1%. This probably happened " "because an update to LOOT changed its metadata syntax support. Your user " @@ -630,33 +625,33 @@ msgid "" "[LOOT's website](https://loot.github.io/)." msgstr "" -#: src/gui/query_handler.cpp:723 src/gui/query_handler.cpp:726 +#: src/gui/query_handler.cpp:721 src/gui/query_handler.cpp:724 msgid "Failed to get game data. Details: %1%" msgstr "" -#: src/gui/query_handler.cpp:808 src/gui/query_handler.cpp:811 +#: src/gui/query_handler.cpp:806 src/gui/query_handler.cpp:809 msgid "Failed to update the masterlist. Details: %1%" msgstr "" -#: src/gui/query_handler.cpp:850 +#: src/gui/query_handler.cpp:848 msgid "Loading plugin contents..." msgstr "" -#: src/gui/query_handler.cpp:854 +#: src/gui/query_handler.cpp:852 msgid "Sorting load order..." msgstr "" -#: src/gui/query_handler.cpp:911 +#: src/gui/query_handler.cpp:909 msgid "Failed to sort plugins. Details: %1%" msgstr "" -#: src/gui/query_handler.cpp:941 +#: src/gui/query_handler.cpp:939 msgid "" "A global message contains a condition that could not be evaluated. Details: " "%1%" msgstr "" -#: src/gui/query_handler.cpp:963 src/backend/plugin/plugin_sorter.cpp:216 +#: src/gui/query_handler.cpp:961 src/backend/plugin/plugin_sorter.cpp:216 msgid "\"%1%\" contains a condition that could not be evaluated. Details: %2%" msgstr "" @@ -764,22 +759,16 @@ msgstr "" msgid "Failed to open file in its default application." msgstr "" -#: src/backend/metadata/condition_grammar.h:146 -#: src/backend/metadata/condition_grammar.h:281 -#: src/backend/metadata/condition_grammar.h:365 +#: src/backend/metadata/condition_evaluator.cpp:173 msgid "Invalid file path:" msgstr "" -#: src/backend/metadata/condition_grammar.h:187 -#: src/backend/metadata/condition_grammar.h:213 +#: src/backend/metadata/condition_evaluator.cpp:183 +#: src/backend/metadata/condition_evaluator.cpp:223 msgid "Invalid regex string \"%1%\": %2%" msgstr "" -#: src/backend/metadata/condition_grammar.h:205 -msgid "Invalid folder path:" -msgstr "" - -#: src/backend/metadata/condition_grammar.h:385 +#: src/backend/metadata/condition_grammar.h:202 msgid "Expected \"%1%\" at \"%2%\"." msgstr "" @@ -793,39 +782,37 @@ msgstr "" msgid "Failed to parse condition \"%1%\"." msgstr "" -#: src/backend/metadata/plugin_cleaning_data.cpp:71 -msgid "" -"Contains %1% ITM records, %2% deleted references and %3% deleted navmeshes. " -"Clean with %4%." -msgstr "" - -#: src/backend/metadata/plugin_cleaning_data.cpp:73 -msgid "Clean with %1%." -msgstr "" - -#: src/backend/metadata/plugin_cleaning_data.cpp:76 -msgid "" -"Contains %1% deleted references and %2% deleted navmeshes. Clean with %3%." -msgstr "" - -#: src/backend/metadata/plugin_cleaning_data.cpp:78 -msgid "Contains %1% deleted navmeshes. Clean with %2%." -msgstr "" - -#: src/backend/metadata/plugin_cleaning_data.cpp:80 -msgid "Contains %1% deleted references. Clean with %2%." -msgstr "" - -#: src/backend/metadata/plugin_cleaning_data.cpp:83 -msgid "Contains %1% ITM records and %2% deleted navmeshes. Clean with %3%." -msgstr "" - -#: src/backend/metadata/plugin_cleaning_data.cpp:85 -msgid "Contains %1% ITM records. Clean with %2%." -msgstr "" - #: src/backend/metadata/plugin_cleaning_data.cpp:88 -msgid "Contains %1% ITM records and %2% deleted references. Clean with %3%." +msgid "" +"%1% found %2% ITM records, %3% deleted references and %4% deleted navmeshes." +msgstr "" + +#: src/backend/metadata/plugin_cleaning_data.cpp:90 +msgid "%1% found dirty edits." +msgstr "" + +#: src/backend/metadata/plugin_cleaning_data.cpp:93 +msgid "%1% found %2% deleted references and %3% deleted navmeshes." +msgstr "" + +#: src/backend/metadata/plugin_cleaning_data.cpp:95 +msgid "%1% found %2% deleted navmeshes." +msgstr "" + +#: src/backend/metadata/plugin_cleaning_data.cpp:97 +msgid "%1% found %2% deleted references." +msgstr "" + +#: src/backend/metadata/plugin_cleaning_data.cpp:100 +msgid "%1% found %2% ITM records and %3% deleted navmeshes." +msgstr "" + +#: src/backend/metadata/plugin_cleaning_data.cpp:102 +msgid "%1% found %2% ITM records." +msgstr "" + +#: src/backend/metadata/plugin_cleaning_data.cpp:105 +msgid "%1% found %2% ITM records and %3% deleted references." msgstr "" #: src/backend/plugin/plugin.cpp:115 diff --git a/src/backend/metadata/message.cpp b/src/backend/metadata/message.cpp index 293d0d19..9694fbb5 100644 --- a/src/backend/metadata/message.cpp +++ b/src/backend/metadata/message.cpp @@ -73,20 +73,7 @@ bool Message::EvalCondition(loot::Game& game, const Language::Code language) { MessageContent Message::ChooseContent(const Language::Code language) const { BOOST_LOG_TRIVIAL(trace) << "Choosing message content."; - if (content_.empty()) - return MessageContent(); - else if (content_.size() == 1) - return content_[0]; - else { - MessageContent english; - for (const auto &mc : content_) { - if (mc.GetLanguage() == language) { - return mc; - } else if (mc.GetLanguage() == Language::Code::english) - english = mc; - } - return english; - } + return MessageContent::Choose(content_, language); } Message::Type Message::GetType() const { diff --git a/src/backend/metadata/message_content.cpp b/src/backend/metadata/message_content.cpp index 986d3381..de6c521d 100644 --- a/src/backend/metadata/message_content.cpp +++ b/src/backend/metadata/message_content.cpp @@ -48,6 +48,23 @@ bool MessageContent::operator < (const MessageContent& rhs) const { bool MessageContent::operator == (const MessageContent& rhs) const { return (boost::iequals(text_, rhs.GetText())); } +MessageContent MessageContent::Choose(const std::vector content, + const Language::Code language) { + if (content.empty()) + return MessageContent(); + else if (content.size() == 1) + return content[0]; + else { + MessageContent english; + for (const auto &mc : content) { + if (mc.GetLanguage() == language) { + return mc; + } else if (mc.GetLanguage() == Language::Code::english) + english = mc; + } + return english; + } +} } namespace YAML { diff --git a/src/backend/metadata/message_content.h b/src/backend/metadata/message_content.h index ce55ad5b..28f2d858 100644 --- a/src/backend/metadata/message_content.h +++ b/src/backend/metadata/message_content.h @@ -41,6 +41,9 @@ public: bool operator < (const MessageContent& rhs) const; bool operator == (const MessageContent& rhs) const; + + static MessageContent Choose(const std::vector content, + const Language::Code language); private: std::string text_; Language::Code language_; diff --git a/src/backend/metadata/plugin_cleaning_data.cpp b/src/backend/metadata/plugin_cleaning_data.cpp index f4fdc6dc..308399f2 100644 --- a/src/backend/metadata/plugin_cleaning_data.cpp +++ b/src/backend/metadata/plugin_cleaning_data.cpp @@ -26,6 +26,7 @@ #include #include +#include #include "backend/game/game.h" #include "backend/helpers/helpers.h" @@ -33,9 +34,16 @@ namespace loot { PluginCleaningData::PluginCleaningData() : crc_(0), itm_(0), ref_(0), nav_(0) {} -PluginCleaningData::PluginCleaningData(uint32_t crc, const std::string& utility) : crc_(crc), utility_(utility), itm_(0), ref_(0), nav_(0) {} +PluginCleaningData::PluginCleaningData(uint32_t crc, const std::string& utility) + : crc_(crc), utility_(utility), itm_(0), ref_(0), nav_(0) {} -PluginCleaningData::PluginCleaningData(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility) : crc_(crc), itm_(itm), ref_(ref), nav_(nav), utility_(utility) {} +PluginCleaningData::PluginCleaningData(uint32_t crc, + const std::string& utility, + const std::vector& info, + unsigned int itm, + unsigned int ref, + unsigned int nav) + : crc_(crc), itm_(itm), ref_(ref), nav_(nav), utility_(utility), info_(info) {} bool PluginCleaningData::operator < (const PluginCleaningData& rhs) const { return crc_ < rhs.CRC(); @@ -65,29 +73,44 @@ std::string PluginCleaningData::CleaningUtility() const { return utility_; } +std::vector PluginCleaningData::Info() const { + return info_; +} + +MessageContent PluginCleaningData::ChooseInfo(const Language::Code language) const { + BOOST_LOG_TRIVIAL(trace) << "Choosing dirty info content."; + return MessageContent::Choose(info_, language); +} + Message PluginCleaningData::AsMessage() const { boost::format f; - if (this->itm_ > 0 && this->ref_ > 0 && this->nav_ > 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records, %2% deleted references and %3% deleted navmeshes. Clean with %4%.")) % this->itm_ % this->ref_ % this->nav_ % this->utility_; - else if (this->itm_ == 0 && this->ref_ == 0 && this->nav_ == 0) - f = boost::format(boost::locale::translate("Clean with %1%.")) % this->utility_; + if (itm_ > 0 && ref_ > 0 && nav_ > 0) + f = boost::format(boost::locale::translate("%1% found %2% ITM records, %3% deleted references and %4% deleted navmeshes.")) % utility_ % itm_ % ref_ % nav_; + else if (itm_ == 0 && ref_ == 0 && nav_ == 0) + f = boost::format(boost::locale::translate("%1% found dirty edits.")) % utility_; - else if (this->itm_ == 0 && this->ref_ > 0 && this->nav_ > 0) - f = boost::format(boost::locale::translate("Contains %1% deleted references and %2% deleted navmeshes. Clean with %3%.")) % this->ref_ % this->nav_ % this->utility_; - else if (this->itm_ == 0 && this->ref_ == 0 && this->nav_ > 0) - f = boost::format(boost::locale::translate("Contains %1% deleted navmeshes. Clean with %2%.")) % this->nav_ % this->utility_; - else if (this->itm_ == 0 && this->ref_ > 0 && this->nav_ == 0) - f = boost::format(boost::locale::translate("Contains %1% deleted references. Clean with %2%.")) % this->ref_ % this->utility_; + else if (itm_ == 0 && ref_ > 0 && nav_ > 0) + f = boost::format(boost::locale::translate("%1% found %2% deleted references and %3% deleted navmeshes.")) % utility_ % ref_ % nav_; + else if (itm_ == 0 && ref_ == 0 && nav_ > 0) + f = boost::format(boost::locale::translate("%1% found %2% deleted navmeshes.")) % utility_ % nav_; + else if (itm_ == 0 && ref_ > 0 && nav_ == 0) + f = boost::format(boost::locale::translate("%1% found %2% deleted references.")) % utility_ % ref_; - else if (this->itm_ > 0 && this->ref_ == 0 && this->nav_ > 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted navmeshes. Clean with %3%.")) % this->itm_ % this->nav_ % this->utility_; - else if (this->itm_ > 0 && this->ref_ == 0 && this->nav_ == 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records. Clean with %2%.")) % this->itm_ % this->utility_; + else if (itm_ > 0 && ref_ == 0 && nav_ > 0) + f = boost::format(boost::locale::translate("%1% found %2% ITM records and %3% deleted navmeshes.")) % utility_ % itm_ % nav_; + else if (itm_ > 0 && ref_ == 0 && nav_ == 0) + f = boost::format(boost::locale::translate("%1% found %2% ITM records.")) % utility_ % itm_; - else if (this->itm_ > 0 && this->ref_ > 0 && this->nav_ == 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted references. Clean with %3%.")) % this->itm_ % this->ref_ % this->utility_; + else if (itm_ > 0 && ref_ > 0 && nav_ == 0) + f = boost::format(boost::locale::translate("%1% found %2% ITM records and %3% deleted references.")) % utility_ % itm_ % ref_; - return Message(Message::Type::warn, f.str()); + std::string message = f.str(); + auto info = info_; + for (auto& content : info) { + content = MessageContent(message + " " + content.GetText(), content.GetLanguage()); + } + + return Message(Message::Type::warn, info); } bool PluginCleaningData::EvalCondition(Game& game, const std::string& pluginName) const { @@ -119,7 +142,14 @@ namespace YAML { Emitter& operator << (Emitter& out, const loot::PluginCleaningData& rhs) { out << BeginMap << Key << "crc" << Value << Hex << rhs.CRC() << Dec - << Key << "util" << Value << YAML::SingleQuoted << rhs.CleaningUtility(); + << Key << "utility" << Value << YAML::SingleQuoted << rhs.CleaningUtility(); + + if (!rhs.Info().empty()) { + if (rhs.Info().size() == 1) + out << Key << "info" << Value << YAML::SingleQuoted << rhs.Info().front().GetText(); + else + out << Key << "info" << Value << rhs.Info(); + } if (rhs.ITMs() > 0) out << Key << "itm" << Value << rhs.ITMs(); diff --git a/src/backend/metadata/plugin_cleaning_data.h b/src/backend/metadata/plugin_cleaning_data.h index b5d979a1..65e84a0c 100644 --- a/src/backend/metadata/plugin_cleaning_data.h +++ b/src/backend/metadata/plugin_cleaning_data.h @@ -39,7 +39,12 @@ class PluginCleaningData { public: PluginCleaningData(); PluginCleaningData(uint32_t crc, const std::string& utility); - PluginCleaningData(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility); + PluginCleaningData(uint32_t crc, + const std::string& utility, + const std::vector& info, + unsigned int itm, + unsigned int ref, + unsigned int nav); bool operator < (const PluginCleaningData& rhs) const; bool operator == (const PluginCleaningData& rhs) const; @@ -49,7 +54,9 @@ public: unsigned int DeletedRefs() const; unsigned int DeletedNavmeshes() const; std::string CleaningUtility() const; + std::vector Info() const; + MessageContent ChooseInfo(const Language::Code language) const; Message AsMessage() const; bool EvalCondition(Game& game, const std::string& pluginName) const; @@ -59,6 +66,7 @@ private: unsigned int ref_; unsigned int nav_; std::string utility_; + std::vector info_; }; } @@ -68,7 +76,8 @@ struct convert { static Node encode(const loot::PluginCleaningData& rhs) { Node node; node["crc"] = rhs.CRC(); - node["util"] = rhs.CleaningUtility(); + node["utility"] = rhs.CleaningUtility(); + node["info"] = rhs.Info(); if (rhs.ITMs() > 0) node["itm"] = rhs.ITMs(); @@ -85,7 +94,7 @@ struct convert { throw RepresentationException(node.Mark(), "bad conversion: 'cleaning data' object must be a map"); if (!node["crc"]) throw RepresentationException(node.Mark(), "bad conversion: 'crc' key missing from 'cleaning data' object"); - if (!node["util"]) + if (!node["utility"]) throw RepresentationException(node.Mark(), "bad conversion: 'util' key missing from 'cleaning data' object"); uint32_t crc = node["crc"].as(); @@ -98,9 +107,29 @@ struct convert { if (node["nav"]) nav = node["nav"].as(); - std::string utility = node["util"].as(); + std::string utility = node["utility"].as(); - rhs = loot::PluginCleaningData(crc, itm, ref, nav, utility); + std::vector info; + if (node["info"]) { + if (node["info"].IsSequence()) + info = node["info"].as>(); + else { + info.push_back(loot::MessageContent(node["info"].as(), loot::Language::Code::english)); + } + } + + //Check now that at least one item in info is English if there are multiple items. + if (info.size() > 1) { + bool found = false; + for (const auto &mc : info) { + if (mc.GetLanguage() == loot::Language::Code::english) + found = true; + } + if (!found) + throw RepresentationException(node.Mark(), "bad conversion: multilingual messages must contain an English info string"); + } + + rhs = loot::PluginCleaningData(crc, utility, info, itm, ref, nav); return true; } diff --git a/src/backend/plugin/plugin.cpp b/src/backend/plugin/plugin.cpp index 7e414499..1530e745 100644 --- a/src/backend/plugin/plugin.cpp +++ b/src/backend/plugin/plugin.cpp @@ -171,11 +171,11 @@ bool Plugin::IsValid(const std::string& filename, const Game& game) { else name = filename; -// Check that the file has a valid extension. + // Check that the file has a valid extension. if (!boost::iends_with(name, ".esm") && !boost::iends_with(name, ".esp")) return false; -// Add the ".ghost" file extension if the plugin is ghosted. + // Add the ".ghost" file extension if the plugin is ghosted. boost::filesystem::path filepath = game.DataPath() / name; if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) filepath += ".ghost"; @@ -199,7 +199,7 @@ uint32_t Plugin::Crc() const { return crc_; } -bool Plugin::CheckInstallValidity(const Game& game) { +void Plugin::CheckInstallValidity(const Game& game) { BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to " << Name() << "'s data."; if (IsActive()) { auto pluginExists = [](const Game& game, const std::string& file) { @@ -236,8 +236,6 @@ bool Plugin::CheckInstallValidity(const Game& game) { for (const auto &element : DirtyInfo()) { messages_.push_back(element.AsMessage()); } - - return !DirtyInfo().empty(); } bool Plugin::LoadsArchive() const { diff --git a/src/backend/plugin/plugin.h b/src/backend/plugin/plugin.h index fb30d93b..c6400fd0 100644 --- a/src/backend/plugin/plugin.h +++ b/src/backend/plugin/plugin.h @@ -58,8 +58,9 @@ public: bool DoFormIDsOverlap(const Plugin& plugin) const; std::set OverlapFormIDs(const Plugin& plugin) const; - //Validity checks. - bool CheckInstallValidity(const Game& game); //Checks that reqs and masters are all present, and that no incs are present. Returns true if the plugin is dirty. + // Validity checks. + // Checks that reqs and masters are all present, and that no incs are present. + void CheckInstallValidity(const Game& game); static bool IsValid(const std::string& filename, const Game& game); bool operator < (const Plugin& rhs) const; diff --git a/src/gui/query_handler.cpp b/src/gui/query_handler.cpp index 29e56475..fed99b38 100644 --- a/src/gui/query_handler.cpp +++ b/src/gui/query_handler.cpp @@ -963,7 +963,7 @@ YAML::Node QueryHandler::GenerateDerivedMetadata(const Plugin& file, const Plugi } //Also check install validity. - bool isDirty = tempPlugin.CheckInstallValidity(lootState_.getCurrentGame()); + tempPlugin.CheckInstallValidity(lootState_.getCurrentGame()); // Now add to pluginNode. YAML::Node pluginNode; @@ -972,7 +972,7 @@ YAML::Node QueryHandler::GenerateDerivedMetadata(const Plugin& file, const Plugi pluginNode["globalPriority"] = tempPlugin.GlobalPriority().getValue(); pluginNode["messages"] = tempPlugin.Messages(); pluginNode["tags"] = tempPlugin.Tags(); - pluginNode["isDirty"] = isDirty; + pluginNode["isDirty"] = !tempPlugin.DirtyInfo().empty(); pluginNode["loadOrderIndex"] = lootState_.getCurrentGame().GetActiveLoadOrderIndex(tempPlugin.Name()); if (!tempPlugin.CleanInfo().empty()) { diff --git a/src/tests/api/api_game_operations_test.h b/src/tests/api/api_game_operations_test.h index d8857472..25c31bbb 100644 --- a/src/tests/api/api_game_operations_test.h +++ b/src/tests/api/api_game_operations_test.h @@ -84,7 +84,7 @@ protected: << " content: '" << warningMessage << "'" << endl << " dirty:" << endl << " - crc: 0x7d22f9df" << endl - << " util: TES4Edit" << endl + << " utility: TES4Edit" << endl << " udr: 4" << endl << " - name: " << blankDifferentEsp << endl << " after:" << endl diff --git a/src/tests/api/loot_get_masterlist_revision_test.h b/src/tests/api/loot_get_masterlist_revision_test.h index b411ab6b..d98c7a11 100644 --- a/src/tests/api/loot_get_masterlist_revision_test.h +++ b/src/tests/api/loot_get_masterlist_revision_test.h @@ -34,11 +34,13 @@ namespace test { class loot_get_masterlist_revision_test : public ApiGameOperationsTest { protected: loot_get_masterlist_revision_test() : + branch_("2.x"), revisionId_("foo"), revisionDate_("bar"), isModified_(true), updated_(false) {} + const std::string branch_; const char * revisionId_; const char * revisionDate_; bool isModified_; @@ -80,7 +82,7 @@ TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfANonVersionControlledMa } TEST_P(loot_get_masterlist_revision_test, shouldOutputLongStringsAndBooleanFalseIfAVersionControlledMasterlistIsPresentAndGetShortIdParameterIsFalse) { - ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); EXPECT_STRNE(NULL, revisionId_); @@ -91,7 +93,7 @@ TEST_P(loot_get_masterlist_revision_test, shouldOutputLongStringsAndBooleanFalse } TEST_P(loot_get_masterlist_revision_test, shouldOutputShortStringsAndBooleanFalseIfAVersionControlledMasterlistIsPresentAndGetShortIdParameterIsTrue) { - ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); EXPECT_STRNE(NULL, revisionId_); @@ -103,7 +105,7 @@ TEST_P(loot_get_masterlist_revision_test, shouldOutputShortStringsAndBooleanFals } TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfAnEditedVersionControlledMasterlistIsPresent) { - ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); ASSERT_NO_THROW(GenerateMasterlist()); EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); diff --git a/src/tests/api/loot_update_masterlist_test.h b/src/tests/api/loot_update_masterlist_test.h index 15660ae3..0f1b4824 100644 --- a/src/tests/api/loot_update_masterlist_test.h +++ b/src/tests/api/loot_update_masterlist_test.h @@ -34,6 +34,7 @@ namespace test { class loot_update_masterlist_test : public ApiGameOperationsTest { protected: loot_update_masterlist_test() : + branch_("2.x"), updated_(false) {} inline void TearDown() { @@ -43,6 +44,7 @@ protected: ASSERT_NO_THROW(boost::filesystem::remove_all(masterlistPath.parent_path() / ".git")); } + const std::string branch_; bool updated_; }; @@ -58,27 +60,27 @@ INSTANTIATE_TEST_CASE_P(, loot_game_fo4)); TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(NULL, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, NULL, "https://github.com/loot/testing-metadata.git", "master", &updated_)); - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), NULL, "master", &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(NULL, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, NULL, "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), NULL, branch_.c_str(), &updated_)); EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", NULL, &updated_)); - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", NULL)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), NULL)); } TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheMasterlistPathGivenIsInvalid) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, ";//\?", "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, ";//\?", "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); } TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheMasterlistPathGivenIsEmpty) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, "", "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, "", "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); } TEST_P(loot_update_masterlist_test, shouldReturnAGitErrorIfTheRepositoryUrlGivenCannotBeFound) { - EXPECT_EQ(loot_error_git_error, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/oblivion-does-not-exist.git", "master", &updated_)); + EXPECT_EQ(loot_error_git_error, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/oblivion-does-not-exist.git", branch_.c_str(), &updated_)); } TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheRepositoryUrlGivenIsEmpty) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "", "master", &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "", branch_.c_str(), &updated_)); } TEST_P(loot_update_masterlist_test, shouldReturnAGitErrorIfTheRepositoryBranchGivenCannotBeFound) { @@ -90,16 +92,16 @@ TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheRepositor } TEST_P(loot_update_masterlist_test, shouldSucceedIfPassedValidParametersAndOutputTrueIfTheMasterlistWasUpdated) { - EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); EXPECT_TRUE(updated_); EXPECT_TRUE(boost::filesystem::exists(masterlistPath)); } TEST_P(loot_update_masterlist_test, shouldSucceedIfCalledRepeatedlyButOnlyOutputTrueForTheFirstCall) { - EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); EXPECT_TRUE(updated_); - EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", branch_.c_str(), &updated_)); EXPECT_FALSE(updated_); EXPECT_TRUE(boost::filesystem::exists(masterlistPath)); } diff --git a/src/tests/api/loot_write_minimal_list_test.h b/src/tests/api/loot_write_minimal_list_test.h index 54ecd7d5..0ba4ca32 100644 --- a/src/tests/api/loot_write_minimal_list_test.h +++ b/src/tests/api/loot_write_minimal_list_test.h @@ -68,7 +68,7 @@ protected: << " - name: '" << blankDifferentEsm << "'" << endl << " dirty:" << endl << " - crc: 0x7d22f9df" << endl - << " util: 'TES4Edit'" << endl + << " utility: 'TES4Edit'" << endl << " udr: 4"; return expectedContent.str(); diff --git a/src/tests/backend/masterlist_test.h b/src/tests/backend/masterlist_test.h index 0cb6af8c..0e5021ff 100644 --- a/src/tests/backend/masterlist_test.h +++ b/src/tests/backend/masterlist_test.h @@ -35,7 +35,7 @@ namespace test { class MasterlistTest : public BaseGameTest { protected: MasterlistTest() : - repoBranch("master"), + repoBranch("2.x"), repoUrl("https://github.com/loot/testing-metadata.git"), masterlistPath(localPath / "masterlist.yaml") {} diff --git a/src/tests/backend/metadata/plugin_cleaning_data_test.h b/src/tests/backend/metadata/plugin_cleaning_data_test.h index c11010ab..33b3c480 100644 --- a/src/tests/backend/metadata/plugin_cleaning_data_test.h +++ b/src/tests/backend/metadata/plugin_cleaning_data_test.h @@ -32,7 +32,14 @@ along with LOOT. If not, see namespace loot { namespace test { -class PluginCleaningDataTest : public BaseGameTest {}; +class PluginCleaningDataTest : public BaseGameTest { +protected: + PluginCleaningDataTest() : info_(std::vector({ + MessageContent("info", Language::Code::english), + })) {} + + const std::vector info_; +}; // Pass an empty first argument, as it's a prefix for the test instantation, // but we only have the one so no prefix is necessary. @@ -48,69 +55,71 @@ TEST_P(PluginCleaningDataTest, defaultConstructorShouldLeaveAllCountsAtZeroAndTh EXPECT_EQ(0, info.DeletedRefs()); EXPECT_EQ(0, info.DeletedNavmeshes()); EXPECT_TRUE(info.CleaningUtility().empty()); + EXPECT_TRUE(info.Info().empty()); } TEST_P(PluginCleaningDataTest, contentConstructorShouldStoreAllGivenData) { - PluginCleaningData info(0x12345678, 2, 10, 30, "cleaner"); + PluginCleaningData info(0x12345678, "cleaner", info_, 2, 10, 30); EXPECT_EQ(0x12345678, info.CRC()); EXPECT_EQ(2, info.ITMs()); EXPECT_EQ(10, info.DeletedRefs()); EXPECT_EQ(30, info.DeletedNavmeshes()); EXPECT_EQ("cleaner", info.CleaningUtility()); + EXPECT_EQ(info_, info.Info()); } TEST_P(PluginCleaningDataTest, asMessageShouldOutputAllNonZeroCounts) { - Message message = PluginCleaningData(0x12345678, 2, 10, 30, "cleaner").AsMessage(); + Message message = PluginCleaningData(0x12345678, "cleaner", info_, 2, 10, 30).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records, 10 deleted references and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found 2 ITM records, 10 deleted references and 30 deleted navmeshes. info", message.ChooseContent(Language::Code::english).GetText()); - message = PluginCleaningData(0x12345678, 0, 0, 0, "cleaner").AsMessage(); + message = PluginCleaningData(0x12345678, "cleaner", info_, 0, 0, 0).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found dirty edits. info", message.ChooseContent(Language::Code::english).GetText()); - message = PluginCleaningData(0x12345678, 0, 10, 30, "cleaner").AsMessage(); + message = PluginCleaningData(0x12345678, "cleaner", info_, 0, 10, 30).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 10 deleted references and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found 10 deleted references and 30 deleted navmeshes. info", message.ChooseContent(Language::Code::english).GetText()); - message = PluginCleaningData(0x12345678, 0, 0, 30, "cleaner").AsMessage(); + message = PluginCleaningData(0x12345678, "cleaner", info_, 0, 0, 30).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found 30 deleted navmeshes. info", message.ChooseContent(Language::Code::english).GetText()); - message = PluginCleaningData(0x12345678, 0, 10, 0, "cleaner").AsMessage(); + message = PluginCleaningData(0x12345678, "cleaner", info_, 0, 10, 0).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 10 deleted references. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found 10 deleted references. info", message.ChooseContent(Language::Code::english).GetText()); - message = PluginCleaningData(0x12345678, 2, 0, 30, "cleaner").AsMessage(); + message = PluginCleaningData(0x12345678, "cleaner", info_, 2, 0, 30).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found 2 ITM records and 30 deleted navmeshes. info", message.ChooseContent(Language::Code::english).GetText()); - message = PluginCleaningData(0x12345678, 2, 0, 0, "cleaner").AsMessage(); + message = PluginCleaningData(0x12345678, "cleaner", info_, 2, 0, 0).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found 2 ITM records. info", message.ChooseContent(Language::Code::english).GetText()); - message = PluginCleaningData(0x12345678, 2, 10, 0, "cleaner").AsMessage(); + message = PluginCleaningData(0x12345678, "cleaner", info_, 2, 10, 0).AsMessage(); EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records and 10 deleted references. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + EXPECT_EQ("cleaner found 2 ITM records and 10 deleted references. info", message.ChooseContent(Language::Code::english).GetText()); } TEST_P(PluginCleaningDataTest, dirtyInfoShouldBeEqualIfCrcValuesAreEqual) { - PluginCleaningData info1(0x12345678, 2, 10, 30, "cleaner1"); - PluginCleaningData info2(0x12345678, 4, 20, 60, "cleaner2"); + PluginCleaningData info1(0x12345678, "cleaner1", info_, 2, 10, 30); + PluginCleaningData info2(0x12345678, "cleaner2", info_, 4, 20, 60); EXPECT_TRUE(info1 == info2); - info1 = PluginCleaningData(0x12345678, 2, 10, 30, "cleaner"); - info2 = PluginCleaningData(0x87654321, 2, 10, 30, "cleaner"); + info1 = PluginCleaningData(0x12345678, "cleaner", info_, 2, 10, 30); + info2 = PluginCleaningData(0x87654321, "cleaner", info_, 2, 10, 30); EXPECT_FALSE(info1 == info2); } TEST_P(PluginCleaningDataTest, LessThanOperatorShouldCompareCrcValues) { - PluginCleaningData info1(0x12345678, 2, 10, 30, "cleaner1"); - PluginCleaningData info2(0x12345678, 4, 20, 60, "cleaner2"); + PluginCleaningData info1(0x12345678, "cleaner1", info_, 2, 10, 30); + PluginCleaningData info2(0x12345678, "cleaner2", info_, 4, 20, 60); EXPECT_FALSE(info1 < info2); EXPECT_FALSE(info2 < info1); - info1 = PluginCleaningData(0x12345678, 2, 10, 30, "cleaner"); - info2 = PluginCleaningData(0x87654321, 2, 10, 30, "cleaner"); + info1 = PluginCleaningData(0x12345678, "cleaner", info_, 2, 10, 30); + info2 = PluginCleaningData(0x87654321, "cleaner", info_, 2, 10, 30); EXPECT_TRUE(info1 < info2); EXPECT_FALSE(info2 < info1); } @@ -119,7 +128,7 @@ TEST_P(PluginCleaningDataTest, evalConditionShouldBeTrueIfTheCrcGivenMatchesTheR Game game(GetParam()); game.SetGamePath(dataPath.parent_path()); - PluginCleaningData dirtyInfo(blankEsmCrc, 2, 10, 30, "cleaner"); + PluginCleaningData dirtyInfo(blankEsmCrc, "cleaner", info_, 2, 10, 30); EXPECT_TRUE(dirtyInfo.EvalCondition(game, blankEsm)); } @@ -127,7 +136,7 @@ TEST_P(PluginCleaningDataTest, evalConditionShouldBeFalseIfTheCrcGivenDoesNotMat Game game(GetParam()); game.SetGamePath(dataPath.parent_path()); - PluginCleaningData dirtyInfo(0xDEADBEEF, 2, 10, 30, "cleaner"); + PluginCleaningData dirtyInfo(0xDEADBEEF, "cleaner", info_, 2, 10, 30); EXPECT_FALSE(dirtyInfo.EvalCondition(game, blankEsm)); } @@ -139,51 +148,89 @@ TEST_P(PluginCleaningDataTest, evalConditionShouldBeFalseIfAnEmptyPluginFilename EXPECT_FALSE(dirtyInfo.EvalCondition(game, "")); } +TEST_P(PluginCleaningDataTest, chooseInfoShouldCreateADefaultContentObjectIfNoneExists) { + PluginCleaningData dirtyInfo(0xDEADBEEF, "cleaner", std::vector(), 2, 10, 30); + EXPECT_EQ(MessageContent(), dirtyInfo.ChooseInfo(Language::Code::english)); +} + +TEST_P(PluginCleaningDataTest, chooseInfoShouldLeaveTheContentUnchangedIfOnlyOneStringExists) { + PluginCleaningData dirtyInfo(0xDEADBEEF, "cleaner", info_, 2, 10, 30); + + EXPECT_EQ(info_[0], dirtyInfo.ChooseInfo(Language::Code::french)); + EXPECT_EQ(info_[0], dirtyInfo.ChooseInfo(Language::Code::english)); +} + +TEST_P(PluginCleaningDataTest, chooseInfoShouldSelectTheEnglishStringIfNoStringExistsForTheGivenLanguage) { + MessageContent content("content1", Language::Code::english); + std::vector info({ + content, + MessageContent("content1", Language::Code::german), + }); + PluginCleaningData dirtyInfo(0xDEADBEEF, "cleaner", info, 2, 10, 30); + + EXPECT_EQ(content, dirtyInfo.ChooseInfo(Language::Code::french)); +} + +TEST_P(PluginCleaningDataTest, chooseInfoShouldSelectTheStringForTheGivenLanguageIfOneExists) { + MessageContent french("content3", Language::Code::french); + std::vector info({ + MessageContent("content1", Language::Code::german), + MessageContent("content2", Language::Code::english), + french, + }); + PluginCleaningData dirtyInfo(0xDEADBEEF, "cleaner", info, 2, 10, 30); + + EXPECT_EQ(french, dirtyInfo.ChooseInfo(Language::Code::french)); +} + TEST_P(PluginCleaningDataTest, emittingAsYamlShouldOutputAllNonZeroCounts) { - PluginCleaningData info(0x12345678, 2, 10, 30, "cleaner"); + PluginCleaningData info(0x12345678, "cleaner", info_, 2, 10, 30); YAML::Emitter emitter; emitter << info; - EXPECT_STREQ("crc: 0x12345678\nutil: 'cleaner'\nitm: 2\nudr: 10\nnav: 30", emitter.c_str()); + EXPECT_STREQ("crc: 0x12345678\nutility: 'cleaner'\ninfo: 'info'\nitm: 2\nudr: 10\nnav: 30", emitter.c_str()); } TEST_P(PluginCleaningDataTest, emittingAsYamlShouldOmitAllZeroCounts) { - PluginCleaningData info(0x12345678, 0, 0, 0, "cleaner"); + PluginCleaningData info(0x12345678, "cleaner", info_, 0, 0, 0); YAML::Emitter emitter; emitter << info; - EXPECT_STREQ("crc: 0x12345678\nutil: 'cleaner'", emitter.c_str()); + EXPECT_STREQ("crc: 0x12345678\nutility: 'cleaner'\ninfo: 'info'", emitter.c_str()); } TEST_P(PluginCleaningDataTest, encodingAsYamlShouldOmitAllZeroCountFields) { - PluginCleaningData info(0x12345678, 0, 0, 0, "cleaner"); + PluginCleaningData info(0x12345678, "cleaner", info_, 0, 0, 0); YAML::Node node; node = info; EXPECT_EQ(0x12345678, node["crc"].as()); - EXPECT_EQ("cleaner", node["util"].as()); + EXPECT_EQ("cleaner", node["utility"].as()); + EXPECT_EQ(info_, node["info"].as>()); EXPECT_FALSE(node["itm"]); EXPECT_FALSE(node["udr"]); EXPECT_FALSE(node["nav"]); } TEST_P(PluginCleaningDataTest, encodingAsYamlShouldOutputAllNonZeroCountFields) { - PluginCleaningData info(0x12345678, 2, 10, 30, "cleaner"); + PluginCleaningData info(0x12345678, "cleaner", info_, 2, 10, 30); YAML::Node node; node = info; EXPECT_EQ(0x12345678, node["crc"].as()); - EXPECT_EQ("cleaner", node["util"].as()); + EXPECT_EQ("cleaner", node["utility"].as()); + EXPECT_EQ(info_, node["info"].as>()); EXPECT_EQ(2, node["itm"].as()); EXPECT_EQ(10, node["udr"].as()); EXPECT_EQ(30, node["nav"].as()); } TEST_P(PluginCleaningDataTest, decodingFromYamlShouldLeaveMissingFieldsWithZeroValues) { - YAML::Node node = YAML::Load("{crc: 0x12345678, util: cleaner}"); + YAML::Node node = YAML::Load("{crc: 0x12345678, utility: cleaner}"); PluginCleaningData info = node.as(); EXPECT_EQ(0x12345678, info.CRC()); + EXPECT_TRUE(info.Info().empty()); EXPECT_EQ(0, info.ITMs()); EXPECT_EQ(0, info.DeletedRefs()); EXPECT_EQ(0, info.DeletedNavmeshes()); @@ -191,10 +238,11 @@ TEST_P(PluginCleaningDataTest, decodingFromYamlShouldLeaveMissingFieldsWithZeroV } TEST_P(PluginCleaningDataTest, decodingFromYamlShouldStoreAllNonZeroCounts) { - YAML::Node node = YAML::Load("{crc: 0x12345678, util: cleaner, itm: 2, udr: 10, nav: 30}"); + YAML::Node node = YAML::Load("{crc: 0x12345678, utility: cleaner, info: info, itm: 2, udr: 10, nav: 30}"); PluginCleaningData info = node.as(); EXPECT_EQ(0x12345678, info.CRC()); + EXPECT_EQ(info_, info.Info()); EXPECT_EQ(2, info.ITMs()); EXPECT_EQ(10, info.DeletedRefs()); EXPECT_EQ(30, info.DeletedNavmeshes()); diff --git a/src/tests/backend/metadata/plugin_metadata_test.h b/src/tests/backend/metadata/plugin_metadata_test.h index d32b0929..7717bf46 100644 --- a/src/tests/backend/metadata/plugin_metadata_test.h +++ b/src/tests/backend/metadata/plugin_metadata_test.h @@ -31,7 +31,14 @@ along with LOOT. If not, see namespace loot { namespace test { -class PluginMetadataTest : public BaseGameTest {}; +class PluginMetadataTest : public BaseGameTest { +protected: + PluginMetadataTest() : info_(std::vector({ + MessageContent("info", Language::Code::english), + })) {} + + const std::vector info_; +}; // Pass an empty first argument, as it's a prefix for the test instantation, // but we only have the one so no prefix is necessary. @@ -254,8 +261,8 @@ TEST_P(PluginMetadataTest, mergeMetadataShouldMergeTags) { TEST_P(PluginMetadataTest, mergeMetadataShouldMergeDirtyInfoData) { PluginMetadata plugin1; PluginMetadata plugin2; - PluginCleaningData info1(0x5, 1, 2, 3, "utility"); - PluginCleaningData info2(0xA, 1, 2, 3, "utility"); + PluginCleaningData info1(0x5, "utility", info_, 1, 2, 3); + PluginCleaningData info2(0xA, "utility", info_, 1, 2, 3); plugin1.DirtyInfo({info1}); plugin2.DirtyInfo({info1, info2}); @@ -432,9 +439,9 @@ TEST_P(PluginMetadataTest, diffMetadataShouldOutputTagsThatAreNotCommonToBothInp TEST_P(PluginMetadataTest, diffMetadataShouldOutputDirtyInfoObjectsThatAreNotCommonToBothInputPlugins) { PluginMetadata plugin1; PluginMetadata plugin2; - PluginCleaningData info1(0x5, 1, 2, 3, "utility"); - PluginCleaningData info2(0xA, 1, 2, 3, "utility"); - PluginCleaningData info3(0x1, 1, 2, 3, "utility"); + PluginCleaningData info1(0x5, "utility", info_, 1, 2, 3); + PluginCleaningData info2(0xA, "utility", info_, 1, 2, 3); + PluginCleaningData info3(0x1, "utility", info_, 1, 2, 3); plugin1.DirtyInfo({info1, info2}); plugin2.DirtyInfo({info1, info3}); @@ -588,9 +595,9 @@ TEST_P(PluginMetadataTest, newMetadataShouldOutputTagsThatAreNotCommonToBothInpu TEST_P(PluginMetadataTest, newMetadataShouldOutputDirtyInfoObjectsThatAreNotCommonToBothInputPlugins) { PluginMetadata plugin1; PluginMetadata plugin2; - PluginCleaningData info1(0x5, 1, 2, 3, "utility"); - PluginCleaningData info2(0xA, 1, 2, 3, "utility"); - PluginCleaningData info3(0x1, 1, 2, 3, "utility"); + PluginCleaningData info1(0x5, "utility", info_, 1, 2, 3); + PluginCleaningData info2(0xA, "utility", info_, 1, 2, 3); + PluginCleaningData info3(0x1, "utility", info_, 1, 2, 3); plugin1.DirtyInfo({info1, info2}); plugin2.DirtyInfo({info1, info3}); @@ -647,8 +654,8 @@ TEST_P(PluginMetadataTest, evalAllConditionsShouldEvaluateAllMetadataConditions) Tag tag2("Relev", true, "file(\"" + missingEsp + "\")"); plugin.Tags({tag1, tag2}); - PluginCleaningData info1(blankEsmCrc, 1, 2, 3, "utility"); - PluginCleaningData info2(0xDEADBEEF, 1, 2, 3, "utility"); + PluginCleaningData info1(blankEsmCrc, "utility", info_, 1, 2, 3); + PluginCleaningData info2(0xDEADBEEF, "utility", info_, 1, 2, 3); plugin.DirtyInfo({info1, info2}); plugin.CleanInfo({info1, info2}); @@ -734,7 +741,7 @@ TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfTagsExist) { TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfDirtyInfoExists) { PluginMetadata plugin(blankEsp); - plugin.DirtyInfo({PluginCleaningData(5, 0, 1, 2, "utility")}); + plugin.DirtyInfo({PluginCleaningData(5, "utility", info_, 0, 1, 2)}); EXPECT_FALSE(plugin.HasNameOnly()); } @@ -911,7 +918,7 @@ TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithTagsCorrectly) { TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithDirtyInfoCorrectly) { PluginMetadata plugin(blankEsp); - plugin.DirtyInfo({PluginCleaningData(5, 0, 1, 2, "utility")}); + plugin.DirtyInfo({PluginCleaningData(5, "utility", info_, 0, 1, 2)}); YAML::Emitter emitter; emitter << plugin; @@ -919,7 +926,8 @@ TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithDirtyInfoCorrect EXPECT_STREQ("name: 'Blank.esp'\n" "dirty:\n" " - crc: 0x5\n" - " util: 'utility'\n" + " utility: 'utility'\n" + " info: 'info'\n" " udr: 1\n" " nav: 2", emitter.c_str()); } @@ -934,7 +942,7 @@ TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithCleanInfoCorrect EXPECT_STREQ("name: 'Blank.esp'\n" "clean:\n" " - crc: 0x5\n" - " util: 'utility'", emitter.c_str()); + " utility: 'utility'", emitter.c_str()); } TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithLocationsCorrectly) { @@ -1057,7 +1065,7 @@ TEST_P(PluginMetadataTest, encodingAsYamlShouldSetTagFieldIfTagsExist) { TEST_P(PluginMetadataTest, encodingAsYamlShouldSetDirtyFieldIfDirtyInfoExists) { PluginMetadata plugin(blankEsp); - plugin.DirtyInfo({PluginCleaningData(5, 0, 1, 2, "utility")}); + plugin.DirtyInfo({PluginCleaningData(5, "utility", info_, 0, 1, 2)}); YAML::Node node; node = plugin; @@ -1111,12 +1119,12 @@ TEST_P(PluginMetadataTest, decodingFromYamlShouldStoreAllGivenData) { " - Relev\n" "dirty:\n" " - crc: 0x5\n" - " util: 'utility'\n" + " utility: 'utility'\n" " udr: 1\n" " nav: 2\n" "clean:\n" " - crc: 0x6\n" - " util: 'utility'\n" + " utility: 'utility'\n" "url:\n" " - 'http://www.example.com'"); PluginMetadata plugin = node.as(); @@ -1140,7 +1148,7 @@ TEST_P(PluginMetadataTest, decodingFromYamlShouldStoreAllGivenData) { Tag("Relev") }), plugin.Tags()); EXPECT_EQ(std::set({ - PluginCleaningData(5, 0, 1, 2, "utility") + PluginCleaningData(5, "utility", info_, 0, 1, 2) }), plugin.DirtyInfo()); EXPECT_EQ(std::set({ PluginCleaningData(6, "utility") @@ -1154,7 +1162,7 @@ TEST_P(PluginMetadataTest, decodingFromYamlWithDirtyInfoInARegexPluginMetadataOb YAML::Node node = YAML::Load("name: 'Blank\\.esp'\n" "dirty:\n" " - crc: 0x5\n" - " util: 'utility'\n" + " utility: 'utility'\n" " udr: 1\n" " nav: 2"); @@ -1165,7 +1173,7 @@ TEST_P(PluginMetadataTest, decodingFromYamlWithCleanInfoInARegexPluginMetadataOb YAML::Node node = YAML::Load("name: 'Blank\\.esp'\n" "clean:\n" " - crc: 0x5\n" - " util: 'utility'"); + " utility: 'utility'"); EXPECT_THROW(node.as(), YAML::RepresentationException); } @@ -1174,7 +1182,7 @@ TEST_P(PluginMetadataTest, decodingFromYamlWithAnInvalidRegexNameShouldThrow) { YAML::Node node = YAML::Load("name: 'RagnvaldBook(Farengar(+Ragnvald)?)?\\.esp'\n" "dirty:\n" " - crc: 0x5\n" - " util: 'utility'\n" + " utility: 'utility'\n" " udr: 1\n" " nav: 2"); diff --git a/src/tests/backend/plugin/plugin_test.h b/src/tests/backend/plugin/plugin_test.h index cf841793..9bc42107 100644 --- a/src/tests/backend/plugin/plugin_test.h +++ b/src/tests/backend/plugin/plugin_test.h @@ -280,7 +280,7 @@ TEST_P(PluginTest, checkInstallValidityShouldCheckThatRequirementsArePresent) { File(blankEsp), }); - EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + plugin.CheckInstallValidity(game_); EXPECT_EQ(std::list({ Message(Message::Type::error, "This plugin requires \"" + missingEsp + "\" to be installed, but it is missing."), }), plugin.Messages()); @@ -293,30 +293,34 @@ TEST_P(PluginTest, checkInstallValidityShouldCheckThatIncompatibilitiesAreAbsent File(masterFile), }); - EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + plugin.CheckInstallValidity(game_); EXPECT_EQ(std::list({ Message(Message::Type::error, "This plugin is incompatible with \"" + masterFile + "\", but both are present."), }), plugin.Messages()); } TEST_P(PluginTest, checkInstallValidityShouldGenerateMessagesFromDirtyInfo) { - Plugin plugin(game_, blankEsm, false); - plugin.DirtyInfo({ - PluginCleaningData(blankEsmCrc, 0, 1, 2, "utility1"), - PluginCleaningData(0xDEADBEEF, 0, 5, 10, "utility2"), + const std::vector info = std::vector({ + MessageContent("info", Language::Code::english), }); - EXPECT_TRUE(plugin.CheckInstallValidity(game_)); + Plugin plugin(game_, blankEsm, false); + plugin.DirtyInfo({ + PluginCleaningData(blankEsmCrc, "utility1", info, 0, 1, 2), + PluginCleaningData(0xDEADBEEF, "utility2", info, 0, 5, 10), + }); + + plugin.CheckInstallValidity(game_); EXPECT_EQ(std::list({ - PluginCleaningData(blankEsmCrc, 0, 1, 2, "utility1").AsMessage(), - PluginCleaningData(0xDEADBEEF, 0, 5, 10, "utility2").AsMessage(), + PluginCleaningData(blankEsmCrc, "utility1", info, 0, 1, 2).AsMessage(), + PluginCleaningData(0xDEADBEEF, "utility2", info, 0, 5, 10).AsMessage(), }), plugin.Messages()); } TEST_P(PluginTest, checkInstallValidityShouldCheckIfAPluginsMastersAreAllPresentAndActiveIfNoFilterTagIsPresent) { Plugin plugin(game_, blankDifferentMasterDependentEsp, false); - EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + plugin.CheckInstallValidity(game_); EXPECT_EQ(std::list({ Message(Message::Type::error, "This plugin requires \"" + blankDifferentEsm + "\" to be active, but it is inactive."), }), plugin.Messages()); @@ -326,7 +330,7 @@ TEST_P(PluginTest, checkInstallValidityShouldNotCheckIfAPluginsMastersAreAllActi Plugin plugin(game_, blankDifferentMasterDependentEsp, false); plugin.Tags({Tag("Filter")}); - EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + plugin.CheckInstallValidity(game_); EXPECT_TRUE(plugin.Messages().empty()); } }