1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-14 01:19:59 +00:00

Merge branch 'detain-hash-selectively-reluctant' into 'master'

Get rid of EscapeHashX classes option 5 (attempt 2): Use boost::filesystem::path rules if the path starts with ", and consume the whole thing verbatim otherwise

Closes #5804

See merge request OpenMW/openmw!1436
This commit is contained in:
psi29a 2021-12-10 08:38:06 +00:00
commit 4883cbf590
14 changed files with 488 additions and 474 deletions

View File

@ -88,16 +88,16 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
desc.add_options()
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
("data-local", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""))
("data", boost::program_options::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing())
("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""))
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
("encoding", boost::program_options::value<Files::EscapeHashString>()->default_value("win1252"))
("resources", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), "resources"))
("fallback-archive", boost::program_options::value<Files::EscapeStringVector>()->
default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken())
("encoding", boost::program_options::value<std::string>()->default_value("win1252"))
("resources", boost::program_options::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"))
("fallback-archive", boost::program_options::value<std::vector<std::string>>()->
default_value(std::vector<std::string>(), "fallback-archive")->multitoken())
("fallback", boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")
->multitoken()->composing(), "fallback values")
("script-blacklist", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
("script-blacklist", boost::program_options::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "")
->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)")
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
->default_value(true), "enable script blacklisting");
@ -108,24 +108,24 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);
mEncodingName = variables["encoding"].as<Files::EscapeHashString>().toStdString();
mEncodingName = variables["encoding"].as<std::string>();
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));
mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str()));
mDocumentManager.setResourceDir (mResources = variables["resources"].as<Files::EscapePath>().mPath);
mDocumentManager.setResourceDir (mResources = variables["resources"].as<Files::MaybeQuotedPath>());
if (variables["script-blacklist-use"].as<bool>())
mDocumentManager.setBlacklistedScripts (
variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector());
variables["script-blacklist"].as<std::vector<std::string>>());
mFsStrict = variables["fs-strict"].as<bool>();
Files::PathContainer dataDirs, dataLocal;
if (!variables["data"].empty()) {
dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>()));
dataDirs = asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>());
}
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().mPath);
Files::PathContainer::value_type local(variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>());
if (!local.empty())
dataLocal.push_back(local);
@ -151,7 +151,7 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
//iterate the data directories and add them to the file dialog for loading
mFileDialog.addFiles(dataDirs);
return std::make_pair (dataDirs, variables["fallback-archive"].as<Files::EscapeStringVector>().toStdStringVector());
return std::make_pair (dataDirs, variables["fallback-archive"].as<std::vector<std::string>>());
}
void CS::Editor::createGame()

View File

@ -1,6 +1,5 @@
#include <components/version/version.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/fallback/fallback.hpp>
#include <components/fallback/validate.hpp>
#include <components/debug/debugging.hpp>
@ -57,7 +56,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
{
cfgMgr.readConfiguration(variables, desc, true);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string());
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string());
getRawStdout() << v.describe() << std::endl;
return false;
}
@ -66,38 +65,38 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
cfgMgr.readConfiguration(variables, desc);
Files::mergeComposingVariables(variables, composingVariables, desc);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string());
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string());
Log(Debug::Info) << v.describe();
engine.setGrabMouse(!variables["no-grab"].as<bool>());
// Font encoding settings
std::string encoding(variables["encoding"].as<Files::EscapeHashString>().toStdString());
std::string encoding(variables["encoding"].as<std::string>());
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
engine.setEncoding(ToUTF8::calculateEncoding(encoding));
// directory settings
engine.enableFSStrict(variables["fs-strict"].as<bool>());
Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>()));
Files::PathContainer dataDirs(asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()));
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().mPath);
Files::PathContainer::value_type local(variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>());
if (!local.empty())
dataDirs.push_back(local);
cfgMgr.processPaths(dataDirs);
engine.setResourceDir(variables["resources"].as<Files::EscapePath>().mPath);
engine.setResourceDir(variables["resources"].as<Files::MaybeQuotedPath>());
engine.setDataDirs(dataDirs);
// fallback archives
StringsVector archives = variables["fallback-archive"].as<Files::EscapeStringVector>().toStdStringVector();
StringsVector archives = variables["fallback-archive"].as<StringsVector>();
for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it)
{
engine.addArchive(*it);
}
StringsVector content = variables["content"].as<Files::EscapeStringVector>().toStdStringVector();
StringsVector content = variables["content"].as<StringsVector>();
if (content.empty())
{
Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting...";
@ -118,7 +117,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
engine.addContentFile(file);
}
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector();
StringsVector groundcover = variables["groundcover"].as<StringsVector>();
for (auto& file : groundcover)
{
engine.addGroundcoverFile(file);
@ -131,7 +130,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
}
// startup-settings
engine.setCell(variables["start"].as<Files::EscapeHashString>().toStdString());
engine.setCell(variables["start"].as<std::string>());
engine.setSkipMenu (variables["skip-menu"].as<bool>(), variables["new-game"].as<bool>());
if (!variables["skip-menu"].as<bool>() && variables["new-game"].as<bool>())
Log(Debug::Warning) << "Warning: new-game used without skip-menu -> ignoring it";
@ -140,11 +139,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
engine.setCompileAll(variables["script-all"].as<bool>());
engine.setCompileAllDialogue(variables["script-all-dialogue"].as<bool>());
engine.setScriptConsoleMode (variables["script-console"].as<bool>());
engine.setStartupScript (variables["script-run"].as<Files::EscapeHashString>().toStdString());
engine.setStartupScript (variables["script-run"].as<std::string>());
engine.setWarningsMode (variables["script-warn"].as<int>());
engine.setScriptBlacklist (variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector());
engine.setScriptBlacklist (variables["script-blacklist"].as<StringsVector>());
engine.setScriptBlacklistUse (variables["script-blacklist-use"].as<bool>());
engine.setSaveGameFile (variables["load-savegame"].as<Files::EscapePath>().mPath.string());
engine.setSaveGameFile (variables["load-savegame"].as<Files::MaybeQuotedPath>().string());
// other settings
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);

