Merge branch 'many-condition-function' into dev

Conflicts:
	docs/LOOT Metadata Syntax.html
This commit is contained in:
Oliver Hamlet
2015-07-15 17:38:50 +01:00
3 changed files with 101 additions and 27 deletions
+2
View File
@@ -491,6 +491,7 @@ msg:
<tbody>
<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>Many<td><code>many("<var>regex</var>")</code><td>Returns true if more than one file matching <var>regex</var> is found, and 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>(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. The comparison is not a straightforward per-character comparison, but instead uses the <a href="http://www.davekoelle.com/alphanum.html">Alphanum</a> algorithm.
<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.
@@ -596,6 +597,7 @@ msg:
<tr><td>YAML merge keys (ie. <code>&lt;&lt;:</code>)<td colspan="2">No<td>Yes
<tr><td>Location <code>ver</code> key<td colspan="3">Yes<td>No
<tr><td>Location <code>name</code> key<td colspan="3">No<td>Yes
<tr><td><code>many("<var>regex</var>")</code> condition function<td colspan="3">No<td>Yes
</table>
<h2 id="license">License</h2>
+63 -27
View File
@@ -88,6 +88,7 @@ namespace loot {
function =
("file(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckFile, this, qi::labels::_val, qi::labels::_1)]
| ("regex(" > quotedStr > ')')[phoenix::bind(&ConditionGrammar::CheckRegex, 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(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckActive, this, qi::labels::_val, qi::labels::_1)]
@@ -144,7 +145,7 @@ namespace loot {
bool _parseOnly;
//Eval's exact paths. Check for files and ghosted plugins.
void CheckFile(bool& result, const std::string& file) {
void CheckFile(bool& result, const std::string& file) const {
if (_parseOnly)
return;
@@ -171,10 +172,8 @@ namespace loot {
BOOST_LOG_TRIVIAL(trace) << "The file does not exist.";
}
void CheckRegex(bool& result, const std::string& regexStr) {
if (_parseOnly)
return;
result = false;
// Split a regex string into the non-regex filesystem parent path, and the regex filename.
std::pair<boost::filesystem::path, std::regex> SplitRegex(const std::string& regex) const {
//Can't support a regex string where all path components may be regex, since this could
//lead to massive scanning if an unfortunately-named directory is encountered.
//As such, only the filename portion can be a regex. Need to separate that from the rest
@@ -185,14 +184,10 @@ namespace loot {
In C++ string literals, the backslash must be escaped once more to give "\\\\".
Split the regex with another regex! */
//Need to also check if the regex is for a safe path.
BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist.";
std::regex sepReg("/|(\\\\\\\\)", std::regex::ECMAScript | std::regex::icase);
std::vector<std::string> components;
std::sregex_token_iterator it(regexStr.begin(), regexStr.end(), sepReg, -1);
std::sregex_token_iterator it(regex.begin(), regex.end(), sepReg, -1);
std::sregex_token_iterator itend;
for (; it != itend; ++it) {
components.push_back(*it);
@@ -201,26 +196,17 @@ namespace loot {
std::string filename = components.back();
components.pop_back();
std::string parent;
boost::filesystem::path parent;
for (std::vector<std::string>::const_iterator it = components.begin(), endIt = components.end()--; it != endIt; ++it) {
if (*it == ".")
continue;
parent += *it + '/';
parent += *it;
}
if (boost::contains(parent, "../../")) {
if (!IsSafePath(parent.string())) {
BOOST_LOG_TRIVIAL(error) << "Invalid folder path: " << parent;
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid folder path:").str() + " " + parent);
}
//Now we have a valid parent path and a regex filename. Check that
//the parent path exists and is a directory.
boost::filesystem::path parent_path = _game->DataPath() / parent;
if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) {
BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory.";
return;
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid folder path:").str() + " " + parent.string());
}
std::regex reg;
@@ -232,8 +218,29 @@ namespace loot {
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid regex string:").str() + " " + filename);
}
return std::pair<boost::filesystem::path, std::regex>(parent, reg);
}
void CheckRegex(bool& result, const std::string& regexStr) const {
if (_parseOnly)
return;
result = false;
BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist.";
std::pair<boost::filesystem::path, std::regex> pathRegex = SplitRegex(regexStr);
//Now we have a valid parent path and a regex filename. Check that
//the parent path exists and is a directory.
boost::filesystem::path parent_path = _game->DataPath() / pathRegex.first;
if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) {
BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory.";
return;
}
for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) {
if (std::regex_match(itr->path().filename().string(), reg)) {
if (std::regex_match(itr->path().filename().string(), pathRegex.second)) {
result = true;
BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path();
return;
@@ -241,6 +248,35 @@ namespace loot {
}
}
void CheckMany(bool& result, const std::string& regexStr) const {
if (_parseOnly)
return;
result = false;
BOOST_LOG_TRIVIAL(trace) << "Checking to see if more than one file matching the regex \"" << regexStr << "\" exist.";
std::pair<boost::filesystem::path, std::regex> pathRegex = SplitRegex(regexStr);
//Now we have a valid parent path and a regex filename. Check that
//the parent path exists and is a directory.
boost::filesystem::path parent_path = _game->DataPath() / pathRegex.first;
if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) {
BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory.";
return;
}
size_t count = 0;
for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) {
if (std::regex_match(itr->path().filename().string(), pathRegex.second)) {
++count;
BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path();
}
}
result = count > 1;
}
void CheckSum(bool& result, const std::string& file, const uint32_t checksum) {
if (_parseOnly)
return;
@@ -275,7 +311,7 @@ namespace loot {
result = checksum == crc;
}
void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) {
void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) const {
if (_parseOnly)
return;
@@ -313,7 +349,7 @@ namespace loot {
BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result;
}
void CheckActive(bool& result, const std::string& file) {
void CheckActive(bool& result, const std::string& file) const {
if (_parseOnly)
return;
@@ -340,7 +376,7 @@ namespace loot {
}
//Checks that the path (not regex) doesn't go outside any game folders.
bool IsSafePath(const std::string& path) {
bool IsSafePath(const std::string& path) const {
BOOST_LOG_TRIVIAL(trace) << "Checking to see if the path \"" << path << "\" is safe.";
std::vector<std::string> components;
@@ -164,6 +164,42 @@ TEST_F(ConditionGrammar, RegexConditionFalse) {
EXPECT_FALSE(eval);
}
TEST_F(ConditionGrammar, ManyConditionTrue) {
loot::Game game(loot::Game::tes5);
game.SetGamePath(dataPath.parent_path());
boost::spirit::qi::space_type skipper;
bool eval = false;
bool r = false;
Grammar cg(&game, false);
std::string condition("many(\"Blank.+\\.esm\")");
std::string::const_iterator begin = condition.begin();
std::string::const_iterator end = condition.end();
EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval));
EXPECT_TRUE(r);
EXPECT_TRUE(eval);
}
TEST_F(ConditionGrammar, ManyConditionFalse) {
loot::Game game(loot::Game::tes5);
game.SetGamePath(dataPath.parent_path());
boost::spirit::qi::space_type skipper;
bool eval = true;
bool r = false;
Grammar cg(&game, false);
std::string condition("many(\"Blank\\.esm\")");
std::string::const_iterator begin = condition.begin();
std::string::const_iterator end = condition.end();
EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval));
EXPECT_TRUE(r);
EXPECT_FALSE(eval);
}
TEST_F(ConditionGrammar, ChecksumConditionTrue) {
loot::Game game(loot::Game::tes5);
game.SetGamePath(dataPath.parent_path());