diff --git a/rwgame/GameBase.cpp b/rwgame/GameBase.cpp index 1b1b2766..9eb4e7b3 100644 --- a/rwgame/GameBase.cpp +++ b/rwgame/GameBase.cpp @@ -17,8 +17,7 @@ GameBase::GameBase(Logger &inlog, int argc, char *argv[]) : log(inlog) { + config.getConfigFile() + "\".\n" + "Adapt the following default INI to your configuration.\n" + config.getDefaultINIString()); - throw std::runtime_error("Invalid configuration file at: " + - config.getConfigFile()); + throw std::runtime_error(config.getParseResult().what()); } size_t w = kWindowWidth, h = kWindowHeight; @@ -28,11 +27,12 @@ GameBase::GameBase(Logger &inlog, int argc, char *argv[]) : log(inlog) { // Define and parse command line options namespace po = boost::program_options; po::options_description desc("Available options"); - desc.add_options()("help", "Show this help message")( + desc.add_options()( + "help", "Show this help message")( "width,w", po::value(), "Game resolution width in pixel")( "height,h", po::value(), "Game resolution height in pixel")( - "fullscreen,f", "Enable fullscreen mode")("newgame,n", - "Directly start a new game")( + "fullscreen,f", "Enable fullscreen mode")( + "newgame,n", "Directly start a new game")( "test,t", "Starts a new game in a test location")( "load,l", po::value(), "Load save file")( "benchmark,b", po::value(), "Run benchmark from file"); diff --git a/rwgame/GameConfig.cpp b/rwgame/GameConfig.cpp index 076691a2..b8ab4e08 100644 --- a/rwgame/GameConfig.cpp +++ b/rwgame/GameConfig.cpp @@ -130,7 +130,7 @@ GameConfig::ParseResult GameConfig::parseConfig( ParseType destType, std::string &destination) { pt::ptree srcTree; - ParseResult parseResult; + ParseResult parseResult(srcType, source, destType, destination); try { if (srcType == ParseType::STRING) { @@ -140,7 +140,7 @@ GameConfig::ParseResult GameConfig::parseConfig( } } catch (pt::ini_parser_error &e) { // Catches illegal input files (nonsensical input, duplicate keys) - parseResult.failInputFile(e.filename(), e.line(), e.message()); + parseResult.failInputFile(e.line(), e.message()); RW_MESSAGE(e.what()); return parseResult; } @@ -226,16 +226,44 @@ GameConfig::ParseResult GameConfig::parseConfig( pt::write_ini(destination, srcTree); } } catch (pt::ini_parser_error &e) { - parseResult.failOutputFile(e.filename(), e.line(), e.message()); + parseResult.failOutputFile(e.line(), e.message()); RW_MESSAGE(e.what()); } return parseResult; } +std::string GameConfig::extractFilenameParseTypeData(ParseType type, const std::string &data) +{ + switch (type) { + case ParseType::CONFIG: + return ""; + case ParseType::FILE: + return data; + case ParseType::STRING: + return ""; + case ParseType::DEFAULT: + default: + return ""; + } +} + +GameConfig::ParseResult::ParseResult( + GameConfig::ParseType srcType, const std::string &source, + GameConfig::ParseType destType, const std::string &destination) + : m_result(ErrorType::GOOD) + , m_inputfilename(GameConfig::extractFilenameParseTypeData(srcType, source)) + , m_outputfilename(GameConfig::extractFilenameParseTypeData(destType, destination)) + , m_line(0) + , m_message() + , m_keys_requiredMissing() + , m_keys_invalidData() { +} + GameConfig::ParseResult::ParseResult() : m_result(ErrorType::GOOD) - , m_filename() + , m_inputfilename() + , m_outputfilename() , m_line(0) , m_message() , m_keys_requiredMissing() @@ -250,10 +278,9 @@ bool GameConfig::ParseResult::isValid() const { return this->type() == ErrorType::GOOD; } -void GameConfig::ParseResult::failInputFile(const std::string &filename, size_t line, +void GameConfig::ParseResult::failInputFile(size_t line, const std::string &message) { this->m_result = ParseResult::ErrorType::INVALIDINPUTFILE; - this->m_filename = filename; this->m_line = line; this->m_message = message; } @@ -272,9 +299,9 @@ void GameConfig::ParseResult::failInvalidData(const std::string &key) { this->m_keys_invalidData.push_back(key); } -void GameConfig::ParseResult::failOutputFile(const std::string &filename, size_t line, const std::string &message) { +void GameConfig::ParseResult::failOutputFile(size_t line, + const std::string &message) { this->m_result = ParseResult::ErrorType::INVALIDOUTPUTFILE; - this->m_filename = filename; this->m_line = line; this->m_message = message; } @@ -287,3 +314,48 @@ const std::vector &GameConfig::ParseResult::getKeysInvalidData() co return this->m_keys_invalidData; } +std::string GameConfig::ParseResult::what() const { + switch (this->m_result) { + case ErrorType::GOOD: + return "Good"; + case ErrorType::INVALIDARGUMENT: + return "Invalid argument: destination cannot be the default config"; + case ErrorType::INVALIDINPUTFILE: + { + std::ostringstream oss; + oss << "Error while reading \"" + << this->m_inputfilename << "\":" << this->m_line << ":\n" + << this->m_message; + return oss.str(); + } + case ErrorType::INVALIDOUTPUTFILE: + { + std::ostringstream oss; + oss << "Error while writing \"" + << this->m_inputfilename << "\":" << this->m_line << ":\n" + << this->m_message; + return oss.str(); + } + case ErrorType::INVALIDCONTENT: + { + std::ostringstream oss; + oss << "Error while parsing \"" + << this->m_inputfilename << "\"."; + if (this->m_keys_requiredMissing.size()) { + oss << "\nRequired keys that are missing:"; + for (auto &key : this->m_keys_requiredMissing) { + oss << "\n - " << key; + } + } + if (this->m_keys_invalidData.size()) { + oss << "\nKeys that contain invalid data:"; + for (auto &key : this->m_keys_invalidData) { + oss << "\n - " << key; + } + } + return oss.str(); + } + default: + return "Unknown error"; + } +} diff --git a/rwgame/GameConfig.hpp b/rwgame/GameConfig.hpp index 7824a8c1..51a24522 100644 --- a/rwgame/GameConfig.hpp +++ b/rwgame/GameConfig.hpp @@ -4,6 +4,19 @@ #include class GameConfig { +private: + enum ParseType { + DEFAULT, + CONFIG, + FILE, + STRING + }; + + /** + * @brief extractFilenameParseTypeData Get a human readable filename string + * @return file path or a description of the data type + */ + static std::string extractFilenameParseTypeData(ParseType type, const std::string &data); public: class ParseResult { public: @@ -19,11 +32,23 @@ public: /// INVALIDOUTPUTFILE: There was some error while writing to a file or string INVALIDOUTPUTFILE }; + private: /** * @brief ParseResult Holds the issues occurred while parsing of a config file. + * @param srcType Type of the source + * @param source The source of the parser + * @param destType Type of the destination + * @param destination The destination */ + ParseResult(ParseType srcType, const std::string &source, + ParseType destType, const std::string &destination); + /** + * @brief ParseResult Create empty ParseResult + */ ParseResult(); + + public: /** * @brief type Get the type of error * @return Type of error or GOOD if there was no error @@ -43,14 +68,15 @@ public: const std::vector &getKeysInvalidData() const; /** * @brief failInputFile Fail because the input file was invalid - * @param filename Filename of the invalid file * @param line Line number where the error is located * @param message Description of the error */ - void failInputFile(const std::string &filename, size_t line, const std::string &message); + void failInputFile(size_t line, const std::string &message); /** * @brief failArgument Fail because an argument was invalid + * @param srcType type of the source + * @param destType type of the destination */ void failArgument(); @@ -68,23 +94,31 @@ public: /** * @brief failOutputFile Fail because an error occurred while while writing to the output - * @param filename Filename of the invalid file * @param line Line number where the error is located * @param message Description of the error */ - void failOutputFile(const std::string &filename, size_t line, const std::string &message); + void failOutputFile(size_t line, const std::string &message); /** * @brief isValid * @return True if the loaded configuration is valid */ bool isValid() const; + + /** + * @brief what Get a string representing the error + * @return String with the error description + */ + std::string what() const; private: /// Type of the failure ErrorType m_result; - /// Filename of the invalid input or output file - std::string m_filename; + /// Filename of the input file + std::string m_inputfilename; + + /// Filename of the output file + std::string m_outputfilename; /// Line number where the failure occurred (on invalid input or output file) size_t m_line; @@ -97,6 +131,8 @@ public: /// All keys that contain invalid data std::vector m_keys_invalidData; + + friend class GameConfig; }; /** @@ -148,13 +184,6 @@ public: private: static std::string getDefaultConfigPath(); - 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 diff --git a/tests/test_config.cpp b/tests/test_config.cpp index 252c43ca..d73fce1f 100644 --- a/tests/test_config.cpp +++ b/tests/test_config.cpp @@ -126,7 +126,7 @@ BOOST_AUTO_TEST_CASE(test_config_valid) { GameConfig config(tempFile.filename(), tempFile.dirname()); BOOST_CHECK(config.isValid()); - BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::GOOD); + BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::ErrorType::GOOD); BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(), 0); BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0); @@ -147,7 +147,7 @@ BOOST_AUTO_TEST_CASE(test_config_valid_modified) { GameConfig config(tempFile.filename(), tempFile.dirname()); BOOST_CHECK(config.isValid()); - BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::GOOD); + BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::ErrorType::GOOD); BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(), 0); BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0); @@ -191,7 +191,7 @@ BOOST_AUTO_TEST_CASE(test_config_save_readonly) { auto writeResult = config.saveConfig(); BOOST_CHECK(!writeResult.isValid()); - BOOST_CHECK_EQUAL(writeResult.type(), GameConfig::ParseResult::INVALIDOUTPUTFILE); + BOOST_CHECK_EQUAL(writeResult.type(), GameConfig::ParseResult::ErrorType::INVALIDOUTPUTFILE); } BOOST_AUTO_TEST_CASE(test_config_valid_default) { @@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_duplicate) { BOOST_CHECK(!config.isValid()); const auto &parseResult = config.getParseResult(); - BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::INVALIDINPUTFILE); + BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDINPUTFILE); } BOOST_AUTO_TEST_CASE(test_config_invalid_required_missing) { @@ -237,7 +237,7 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_required_missing) { BOOST_CHECK(!config.isValid()); const auto &parseResult = config.getParseResult(); - BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::INVALIDCONTENT); + BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDCONTENT); BOOST_CHECK_EQUAL(parseResult.getKeysRequiredMissing().size(), 1); BOOST_CHECK_EQUAL(parseResult.getKeysInvalidData().size(), 0); @@ -258,7 +258,7 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_wrong_type) { BOOST_CHECK(!config.isValid()); const auto &parseResult = config.getParseResult(); - BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::INVALIDCONTENT); + BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDCONTENT); BOOST_CHECK_EQUAL(parseResult.getKeysRequiredMissing().size(), 0); BOOST_CHECK_EQUAL(parseResult.getKeysInvalidData().size(), 1); @@ -278,7 +278,7 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_empty) { BOOST_CHECK(!config.isValid()); const auto &parseResult = config.getParseResult(); - BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::INVALIDCONTENT); + BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDCONTENT); BOOST_CHECK_GE(parseResult.getKeysRequiredMissing().size(), 1); } @@ -292,7 +292,7 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_nonexisting) { BOOST_CHECK(!config.isValid()); const auto &parseResult = config.getParseResult(); - BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::INVALIDINPUTFILE); + BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDINPUTFILE); } BOOST_AUTO_TEST_SUITE_END()