From f30672ebe08b812d92a8bea8cfcef1c893446c4d Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Fri, 14 Jun 2013 18:11:18 +0100 Subject: [PATCH] Implemented issue #24, started work on issue #25. Metadata Editor not updated to handle the new message structure. --- docs/BOSS Metadata Syntax.html | 22 ++--- src/backend/generators.h | 21 +++-- src/backend/metadata.cpp | 141 ++++++++++++++++++++++----------- src/backend/metadata.h | 48 +++++++---- src/backend/network.cpp | 1 + src/backend/parsers.h | 95 ++++++++++++++++------ 6 files changed, 224 insertions(+), 104 deletions(-) diff --git a/docs/BOSS Metadata Syntax.html b/docs/BOSS Metadata Syntax.html index 20a18f32..1ff774f2 100644 --- a/docs/BOSS Metadata Syntax.html +++ b/docs/BOSS Metadata Syntax.html @@ -133,7 +133,7 @@ This documentation is a work in progress, covering a syntax that is also still a globals: - type: say content: You are using the latest version of BOSS. - condition: 'version("../BOSS/BOSS.exe", "2.1.1.0", ==)' + condition: 'version("BOSS", "3.0.0.0", ==)' plugins: - name: Armamentarium.esm tag: @@ -181,7 +181,7 @@ condition: "file(\"Mart's Monster Mod for OOO.esm\") or file(\"FCOM_Convergence. -
Key NameRequiredNotes
nameA file path relative to the game's Data folder. +
nameA file path or name.
displayA substitute string to be displayed instead of the file path in any generated messages, eg. the name of the mod the file belongs to.
conditionA condition string that is evaluated to determine whether this file data should be used: if it evaluates to true, the data is used, otherwise it is ignored. See Condition Strings for details.
@@ -190,7 +190,7 @@ condition: "file(\"Mart's Monster Mod for OOO.esm\") or file(\"FCOM_Convergence. ../obse_loader.exe or name: ../obse_loader.exe -condition: 'version("../obse_loader.exe", "0.0.18.0", <)' +condition: 'version("../obse_loader.exe", "0.0.18.0", >=)' display: OBSE v18+ @@ -222,7 +222,7 @@ display: OBSE v18+

BOSS handles messages and languages as follows:

    -
  1. If a message's content value is a string, the message will the string as its content if displayed. +
  2. If a message's content value is a string, the message will use the string as its content if displayed.
  3. If a message's content value is a list of content structures, then the first structure with a language that matches BOSS's current language will be used as the message's content if displayed. If there are no matches, then the first structure in English will be used.
