Implemented issue #24, started work on issue #25.

Metadata Editor not updated to handle the new message structure.
This commit is contained in:
WrinklyNinja
2013-06-14 18:11:18 +01:00
parent d13f47914b
commit f30672ebe0
6 changed files with 224 additions and 104 deletions
+11 -11
View File
@@ -133,7 +133,7 @@ This documentation is a work in progress, covering a syntax that is also still a
<code class="box">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.
<table>
<thead><tr><th>Key Name<th>Required<th>Notes
<tbody>
<tr><td><code>name</code><td>&#x2713;<td>A file path relative to the game's Data folder.
<tr><td><code>name</code><td>&#x2713;<td>A file path or name.
<tr><td><code>display</code><td>&#x2717;<td>A substitute string to be displayed instead of the file path in any generated messages, eg. the name of the mod the file belongs to.
<tr><td><code>condition</code><td>&#x2717;<td>A 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 <a href="#cond">Condition Strings</a> for details.
</table>
@@ -190,7 +190,7 @@ condition: "file(\"Mart's Monster Mod for OOO.esm\") or file(\"FCOM_Convergence.
<code class="box">../obse_loader.exe</code>
or
<code class="box">name: ../obse_loader.exe
condition: 'version("../obse_loader.exe", "0.0.18.0", &lt;)'
condition: 'version("../obse_loader.exe", "0.0.18.0", &gt;=)'
display: OBSE v18+
</code>
@@ -222,7 +222,7 @@ display: OBSE v18+
<p>BOSS handles messages and languages as follows:
<ol>
<li>If a message's <code>content</code> value is a string, the message will the string as its content if displayed.
<li>If a message's <code>content</code> value is a string, the message will use the string as its content if displayed.
<li>If a message's <code>content</code> 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.
</ol>
@@ -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'
</code>
would be displayed as
<blockquote>
@@ -295,7 +295,7 @@ msg:
<tr><td>File<td><code>file("<var>filepath</var>")</code><td>Returns true if <var>filepath</var> is installed, false otherwise.
<tr><td>File<td><code>regex("<var>regex</var>")</code><td>Returns true if a file matching <var>regex</var> is found, false otherwise.
<tr><td>Checksum<td><code>checksum("<var>filepath</var>", <var>checksum</var>)</code><td>Returns true if the calculated checksum of <var>filepath</var> matches <var>checksum</var>, false otherwise. If <var>filepath</var> does not exist, returns false.
<tr><td>Version<td><code>version("<var>filepath</var>", "<var>version</var>", <var>comparator</var>)</code><td>Returns true if the boolean expression <code><var>version comparator</var> (actual version of <var>filepath</var>)</code> holds true, false otherwise. If <var>filepath</var> does not exist and <var>comparator</var> is <code>==</code>, <code>&gt;</code> or <code>&gt;=</code>, returns false. If <var>filepath</var> does not exist and <var>comparator</var> is <code>!=</code>, <code>&lt;</code> or <code>&lt;=</code>, returns true.
<tr><td>Version<td><code>version("<var>filepath</var>", "<var>version</var>", <var>comparator</var>)</code><td>Returns true if the boolean expression <code>(actual version of <var>filepath</var>) <var>comparator version</var></code> holds true, false otherwise. If <var>filepath</var> does not exist and <var>comparator</var> is <code>==</code>, <code>&gt;</code> or <code>&gt;=</code>, returns false. If <var>filepath</var> does not exist and <var>comparator</var> is <code>!=</code>, <code>&lt;</code> or <code>&lt;=</code>, returns true.
<tr><td>Plugin Active Status<td><code>active("<var>filepath</var>")</code><td>Returns true if <var>filepath</var> is a <code>.esp</code> or <code>.esm</code> file that is both installed and active, false otherwise.
</table>
@@ -303,7 +303,7 @@ msg:
<table>
<thead><tr><th>Variable Type<th>Description
<tbody>
<tr><td><code>filepath</code><td>A Windows file path.
<tr><td><code>filepath</code><td>A Windows file path, or <code>BOSS</code>, which references the BOSS executable being run.
<tr><td><code>regex</code><td>A regular expression string to match file paths to.
<tr><td><code>checksum</code><td>A 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.
<tr><td><code>version</code><td>A 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:
<li>Logical <q>and</q>
<li>Logical <q>or</q>
</ol>
<p>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 <code><var>keyword</var> <var>function</var></code> pair is replaced with <code><var>C</var></code> after the first example.
<p>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 <code><var>negator</var> <var>function</var></code> pair is shortened to <code><var>C</var></code> after the first example.
<table>
<thead><tr><th>String Structure<th>Evaluated As
<tbody>
<tr><td><code><var>keyword</var> <var>function</var></code><td><code>( <var>keyword</var> ( <var>function</var> ) )</code>
<tr><td><code><var>negator</var> <var>function</var></code><td><code>( <var>negator</var> ( <var>function</var> ) )</code>
<tr><td><code><var>C</var> and <var>C</var></code><td><code>( <var>C</var> and <var>C</var> )</code>
<tr><td><code><var>C</var> or <var>C</var></code><td><code>( <var>C</var> or <var>C</var> )</code>
<tr><td><code><var>C</var> and <var>C</var> and <var>C</var></code><td><code>( ( <var>C</var> and <var>C</var> ) and <var>C</var> )</code>
+16 -5
View File
@@ -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();
+95 -46
View File
@@ -29,6 +29,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
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<std::string, bool>::const_iterator it = game.conditionCache.find(boost::to_lower_copy(condition));
boost::unordered_map<std::string, bool>::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<MessageContent>& 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<MessageContent>::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<MessageContent>::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<MessageContent> 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 {
+31 -17
View File
@@ -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<MessageContent>& 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<MessageContent> Content() const;
MessageContent ChooseContent(const unsigned int language) const;
private:
unsigned int _type;
unsigned int _language;
std::vector<MessageContent> _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;
};
+1
View File
@@ -143,6 +143,7 @@ namespace boss {
return revision;
else
return "N/A";
}
if (!RunCommand(command, output)) {
//Working copy not set up, perform a checkout.
+70 -25
View File
@@ -103,6 +103,39 @@ namespace YAML {
}
};
template<>
struct convert<boss::MessageContent> {
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>();
std::string lang = node["lang"].as<std::string>();
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<boss::Message> {
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<std::string>();
if (node["content"])
content = node["content"].as<std::string>();
unsigned int typeNo;
if (node["type"]) {
std::string type;
type = node["type"].as<std::string>();
@@ -148,19 +170,28 @@ namespace YAML {
typeNo = boss::MESSAGE_ERROR;
}
if (node["lang"]) {
std::string lang;
lang = node["lang"].as<std::string>();
std::vector<boss::MessageContent> content;
if (node["content"].IsSequence())
content = node["content"].as< std::vector<boss::MessageContent> >();
else
content.push_back(node["content"].as<std::string>());
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<boss::MessageContent>::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<std::string>();
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) {