View File

@ -1,7 +1,7 @@
#include "options.hpp"
#include <components/fallback/validate.hpp>
#include <components/files/escape.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/misc/rng.hpp>
#include <boost/program_options.hpp>
@ -9,6 +9,7 @@
namespace
{
namespace bpo = boost::program_options;
typedef std::vector<std::string> StringsVector;
}
namespace OpenMW
@ -21,28 +22,28 @@ namespace OpenMW
("help", "print help message")
("version", "print version information and quit")
("replace", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
("replace", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended")
("data", bpo::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")
("data", bpo::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")
->multitoken()->composing(), "set data directories (later directories have higher priority)")
("data-local", bpo::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""),
("data-local", bpo::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""),
"set local data directory (highest priority)")
("fallback-archive", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "fallback-archive")
("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)")
("resources", bpo::value<Files::EscapePath>()->default_value(Files::EscapePath(), "resources"),
("resources", bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
"set resources directory")
("start", bpo::value<Files::EscapeHashString>()->default_value(""),
("start", bpo::value<std::string>()->default_value(""),
"set initial cell")
("content", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts")
("groundcover", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
("groundcover", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon")
("no-sound", bpo::value<bool>()->implicit_value(true)
@ -57,7 +58,7 @@ namespace OpenMW
("script-console", bpo::value<bool>()->implicit_value(true)
->default_value(false), "enable console-only script functionality")
("script-run", bpo::value<Files::EscapeHashString>()->default_value(""),
("script-run", bpo::value<std::string>()->default_value(""),
"select a file containing a list of console commands that is executed on startup")
("script-warn", bpo::value<int>()->implicit_value (1)
@ -67,13 +68,13 @@ namespace OpenMW
"\t1 - show warning but consider script as correctly compiled anyway\n"
"\t2 - treat warnings as errors")
("script-blacklist", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
("script-blacklist", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)")
("script-blacklist-use", bpo::value<bool>()->implicit_value(true)
->default_value(true), "enable script blacklisting")
("load-savegame", bpo::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""),
("load-savegame", bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), ""),
"load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)")
("skip-menu", bpo::value<bool>()->implicit_value(true)
@ -85,7 +86,7 @@ namespace OpenMW
("fs-strict", bpo::value<bool>()->implicit_value(true)
->default_value(false), "strict file system handling (no case folding)")
("encoding", bpo::value<Files::EscapeHashString>()->
("encoding", bpo::value<std::string>()->
default_value("win1252"),
"Character encoding used in OpenMW game messages:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"

View File

@ -3,7 +3,6 @@
#include <boost/filesystem/fstream.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
@ -55,10 +54,10 @@ struct ContentFileTest : public ::testing::Test
boost::program_options::options_description desc("Allowed options");
desc.add_options()
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
("content", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
("data", boost::program_options::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing())
("content", boost::program_options::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""));
("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""));
boost::program_options::notify(variables);
@ -66,10 +65,10 @@ struct ContentFileTest : public ::testing::Test
Files::PathContainer dataDirs, dataLocal;
if (!variables["data"].empty()) {
dataDirs = Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>());
dataDirs = asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>());
}
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().mPath);
Files::PathContainer::value_type local(variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>());
if (!local.empty()) {
dataLocal.push_back(local);
}
@ -82,7 +81,7 @@ struct ContentFileTest : public ::testing::Test
Files::Collections collections (dataDirs, true);
std::vector<std::string> contentFiles = variables["content"].as<Files::EscapeStringVector>().toStdStringVector();
std::vector<std::string> contentFiles = variables["content"].as<std::vector<std::string>>();
for (auto & contentFile : contentFiles)
{
if (!Misc::StringUtils::ciEndsWith(contentFile, ".omwscripts"))

View File

@ -1,6 +1,5 @@
#include <apps/openmw/options.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
@ -22,17 +21,12 @@ namespace
{
std::vector<std::string> result = std::move(base);
for (int i = 1; i <= std::numeric_limits<char>::max(); ++i)
if (i != '&' && i != '"' && i != ' ' && i != '@' && i != '\n')
if (i != '&' && i != '"' && i != ' ' && i != '\n')
result.push_back(std::string(1, i));
return result;
}
constexpr std::array supportedAtSignEscapings {
std::pair {'a', '@'},
std::pair {'h', '#'},
};
MATCHER_P(IsEscapePath, v, "") { return arg.mPath.string() == v; }
MATCHER_P(IsPath, v, "") { return arg.string() == v; }
template <class T>
void parseArgs(const T& arguments, bpo::variables_map& variables, bpo::options_description& description)
@ -46,7 +40,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame=save.omwsave"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save.omwsave");
}
TEST(OpenMWOptionsFromArguments, should_support_single_word_load_savegame_path)
@ -55,7 +49,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", "save.omwsave"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save.omwsave");
}
TEST(OpenMWOptionsFromArguments, should_support_multi_component_load_savegame_path)
@ -64,7 +58,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", "/home/user/openmw/save.omwsave"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "/home/user/openmw/save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "/home/user/openmw/save.omwsave");
}
TEST(OpenMWOptionsFromArguments, should_support_windows_multi_component_load_savegame_path)
@ -73,7 +67,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", R"(C:\OpenMW\save.omwsave)"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(C:\OpenMW\save.omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(C:\OpenMW\save.omwsave)");
}
TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_spaces)
@ -82,17 +76,16 @@ namespace
const std::array arguments {"openmw", "--load-savegame", "my save.omwsave"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "my");
// EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "my save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "my save.omwsave");
}
TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_number_sign)
TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_octothorpe)
{
bpo::options_description description = makeOptionsDescription();
const std::array arguments {"openmw", "--load-savegame", "my#save.omwsave"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "my#save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "my#save.omwsave");
}
TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_at_sign)
@ -101,8 +94,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", "my@save.omwsave"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "my?ave.omwsave");
// EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "my@save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "my@save.omwsave");
}
TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_quote)
@ -111,17 +103,16 @@ namespace
const std::array arguments {"openmw", "--load-savegame", R"(my"save.omwsave)"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(my"save.omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(my"save.omwsave)");
}
TEST(OpenMWOptionsFromArguments, should_support_quted_load_savegame_path)
TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path)
{
bpo::options_description description = makeOptionsDescription();
const std::array arguments {"openmw", "--load-savegame", R"("save".omwsave)"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(save)");
// EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"("save".omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(save)");
}
TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand)
@ -130,7 +121,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", R"("save&".omwsave")"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(save".omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(save".omwsave)");
}
TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_ampersand)
@ -139,7 +130,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", R"("save.omwsave&&")"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save.omwsave&");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save.omwsave&");
}
TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_ampersand)
@ -148,7 +139,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", "save&.omwsave"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save&.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save&.omwsave");
}
TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_multiple_quotes)
@ -157,7 +148,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", R"(my"save".omwsave)"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(my"save".omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(my"save".omwsave)");
}
TEST(OpenMWOptionsFromArguments, should_compose_data)
@ -166,7 +157,7 @@ namespace
const std::array arguments {"openmw", "--data", "1", "--data", "2"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_THAT(variables["data"].as<Files::EscapePathContainer>(), ElementsAre(IsEscapePath("1"), IsEscapePath("2")));
EXPECT_THAT(variables["data"].as<Files::MaybeQuotedPathContainer>(), ElementsAre(IsPath("1"), IsPath("2")));
}
TEST(OpenMWOptionsFromArguments, should_compose_data_from_single_flag)
@ -175,7 +166,7 @@ namespace
const std::array arguments {"openmw", "--data", "1", "2"};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_THAT(variables["data"].as<Files::EscapePathContainer>(), ElementsAre(IsEscapePath("1"), IsEscapePath("2")));
EXPECT_THAT(variables["data"].as<Files::MaybeQuotedPathContainer>(), ElementsAre(IsPath("1"), IsPath("2")));
}
TEST(OpenMWOptionsFromArguments, should_throw_on_multiple_load_savegame)
@ -196,7 +187,7 @@ namespace
const std::array arguments {"openmw", "--load-savegame", pathArgument.c_str()};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), path);
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), path);
}
INSTANTIATE_TEST_SUITE_P(
@ -205,32 +196,13 @@ namespace
ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ", "\n"}))
);
struct OpenMWOptionsFromArgumentsEscapings : TestWithParam<std::pair<char, char>> {};
TEST_P(OpenMWOptionsFromArgumentsEscapings, should_support_escaping_with_at_sign_in_load_savegame_path)
{
bpo::options_description description = makeOptionsDescription();
const std::string path = "save_@" + std::string(1, GetParam().first) + ".omwsave";
const std::array arguments {"openmw", "--load-savegame", path.c_str()};
bpo::variables_map variables;
parseArgs(arguments, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(),
"save_" + std::string(1, GetParam().second) + ".omwsave");
}
INSTANTIATE_TEST_SUITE_P(
SupportedEscapingsWithAtSign,
OpenMWOptionsFromArgumentsEscapings,
ValuesIn(supportedAtSignEscapings)
);
TEST(OpenMWOptionsFromConfig, should_support_single_word_load_savegame_path)
{
bpo::options_description description = makeOptionsDescription();
std::istringstream stream("load-savegame=save.omwsave");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save.omwsave");
}
TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path)
@ -239,7 +211,7 @@ namespace
std::istringstream stream(R"(load-savegame="save.omwsave")");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save.omwsave");
}
TEST(OpenMWOptionsFromConfig, should_strip_outer_quotes_from_load_savegame_path)
@ -248,8 +220,7 @@ namespace
std::istringstream stream(R"(load-savegame=""save".omwsave")");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "");
// EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(""save".omwsave")");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "");
}
TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path_with_space)
@ -258,16 +229,16 @@ namespace
std::istringstream stream(R"(load-savegame="my save.omwsave")");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "my save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "my save.omwsave");
}
TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_number_sign)
TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_octothorpe)
{
bpo::options_description description = makeOptionsDescription();
std::istringstream stream("load-savegame=save#.omwsave");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save#.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save#.omwsave");
}
TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_at_sign)
@ -276,7 +247,7 @@ namespace
std::istringstream stream("load-savegame=save@.omwsave");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save@.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save@.omwsave");
}
TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_quote)
@ -285,7 +256,25 @@ namespace
std::istringstream stream(R"(load-savegame=save".omwsave)");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(save".omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(save".omwsave)");
}
TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_lots_going_on)
{
bpo::options_description description = makeOptionsDescription();
std::istringstream stream(R"(load-savegame="one &"two"three".omwsave")");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(one "two)");
}
TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_even_more_going_on)
{
bpo::options_description description = makeOptionsDescription();
std::istringstream stream(R"(load-savegame="one &"two"three ".omwsave")");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(one "two)");
}
TEST(OpenMWOptionsFromConfig, should_ignore_commented_option)
@ -294,7 +283,25 @@ namespace
std::istringstream stream("#load-savegame=save.omwsave");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "");
}
TEST(OpenMWOptionsFromConfig, should_ignore_whitespace_prefixed_commented_option)
{
bpo::options_description description = makeOptionsDescription();
std::istringstream stream(" \t#load-savegame=save.omwsave");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "");
}
TEST(OpenMWOptionsFromConfig, should_support_whitespace_around_option)
{
bpo::options_description description = makeOptionsDescription();
std::istringstream stream(" load-savegame = save.omwsave ");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save.omwsave");
}
TEST(OpenMWOptionsFromConfig, should_throw_on_multiple_load_savegame)
@ -311,7 +318,7 @@ namespace
std::istringstream stream("load-savegame=/home/user/openmw/save.omwsave");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "/home/user/openmw/save.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "/home/user/openmw/save.omwsave");
}
TEST(OpenMWOptionsFromConfig, should_support_windows_multi_component_load_savegame_path)
@ -320,7 +327,7 @@ namespace
std::istringstream stream(R"(load-savegame=C:\OpenMW\save.omwsave)");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(C:\OpenMW\save.omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(C:\OpenMW\save.omwsave)");
}
TEST(OpenMWOptionsFromConfig, should_compose_data)
@ -329,7 +336,7 @@ namespace
std::istringstream stream("data=1\ndata=2");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_THAT(variables["data"].as<Files::EscapePathContainer>(), ElementsAre(IsEscapePath("1"), IsEscapePath("2")));
EXPECT_THAT(variables["data"].as<Files::MaybeQuotedPathContainer>(), ElementsAre(IsPath("1"), IsPath("2")));
}
TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand)
@ -338,7 +345,7 @@ namespace
std::istringstream stream(R"(load-savegame="save&".omwsave")");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), R"(save".omwsave)");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), R"(save".omwsave)");
}
TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_ampersand)
@ -347,7 +354,7 @@ namespace
std::istringstream stream(R"(load-savegame="save.omwsave&&")");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save.omwsave&");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save.omwsave&");
}
TEST(OpenMWOptionsFromConfig, should_support_load_savegame_path_with_ampersand)
@ -356,7 +363,7 @@ namespace
std::istringstream stream("load-savegame=save&.omwsave");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), "save&.omwsave");
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), "save&.omwsave");
}
struct OpenMWOptionsFromConfigStrings : TestWithParam<std::string> {};
@ -368,7 +375,7 @@ namespace
std::istringstream stream("load-savegame=\"" + path + "\"");
bpo::variables_map variables;
Files::parseConfig(stream, variables, description);
EXPECT_EQ(variables["load-savegame"].as<Files::EscapePath>().mPath.string(), path);
EXPECT_EQ(variables["load-savegame"].as<Files::MaybeQuotedPath>().string(), path);
}
INSTANTIATE_TEST_SUITE_P(

View File

@ -105,8 +105,8 @@ IF(NOT WIN32 AND NOT APPLE)
add_definitions(-DGLOBAL_CONFIG_PATH="${GLOBAL_CONFIG_PATH}")
ENDIF()
add_component_dir (files
linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager escape
lowlevelfile constrainedfilestream memorystream hash
linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager
lowlevelfile constrainedfilestream memorystream hash configfileparser
)
add_component_dir (compiler

View File

@ -11,13 +11,12 @@ void Fallback::validate(boost::any& v, std::vector<std::string> const& tokens, F
for (const auto& token : tokens)
{
std::string temp = Files::EscapeHashString::processString(token);
size_t sep = temp.find(',');
if (sep < 1 || sep == temp.length() - 1 || sep == std::string::npos)
size_t sep = token.find(',');
if (sep < 1 || sep == token.length() - 1 || sep == std::string::npos)
throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value);
std::string key(temp.substr(0, sep));
std::string value(temp.substr(sep + 1));
std::string key(token.substr(0, sep));
std::string value(token.substr(sep + 1));
map->mMap[key] = value;
}

View File

@ -3,8 +3,6 @@
#include <boost/program_options.hpp>
#include <components/files/escape.hpp>
// Parses and validates a fallback map from boost program_options.
// Note: for boost to pick up the validate function, you need to pull in the namespace e.g.
// by using namespace Fallback;

View File

@ -0,0 +1,297 @@
// This file's contents is largely lifted from boost::program_options with only minor modification.
// Its original preamble (without updated dates) from those source files is below:
// Copyright Vladimir Prus 2002-2004.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "configfileparser.hpp"
#include <boost/program_options/detail/config_file.hpp>
#include <boost/program_options/detail/convert.hpp>
namespace Files
{
namespace
{
/** Standalone parser for config files in ini-line format.
The parser is a model of single-pass lvalue iterator, and
default constructor creates past-the-end-iterator. The typical usage is:
config_file_iterator i(is, ... set of options ...), e;
for(; i !=e; ++i) {
*i;
}
Syntax conventions:
- config file can not contain positional options
- '#' is comment character: it is ignored together with
the rest of the line.
- variable assignments are in the form
name '=' value.
spaces around '=' are trimmed.
- Section names are given in brackets.
The actual option name is constructed by combining current section
name and specified option name, with dot between. If section_name
already contains dot at the end, new dot is not inserted. For example:
@verbatim
[gui.accessibility]
visual_bell=yes
@endverbatim
will result in option "gui.accessibility.visual_bell" with value
"yes" been returned.
TODO: maybe, we should just accept a pointer to options_description
class.
*/
class common_config_file_iterator
: public boost::eof_iterator<common_config_file_iterator, bpo::option>
{
public:
common_config_file_iterator() { found_eof(); }
common_config_file_iterator(
const std::set<std::string>& allowed_options,
bool allow_unregistered = false);
virtual ~common_config_file_iterator() {}
public: // Method required by eof_iterator
void get();
#if BOOST_WORKAROUND(_MSC_VER, <= 1900)
void decrement() {}
void advance(difference_type) {}
#endif
protected: // Stubs for derived classes
// Obtains next line from the config file
// Note: really, this design is a bit ugly
// The most clean thing would be to pass 'line_iterator' to
// constructor of this class, but to avoid templating this class
// we'd need polymorphic iterator, which does not exist yet.
virtual bool getline(std::string&) { return false; }
protected:
/** Adds another allowed option. If the 'name' ends with
'*', then all options with the same prefix are
allowed. For example, if 'name' is 'foo*', then 'foo1' and
'foo_bar' are allowed. */
void add_option(const char* name);
// Returns true if 's' is a registered option name.
bool allowed_option(const std::string& s) const;
// That's probably too much data for iterator, since
// it will be copied, but let's not bother for now.
std::set<std::string> allowed_options;
// Invariant: no element is prefix of other element.
std::set<std::string> allowed_prefixes;
std::string m_prefix;
bool m_allow_unregistered;
};
common_config_file_iterator::common_config_file_iterator(
const std::set<std::string>& allowed_options,
bool allow_unregistered)
: allowed_options(allowed_options),
m_allow_unregistered(allow_unregistered)
{
for (std::set<std::string>::const_iterator i = allowed_options.begin();
i != allowed_options.end();
++i)
{
add_option(i->c_str());
}
}
void common_config_file_iterator::add_option(const char* name)
{
std::string s(name);
assert(!s.empty());
if (*s.rbegin() == '*') {
s.resize(s.size() - 1);
bool bad_prefixes(false);
// If 's' is a prefix of one of allowed suffix, then
// lower_bound will return that element.
// If some element is prefix of 's', then lower_bound will
// return the next element.
std::set<std::string>::iterator i = allowed_prefixes.lower_bound(s);
if (i != allowed_prefixes.end()) {
if (i->find(s) == 0)
bad_prefixes = true;
}
if (i != allowed_prefixes.begin()) {
--i;
if (s.find(*i) == 0)
bad_prefixes = true;
}
if (bad_prefixes)
boost::throw_exception(bpo::error("options '" + std::string(name) + "' and '" +
*i + "*' will both match the same "
"arguments from the configuration file"));
allowed_prefixes.insert(s);
}
}
std::string trim_ws(const std::string& s)
{
std::string::size_type n, n2;
n = s.find_first_not_of(" \t\r\n");
if (n == std::string::npos)
return std::string();
else {
n2 = s.find_last_not_of(" \t\r\n");
return s.substr(n, n2 - n + 1);
}
}
void common_config_file_iterator::get()
{
std::string s;
std::string::size_type n;
bool found = false;
while (this->getline(s)) {
// strip '#' comments and whitespace
if (s.find('#') == s.find_first_not_of(" \t\r\n"))
continue;
s = trim_ws(s);
if (!s.empty()) {
// Handle section name
if (*s.begin() == '[' && *s.rbegin() == ']') {
m_prefix = s.substr(1, s.size() - 2);
if (*m_prefix.rbegin() != '.')
m_prefix += '.';
}
else if ((n = s.find('=')) != std::string::npos) {
std::string name = m_prefix + trim_ws(s.substr(0, n));
std::string value = trim_ws(s.substr(n + 1));
bool registered = allowed_option(name);
if (!registered && !m_allow_unregistered)
boost::throw_exception(bpo::unknown_option(name));
found = true;
this->value().string_key = name;
this->value().value.clear();
this->value().value.push_back(value);
this->value().unregistered = !registered;
this->value().original_tokens.clear();
this->value().original_tokens.push_back(name);
this->value().original_tokens.push_back(value);
break;
} else {
boost::throw_exception(bpo::invalid_config_file_syntax(s, bpo::invalid_syntax::unrecognized_line));
}
}
}
if (!found)
found_eof();
}
bool common_config_file_iterator::allowed_option(const std::string& s) const
{
std::set<std::string>::const_iterator i = allowed_options.find(s);
if (i != allowed_options.end())
return true;
// If s is "pa" where "p" is allowed prefix then
// lower_bound should find the element after "p".
// This depends on 'allowed_prefixes' invariant.
i = allowed_prefixes.lower_bound(s);
if (i != allowed_prefixes.begin() && s.find(*--i) == 0)
return true;
return false;
}
template<class charT>
class basic_config_file_iterator : public Files::common_config_file_iterator {
public:
basic_config_file_iterator()
{
found_eof();
}
/** Creates a config file parser for the specified stream.
*/
basic_config_file_iterator(std::basic_istream<charT>& is,
const std::set<std::string>& allowed_options,
bool allow_unregistered = false);
private: // base overrides
bool getline(std::string&);
private: // internal data
std::shared_ptr<std::basic_istream<charT> > is;
};
template<class charT>
basic_config_file_iterator<charT>::
basic_config_file_iterator(std::basic_istream<charT>& is,
const std::set<std::string>& allowed_options,
bool allow_unregistered)
: common_config_file_iterator(allowed_options, allow_unregistered)
{
this->is.reset(&is, bpo::detail::null_deleter());
get();
}
template<class charT>
bool basic_config_file_iterator<charT>::getline(std::string& s)
{
std::basic_string<charT> in;
if (std::getline(*is, in)) {
s = bpo::to_internal(in);
return true;
} else {
return false;
}
}
}
template<class charT>
bpo::basic_parsed_options<charT>
parse_config_file(std::basic_istream<charT>& is,
const bpo::options_description& desc,
bool allow_unregistered)
{
std::set<std::string> allowed_options;
const std::vector<boost::shared_ptr<bpo::option_description> >& options = desc.options();
for (unsigned i = 0; i < options.size(); ++i)
{
const bpo::option_description& d = *options[i];
if (d.long_name().empty())
boost::throw_exception(
bpo::error("abbreviated option names are not permitted in options configuration files"));
allowed_options.insert(d.long_name());
}
// Parser return char strings
bpo::parsed_options result(&desc);
copy(basic_config_file_iterator<charT>(
is, allowed_options, allow_unregistered),
basic_config_file_iterator<charT>(),
back_inserter(result.options));
// Convert char strings into desired type.
return bpo::basic_parsed_options<charT>(result);
}
template
bpo::basic_parsed_options<char>
parse_config_file(std::basic_istream<char>& is,
const bpo::options_description& desc,
bool allow_unregistered);
}

View File

@ -0,0 +1,17 @@
#ifndef COMPONENTS_FILES_CONFIGFILEPARSER_HPP
#define COMPONENTS_FILES_CONFIGFILEPARSER_HPP
#include <boost/program_options/parsers.hpp>
namespace Files
{
namespace bpo = boost::program_options;
template<class charT>
bpo::basic_parsed_options<charT> parse_config_file(std::basic_istream<charT>&, const bpo::options_description&,
bool allow_unregistered = false);
}
#endif // COMPONENTS_FILES_CONFIGFILEPARSER_HPP

View File

@ -1,7 +1,7 @@
#include "configurationmanager.hpp"
#include <components/debug/debuglog.hpp>
#include <components/files/escape.hpp>
#include <components/files/configfileparser.hpp>
#include <components/fallback/validate.hpp>
#include <boost/filesystem/fstream.hpp>
@ -109,7 +109,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost
auto replace = second["replace"];
if (!replace.defaulted() && !replace.empty())
{
std::vector<std::string> replaceVector = replace.as<Files::EscapeStringVector>().toStdStringVector();
std::vector<std::string> replaceVector = replace.as<std::vector<std::string>>();
replacedVariables.insert(replaceVector.begin(), replaceVector.end());
}
}
@ -138,19 +138,19 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost
boost::any& firstValue = firstPosition->second.value();
const boost::any& secondValue = second[name].value();
if (firstValue.type() == typeid(Files::EscapePathContainer))
if (firstValue.type() == typeid(Files::MaybeQuotedPathContainer))
{
auto& firstPathContainer = boost::any_cast<Files::EscapePathContainer&>(firstValue);
const auto& secondPathContainer = boost::any_cast<const Files::EscapePathContainer&>(secondValue);
auto& firstPathContainer = boost::any_cast<Files::MaybeQuotedPathContainer&>(firstValue);
const auto& secondPathContainer = boost::any_cast<const Files::MaybeQuotedPathContainer&>(secondValue);
firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end());
}
else if (firstValue.type() == typeid(Files::EscapeStringVector))
else if (firstValue.type() == typeid(std::vector<std::string>))
{
auto& firstVector = boost::any_cast<Files::EscapeStringVector&>(firstValue);
const auto& secondVector = boost::any_cast<const Files::EscapeStringVector&>(secondValue);
auto& firstVector = boost::any_cast<std::vector<std::string>&>(firstValue);
const auto& secondVector = boost::any_cast<const std::vector<std::string>&>(secondValue);
firstVector.mVector.insert(firstVector.mVector.end(), secondVector.mVector.begin(), secondVector.mVector.end());
firstVector.insert(firstVector.end(), secondVector.begin(), secondVector.end());
}
else if (firstValue.type() == typeid(Fallback::FallbackMap))
{
@ -235,11 +235,11 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path,
if (!mSilent)
Log(Debug::Info) << "Loading config file: " << cfgFile.string();
boost::filesystem::ifstream configFileStreamUnfiltered(cfgFile);
boost::filesystem::ifstream configFileStream(cfgFile);
if (configFileStreamUnfiltered.is_open())
if (configFileStream.is_open())
{
parseConfig(configFileStreamUnfiltered, variables, description);
parseConfig(configFileStream, variables, description);
return true;
}
@ -311,14 +311,37 @@ void parseArgs(int argc, const char* const argv[], boost::program_options::varia
void parseConfig(std::istream& stream, boost::program_options::variables_map& variables,
boost::program_options::options_description& description)
{
boost::iostreams::filtering_istream configFileStream;
configFileStream.push(escape_hash_filter());
configFileStream.push(stream);
boost::program_options::store(
boost::program_options::parse_config_file(configFileStream, description, true),
Files::parse_config_file(stream, description, true),
variables
);
}
std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath)
{
// If the stream starts with a double quote, read from stream using boost::filesystem::path rules, then discard anything remaining.
// This prevents boost::program_options getting upset that we've not consumed the whole stream.
// If it doesn't start with a double quote, read the whole thing verbatim
if (istream.peek() == '"')
{
istream >> static_cast<boost::filesystem::path&>(MaybeQuotedPath);
if (istream && !istream.eof() && istream.peek() != EOF)
{
std::string remainder{std::istreambuf_iterator(istream), {}};
Log(Debug::Warning) << "Trailing data in path setting. Used '" << MaybeQuotedPath.string() << "' but '" << remainder << "' remained";
}
}
else
{
std::string intermediate{std::istreambuf_iterator(istream), {}};
static_cast<boost::filesystem::path&>(MaybeQuotedPath) = intermediate;
}
return istream;
}
PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathContainer)
{
return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end());
}
} /* namespace Cfg */

