From 735e4928c497dee27a569aac62f1d0deae945b72 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Thu, 2 May 2013 19:41:42 +0100 Subject: [PATCH] Tied in masterlist and userlist metadata with plugin data read, implemented merging and evaluation of conditions. --- README | 16 ++-- docs/BOSS Metadata File Syntax.html | 10 +-- src/metadata.cpp | 65 +++++++++++--- src/metadata.h | 21 ++--- src/parsers.h | 6 +- src/tester.cpp | 135 ++++++++++++++++++---------- 6 files changed, 170 insertions(+), 83 deletions(-) diff --git a/README b/README index bcb184dd..a20ef096 100644 --- a/README +++ b/README @@ -42,13 +42,15 @@ BOSSv3 won't have a command line interface. Command line options may be added to Roadmap ======= -1. Write new masterlist, userlist, settings file parsers. DONE -2. Implement per-game handling. DONE -3. Write API. DONE -4. Develop sorting algorithm. -5. Tie together automatic 'simple' sorting with masterlist, userlist data parsing and usage. -6. Write masterlist updating code. -7. Develop graphical user interface. +- Write new masterlist, userlist, settings file parsers. DONE +- Implement per-game handling. DONE +- Write API. DONE +- Develop sorting algorithm. +- Tie together automatic 'simple' sorting with masterlist, userlist data parsing and usage. DONE +- Write masterlist updating code. +- Develop graphical user interface. +- Error handling. +- Initialisation and finishing routines (read settings file, write log file, etc.). Sorting Algorithm diff --git a/docs/BOSS Metadata File Syntax.html b/docs/BOSS Metadata File Syntax.html index 29905d9a..f027c0ae 100644 --- a/docs/BOSS Metadata File Syntax.html +++ b/docs/BOSS Metadata File Syntax.html @@ -234,11 +234,11 @@ condition: "file(\"Mart's Monster Mod for OOO.esm\") or file(\"FCOM_Convergence. namestring✓Can be an exact plugin filename or a regular expression plugin filename. If the period that precedes the file extension has been escaped (eg. \.esp, \.esm), the string is treated as a regular expression, otherwise it is treated as an exact filename. enabledboolean✗Enables or disables use of the plugin object. Used for user rules, but no reason to use it in the masterlist. If unspecified, defaults to true. priorityinteger✗??? If unspecified, defaults to 0. - afterfile list✗Plugins that this plugin must load after, but which are not dependencies. Used for resolving specific compatibility issues and by user rules for specifying custom plugin positions. - reqfile list✗Files that this plugin requires to be present. If any of these files are missing, an error message will be displayed. The list can contain a mixture of plugins and other files, but all plugins must be given in their required load order relative to one another. Intended for use specifying implicit dependencies, as BOSS will detect a plugin's explicit masters itself. - incfile list✗Files that this plugin is incompatible with. If any of these files are present, an error message will be displayed. - msgmessage list✗The messages attached to this plugin. - tagtag list✗The Bash Tags suggested for this plugin. + afterfile list✗An unordered list of plugins that this plugin must load after, but which are not dependencies. Used for resolving specific compatibility issues and by user rules for specifying custom plugin positions. + reqfile list✗An unordered list of files that this plugin requires to be present. If any of these files are missing, an error message will be displayed. Intended for use specifying implicit dependencies, as BOSS will detect a plugin's explicit masters itself. + incfile list✗An unordered list of files that this plugin is incompatible with. If any of these files are present, an error message will be displayed. + msgmessage list✗The messages attached to this plugin. The messages will be displayed in the order that they are listed. + tagtag list✗An unordered list of Bash Tags suggested for this plugin.

