mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
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:
+18
-13
@@ -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})
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_);
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user