View File

@ -77,6 +77,16 @@ void parseArgs(int argc, const char* const argv[], boost::program_options::varia
void parseConfig(std::istream& stream, boost::program_options::variables_map& variables,
boost::program_options::options_description& description);
class MaybeQuotedPath : public boost::filesystem::path
{
};
std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath);
typedef std::vector<MaybeQuotedPath> MaybeQuotedPathContainer;
PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathContainer);
} /* namespace Cfg */
#endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */

View File

@ -1,145 +0,0 @@
#include "escape.hpp"
#include <components/misc/stringops.hpp>
namespace Files
{
const int escape_hash_filter::sEscape = '@';
const int escape_hash_filter::sEscapeIdentifier = 'a';
const int escape_hash_filter::sHashIdentifier = 'h';
escape_hash_filter::escape_hash_filter() : mSeenNonWhitespace(false), mFinishLine(false)
{
}
escape_hash_filter::~escape_hash_filter()
{
}
unescape_hash_filter::unescape_hash_filter() : expectingIdentifier(false)
{
}
unescape_hash_filter::~unescape_hash_filter()
{
}
std::string EscapeHashString::processString(const std::string & str)
{
std::string temp = str;
static const char hash[] = { escape_hash_filter::sEscape, escape_hash_filter::sHashIdentifier };
Misc::StringUtils::replaceAll(temp, std::string_view(hash, 2), "#");
static const char escape[] = { escape_hash_filter::sEscape, escape_hash_filter::sEscapeIdentifier };
Misc::StringUtils::replaceAll(temp, std::string_view(escape, 2), "@");
return temp;
}
EscapeHashString::EscapeHashString() : mData()
{
}
EscapeHashString::EscapeHashString(const std::string & str) : mData(EscapeHashString::processString(str))
{
}
EscapeHashString::EscapeHashString(const std::string & str, size_t pos, size_t len) : mData(EscapeHashString::processString(str), pos, len)
{
}
EscapeHashString::EscapeHashString(const char * s) : mData(EscapeHashString::processString(std::string(s)))
{
}
EscapeHashString::EscapeHashString(const char * s, size_t n) : mData(EscapeHashString::processString(std::string(s)), 0, n)
{
}
EscapeHashString::EscapeHashString(size_t n, char c) : mData(n, c)
{
}
template <class InputIterator>
EscapeHashString::EscapeHashString(InputIterator first, InputIterator last) : mData(EscapeHashString::processString(std::string(first, last)))
{
}
std::string EscapeHashString::toStdString() const
{
return std::string(mData);
}
std::istream & operator>> (std::istream & is, EscapeHashString & eHS)
{
std::string temp;
is >> temp;
eHS = EscapeHashString(temp);
return is;
}
std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS)
{
os << eHS.mData;
return os;
}
EscapeStringVector::EscapeStringVector() : mVector()
{
}
EscapeStringVector::~EscapeStringVector()
{
}
std::vector<std::string> EscapeStringVector::toStdStringVector() const
{
std::vector<std::string> temp = std::vector<std::string>();
for (std::vector<EscapeHashString>::const_iterator it = mVector.begin(); it != mVector.end(); ++it)
{
temp.push_back(it->toStdString());
}
return temp;
}
// boost program options validation
void validate(boost::any &v, const std::vector<std::string> &tokens, Files::EscapeHashString * eHS, int a)
{
boost::program_options::validators::check_first_occurrence(v);
if (v.empty())
v = boost::any(EscapeHashString(boost::program_options::validators::get_single_string(tokens)));
}
void validate(boost::any &v, const std::vector<std::string> &tokens, EscapeStringVector *, int)
{
if (v.empty())
v = boost::any(EscapeStringVector());
EscapeStringVector * eSV = boost::any_cast<EscapeStringVector>(&v);
for (std::vector<std::string>::const_iterator it = tokens.begin(); it != tokens.end(); ++it)
eSV->mVector.emplace_back(*it);
}
PathContainer EscapePath::toPathContainer(const EscapePathContainer & escapePathContainer)
{
PathContainer temp;
for (EscapePathContainer::const_iterator it = escapePathContainer.begin(); it != escapePathContainer.end(); ++it)
temp.push_back(it->mPath);
return temp;
}
std::istream & operator>> (std::istream & istream, EscapePath & escapePath)
{
boost::iostreams::filtering_istream filteredStream;
filteredStream.push(unescape_hash_filter());
filteredStream.push(istream);
filteredStream >> escapePath.mPath;
return istream;
}
}

