diff --git a/CMakeLists.txt b/CMakeLists.txt index 3828bd7..0bc8ef8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ add_subdirectory(${PYBIND11_EXTRACTED_PATH}) ExternalProject_Add(loot-api-c++ PREFIX "external" - URL "https://github.com/loot/loot/releases/download/0.10.2/loot-api_0.10.2-0-g9eb7d26_dev.7z" + URL "https://github.com/loot/loot-api/releases/download/0.11.0/loot_api-0.11.0-0-g8864f78_dev-win32.7z" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "") @@ -79,7 +79,9 @@ ExternalProject_Add(test-masterlist # Python Module ####################################### -pybind11_add_module(loot_api "${CMAKE_SOURCE_DIR}/src/main.cpp" "${CMAKE_BINARY_DIR}/generated/wrapper_version.cpp") +pybind11_add_module(loot_api "${CMAKE_SOURCE_DIR}/src/main.cpp" + "${CMAKE_SOURCE_DIR}/src/convenience.cpp" + "${CMAKE_BINARY_DIR}/generated/wrapper_version.cpp") add_dependencies(loot_api loot-api-c++ test-masterlist) diff --git a/README.md b/README.md index 6b5fed5..6f23f23 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ LOOT API Python Module [![Travis Build Status](https://travis-ci.org/loot/loot-api-python.svg?branch=master)](https://travis-ci.org/loot/loot-api-python) [![Documentation Status](https://readthedocs.org/projects/loot-api-python/badge/)](http://loot-api-python.readthedocs.io/) -A Python module that wraps the LOOT API, generated by [pybind11](https://github.com/pybind/pybind11). +A Python module that wraps the LOOT API, generated by [pybind11](https://github.com/pybind/pybind11). Not everything in the API is exposed: coverage can be extended on request (or by pull request). ## Downloads diff --git a/src/convenience.cpp b/src/convenience.cpp new file mode 100644 index 0000000..2d58786 --- /dev/null +++ b/src/convenience.cpp @@ -0,0 +1,79 @@ +/* LOOT + +A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and +Fallout: New Vegas. + +Copyright (C) 2014-2017 Oliver Hamlet + +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 +. +*/ + +#include "convenience.h" + +#include + +namespace loot { +std::shared_ptr CreateDatabase(const GameType game, const std::string & game_path, const std::string & game_local_path) { + auto gameHandle = CreateGameHandle(game, game_path, game_local_path); + + return gameHandle->GetDatabase(); +} +PluginTags GetPluginTags(const std::shared_ptr db, const std::string& plugin) { + PluginTags tags; + + auto metadata = db->GetPluginMetadata(plugin, false, false); + for (const auto &tag : metadata.GetTags()) { + if (tag.IsAddition()) + tags.added.insert(tag.GetName()); + else + tags.removed.insert(tag.GetName()); + } + + metadata = db->GetPluginUserMetadata(plugin, false); + tags.userlist_modified = !metadata.GetTags().empty(); + for (const auto &tag : metadata.GetTags()) { + if (tag.IsAddition()) + tags.added.insert(tag.GetName()); + else + tags.removed.insert(tag.GetName()); + } + + return tags; +} + +PluginCleanliness GetPluginCleanliness(const std::shared_ptr db, const std::string& plugin) { + auto metadata = db->GetPluginMetadata(plugin, true, false); + + if (metadata.GetDirtyInfo().empty()) { + if (metadata.GetCleanInfo().empty()) { + return PluginCleanliness::unknown; + } else { + return PluginCleanliness::clean; + } + } else if (!metadata.GetCleanInfo().empty()) { + return PluginCleanliness::unknown; + } + + for (const auto& info : metadata.GetDirtyInfo()) { + if (info.ChooseInfo("en").GetText().find("Do not clean") != std::string::npos) { + return PluginCleanliness::do_not_clean; + } + } + + return PluginCleanliness::unknown; +} +} diff --git a/src/convenience.h b/src/convenience.h new file mode 100644 index 0000000..b19484a --- /dev/null +++ b/src/convenience.h @@ -0,0 +1,46 @@ +/* LOOT + +A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and +Fallout: New Vegas. + +Copyright (C) 2014-2017 Oliver Hamlet + +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 +. +*/ + +#ifndef LOOT_API_PYTHON_CONVENIENCE +#define LOOT_API_PYTHON_CONVENIENCE + +#include + +#include +#include + +#include "plugin_cleanliness.h" +#include "plugin_tags.h" + +namespace loot { +std::shared_ptr CreateDatabase(const GameType game, + const std::string& game_path = "", + const std::string& game_local_path = ""); + +PluginTags GetPluginTags(const std::shared_ptr db, const std::string& plugin); + +PluginCleanliness GetPluginCleanliness(const std::shared_ptr db, const std::string& plugin); +} + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e71ca47..afdea1f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,29 +26,16 @@ #include #include +#include "convenience.h" #include "wrapper_version.h" -PYBIND11_PLUGIN(loot_api) { - using loot::CreateDatabase; - using loot::DatabaseInterface; - using loot::IsCompatible; - using loot::GameType; - using loot::LanguageCode; - using loot::LootVersion; - using loot::MessageType; - using loot::MasterlistInfo; - using loot::PluginCleanliness; - using loot::SimpleMessage; - using loot::PluginTags; - using loot::WrapperVersion; - - using pybind11::arg; - using pybind11::enum_; - using pybind11::class_; - using pybind11::metaclass; - - pybind11::module module("loot_api", "A Python module that wraps the LOOT API, generated by pybind11."); +using pybind11::arg; +using pybind11::enum_; +using pybind11::class_; +using pybind11::metaclass; +namespace loot { +void bindEnums(pybind11::module& module) { enum_(module, "GameType") .value("tes4", GameType::tes4) .value("tes5", GameType::tes5) @@ -57,18 +44,10 @@ PYBIND11_PLUGIN(loot_api) { .value("fonv", GameType::fonv) .value("fo4", GameType::fo4); - enum_(module, "LanguageCode") - .value("english", LanguageCode::english) - .value("spanish", LanguageCode::spanish) - .value("russian", LanguageCode::russian) - .value("french", LanguageCode::french) - .value("chinese", LanguageCode::chinese) - .value("polish", LanguageCode::polish) - .value("brazilian_portuguese", LanguageCode::brazilian_portuguese) - .value("finnish", LanguageCode::finnish) - .value("german", LanguageCode::german) - .value("danish", LanguageCode::danish) - .value("korean", LanguageCode::korean); + enum_(module, "LogVerbosity") + .value("off", LogVerbosity::off) + .value("trace", LogVerbosity::trace) + .value("warning", LogVerbosity::warning); enum_(module, "MessageType") .value("say", MessageType::say) @@ -80,13 +59,15 @@ PYBIND11_PLUGIN(loot_api) { .value("dirty", PluginCleanliness::dirty) .value("do_not_clean", PluginCleanliness::do_not_clean) .value("unknown", PluginCleanliness::unknown); +} +void bindMetadataClasses(pybind11::module& module) { class_(module, "MasterlistInfo") .def_readwrite("revision_id", &MasterlistInfo::revision_id) .def_readwrite("revision_date", &MasterlistInfo::revision_date) .def_readwrite("is_modified", &MasterlistInfo::is_modified); - class_(module, "Message") + class_(module, "SimpleMessage") .def_readwrite("type", &SimpleMessage::type) .def_readwrite("language", &SimpleMessage::language) .def_readwrite("text", &SimpleMessage::text); @@ -96,6 +77,11 @@ PYBIND11_PLUGIN(loot_api) { .def_readwrite("removed", &PluginTags::removed) .def_readwrite("userlist_modified", &PluginTags::userlist_modified); + class_(module, "PluginMetadata") + .def("get_simple_messages", &PluginMetadata::GetSimpleMessages); +} + +void bindVersionClasses(pybind11::module& module) { class_(module, "Version", metaclass()) .def_readonly_static("major", &LootVersion::major) .def_readonly_static("minor", &LootVersion::minor) @@ -109,21 +95,48 @@ PYBIND11_PLUGIN(loot_api) { .def_readonly_static("patch", &WrapperVersion::patch) .def_readonly_static("revision", &WrapperVersion::revision) .def_static("string", WrapperVersion::string); +} +void bindInterfaceClasses(pybind11::module& module) { class_>(module, "DatabaseInterface") .def("load_lists", &DatabaseInterface::LoadLists, arg("masterlist_path"), arg("userlist_path") = "") .def("eval_lists", &DatabaseInterface::EvalLists) - .def("sort_plugins", &DatabaseInterface::SortPlugins) .def("update_masterlist", &DatabaseInterface::UpdateMasterlist) .def("get_masterlist_revision", &DatabaseInterface::GetMasterlistRevision) - .def("get_plugin_tags", &DatabaseInterface::GetPluginTags) - .def("get_plugin_messages", &DatabaseInterface::GetPluginMessages) - .def("get_plugin_cleanliness", &DatabaseInterface::GetPluginCleanliness) + .def("get_plugin_metadata", &DatabaseInterface::GetPluginMetadata, + arg("plugin"), + arg("includeUserMetadata") = true, + arg("evaluateConditions") = false) + .def("get_plugin_tags", &GetPluginTags) + .def("get_plugin_cleanliness", &GetPluginCleanliness) .def("write_minimal_list", &DatabaseInterface::WriteMinimalList); +} + +void bindClasses(pybind11::module& module) { + bindMetadataClasses(module); + bindVersionClasses(module); + bindInterfaceClasses(module); +} + +void bindFunctions(pybind11::module& module) { + module.def("set_logging_verbosity", &SetLoggingVerbosity); + + module.def("set_log_file", &SetLogFile); module.def("is_compatible", &IsCompatible); + module.def("initialise_locale", &InitialiseLocale); + module.def("create_database", &CreateDatabase, arg("game"), arg("game_path") = "", arg("game_local_path") = ""); +} +} + +PYBIND11_PLUGIN(loot_api) { + pybind11::module module("loot_api", "A Python module that wraps the LOOT API, generated by pybind11."); + + loot::bindEnums(module); + loot::bindClasses(module); + loot::bindFunctions(module); return module.ptr(); } diff --git a/src/plugin_cleanliness.h b/src/plugin_cleanliness.h new file mode 100644 index 0000000..58f4a0b --- /dev/null +++ b/src/plugin_cleanliness.h @@ -0,0 +1,52 @@ +/* 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 + . + */ +#ifndef LOOT_PLUGIN_CLEANLINESS +#define LOOT_PLUGIN_CLEANLINESS + +/** + * The namespace used by the LOOT API. + */ +namespace loot { +/** + * @brief Codes used to indicate the cleanliness of a plugin according to the + * information contained within the loaded masterlist/userlist. + */ +enum struct PluginCleanliness : unsigned int { + /** Indicates that the plugin is clean. */ + clean, + /** Indicates that the plugin is dirty. */ + dirty, + /** + * Indicates that the plugin contains dirty edits, but that they are + * part of the plugin's intended functionality and should not be removed. + */ + do_not_clean, + /** + * Indicates that no data is available on whether the plugin is dirty or not. + */ + unknown, +}; +} + +#endif diff --git a/src/plugin_tags.h b/src/plugin_tags.h new file mode 100644 index 0000000..ef45b5d --- /dev/null +++ b/src/plugin_tags.h @@ -0,0 +1,58 @@ +/* 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 + . + */ +#ifndef LOOT_PLUGIN_TAGS +#define LOOT_PLUGIN_TAGS + +#include +#include + +namespace loot { +/** + * @brief A structure that holds data about the Bash Tag suggestions made by + * LOOT for a plugin. + */ +struct PluginTags { + inline PluginTags() : userlist_modified(false) {} + + /** + * @brief A set of Bash Tag names suggested for addition to the specified + * plugin. Empty if no Bash Tag additions are suggested. + */ + std::set added; + + /** + * @brief A set of Bash Tag names suggested for removal from the specified + * plugin. Empty if no Bash Tag removals are suggested. + */ + std::set removed; + + /** + * @brief `true` if the Bash Tag suggestions were modified by data in the + * userlist, `false` otherwise. + */ + bool userlist_modified; +}; +} + +#endif diff --git a/src/test.py b/src/test.py index 779111d..2ba192e 100644 --- a/src/test.py +++ b/src/test.py @@ -7,11 +7,16 @@ import unittest from loot_api import Version from loot_api import WrapperVersion from loot_api import GameType -from loot_api import LanguageCode -from loot_api import Message +from loot_api import LogVerbosity +from loot_api import SimpleMessage from loot_api import MessageType from loot_api import create_database from loot_api import is_compatible +from loot_api import set_logging_verbosity +from loot_api import initialise_locale + +set_logging_verbosity(LogVerbosity.off) +initialise_locale("") class GameFixture(unittest.TestCase): game_path = os.path.join(u'.', u'Oblivion') @@ -33,14 +38,14 @@ class GameFixture(unittest.TestCase): class TestLootApi(GameFixture): def test_is_compatible(self): self.assertFalse(is_compatible(0, 9, 0)) - self.assertTrue(is_compatible(0, 10, 0)) + self.assertTrue(is_compatible(0, 11, 0)) def test_version(self): self.assertEqual(Version.major, 0) - self.assertEqual(Version.minor, 10) - self.assertEqual(Version.patch, 2) + self.assertEqual(Version.minor, 11) + self.assertEqual(Version.patch, 0) self.assertNotEqual(Version.revision, u'') - self.assertEqual(Version.string(), "0.10.2") + self.assertEqual(Version.string(), "0.11.0") def test_wrapper_version(self): self.assertEqual(WrapperVersion.major, 1) @@ -96,18 +101,10 @@ class TestDatabaseInterface(GameFixture): ])) self.assertEqual(tags.removed, set([u'C.Water'])) -# cProfile.runctx('self.test_get_plugin_tags_performance()', globals(), locals(), sort='cumtime') - -# def test_get_plugin_tags_performance(self): -# self.db.load_lists(self.masterlist_path, u'') -# -# for i in xrange(200000): -# tags = self.db.get_plugin_tags(u'Unofficial Oblivion Patch.esp') - def test_get_plugin_messages(self): self.db.load_lists(self.masterlist_path, u'') - messages = self.db.get_plugin_messages(u'Oblivion.esm', LanguageCode.english) + messages = self.db.get_plugin_metadata(u'Oblivion.esm').get_simple_messages(u'en') self.assertEqual(len(messages), 1) self.assertEqual(messages[0].type, MessageType.error)