/*! * * @file configManagement.cpp * @author RUBINI Thomas * @author SIMAILA Djalim * @date January 2022 * @version 1.0 * @brief config parser * */ #include #include "game.h" #include "configManagement.h" #include "errors.h" void trimSpaces(string& str){ str.erase(0, str.find_first_not_of(' ')); } void sanitizeValue(string& val) { trimSpaces(val); for (char c: {'\'', '"'}) { if (val[0] == c && val[val.size() - 1] == c) { val.erase(val.begin()); val.pop_back(); break; } } } void ConfigBuilder::dumpInternalValues() const { for(const auto& ite : internalValues){ cerr << ite.first << " -> " << ite.second << endl; } } /* WARNING : This implementation of YAML is not meant to be complete, but to work with our specific needs * It also can't detect and report errors in a non-YAML-compliant file*/ void ConfigBuilder::parseFile(const string& fname) { ifstream file(fname); if(!file.is_open())throw config_error("Error while opening config.yml. Check file location ?"); vector keyParts; unsigned listIndex; unsigned lineno = 0; // for error handling purposes while (!file.eof()) { string line; getline(file, line); ++lineno; auto match = line.find('#'); if (match != string::npos)line.erase(match); if (line.find_first_not_of(' ')==string::npos)continue; unsigned currentIndent = 0; while (line[currentIndent] == ' ')++currentIndent; if(line[currentIndent]=='-'){ string value = line.substr(currentIndent+1); sanitizeValue(value); string fullKey; for (unsigned i = 0; i < currentIndent; ++i) { fullKey.append(keyParts[i]); fullKey.append("."); } // lists are just treated as sections with key 0,1,2... fullKey.append(to_string((listIndex))); ++listIndex; internalValues[fullKey] = value; }else{ match = line.find(':'); if (match == string::npos)throw config_error("Line "+ to_string(lineno)+" invalid : |"+line+"|"); string key = line.substr(0, match); string value = line.substr(match + 1); trimSpaces(key); sanitizeValue(value); if (value.empty()) { keyParts.resize(currentIndent); keyParts.push_back(key); listIndex = 0; } else { string fullKey; for (unsigned i = 0; i < currentIndent; ++i) { fullKey.append(keyParts[i]); fullKey.append("."); } fullKey.append(key); internalValues[fullKey] = value; } } } file.close(); } void ConfigBuilder::readGrid(const configKey& baseKey) { vector tmp; getList("grid", tmp); // we are essentially going to translate a line-oriented config to a column-oriented grid unsigned maxSize = 0; for(string& s : tmp){ if(s.size()>maxSize)maxSize = s.size(); } collectedData.grid.resize(maxSize); for(string& s : tmp){ unsigned i=0; for(;i max){ throw config_error("Value for key " + key + " do not follow preconditions : " + to_string(min) + "<=" + to_string(val) + "<=" + to_string(max)); } return val; }catch(config_error& e){ cerr << e.what() << " . Using default value" << endl; return def; } } int ConfigBuilder::getInt(const configKey& key) const { try{ return stoi(getString(key)); }catch(invalid_argument& e){ throw config_error("Invalid int data for key "+key+" : |"+getString(key)+"|"); } } char ConfigBuilder::getChar(const configKey& key, char def) const { try{ return getChar(key); }catch(config_error& e){ cerr << e.what() << " . Using default value" << endl; return def; } } char ConfigBuilder::getChar(const configKey& key) const { string s = getString(key); if(s.size()!=1)throw config_error("Invalid char data for key "+key+" : |"+s+"|"); return s[0]; } void ConfigBuilder::getColor(const configKey& key, nsGraphics::RGBAcolor& color, const nsGraphics::RGBAcolor& def) const { try{ getColor(key, color); }catch(config_error& e){ cerr << e.what() << " . Using default value" << endl; color = def; } } void ConfigBuilder::getColor(const configKey& key, nsGraphics::RGBAcolor& color) const { // switch do not work with strings, and I don't want to implement a constexpr hash function string colorStr = getString(key); if (colorStr == "black")color = nsGraphics::KBlack; else if (colorStr == "white")color = nsGraphics::KWhite; else if (colorStr == "red")color = nsGraphics::KRed; else if (colorStr == "lime")color = nsGraphics::KLime; else if (colorStr == "blue")color = nsGraphics::KBlue; else if (colorStr == "yellow")color = nsGraphics::KYellow; else if (colorStr == "cyan")color = nsGraphics::KCyan; else if (colorStr == "magenta")color = nsGraphics::KMagenta; else if (colorStr == "silver")color = nsGraphics::KSilver; else if (colorStr == "gray")color = nsGraphics::KGray; else if (colorStr == "maroon")color = nsGraphics::KMaroon; else if (colorStr == "olive")color = nsGraphics::KOlive; else if (colorStr == "green")color = nsGraphics::KGreen; else if (colorStr == "purple")color = nsGraphics::KPurple; else if (colorStr == "teal")color = nsGraphics::KTeal; else if (colorStr == "navy")color = nsGraphics::KNavy; else throw config_error("Invalid color string : "+colorStr); } void ConfigBuilder::getList(const configKey& baseKey, vector& toPopulate) const { unsigned i=0; string fullKey = baseKey + ".0"; if(!internalValues.contains(fullKey))throw config_error("Non-existent list baseKey requested : " + baseKey); do{ toPopulate.push_back(internalValues.at(fullKey)); ++i; fullKey = baseKey + "." + to_string(i); }while(internalValues.contains(baseKey + "." + to_string(i))); } bool Game::reloadConfig() { map strValues; ConfigBuilder builder; bool parsed = false; try{ builder.parseFile("config.yml"); parsed = true; builder.readConfig(); }catch(config_error& e){ if(parsed)cerr << "An error occured while reading the configuration :" << endl; else cerr << "An error occured while parsing the configuration :" << endl; cerr << e.what() << endl; if(parsed){ cerr << "Parsed keys :" << endl; builder.dumpInternalValues(); } cerr << "(The old configuration was kept in memory)" << endl; return false; } confData = move(builder.collectedData); return true; }