View File

@ -1,191 +0,0 @@
#ifndef COMPONENTS_FILES_ESCAPE_HPP
#define COMPONENTS_FILES_ESCAPE_HPP
#include <queue>
#include <components/files/multidircollection.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/program_options.hpp>
/**
* \namespace Files
*/
namespace Files
{
/**
* \struct escape_hash_filter
*/
struct escape_hash_filter : public boost::iostreams::input_filter
{
static const int sEscape;
static const int sHashIdentifier;
static const int sEscapeIdentifier;
escape_hash_filter();
virtual ~escape_hash_filter();
template <typename Source> int get(Source & src);
private:
std::queue<int> mNext;
bool mSeenNonWhitespace;
bool mFinishLine;
};
template <typename Source>
int escape_hash_filter::get(Source & src)
{
if (mNext.empty())
{
int character = boost::iostreams::get(src);
if (character == boost::iostreams::WOULD_BLOCK)
{
mNext.push(character);
}
else if (character == EOF)
{
mSeenNonWhitespace = false;
mFinishLine = false;
mNext.push(character);
}
else if (character == '\n')
{
mSeenNonWhitespace = false;
mFinishLine = false;
mNext.push(character);
}
else if (mFinishLine)
{
mNext.push(character);
}
else if (character == '#')
{
if (mSeenNonWhitespace)
{
mNext.push(sEscape);
mNext.push(sHashIdentifier);
}
else
{
//it's fine being interpreted by Boost as a comment, and so is anything afterwards
mNext.push(character);
mFinishLine = true;
}
}
else if (character == sEscape)
{
mNext.push(sEscape);
mNext.push(sEscapeIdentifier);
}
else
{
mNext.push(character);
}
if (!mSeenNonWhitespace && !(character >= 0 && character <= 255 && isspace(character)))
mSeenNonWhitespace = true;
}
int retval = mNext.front();
mNext.pop();
return retval;
}
struct unescape_hash_filter : public boost::iostreams::input_filter
{
unescape_hash_filter();
virtual ~unescape_hash_filter();
template <typename Source> int get(Source & src);
private:
bool expectingIdentifier;
};
template <typename Source>
int unescape_hash_filter::get(Source & src)
{
int character;
if (!expectingIdentifier)
character = boost::iostreams::get(src);
else
{
character = escape_hash_filter::sEscape;
expectingIdentifier = false;
}
if (character == escape_hash_filter::sEscape)
{
int nextChar = boost::iostreams::get(src);
int intended;
if (nextChar == escape_hash_filter::sEscapeIdentifier)
intended = escape_hash_filter::sEscape;
else if (nextChar == escape_hash_filter::sHashIdentifier)
intended = '#';
else if (nextChar == boost::iostreams::WOULD_BLOCK)
{
expectingIdentifier = true;
intended = nextChar;
}
else
intended = '?';
return intended;
}
else
return character;
}
/**
* \class EscapeHashString
*/
class EscapeHashString
{
private:
std::string mData;
public:
static std::string processString(const std::string & str);
EscapeHashString();
EscapeHashString(const std::string & str);
EscapeHashString(const std::string & str, size_t pos, size_t len = std::string::npos);
EscapeHashString(const char * s);
EscapeHashString(const char * s, size_t n);
EscapeHashString(size_t n, char c);
template <class InputIterator>
EscapeHashString(InputIterator first, InputIterator last);
std::string toStdString() const;
friend std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS);
};
std::istream & operator>> (std::istream & is, EscapeHashString & eHS);
struct EscapeStringVector
{
std::vector<Files::EscapeHashString> mVector;
EscapeStringVector();
virtual ~EscapeStringVector();
std::vector<std::string> toStdStringVector() const;
};
//boost program options validation
void validate(boost::any &v, const std::vector<std::string> &tokens, Files::EscapeHashString * eHS, int a);
void validate(boost::any &v, const std::vector<std::string> &tokens, EscapeStringVector *, int);
struct EscapePath
{
boost::filesystem::path mPath;
static PathContainer toPathContainer(const std::vector<EscapePath> & escapePathContainer);
};
typedef std::vector<EscapePath> EscapePathContainer;
std::istream & operator>> (std::istream & istream, EscapePath & escapePath);
} /* namespace Files */
#endif /* COMPONENTS_FILES_ESCAPE_HPP */