1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-25 20:02:40 +01:00

config: allow reading INI from string, file, default and current config

This (slightly more complicated implementation) allows us,
once a configuration menu has been implemented,
to store the current/new configuration to a INI file.

The definitions of the parameters in rwgame/GameConfig.cpp
is limited to one location.
This commit is contained in:
Anonymous Maarten 2017-01-15 03:16:46 +01:00 committed by Daniel Evans
parent a725a51baa
commit 05db65dbbc
4 changed files with 157 additions and 36 deletions

View File

@ -13,6 +13,10 @@ GameBase::GameBase(Logger &inlog, int argc, char *argv[]) : log(inlog) {
log.info("Game", "Build: " + kBuildStr); log.info("Game", "Build: " + kBuildStr);
if (!config.isValid()) { if (!config.isValid()) {
log.error("Config", "Invalid INI file at \""
+ config.getConfigFile() + "\".\n"
+ "Adapt the following default INI to your configuration.\n"
+ config.getDefaultINIString());
throw std::runtime_error("Invalid configuration file at: " + throw std::runtime_error("Invalid configuration file at: " +
config.getConfigFile()); config.getConfigFile());
} }

View File

@ -22,14 +22,15 @@ GameConfig::GameConfig(const std::string& configName,
// Look up the path to use // Look up the path to use
auto configFile = getConfigFile(); auto configFile = getConfigFile();
m_valid = readConfig(configFile); std::string dummy;
m_valid = parseConfig(ParseType::FILE, configFile, ParseType::CONFIG, dummy);
} }
std::string GameConfig::getConfigFile() { std::string GameConfig::getConfigFile() const {
return m_configPath + "/" + m_configName; return m_configPath + "/" + m_configName;
} }
bool GameConfig::isValid() { bool GameConfig::isValid() const {
return m_valid; return m_valid;
} }
@ -60,8 +61,7 @@ std::string GameConfig::getDefaultConfigPath() {
return "."; return ".";
} }
std::string stripComments(const std::string &str) std::string stripComments(const std::string &str) {
{
auto s = std::string(str, 0, str.find_first_of(";#")); auto s = std::string(str, 0, str.find_first_of(";#"));
return s.erase(s.find_last_not_of(" \n\r\t")+1); return s.erase(s.find_last_not_of(" \n\r\t")+1);
} }
@ -88,50 +88,139 @@ struct BoolTranslator {
} }
}; };
bool GameConfig::readConfig(const std::string &path) { struct IntTranslator {
pt::ptree config, defaultConfig; typedef std::string internal_type;
typedef int external_type;
boost::optional<external_type> get_value(const internal_type &str) {
return boost::optional<external_type>(std::stoi(stripComments(str)));
}
boost::optional<internal_type> put_value(const external_type &i) {
return boost::optional<internal_type>(std::to_string(i));
}
};
std::string GameConfig::getDefaultINIString() {
std::string result;
parseConfig(ParseType::DEFAULT, "", ParseType::STRING, result);
return result;
}
bool GameConfig::parseConfig(
GameConfig::ParseType srcType, const std::string &source,
ParseType destType, std::string &destination)
{
bool success = true; bool success = true;
pt::ptree srcTree;
if ((srcType == ParseType::STRING) || (srcType == ParseType::FILE)) {
std::istream *istream = nullptr;
switch (srcType) {
case ParseType::STRING:
istream = new std::istringstream(source);
break;
case ParseType::FILE:
istream = new std::ifstream(source);
break;
default:
//Cannot reach here
success = false;
break;
}
if (istream != nullptr) {
try {
pt::read_ini(*istream, srcTree);
} catch (pt::ini_parser_error &e) {
success = false;
RW_MESSAGE(e.what());
}
} else {
success = false;
RW_MESSAGE("Unable to create stream from source.");
}
delete istream;
}
auto read_config = [&](const std::string &key, auto &target, auto read_config = [&](const std::string &key, auto &target,
const auto &defaultValue, auto &translator, const auto &defaultValue, auto &translator,
bool optional=true) { bool optional=true) {
typedef typename std::remove_reference<decltype(target)>::type targetType; typedef typename std::remove_reference<decltype(target)>::type config_t;
defaultConfig.put(key, defaultValue, translator);
try { config_t sourceValue;
target = config.get<targetType>(key, translator);
} catch (pt::ptree_bad_path &e) { switch (srcType) {
if (optional) { case ParseType::DEFAULT:
target = defaultValue; sourceValue = defaultValue;
} else { break;
success = false; case ParseType::CONFIG:
RW_MESSAGE(e.what()); sourceValue = target;
} break;
case ParseType::FILE:
case ParseType::STRING:
try {
sourceValue = srcTree.get<config_t>(key, translator);
} catch (pt::ptree_bad_path &e) {
if (optional) {
sourceValue = defaultValue;
} else {
success = false;
RW_MESSAGE(e.what());
}
}
break;
}
srcTree.put(key, sourceValue, translator);
switch (destType) {
case ParseType::DEFAULT:
RW_ERROR("Target cannot be DEFAULT.");
success = false;
break;
case ParseType::CONFIG:
//Don't care if success == false
target = sourceValue;
break;
case ParseType::FILE:
case ParseType::STRING:
break;
} }
}; };
try {
pt::read_ini(path, config);
} catch (pt::ini_parser_error &e) {
success = false;
RW_MESSAGE(e.what());
}
auto deft = StringTranslator(); auto deft = StringTranslator();
auto boolt = BoolTranslator(); auto boolt = BoolTranslator();
// @todo Don't allow path seperators and relative directories //Add new configuration parameters here.
// @todo Don't allow path separators and relative directories
read_config("game.path", this->m_gamePath, "/opt/games/Grand Theft Auto 3", deft, false); read_config("game.path", this->m_gamePath, "/opt/games/Grand Theft Auto 3", deft, false);
read_config("game.language", this->m_gameLanguage, "american", deft); read_config("game.language", this->m_gameLanguage, "american", deft);
read_config("input.invert_y", this->m_inputInvertY, false, boolt); read_config("input.invert_y", this->m_inputInvertY, false, boolt);
if (!success) { if ((destType == ParseType::STRING) || (destType == ParseType::FILE)) {
std::stringstream strstream; std::ostringstream ostream;
pt::write_ini(strstream, defaultConfig); try {
pt::write_ini(ostream, srcTree);
RW_MESSAGE("Failed to parse configuration file (" + path + ")."); } catch (pt::ini_parser_error &e) {
RW_MESSAGE("Create one looking like this at \"" + path + "\":"); success = false;
RW_MESSAGE(strstream.str()); RW_MESSAGE(e.what());
}
switch (destType) {
case ParseType::STRING:
destination = ostream.str();
break;
case ParseType::FILE:
{
std::ofstream ofs(destination);
ofs << ostream.str();
ofs.close();
success &= !ofs.fail();
break;
}
default:
//Cannot reach here
success = false;
break;
}
} }
return success; return success;

View File

@ -15,13 +15,19 @@ public:
/** /**
* @brief getFilePath Returns the system file path for the configuration * @brief getFilePath Returns the system file path for the configuration
*/ */
std::string getConfigFile(); std::string getConfigFile() const;
/** /**
* @brief isValid * @brief isValid
* @return True if the loaded configuration is valid * @return True if the loaded configuration is valid
*/ */
bool isValid(); bool isValid() const;
/**
* @brief getConfigString Returns the content of the default INI configuration.
* @return INI string
*/
std::string getDefaultINIString();
const std::string& getGameDataPath() const { const std::string& getGameDataPath() const {
return m_gamePath; return m_gamePath;
@ -36,7 +42,27 @@ public:
private: private:
static std::string getDefaultConfigPath(); static std::string getDefaultConfigPath();
bool readConfig(const std::string &path); enum ParseType {
DEFAULT,
CONFIG,
FILE,
STRING
};
/**
* @brief parseConfig Load data from source and write it to destination.
* @param srcType Can be DEFAULT | CONFIG | FILE | STRING
* @param source don't care if srcType == (DEFAULT | CONFIG),
* path of INI file if srcType == FILE
* INI string if srcType == STRING
* @param destType Can be CONFIG | FILE | STRING (DEFAULT is invalid)
* @param destination don't care if srcType == CONFIG
* path of INI file if destType == FILE
* INI string if srcType == STRING
* @return True if the parsing succeeded
*/
bool parseConfig(ParseType srcType, const std::string &source,
ParseType destType, std::string &destination);
/* Config State */ /* Config State */
std::string m_configName; std::string m_configName;

View File

@ -4,6 +4,7 @@
BOOST_AUTO_TEST_SUITE(ConfigTests) BOOST_AUTO_TEST_SUITE(ConfigTests)
BOOST_AUTO_TEST_CASE(test_loading) { BOOST_AUTO_TEST_CASE(test_loading) {
// Write out a temporary file // Write out a temporary file
std::ofstream test_config("/tmp/openrw_test.ini"); std::ofstream test_config("/tmp/openrw_test.ini");
@ -20,6 +21,7 @@ BOOST_AUTO_TEST_CASE(test_loading) {
BOOST_CHECK_EQUAL(config.getGameDataPath(), "/dev/test"); BOOST_CHECK_EQUAL(config.getGameDataPath(), "/dev/test");
BOOST_CHECK_EQUAL(config.getGameLanguage(), "american"); BOOST_CHECK_EQUAL(config.getGameLanguage(), "american");
BOOST_CHECK_EQUAL(config.getInputInvertY(), false);
} }