@@ -232,11 +232,11 @@ display: OBSE v18+ condition: file("foo.esp") content: - lang: eng - str: An example link: http://www.example.com + str: 'An example link: http://www.example.com' - lang: rus - str: Это пример ссылки: http://www.example.com + str: 'Это пример ссылки: http://www.example.com' - lang: ger - str: Ein Beispiel-Link: http://www.example.com + str: 'Ein Beispiel-Link: http://www.example.com'
would be displayed as
@@ -295,7 +295,7 @@ msg: Filefile("filepath")Returns true if filepath is installed, false otherwise. Fileregex("regex")Returns true if a file matching regex is found, false otherwise. Checksumchecksum("filepath", checksum)Returns true if the calculated checksum of filepath matches checksum, false otherwise. If filepath does not exist, returns false. - Versionversion("filepath", "version", comparator)Returns true if the boolean expression version comparator (actual version of filepath) holds true, false otherwise. If filepath does not exist and comparator is ==, > or >=, returns false. If filepath does not exist and comparator is !=, < or <=, returns true. + Versionversion("filepath", "version", comparator)Returns true if the boolean expression (actual version of filepath) comparator version holds true, false otherwise. If filepath does not exist and comparator is ==, > or >=, returns false. If filepath does not exist and comparator is !=, < or <=, returns true. Plugin Active Statusactive("filepath")Returns true if filepath is a .esp or .esm file that is both installed and active, false otherwise. @@ -303,7 +303,7 @@ msg: -
Variable TypeDescription
filepathA Windows file path. +
filepathA Windows file path, or BOSS, which references the BOSS executable being run.
regexA regular expression string to match file paths to.
checksumA string of hexadecimal digits representing an unsigned integer that is the data checksum of a file. BOSS displays the checksums of plugins in its user interface after running.
versionA string of characters representing the version of a plugin or executable. BOSS displays the versions of plugins in its user interface after running. @@ -346,11 +346,11 @@ msg:
  • Logical and
  • Logical or -

    Below is a table that lists some combinations of conditions, showing for each the order of evaluation by explicit bracketing. To make it easier to read, the keyword function pair is replaced with C after the first example. +

    Below is a table that lists some combinations of conditions, showing for each the order of evaluation by explicit bracketing. To make it easier to read, the negator function pair is shortened to C after the first example. -
    String StructureEvaluated As
    keyword function( keyword ( function ) ) +
    negator function( negator ( function ) )
    C and C( C and C )
    C or C( C or C )
    C and C and C( ( C and C ) and C ) diff --git a/src/backend/generators.h b/src/backend/generators.h index 1ff06662..6aa75b9c 100644 --- a/src/backend/generators.h +++ b/src/backend/generators.h @@ -186,7 +186,7 @@ namespace boss { } //Turn any urls into hyperlinks. - WriteMessage(li, it->Type(), it->Content()); + WriteMessage(li, it->Type(), it->Content().front().Str()); } } } @@ -547,6 +547,17 @@ namespace YAML { out << EndMap; } + inline Emitter& operator << (Emitter& out, const boss::MessageContent& rhs) { + out << BeginMap; + + if (rhs.Language() == boss::LANG_ENG) + out << Key << "lang" << Value << "eng"; + + out << Key << "str" << Value << rhs.Str(); + + out << EndMap; + } + inline Emitter& operator << (Emitter& out, const boss::Message& rhs) { out << BeginMap; @@ -557,10 +568,10 @@ namespace YAML { else out << Key << "type" << Value << "error"; - out << Key << "content" << Value << rhs.Content(); - - if (rhs.Language() == boss::LANG_ENG) - out << Key << "lang" << Value << "eng"; + if (rhs.Content().size() == 1) + out << Key << "content" << Value << rhs.Content().front().Str(); + else + out << Key << "content" << Value << rhs.Content(); if (!rhs.Condition().empty()) out << Key << "condition" << Value << rhs.Condition(); diff --git a/src/backend/metadata.cpp b/src/backend/metadata.cpp index ca92185d..b82bb83d 100644 --- a/src/backend/metadata.cpp +++ b/src/backend/metadata.cpp @@ -29,6 +29,7 @@ #include #include +#include using namespace std; @@ -69,34 +70,23 @@ namespace boss { return id; } - ConditionalData::ConditionalData() {} + ConditionStruct::ConditionStruct() {} - ConditionalData::ConditionalData(const string& c) : condition(c) {} + ConditionStruct::ConditionStruct(const string& condition) : _condition(condition) {} - ConditionalData::ConditionalData(const std::string& c, const std::string& d) - : condition(c), data(d) {} - - bool ConditionalData::IsConditional() const { - return !condition.empty(); + bool ConditionStruct::IsConditional() const { + return !_condition.empty(); } - std::string ConditionalData::Condition() const { - return condition; + std::string ConditionStruct::Condition() const { + return _condition; } - std::string ConditionalData::Data() const { - return data; - } - - void ConditionalData::Data(const std::string& d) { - data = d; - } - - bool ConditionalData::EvalCondition(boss::Game& game) const { - if (condition.empty()) + bool ConditionStruct::EvalCondition(boss::Game& game) const { + if (_condition.empty()) return true; - boost::unordered_map::const_iterator it = game.conditionCache.find(boost::to_lower_copy(condition)); + boost::unordered_map::const_iterator it = game.conditionCache.find(boost::to_lower_copy(_condition)); if (it != game.conditionCache.end()) return it->second; @@ -106,60 +96,117 @@ namespace boss { bool eval; grammar.SetGame(game); - begin = condition.begin(); - end = condition.end(); + begin = _condition.begin(); + end = _condition.end(); bool r; try { r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper, eval); } catch (boss::error& e) { - throw boss::error(boss::ERROR_PATH_READ_FAIL, "Parsing of condition \"" + condition + "\" failed: " + e.what()); + throw boss::error(boss::ERROR_PATH_READ_FAIL, "Parsing of condition \"" + _condition + "\" failed: " + e.what()); } if (!r || begin != end) - throw boss::error(boss::ERROR_PATH_READ_FAIL, "Parsing of condition \"" + condition + "\" failed!"); + throw boss::error(boss::ERROR_PATH_READ_FAIL, "Parsing of condition \"" + _condition + "\" failed!"); - game.conditionCache.emplace(boost::to_lower_copy(condition), eval); + game.conditionCache.emplace(boost::to_lower_copy(_condition), eval); return eval; } + MessageContent::MessageContent() : _language(LANG_AUTO) {} + + MessageContent::MessageContent(const std::string& str, const unsigned int language) : _str(str), _language(language) {} + + std::string MessageContent::Str() const { + return _str; + } + + unsigned int MessageContent::Language() const { + return _language; + } + + bool MessageContent::operator < (const MessageContent& rhs) const { + return boost::ilexicographical_compare(_str, rhs.Str()); + } + + bool MessageContent::operator == (const MessageContent& rhs) const { + return (_language == rhs.Language() && boost::iequals(_str, rhs.Str())); + } + Message::Message() {} Message::Message(const unsigned int type, const std::string& content, - const std::string& condition, const unsigned int language) - : _type(type), _language(language), ConditionalData(condition, content) {} + const std::string& condition) : _type(type), ConditionStruct(condition) { + _content.push_back(MessageContent(content)); + } + + Message::Message(const unsigned int type, const std::vector& content, + const std::string& condition) : _type(type), _content(content), ConditionStruct(condition) {} bool Message::operator < (const Message& rhs) const { - return boost::ilexicographical_compare(Content(), rhs.Content()); + return boost::ilexicographical_compare(_content.front().Str(), rhs.Content().front().Str()); } bool Message::operator == (const Message& rhs) const { - return (_type == rhs.Type() && boost::iequals(Content(), rhs.Content())); + return (_type == rhs.Type() && _content == rhs.Content()); } - bool Message::EvalCondition(boss::Game& game, const unsigned int lang) const { - if (_language == LANG_AUTO || _language == lang) - return ConditionalData::EvalCondition(game); - else - return false; + bool Message::EvalCondition(boss::Game& game, const unsigned int language) { + if (_content.size() > 1) { + if (language == LANG_AUTO) //Can use a message of any language, so use the first string. + _content.resize(1); + else { + MessageContent english, match; + for (vector::const_iterator it=_content.begin(), endit=_content.end(); it != endit; ++it) { + if (it->Language() == language) { + match = *it; + break; + } else if (it->Language() == LANG_ENG) + english = *it; + } + _content.resize(1); + if (!match.Str().empty()) + _content[0] = match; + else + _content[0] = english; + } + } + return ConditionStruct::EvalCondition(game); + } + + MessageContent Message::ChooseContent(const unsigned int language) const { + if (_content.size() > 1) { + if (language == LANG_AUTO) //Can use a message of any language, so use the first string. + return _content[0]; + else { + MessageContent english, match; + for (vector::const_iterator it=_content.begin(), endit=_content.end(); it != endit; ++it) { + if (it->Language() == language) { + match = *it; + break; + } else if (it->Language() == LANG_ENG) + english = *it; + } + if (!match.Str().empty()) + return match; + else + return english; + } + } } unsigned int Message::Type() const { return _type; } - unsigned int Message::Language() const { - return _language; - } - - std::string Message::Content() const { - return Data(); + std::vector Message::Content() const { + return _content; } File::File() {} File::File(const std::string& name, const std::string& display, const std::string& condition) - : _display(display), ConditionalData(condition, name) {} + : _name(name), _display(display), ConditionStruct(condition) {} bool File::operator < (const File& rhs) const { return boost::ilexicographical_compare(Name(), rhs.Name()); @@ -170,7 +217,7 @@ namespace boss { } std::string File::Name() const { - return Data(); + return _name; } std::string File::DisplayName() const { @@ -179,7 +226,7 @@ namespace boss { Tag::Tag() : addTag(true) {} - Tag::Tag(const string& tag, const bool isAddition, const string& condition) : addTag(isAddition), ConditionalData(condition, tag) {} + Tag::Tag(const string& tag, const bool isAddition, const string& condition) : _name(tag), addTag(isAddition), ConditionStruct(condition) {} bool Tag::operator < (const Tag& rhs) const { if (addTag != rhs.IsAddition()) @@ -197,7 +244,7 @@ namespace boss { } std::string Tag::Name() const { - return Data(); + return _name; } Plugin::Plugin() : enabled(true), priority(0), isMaster(false) {} @@ -277,7 +324,7 @@ namespace boss { void Plugin::Merge(const Plugin& plugin, bool ifDisabled) { //If 'name' differs or if 'enabled' is false for the given plugin, don't change anything. - if (!boost::iequals(name, plugin.Name()) || (!plugin.Enabled() && !ifDisabled)) + if ((!plugin.Enabled() && !ifDisabled)) return; //The following should be replaced. @@ -457,7 +504,9 @@ namespace boss { } bool Plugin::operator == (const Plugin& rhs) const { - return boost::iequals(name, rhs.Name()); + return (boost::iequals(name, rhs.Name()) + || (IsRegexPlugin() && boost::regex_match(rhs.Name(), boost::regex(name, boost::regex::extended|boost::regex::icase))) + || (rhs.IsRegexPlugin() && boost::regex_match(name, boost::regex(rhs.Name(), boost::regex::extended|boost::regex::icase)))); } bool Plugin::operator != (const Plugin& rhs) const { diff --git a/src/backend/metadata.h b/src/backend/metadata.h index ec736027..b1dccd14 100644 --- a/src/backend/metadata.h +++ b/src/backend/metadata.h @@ -52,44 +52,56 @@ namespace boss { uint32_t id; }; - class ConditionalData { + class ConditionStruct { public: - ConditionalData(); - ConditionalData(const std::string& condition); - ConditionalData(const std::string& condition, const std::string& data); + ConditionStruct(); + ConditionStruct(const std::string& condition); bool IsConditional() const; bool EvalCondition(boss::Game& game) const; std::string Condition() const; - std::string Data() const; - - void Data(const std::string& data); private: - std::string condition; - std::string data; + std::string _condition; }; - class Message : public ConditionalData { + class MessageContent { + public: + MessageContent(); + MessageContent(const std::string& str, const unsigned int language = LANG_AUTO); + + std::string Str() const; + unsigned int Language() const; + + bool operator < (const MessageContent& rhs) const; + bool operator == (const MessageContent& rhs) const; + private: + std::string _str; + unsigned int _language; + }; + + class Message : public ConditionStruct { public: Message(); Message(const unsigned int type, const std::string& content, - const std::string& condition = "", const unsigned int language = LANG_AUTO); + const std::string& condition = ""); + Message(const unsigned int type, const std::vector& content, + const std::string& condition = ""); bool operator < (const Message& rhs) const; bool operator == (const Message& rhs) const; - bool EvalCondition(boss::Game& game, const unsigned int language) const; + bool EvalCondition(boss::Game& game, const unsigned int language); unsigned int Type() const; - unsigned int Language() const; - std::string Content() const; + std::vector Content() const; + MessageContent ChooseContent(const unsigned int language) const; private: unsigned int _type; - unsigned int _language; + std::vector _content; }; - class File : public ConditionalData { + class File : public ConditionStruct { public: File(); File(const std::string& name, const std::string& display = "", @@ -101,10 +113,11 @@ namespace boss { std::string Name() const; std::string DisplayName() const; private: + std::string _name; std::string _display; }; - class Tag : public ConditionalData { + class Tag : public ConditionStruct { public: Tag(); Tag(const std::string& tag, const bool isAddition = true, const std::string& condition = ""); @@ -115,6 +128,7 @@ namespace boss { bool IsAddition() const; std::string Name() const; private: + std::string _name; bool addTag; }; diff --git a/src/backend/network.cpp b/src/backend/network.cpp index bfa173b6..898dc342 100644 --- a/src/backend/network.cpp +++ b/src/backend/network.cpp @@ -143,6 +143,7 @@ namespace boss { return revision; else return "N/A"; + } if (!RunCommand(command, output)) { //Working copy not set up, perform a checkout. diff --git a/src/backend/parsers.h b/src/backend/parsers.h index b323a5e3..743996bd 100644 --- a/src/backend/parsers.h +++ b/src/backend/parsers.h @@ -103,6 +103,39 @@ namespace YAML { } }; + template<> + struct convert { + static Node encode(const boss::MessageContent& rhs) { + Node node; + node["str"] = rhs.Str(); + + if (rhs.Language() == boss::LANG_AUTO) + node["lang"] = ""; + else + node["lang"] = "eng"; + + return node; + } + + static bool decode(const Node& node, boss::MessageContent& rhs) { + if (!node["str"] || !node["lang"]) + return false; + + std::string str = node["str"].as(); + std::string lang = node["lang"].as(); + unsigned int langNo; + + if (boost::iequals(lang, "eng")) + langNo = boss::LANG_ENG; + else + langNo = boss::LANG_AUTO; + + rhs = boss::MessageContent(str, langNo); + + return true; + } + }; + template<> struct convert { static Node encode(const boss::Message& rhs) { @@ -116,26 +149,15 @@ namespace YAML { node["type"] = "warn"; else node["type"] = "error"; - - if (rhs.Language() == boss::LANG_AUTO) - node["lang"] = ""; - else - node["lang"] = "eng"; return node; } static bool decode(const Node& node, boss::Message& rhs) { - if(!node.IsMap()) + if(!node.IsMap() || !node["type"] || !node["content"]) return false; - std::string condition, content; - unsigned int typeNo, langNo; - if (node["condition"]) - condition = node["condition"].as(); - if (node["content"]) - content = node["content"].as(); - + unsigned int typeNo; if (node["type"]) { std::string type; type = node["type"].as(); @@ -148,19 +170,28 @@ namespace YAML { typeNo = boss::MESSAGE_ERROR; } - - if (node["lang"]) { - std::string lang; - lang = node["lang"].as(); + std::vector content; + if (node["content"].IsSequence()) + content = node["content"].as< std::vector >(); + else + content.push_back(node["content"].as()); - if (boost::iequals(lang, "eng")) - langNo = boss::LANG_ENG; - else - langNo = boss::LANG_AUTO; + //Check now that at least one item in content is English if there are multiple items. + if (content.size() > 1) { + bool found = false; + for (std::vector::const_iterator it=content.begin(), endit=content.end(); it != endit; ++it) { + if (it->Language() == boss::LANG_ENG) + found = true; + } + if (!found) + return false; } - + + std::string condition; + if (node["condition"]) + condition = node["condition"].as(); - rhs = boss::Message(typeNo, content, condition, langNo); + rhs = boss::Message(typeNo, content, condition); return true; } }; @@ -383,6 +414,11 @@ namespace boss { //Eval's regex and exact paths. Check for files and ghosted plugins. void CheckFile(bool& result, const std::string& file) { + if (file == "BOSS") { + result = true; + return; + } + if (!IsSafePath(file)) throw boss::error(boss::ERROR_INVALID_ARGS, "The file path \"" + file + "\" is invalid."); @@ -458,6 +494,8 @@ namespace boss { if (it != game->crcCache.end()) crc = it->second; else { + if (file == "BOSS") + crc = GetCrc32(boost::filesystem::absolute("BOSS.exe")); if (boost::filesystem::exists(game->DataPath() / file)) crc = GetCrc32(game->DataPath() / file); else if (IsPlugin(file) && boost::filesystem::exists(game->DataPath() / (file + ".ghost"))) @@ -483,7 +521,11 @@ namespace boss { } Version givenVersion = Version(version); - Version trueVersion = Version(game->DataPath() / file); + Version trueVersion; + if (file == "BOSS") + trueVersion = Version(boost::filesystem::absolute("BOSS.exe")); + else + trueVersion = Version(game->DataPath() / file); if ( (comparator == "==" && trueVersion != givenVersion) || (comparator == "!=" && trueVersion == givenVersion) @@ -495,7 +537,10 @@ namespace boss { } void CheckActive(bool& result, const std::string& file) { - result = game->IsActive(file); + if (file == "BOSS") + result = false; + else + result = game->IsActive(file); } void SyntaxError(Iterator const& /*first*/, Iterator const& last, Iterator const& errorpos, boost::spirit::info const& what) {