mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
@@ -492,6 +492,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>></code> or <code>>=</code>, returns false. If <var>filepath</var> does not exist and <var>comparator</var> is <code>!=</code>, <code><</code> or <code><=</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.
|
||||
@@ -590,11 +591,12 @@ msg:
|
||||
|
||||
<table>
|
||||
<head>
|
||||
<tr><th>Feature Supported?<th>v0.5<th>v0.6<th>v0.7
|
||||
<tr><th>Feature Supported?<th>v0.5<th>v0.6<th>v0.7<th>v0.8
|
||||
<tbody>
|
||||
<tr><td>GitHub Flavored Markdown message formatting<td colspan="2">Only URL hyperlinking is supported, for <code>file:</code>, <code>http:</code> and <code>https:</code> URLs, using the <code>[label](url)</code> and <code><url></code> syntaxes.<td>LOOT uses the <a href="https://github.com/chjj/marked">Marked</a> library (v0.3) to provide support for most of GitHub Flavored Markdown, minus the GitHub-specific features (like @mentions, issue/repo linking and emoji).
|
||||
<tr><td>Message string substitution (ie. <code>sub:</code>)<td colspan="2">No<td>Yes
|
||||
<tr><td>YAML merge keys (ie. <code><<:</code>)<td colspan="2">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>
|
||||
|
||||
@@ -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)]
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user