Example: name: "Oscuro's_Oblivion_Overhaul.esm" diff --git a/src/metadata.cpp b/src/metadata.cpp index 067c2625..edcff95a 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -169,7 +169,7 @@ namespace boss { } bool Tag::operator < (const Tag& rhs) const { - return Name() < rhs.Name(); + return (Name() < rhs.Name()); } bool Tag::IsAddition() const { @@ -216,6 +216,39 @@ namespace boss { } } + void Plugin::Merge(const Plugin& plugin) { + //If 'name' differs or if 'enabled' is false for the given plugin, don't change anything. + if (!boost::iequals(name, plugin.Name()) || !plugin.Enabled()) + return; + + //The following should be replaced. + priority = plugin.Priority(); + masters = plugin.Masters(); + formIDs = plugin.FormIDs(); + isMaster = plugin.IsMaster(); + + //Merge the following. If any files in the source already exist in the destination, they will be skipped. Files have display strings and condition strings which aren't considered when comparing them, so will be lost if the plugin being merged in has additional data in these strings. + std::set files = plugin.LoadAfter(); + loadAfter.insert(files.begin(), files.end()); + + files = plugin.Reqs(); + requirements.insert(files.begin(), files.end()); + + files = plugin.Incs(); + incompatibilities.insert(files.begin(), files.end()); + + //Merge Bash Tags too, but Tags can be added or removed, and BOSS should not be doing both for any one Tag, so whether a tag is added or removed is not considered during comparison. As such, the Tags from the plugin being merged in should override any equal-but-opposite Tags that already exist. + std::set bashTags = plugin.Tags(); + bashTags.insert(tags.begin(), tags.end()); + tags = bashTags; + + //Messages are in an ordered list, and should be fully merged. + std::list pMessages = plugin.Messages(); + messages.insert(messages.end(), pMessages.begin(), pMessages.end()); + + return; + } + std::string Plugin::Name() const { return name; } @@ -228,11 +261,11 @@ namespace boss { return priority; } - std::list Plugin::LoadAfter() const { + std::set Plugin::LoadAfter() const { return loadAfter; } - std::list Plugin::Reqs() const { + std::set Plugin::Reqs() const { return requirements; } @@ -244,7 +277,7 @@ namespace boss { return messages; } - std::list Plugin::Tags() const { + std::set Plugin::Tags() const { return tags; } @@ -260,11 +293,11 @@ namespace boss { priority = p; } - void Plugin::LoadAfter(const std::list& l) { + void Plugin::LoadAfter(const std::set& l) { loadAfter = l; } - void Plugin::Reqs(const std::list& r) { + void Plugin::Reqs(const std::set& r) { requirements = r; } @@ -276,21 +309,21 @@ namespace boss { messages = m; } - void Plugin::Tags(const std::list& t) { + void Plugin::Tags(const std::set& t) { tags = t; } void Plugin::EvalAllConditions(boss::Game& game) { - for (list::iterator it = loadAfter.begin(); it != loadAfter.end();) { + for (set::iterator it = loadAfter.begin(); it != loadAfter.end();) { if (!it->EvalCondition(game)) - it = loadAfter.erase(it); + loadAfter.erase(it++); else ++it; } - for (list::iterator it = requirements.begin(); it != requirements.end();) { + for (set::iterator it = requirements.begin(); it != requirements.end();) { if (!it->EvalCondition(game)) - it = requirements.erase(it); + requirements.erase(it++); else ++it; } @@ -309,9 +342,9 @@ namespace boss { ++it; } - for (list::iterator it = tags.begin(); it != tags.end();) { + for (set::iterator it = tags.begin(); it != tags.end();) { if (!it->EvalCondition(game)) - it = tags.erase(it++); + tags.erase(it++); else ++it; } @@ -352,6 +385,12 @@ namespace boss { if (IsChildOf(rhs)) // Should probably also check for cyclic masters. return false; + if (Priority() > rhs.Priority()) + return true; + + if (Priority() < rhs.Priority()) + return false; + if (!OverlapFormIDs(rhs).empty() && formIDs.size() != rhs.FormIDs().size()) return formIDs.size() > rhs.FormIDs().size(); diff --git a/src/metadata.h b/src/metadata.h index 3824ba92..b262da4f 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -125,23 +125,25 @@ namespace boss { Plugin(const std::string& name); Plugin(const std::string& name, const std::string& path); + void Merge(const Plugin& plugin); + std::string Name() const; bool Enabled() const; int Priority() const; - std::list LoadAfter() const; - std::list Reqs() const; + std::set LoadAfter() const; + std::set Reqs() const; std::set Incs() const; std::list Messages() const; - std::list Tags() const; + std::set Tags() const; void Name(const std::string& name); void Enabled(const bool enabled); void Priority(const int priority); - void LoadAfter(const std::list& after); - void Reqs(const std::list& reqs); + void LoadAfter(const std::set& after); + void Reqs(const std::set& reqs); void Incs(const std::set& incs); void Messages(const std::list& messages); - void Tags(const std::list& tags); + void Tags(const std::set& tags); void EvalAllConditions(boss::Game& game); bool HasNameOnly() const; @@ -167,17 +169,16 @@ namespace boss { std::string name; bool enabled; //Default to true. int priority; //Default to 0 : >0 is higher, <0 is lower priorities. - std::list loadAfter; - std::list requirements; + std::set loadAfter; + std::set requirements; std::set incompatibilities; std::list messages; - std::list tags; + std::set tags; std::vector masters; std::set formIDs; bool isMaster; }; - } #endif diff --git a/src/parsers.h b/src/parsers.h index 02dafc88..55760948 100644 --- a/src/parsers.h +++ b/src/parsers.h @@ -185,15 +185,15 @@ namespace YAML { rhs.Priority(node["priority"].as()); if (node["after"]) - rhs.LoadAfter(node["after"].as< std::list >()); + rhs.LoadAfter(node["after"].as< std::set >()); if (node["req"]) - rhs.Reqs(node["req"].as< std::list >()); + rhs.Reqs(node["req"].as< std::set >()); if (node["inc"]) rhs.Incs(node["inc"].as< std::set >()); if (node["msg"]) rhs.Messages(node["msg"].as< std::list >()); if (node["tag"]) - rhs.Tags(node["tag"].as< std::list >()); + rhs.Tags(node["tag"].as< std::set >()); return true; } }; diff --git a/src/tester.cpp b/src/tester.cpp index 6f273617..e29af193 100644 --- a/src/tester.cpp +++ b/src/tester.cpp @@ -50,58 +50,26 @@ using namespace std; const char * const libespm_options_path = "libespm.cfg"; const char * const libespm_game = "Skyrim"; +const char * const masterlist_path = "masterlist.yaml"; +const char * const userlist_path = "userlist.yaml"; + int main(int argc, char *argv[]) { - /* cout << "Testing masterlist parser." << endl; + /* Stuff still missing from a normal execution of BOSS: - YAML::Node test = YAML::LoadFile("masterlist-example.yaml"); + - Reading of settings file. + - Detecting game to run for. + - Error handling. + - Masterlist updating. + - Writing of log file. - list globalMessages = test["globals"].as< list >(); - list pluginData = test["plugins"].as< list >(); + Need to include libespm setup into game setup, once the former is made to be not global. + */ - cout << "Testing masterlist generator." << endl; - - // minimisePluginList(pluginData); - - ofstream out("generated.yaml"); - YAML::Emitter yout; - yout.SetIndent(2); - yout << YAML::BeginMap - << YAML::Key << "globals" << YAML::Value << globalMessages - << YAML::Key << "plugins" << YAML::Value << pluginData - << YAML::EndMap; - - out << yout.c_str(); - out.close(); - - cout << "Testing game settings structure." << endl; - - boss::Game game(boss::GAME_TES5, "/media/oliver/6CF05918F058EA3A/Program Files (x86)/Steam/steamapps/common/skyrim"); - - for (list::iterator it=pluginData.begin(), endIt=pluginData.end(); it != endIt; ++it) { - try { - it->EvalAllConditions(game); - } catch (boss::error& e) { - cout << e.what() << endl; - } - } - - ofstream out2("evaled.yaml"); - YAML::Emitter yout2; - yout2.SetIndent(2); - yout2 << YAML::BeginMap - << YAML::Key << "globals" << YAML::Value << globalMessages - << YAML::Key << "plugins" << YAML::Value << pluginData - << YAML::EndMap; - - out2 << yout2.c_str(); - out2.close(); - */ cout << "Setting up libespm and BOSS..." << endl; - - + // Set up libesm. common::options::setGame(libespm_game); ifstream input(libespm_options_path); @@ -136,6 +104,82 @@ int main(int argc, char *argv[]) { cout << "Time taken to read plugins: " << (end - start) << " seconds." << endl; start = time(NULL); + YAML::Node mlist, ulist; + list messages, mlist_messages, ulist_messages; + list mlist_plugins, ulist_plugins; + + if (fs::exists(masterlist_path)) { + cout << "Parsing masterlist..." << endl; + + mlist = YAML::LoadFile(masterlist_path); + if (mlist["globals"]) + mlist_messages = mlist["globals"].as< list >(); + if (mlist["plugins"]) + mlist_plugins = mlist["plugins"].as< list >(); + + end = time(NULL); + cout << "Time taken to parse masterlist: " << (end - start) << " seconds." << endl; + start = time(NULL); + } + + if (fs::exists(userlist_path)) { + cout << "Parsing userlist..." << endl; + + ulist = YAML::LoadFile(userlist_path); + if (ulist["globals"]) + ulist_messages = ulist["globals"].as< list >(); + if (ulist["plugins"]) + ulist_plugins = ulist["plugins"].as< list >(); + + end = time(NULL); + cout << "Time taken to parse userlist: " << (end - start) << " seconds." << endl; + start = time(NULL); + } + + if (fs::exists(masterlist_path) || fs::exists(userlist_path)) { + cout << "Merging plugin lists..." << endl; + + //Merge all global message lists. + messages = mlist_messages; + messages.insert(messages.end(), ulist_messages.begin(), ulist_messages.end()); + + //Merge plugin list, masterlist and userlist plugin data. + for (list::iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) { + //Check if there is already a plugin in the 'plugins' list or not. + list::iterator pos = std::find(mlist_plugins.begin(), mlist_plugins.end(), *it); + + if (pos != mlist_plugins.end()) { + //Need to merge plugins. + it->Merge(*pos); + } + + pos = std::find(ulist_plugins.begin(), ulist_plugins.end(), *it); + + if (pos != ulist_plugins.end()) { + //Need to merge plugins. + it->Merge(*pos); + } + } + + end = time(NULL); + cout << "Time taken to merge lists: " << (end - start) << " seconds." << endl; + start = time(NULL); + } + + cout << "Evaluating plugin list..." << endl; + + for (list::iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) { + try { + it->EvalAllConditions(game); + } catch (boss::error& e) { + cout << e.what() << endl; + } + } + + end = time(NULL); + cout << "Time taken to evaluate plugin list: " << (end - start) << " seconds." << endl; + start = time(NULL); + for (list::iterator it=plugins.begin(), endIt = plugins.end(); it != endIt; ++it) { cout << it->Name() << endl << '\t' << "Number of records: " << it->FormIDs().size() << endl @@ -163,7 +207,8 @@ int main(int argc, char *argv[]) { cout << "Sorting plugins..." << endl; plugins.sort(); - + + end = time(NULL); cout << "Time taken to sort plugins: " << (end - start) << " seconds." << endl; for (list::iterator it=plugins.begin(), endIt = plugins.end(); it != endIt; ++it)