Replace condition parsing and evaluation code

With loot-condition-interpreter dependency. This also replaces the
pseudosem dependency, condition and CRC caching, and executable version
extraction.
This commit is contained in:
Oliver Hamlet
2018-10-21 16:32:07 +01:00
parent 44955e8bc0
commit 1c025a4fba
25 changed files with 335 additions and 1933 deletions
+18 -13
View File
@@ -116,14 +116,21 @@ ELSE ()
set (LIBLOADORDER_LIBRARIES ${LIBLOADORDER_LIBRARIES} dl)
ENDIF ()
ExternalProject_Add(pseudosem
ExternalProject_Add(loot-condition-interpreter
PREFIX "external"
URL "https://github.com/WrinklyNinja/pseudosem/archive/1.1.0.tar.gz"
URL "https://github.com/Ortham/loot-condition-interpreter/archive/1.0.0.tar.gz"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_COMMAND cargo build --release --manifest-path ffi/Cargo.toml --features ffi-headers --target ${RUST_TARGET}
INSTALL_COMMAND "")
ExternalProject_Get_Property(pseudosem SOURCE_DIR)
set(PSEUDOSEM_INCLUDE_DIRS "${SOURCE_DIR}/include")
ExternalProject_Get_Property(loot-condition-interpreter SOURCE_DIR)
set (LCI_INCLUDE_DIRS "${SOURCE_DIR}/ffi/include")
set (LCI_LIBRARIES "${SOURCE_DIR}/target/${RUST_TARGET}/release/${CMAKE_STATIC_LIBRARY_PREFIX}loot_condition_interpreter_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}")
IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
set (LCI_LIBRARIES ${LCI_LIBRARIES} Userenv)
ELSE ()
set (LCI_LIBRARIES ${LCI_LIBRARIES} dl)
ENDIF ()
ExternalProject_Add(testing-metadata
PREFIX "external"
@@ -235,7 +242,6 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h"
"${CMAKE_SOURCE_DIR}/include/loot/vertex.h"
"${CMAKE_SOURCE_DIR}/src/api/api_database.h"
"${CMAKE_SOURCE_DIR}/src/api/metadata/condition_evaluator.h"
"${CMAKE_SOURCE_DIR}/src/api/metadata/condition_grammar.h"
"${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/file.h"
"${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/group.h"
"${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/location.h"
@@ -269,7 +275,6 @@ set (LOOT_TESTS_HEADERS "${CMAKE_SOURCE_DIR}/src/tests/api/internals/game/game_t
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/helpers/version_test.h"
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/helpers/yaml_set_helpers_test.h"
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/condition_evaluator_test.h"
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/condition_grammar_test.h"
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/conditional_metadata_test.h"
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/file_test.h"
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/group_test.h"
@@ -310,11 +315,11 @@ include_directories ("${CMAKE_SOURCE_DIR}/src"
${LIBLOADORDER_INCLUDE_DIRS}
${LIBGIT2_INCLUDE_DIRS}
${ESPLUGIN_INCLUDE_DIRS}
${LCI_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
${SPDLOG_INCLUDE_DIRS}
${YAML_CPP_INCLUDE_DIRS}
${GTEST_INCLUDE_DIRS}
${PSEUDOSEM_INCLUDE_DIRS})
${GTEST_INCLUDE_DIRS})
##############################
# System-Specific Settings
@@ -380,13 +385,13 @@ ENDIF ()
# Build tests.
add_executable (loot_api_internals_tests ${LOOT_API_SRC} ${LOOT_API_HEADERS} ${LOOT_TESTS_SRC} ${LOOT_TESTS_HEADERS})
add_dependencies (loot_api_internals_tests esplugin libgit2 libloadorder pseudosem spdlog yaml-cpp GTest testing-metadata testing-plugins)
target_link_libraries(loot_api_internals_tests ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${YAML_CPP_LIBRARIES} ${GTEST_LIBRARIES})
add_dependencies (loot_api_internals_tests esplugin libgit2 libloadorder loot-condition-interpreter spdlog yaml-cpp GTest testing-metadata testing-plugins)
target_link_libraries(loot_api_internals_tests ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${LCI_LIBRARIES} ${YAML_CPP_LIBRARIES} ${GTEST_LIBRARIES})
# Build API.
add_library (loot_api ${LOOT_API_SRC} ${LOOT_API_HEADERS})
add_dependencies (loot_api esplugin libgit2 libloadorder pseudosem spdlog yaml-cpp)
target_link_libraries(loot_api ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${YAML_CPP_LIBRARIES})
add_dependencies (loot_api esplugin libgit2 libloadorder loot-condition-interpreter spdlog yaml-cpp)
target_link_libraries(loot_api ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${LCI_LIBRARIES} ${YAML_CPP_LIBRARIES})
# Build API tests.
add_executable (loot_api_tests ${LOOT_API_TESTS_SRC} ${LOOT_API_TESTS_HEADERS})
+6 -10
View File
@@ -36,12 +36,8 @@
#include "loot/exception/file_access_error.h"
namespace loot {
ApiDatabase::ApiDatabase(const GameType gameType,
const std::filesystem::path& dataPath,
std::shared_ptr<GameCache> gameCache,
std::shared_ptr<LoadOrderHandler> loadOrderHandler) :
gameCache_(gameCache),
conditionEvaluator_(gameType, dataPath, gameCache, loadOrderHandler) {}
ApiDatabase::ApiDatabase(std::shared_ptr<ConditionEvaluator> conditionEvaluator) :
conditionEvaluator_(conditionEvaluator) {}
///////////////////////////////////
// Database Loading Functions
@@ -146,10 +142,10 @@ std::vector<Message> ApiDatabase::GetGeneralMessages(
if (evaluateConditions) {
// Evaluate conditions from scratch.
gameCache_->ClearCachedConditions();
conditionEvaluator_->ClearConditionCache();
for (auto it = std::begin(masterlistMessages);
it != std::end(masterlistMessages);) {
if (!conditionEvaluator_.evaluate(it->GetCondition()))
if (!conditionEvaluator_->Evaluate(it->GetCondition()))
it = masterlistMessages.erase(it);
else
++it;
@@ -224,7 +220,7 @@ std::optional<PluginMetadata> ApiDatabase::GetPluginMetadata(const std::string&
}
if (evaluateConditions && metadata) {
return conditionEvaluator_.evaluateAll(metadata.value());
return conditionEvaluator_->EvaluateAll(metadata.value());
}
return metadata;
@@ -236,7 +232,7 @@ std::optional<PluginMetadata> ApiDatabase::GetPluginUserMetadata(
auto metadata = userlist_.FindPlugin(plugin);
if (evaluateConditions && metadata) {
return conditionEvaluator_.evaluateAll(metadata.value());
return conditionEvaluator_->EvaluateAll(metadata.value());
}
return metadata;
+2 -6
View File
@@ -40,10 +40,7 @@
namespace loot {
struct ApiDatabase : public DatabaseInterface {
ApiDatabase(const GameType gameType,
const std::filesystem::path& dataPath,
std::shared_ptr<GameCache> gameCache,
std::shared_ptr<LoadOrderHandler> loadOrderHandler);
ApiDatabase(std::shared_ptr<ConditionEvaluator> conditionEvaluator);
void LoadLists(const std::filesystem::path& masterlist_path,
const std::filesystem::path& userlist_path = "");
@@ -92,8 +89,7 @@ struct ApiDatabase : public DatabaseInterface {
void DiscardAllUserMetadata();
private:
std::shared_ptr<GameCache> gameCache_;
ConditionEvaluator conditionEvaluator_;
std::shared_ptr<ConditionEvaluator> conditionEvaluator_;
Masterlist masterlist_;
MetadataList userlist_;
};
+6 -2
View File
@@ -71,8 +71,9 @@ Game::Game(const GameType gameType,
loadOrderHandler_->Init(type_, gamePath_, localDataPath);
database_ = std::make_shared<ApiDatabase>(
Type(), DataPath(), GetCache(), GetLoadOrderHandler());
conditionEvaluator_ = std::make_shared<ConditionEvaluator>(Type(), DataPath());
database_ = std::make_shared<ApiDatabase>(conditionEvaluator_);
}
GameType Game::Type() const { return type_; }
@@ -192,6 +193,8 @@ void Game::LoadPlugins(const std::vector<std::string>& plugins,
if (thread.joinable())
thread.join();
}
conditionEvaluator_->RefreshState(cache_);
}
std::shared_ptr<const PluginInterface> Game::GetPlugin(
@@ -225,6 +228,7 @@ std::vector<std::string> Game::SortPlugins(
void Game::LoadCurrentLoadOrderState() {
loadOrderHandler_->LoadCurrentState();
conditionEvaluator_->RefreshState(loadOrderHandler_);
}
bool Game::IsPluginActive(const std::string& pluginName) const {
+2
View File
@@ -30,6 +30,7 @@
#include "api/game/game_cache.h"
#include "api/game/load_order_handler.h"
#include "api/metadata/condition_evaluator.h"
#include "loot/game_interface.h"
namespace loot {
@@ -80,6 +81,7 @@ private:
std::shared_ptr<GameCache> cache_;
std::shared_ptr<LoadOrderHandler> loadOrderHandler_;
std::shared_ptr<ConditionEvaluator> conditionEvaluator_;
std::shared_ptr<DatabaseInterface> database_;
const GameType type_;
-43
View File
@@ -38,52 +38,16 @@ namespace loot {
GameCache::GameCache() {}
GameCache::GameCache(const GameCache& cache) :
conditions_(cache.conditions_),
plugins_(cache.plugins_) {}
GameCache& GameCache::operator=(const GameCache& cache) {
if (&cache != this) {
conditions_ = cache.conditions_;
plugins_ = cache.plugins_;
}
return *this;
}
void GameCache::CacheCondition(const std::string& condition, bool result) {
lock_guard<mutex> guard(mutex_);
conditions_.insert(pair<string, bool>(condition, result));
}
std::pair<bool, bool> GameCache::GetCachedCondition(
const std::string& condition) const {
lock_guard<mutex> guard(mutex_);
auto it = conditions_.find(condition);
if (it != conditions_.end())
return pair<bool, bool>(it->second, true);
else
return pair<bool, bool>(false, false);
}
uint32_t GameCache::GetCachedCrc(const std::string& file) const {
lock_guard<mutex> guard(mutex_);
auto it = crcs_.find(to_lower(file));
if (it != crcs_.end()) {
return it->second;
}
return 0;
}
void GameCache::CacheCrc(const std::string& file, uint32_t crc) {
lock_guard<mutex> guard(mutex_);
crcs_.insert(pair<string, uint32_t>(to_lower(file), crc));
}
std::set<std::shared_ptr<const Plugin>> GameCache::GetPlugins() const {
std::set<std::shared_ptr<const Plugin>> output;
std::transform(
@@ -131,13 +95,6 @@ void GameCache::CacheArchivePath(const std::filesystem::path& path)
archivePaths_.insert(path);
}
void GameCache::ClearCachedConditions() {
lock_guard<mutex> guard(mutex_);
conditions_.clear();
crcs_.clear();
}
void GameCache::ClearCachedPlugins() {
lock_guard<mutex> guard(mutex_);
-10
View File
@@ -39,13 +39,6 @@ public:
GameCache& operator=(const GameCache& cache);
// Returns false for second bool if no cached condition.
std::pair<bool, bool> GetCachedCondition(const std::string& condition) const;
void CacheCondition(const std::string& condition, bool result);
uint32_t GetCachedCrc(const std::string& file) const;
void CacheCrc(const std::string& file, uint32_t crc);
std::set<std::shared_ptr<const Plugin>> GetPlugins() const;
std::shared_ptr<const Plugin> GetPlugin(const std::string& pluginName) const;
void AddPlugin(const Plugin&& plugin);
@@ -53,13 +46,10 @@ public:
std::set<std::filesystem::path> GetArchivePaths() const;
void CacheArchivePath(const std::filesystem::path& path);
void ClearCachedConditions();
void ClearCachedPlugins();
void ClearCachedArchivePaths();
private:
std::unordered_map<std::string, bool> conditions_;
std::unordered_map<std::string, uint32_t> crcs_;
std::unordered_map<std::string, std::shared_ptr<const Plugin>> plugins_;
std::set<std::filesystem::path> archivePaths_;
+20
View File
@@ -125,6 +125,26 @@ std::vector<std::string> LoadOrderHandler::GetLoadOrder() const {
return loadOrder;
}
std::vector<std::string> LoadOrderHandler::GetActivePlugins() const {
auto logger = getLogger();
if (logger) {
logger->debug("Getting active plugins.");
}
char** pluginArr;
size_t pluginArrSize;
unsigned int ret =
lo_get_active_plugins(gh_, &pluginArr, &pluginArrSize);
HandleError("get active plugins", ret);
std::vector<string> loadOrder(pluginArr, pluginArr + pluginArrSize);
lo_free_string_array(pluginArr, pluginArrSize);
return loadOrder;
}
std::vector<std::string> LoadOrderHandler::GetImplicitlyActivePlugins() const {
auto logger = getLogger();
if (logger) {
+2
View File
@@ -48,6 +48,8 @@ public:
std::vector<std::string> GetLoadOrder() const;
std::vector<std::string> GetActivePlugins() const;
std::vector<std::string> GetImplicitlyActivePlugins() const;
bool IsPluginActive(const std::string& pluginName) const;
+7 -93
View File
@@ -25,19 +25,8 @@
#include <regex>
#include <pseudosem.h>
#include <boost/algorithm/string.hpp>
#ifdef _WIN32
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include "windows.h"
#endif
using std::regex;
namespace loot {
@@ -60,7 +49,7 @@ const std::string pseudosemVersionRegex =
/* There are a few different version formats that can appear in strings
together, and in order to extract the correct one, they must be searched
for in order of priority. */
const std::vector<regex> Version::versionRegexes({
const std::vector<regex> versionRegexes({
regex(dateRegex, regex::ECMAScript | regex::icase),
regex(R"(version:?\s)" + pseudosemVersionRegex,
regex::ECMAScript | regex::icase),
@@ -73,97 +62,22 @@ const std::vector<regex> Version::versionRegexes({
regex::ECMAScript | regex::icase),
});
Version::Version() {}
Version::Version(const std::string& ver) {
std::optional<std::string> ExtractVersion(const std::string& text) {
std::smatch what;
for (const auto& versionRegex : versionRegexes) {
if (std::regex_search(ver, what, versionRegex)) {
if (std::regex_search(text, what, versionRegex)) {
for (auto it = next(begin(what)); it != end(what); ++it) {
if (it->str().empty())
continue;
// Use the first non-empty sub-match.
verString_ = *it;
boost::trim(verString_);
return;
std::string version = *it;
boost::trim(version);
return version;
}
}
}
}
Version::Version(const std::filesystem::path& file) {
#ifdef _WIN32
DWORD dummy = 0;
DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy);
if (size > 0) {
LPBYTE point = new BYTE[size];
UINT uLen;
VS_FIXEDFILEINFO* info;
GetFileVersionInfo(file.wstring().c_str(), 0, size, point);
VerQueryValue(point, L"\\", (LPVOID*)&info, &uLen);
DWORD dwLeftMost = HIWORD(info->dwFileVersionMS);
DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS);
DWORD dwSecondRight = HIWORD(info->dwFileVersionLS);
DWORD dwRightMost = LOWORD(info->dwFileVersionLS);
delete[] point;
verString_ =
std::to_string(dwLeftMost) + '.' + std::to_string(dwSecondLeft) + '.' +
std::to_string(dwSecondRight) + '.' + std::to_string(dwRightMost);
}
#else
// ensure filename has no quote characters in it to avoid command injection
// attacks
if (std::string::npos != file.u8string().find('"')) {
// command mostly borrowed from the gnome-exe-thumbnailer.sh script
// wrestool is part of the icoutils package
std::string cmd =
"wrestool --extract --raw --type=version \"" + file.u8string() +
"\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' "
"| sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'";
FILE* fp = popen(cmd.c_str(), "r");
// read out the version string
static const uint32_t BUFSIZE = 32;
char buf[BUFSIZE];
if (nullptr != fgets(buf, BUFSIZE, fp)) {
verString_ = std::string(buf);
}
pclose(fp);
}
#endif
}
std::string Version::AsString() const { return verString_; }
bool Version::operator<(const Version& ver) const {
return pseudosem::compare(this->verString_, ver.AsString()) < 0;
}
bool Version::operator>(const Version& ver) const {
return pseudosem::compare(this->verString_, ver.AsString()) > 0;
}
bool Version::operator>=(const Version& ver) const {
return pseudosem::compare(this->verString_, ver.AsString()) >= 0;
}
bool Version::operator<=(const Version& ver) const {
return pseudosem::compare(this->verString_, ver.AsString()) <= 0;
}
bool Version::operator==(const Version& ver) const {
return pseudosem::compare(this->verString_, ver.AsString()) == 0;
}
bool Version::operator!=(const Version& ver) const {
return pseudosem::compare(this->verString_, ver.AsString()) != 0;
return std::nullopt;
}
}
+2 -22
View File
@@ -25,31 +25,11 @@
#ifndef LOOT_API_HELPERS_VERSION
#define LOOT_API_HELPERS_VERSION
#include <filesystem>
#include <regex>
#include <optional>
#include <string>
namespace loot {
// Version class for more robust version comparisons.
class Version {
public:
Version();
Version(const std::string& ver);
Version(const std::filesystem::path& file);
std::string AsString() const;
bool operator>(const Version&) const;
bool operator<(const Version&) const;
bool operator>=(const Version&) const;
bool operator<=(const Version&) const;
bool operator==(const Version&) const;
bool operator!=(const Version&) const;
private:
std::string verString_;
static const std::vector<std::regex> versionRegexes;
};
std::optional<std::string> ExtractVersion(const std::string& text);
}
#endif
File diff suppressed because it is too large Load Diff
+13 -56
View File
@@ -26,78 +26,35 @@
#define LOOT_API_METADATA_CONDITION_EVALUATOR
#include <filesystem>
#include <regex>
#include <string>
#include <loot_condition_interpreter.hpp>
#include "api/game/game_cache.h"
#include "api/game/load_order_handler.h"
#include "api/helpers/version.h"
#include "loot/metadata/plugin_cleaning_data.h"
#include "loot/metadata/plugin_metadata.h"
namespace loot {
class ConditionEvaluator {
public:
ConditionEvaluator();
ConditionEvaluator(const GameType gameType,
const std::filesystem::path& dataPath,
std::shared_ptr<GameCache> gameCache,
std::shared_ptr<LoadOrderHandler> loadOrderHandler);
const std::filesystem::path& dataPath);
bool evaluate(const std::string& condition) const;
bool evaluate(const PluginCleaningData& cleaningData,
const std::string& pluginName) const;
PluginMetadata evaluateAll(const PluginMetadata& pluginMetadata) const;
bool fileExists(const std::string& filePath) const;
bool regexMatchExists(const std::string& regexString) const;
bool regexMatchesExist(const std::string& regexString) const;
bool isPluginActive(const std::string& pluginName) const;
bool isPluginMatchingRegexActive(const std::string& regexString) const;
bool arePluginsActive(const std::string& regexString) const;
bool checksumMatches(const std::string& filePath,
const uint32_t checksum) const;
bool compareVersions(const std::string& filePath,
const std::string& testVersion,
const std::string& comparator) const;
bool Evaluate(const std::string& condition);
PluginMetadata EvaluateAll(const PluginMetadata& pluginMetadata);
void ClearConditionCache();
void RefreshState(std::shared_ptr<LoadOrderHandler> loadOrderHandler);
void RefreshState(std::shared_ptr<GameCache> gameCache);
private:
static void validatePath(const std::filesystem::path& path);
static void validateRegex(const std::string& regexString);
bool Evaluate(const PluginCleaningData& cleaningData,
const std::string& pluginName);
static std::filesystem::path getRegexParentPath(
const std::string& regexString);
static std::string getRegexFilename(const std::string& regexString);
// Split a regex string into the non-regex filesystem parent path, and the
// regex filename.
static std::pair<std::filesystem::path, std::regex> splitRegex(
const std::string& regexString);
bool isGameSubdirectory(const std::filesystem::path& path) const;
bool isRegexMatchInDataDirectory(
const std::pair<std::filesystem::path, std::regex>& pathRegex,
const std::function<bool(const std::string&)> condition) const;
bool areRegexMatchesInDataDirectory(
const std::pair<std::filesystem::path, std::regex>& pathRegex,
const std::function<bool(const std::string&)> condition) const;
bool parseCondition(const std::string& condition) const;
Version getVersion(const std::string& filePath) const;
bool shouldParseOnly() const;
uint32_t getCrc(const std::string& file) const;
const GameType gameType_;
const std::filesystem::path dataPath_;
const std::shared_ptr<GameCache> gameCache_;
const std::shared_ptr<LoadOrderHandler> loadOrderHandler_;
std::shared_ptr<lci_state> lciState_;
};
void ParseCondition(const std::string& condition);
}
#endif
-304
View File
@@ -1,304 +0,0 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2016 WrinklyNinja
This file is part of LOOT.
LOOT is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
LOOT is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with LOOT. If not, see
<https://www.gnu.org/licenses/>.
*/
#ifndef LOOT_API_METADATA_CONDITION_GRAMMAR
#define LOOT_API_METADATA_CONDITION_GRAMMAR
#ifndef BOOST_SPIRIT_UNICODE
#define BOOST_SPIRIT_UNICODE
#endif
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
#define BOOST_SPIRIT_USE_PHOENIX_V3 1
#endif
#include <cstdint>
#include <filesystem>
#include <regex>
#include <boost/algorithm/string.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/qi.hpp>
#include "api/game/game.h"
#include "api/helpers/logging.h"
#include "api/helpers/version.h"
#include "api/metadata/condition_evaluator.h"
#include "api/plugin.h"
#include "loot/exception/condition_syntax_error.h"
namespace loot {
template<typename Iterator, typename Skipper>
class ConditionGrammar
: public boost::spirit::qi::grammar<Iterator, bool(), Skipper> {
public:
ConditionGrammar(const ConditionEvaluator& evaluator) :
ConditionGrammar::base_type(expression_, "condition grammar"),
evaluator_(evaluator) {
using boost::spirit::unicode::char_;
using boost::spirit::unicode::string;
namespace phoenix = boost::phoenix;
namespace qi = boost::spirit::qi;
expression_ =
qi::eps > compound_[qi::labels::_val = qi::labels::_1] >>
*((qi::lit("or") >>
compound_)[qi::labels::_val = qi::labels::_val || qi::labels::_1]);
compound_ =
condition_[qi::labels::_val = qi::labels::_1] >>
*((qi::lit("and") >>
condition_)[qi::labels::_val = qi::labels::_val && qi::labels::_1]);
condition_ =
function_[qi::labels::_val = qi::labels::_1] |
(qi::lit("not") > condition_)[qi::labels::_val = !qi::labels::_1] |
('(' > expression_ > ')')[qi::labels::_val = qi::labels::_1];
function_ =
("file(" > quotedStr_ > ')')[phoenix::bind(&ConditionGrammar::CheckFile,
this,
qi::labels::_val,
qi::labels::_1)] |
("many(" > quotedStr_ > ')')[phoenix::bind(&ConditionGrammar::CheckMany,
this,
qi::labels::_val,
qi::labels::_1)] |
("checksum(" > filePath_ > ',' > qi::hex >
')')[phoenix::bind(&ConditionGrammar::CheckSum,
this,
qi::labels::_val,
qi::labels::_1,
qi::labels::_2)] |
("version(" > filePath_ > ',' > quotedStr_ > ',' > comparator_ >
')')[phoenix::bind(&ConditionGrammar::CheckVersion,
this,
qi::labels::_val,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3)] |
("active(" > quotedStr_ >
')')[phoenix::bind(&ConditionGrammar::CheckActive,
this,
qi::labels::_val,
qi::labels::_1)] |
("many_active(" > quotedStr_ >
')')[phoenix::bind(&ConditionGrammar::CheckManyActive,
this,
qi::labels::_val,
qi::labels::_1)];
quotedStr_ %= '"' > +(char_ - '"') > '"';
filePath_ %= '"' > +(char_ - invalidPathChars_) > '"';
invalidPathChars_ %= char_(':') | char_('*') | char_('?') | char_('"') |
char_('<') | char_('>') | char_('|');
comparator_ %= string("==") | string("!=") | string("<=") | string(">=") |
string("<") | string(">");
expression_.name("expression");
compound_.name("compound condition");
condition_.name("condition");
function_.name("function");
quotedStr_.name("quoted string");
filePath_.name("file path");
comparator_.name("comparator");
invalidPathChars_.name("invalid file path characters");
qi::on_error<qi::fail>(expression_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
qi::on_error<qi::fail>(compound_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
qi::on_error<qi::fail>(condition_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
qi::on_error<qi::fail>(function_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
qi::on_error<qi::fail>(quotedStr_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
qi::on_error<qi::fail>(filePath_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
qi::on_error<qi::fail>(comparator_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
qi::on_error<qi::fail>(invalidPathChars_,
phoenix::bind(&ConditionGrammar::SyntaxError,
this,
qi::labels::_1,
qi::labels::_2,
qi::labels::_3,
qi::labels::_4));
logger_ = getLogger();
}
private:
bool IsRegex(const std::string& file) const {
// Treat as regex if the plugin filename contains any of ":\*?|" as
// they are not valid Windows filename characters, but have meaning
// in regexes.
return strpbrk(file.c_str(), ":\\*?|") != nullptr;
}
// Eval's exact paths. Check for files and ghosted plugins.
void CheckFile(bool& result, const std::string& file) const {
if (logger_) {
logger_->trace("Checking to see if the file \"{}\" exists.", file);
}
result = false;
if (IsRegex(file))
result = evaluator_.regexMatchExists(file);
else
result = evaluator_.fileExists(file);
if (logger_) {
logger_->trace("File check result: {}", result);
}
}
void CheckMany(bool& result, const std::string& regexStr) const {
if (logger_) {
logger_->trace(
"Checking to see if more than one file matching the regex \"{}\" "
"exists.",
regexStr);
}
result = false;
result = evaluator_.regexMatchesExist(regexStr);
}
void CheckSum(bool& result,
const std::string& file,
const uint32_t checksum) {
if (logger_) {
logger_->trace("Checking the CRC of the file \"{}\".", file);
}
result = false;
result = evaluator_.checksumMatches(file, checksum);
}
void CheckVersion(bool& result,
const std::string& file,
const std::string& version,
const std::string& comparator) const {
if (logger_) {
logger_->trace("Checking the version of the file \"{}\".", file);
}
result = false;
result = evaluator_.compareVersions(file, version, comparator);
if (logger_) {
logger_->trace("Version check result: {}", result);
}
}
void CheckActive(bool& result, const std::string& file) const {
result = false;
if (IsRegex(file))
result = evaluator_.isPluginMatchingRegexActive(file);
else
result = evaluator_.isPluginActive(file);
if (logger_) {
logger_->trace("Active check result: {}", result);
}
}
void CheckManyActive(bool& result, const std::string& regexStr) const {
if (logger_) {
logger_->trace(
"Checking to see if more than one file matching the regex \"{}\" is "
"active.",
regexStr);
}
result = false;
result = evaluator_.arePluginsActive(regexStr);
}
void SyntaxError(Iterator const& first,
Iterator const& last,
Iterator const& errorpos,
boost::spirit::info const& what) {
std::string condition(first, last);
std::string context(errorpos, last);
boost::trim(context);
throw ConditionSyntaxError("Failed to parse condition \"" + condition +
"\": expected \"" + what.tag + "\" at \"" +
context + "\".");
}
boost::spirit::qi::rule<Iterator, bool(), Skipper> expression_, compound_,
condition_, function_;
boost::spirit::qi::rule<Iterator, std::string()> quotedStr_, filePath_,
comparator_;
boost::spirit::qi::rule<Iterator, char()> invalidPathChars_;
const ConditionEvaluator& evaluator_;
std::shared_ptr<spdlog::logger> logger_;
};
}
#endif
+1 -5
View File
@@ -42,11 +42,7 @@ std::string ConditionalMetadata::GetCondition() const { return condition_; }
void ConditionalMetadata::ParseCondition() const {
if (!condition_.empty()) {
auto logger = getLogger();
if (logger) {
logger->trace("Testing condition syntax: {}", condition_);
}
ConditionEvaluator().evaluate(condition_);
loot::ParseCondition(condition_);
}
}
}
+4 -4
View File
@@ -195,14 +195,14 @@ void MetadataList::AppendMessage(const Message& message) {
}
void MetadataList::EvalAllConditions(
const ConditionEvaluator& conditionEvaluator) {
ConditionEvaluator& conditionEvaluator) {
if (unevaluatedPlugins_.empty())
unevaluatedPlugins_.swap(plugins_);
else
plugins_.clear();
for (const auto& plugin : unevaluatedPlugins_) {
plugins_.insert(conditionEvaluator.evaluateAll(plugin));
plugins_.insert(conditionEvaluator.EvaluateAll(plugin));
}
if (unevaluatedRegexPlugins_.empty())
@@ -211,7 +211,7 @@ void MetadataList::EvalAllConditions(
regexPlugins_ = unevaluatedRegexPlugins_;
for (auto& plugin : regexPlugins_) {
plugin = conditionEvaluator.evaluateAll(plugin);
plugin = conditionEvaluator.EvaluateAll(plugin);
}
if (unevaluatedMessages_.empty())
@@ -220,7 +220,7 @@ void MetadataList::EvalAllConditions(
messages_.clear();
for (const auto& message : unevaluatedMessages_) {
if (conditionEvaluator.evaluate(message.GetCondition()))
if (conditionEvaluator.Evaluate(message.GetCondition()))
messages_.push_back(message);
}
}
+1 -1
View File
@@ -60,7 +60,7 @@ public:
void AppendMessage(const Message& message);
// Eval plugin conditions.
void EvalAllConditions(const ConditionEvaluator& conditionEvaluator);
void EvalAllConditions(ConditionEvaluator& conditionEvaluator);
protected:
std::unordered_set<Group> groups_;
+1 -6
View File
@@ -139,12 +139,7 @@ float Plugin::GetHeaderVersion() const {
}
std::optional<std::string> Plugin::GetVersion() const {
std::string version = Version(GetDescription()).AsString();
if (version.empty()) {
return std::nullopt;
}
return version;
return ExtractVersion(GetDescription());
}
std::vector<std::string> Plugin::GetMasters() const {
@@ -52,40 +52,6 @@ protected:
// all.
INSTANTIATE_TEST_CASE_P(, GameCacheTest, ::testing::Values(GameType::tes5));
TEST_P(GameCacheTest, gettingATrueConditionShouldReturnATrueTruePair) {
EXPECT_NO_THROW(cache_.CacheCondition(condition, true));
EXPECT_EQ(std::make_pair(true, true),
cache_.GetCachedCondition(condition));
}
TEST_P(GameCacheTest, gettingAFalseConditionShouldReturnAFalseTruePair) {
EXPECT_NO_THROW(cache_.CacheCondition(condition, false));
EXPECT_EQ(std::make_pair(false, true),
cache_.GetCachedCondition(condition));
}
TEST_P(GameCacheTest, gettingACachedConditionShouldBeCaseSensitive) {
EXPECT_NO_THROW(cache_.CacheCondition(condition, false));
EXPECT_EQ(std::make_pair(false, false),
cache_.GetCachedCondition(conditionLowercase));
}
TEST_P(GameCacheTest, gettingANonCachedConditionShouldReturnAFalseFalsePair) {
EXPECT_EQ(std::make_pair(false, false), cache_.GetCachedCondition(condition));
}
TEST_P(GameCacheTest, gettingACachedCrcShouldReturnTheValue) {
cache_.CacheCrc(boost::locale::to_upper(blankEsm), 5);
EXPECT_EQ(5, cache_.GetCachedCrc(blankEsm));
}
TEST_P(GameCacheTest, gettingAnUncachedCrcShouldReturnZero) {
EXPECT_EQ(0, cache_.GetCachedCrc(blankEsm));
}
TEST_P(GameCacheTest, addingAPluginThatDoesNotExistShouldSucceed) {
cache_.AddPlugin(Plugin(game_.Type(),
std::make_shared<GameCache>(GameCache()),
@@ -158,22 +124,6 @@ TEST_P(GameCacheTest,
EXPECT_EQ(expected, cache_.GetArchivePaths());
}
TEST_P(GameCacheTest,
clearingCachedConditionsShouldNotThrowIfNoConditionsAreCached) {
EXPECT_NO_THROW(cache_.ClearCachedConditions());
}
TEST_P(GameCacheTest, clearingCachedConditionsShouldClearAnyCachedConditionsAndCrcs) {
EXPECT_NO_THROW(cache_.CacheCondition(condition, true));
cache_.CacheCrc(blankEsm, 5);
EXPECT_NO_THROW(cache_.ClearCachedConditions());
EXPECT_EQ(std::make_pair(false, false),
cache_.GetCachedCondition(conditionLowercase));
EXPECT_EQ(0, cache_.GetCachedCrc(blankEsm));
}
TEST_P(GameCacheTest, clearingCachedPluginsShouldNotThrowIfNoPluginsAreCached) {
EXPECT_NO_THROW(cache_.ClearCachedPlugins());
}
@@ -90,6 +90,16 @@ protected:
}
}
std::vector<std::string> getActivePlugins() {
std::vector<std::string> activePlugins;
for (auto& pair : getInitialLoadOrder()) {
if (pair.second) {
activePlugins.push_back(pair.first);
}
}
return activePlugins;
}
LoadOrderHandler loadOrderHandler_;
std::vector<std::string> loadOrderToSet_;
};
@@ -170,6 +180,28 @@ TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
ASSERT_EQ(getLoadOrder(), loadOrderHandler_.GetLoadOrder());
}
TEST_P(LoadOrderHandlerTest,
getActivePluginsShouldThrowIfTheHandlerHasNotBeenInitialised) {
EXPECT_THROW(loadOrderHandler_.GetActivePlugins(),
std::system_error);
}
TEST_P(LoadOrderHandlerTest,
getActivePluginsShouldReturnAnEmptyVectorIfStateHasNotBeenLoaded) {
initialiseHandler();
EXPECT_TRUE(loadOrderHandler_.GetActivePlugins().empty());
}
TEST_P(
LoadOrderHandlerTest,
getActivePluginsShouldReturnOnlyActivePlugins) {
initialiseHandler();
loadOrderHandler_.LoadCurrentState();
ASSERT_EQ(getActivePlugins(),
loadOrderHandler_.GetActivePlugins());
}
TEST_P(LoadOrderHandlerTest,
getImplicitlyActivePluginsShouldThrowIfTheHandlerHasNotBeenInitialised) {
EXPECT_THROW(loadOrderHandler_.GetImplicitlyActivePlugins(),

Some files were not shown because too many files have changed in this diff Show More