#include "ConfigFile.h" #include "FileUtils.h" #include "compatibility/ConfigFileConf.h" #include "xml/XML.h" #include #include ConfigFile::ConfigFile(const std::string& path, int) : configPath{path} { // Converts project name (current directory) to lowercase // and replace whitespace with underscore // Create xml XMLObject makegen("makegen", {}, std::map>{}); // Version, target and configuration is probably going to be used in the future makegen.AddXMLObject(XMLObject("version", {}, "v1.3.0")); makegen.AddXMLObject(XMLObject("target", {}, "Release")); XMLObject configuration("configuration", {{"name", "Release"}}, std::map>{}); configuration.AddXMLObject(XMLObject("projectname", {}, ConfigUtils::GetDefaultProjectName(configPath))); configuration.AddXMLObject(XMLObject("outputname", {}, ConfigUtils::GetDefaultOutputName(configPath))); configuration.AddXMLObject(XMLObject("srcdir", {}, "src/")); configuration.AddXMLObject(XMLObject("outputdir", {}, "bin/")); configuration.AddXMLObject(XMLObject("hfilename", {}, ConfigUtils::GetDefaultHFileName(configPath))); configuration.AddXMLObject(XMLObject("outputtype", {}, "executable")); configuration.AddXMLObject(XMLObject("generatehfile", {}, "false")); makegen.AddXMLObject(configuration); config = makegen; Init(); } ConfigFile::ConfigFile(const std::string& path) : config{XML::FromFile(path + CONFIG_FILENAME)}, configPath{path} { Init(); } ConfigFile::ConfigFile(XMLObject& config, const std::string& path) : config{config}, configPath{path} { Init(); } void ConfigFile::Init() { const std::vector* targetXml = config.GetObjectPtr("target"); target = "Release"; if(!targetXml || targetXml->size() == 0) { LOG_ERROR("No target found in config file. Using target=", target); return; } if(targetXml->size() > 1) LOG_ERROR("To many targets in config file. Using target=", (*targetXml)[0].GetText()); if(targetXml->size() > 0) target = (*targetXml)[0].GetText(); } std::string& ConfigFile::GetSettingString(ConfigSetting setting) { // Adding it to the cache since we need to return a reference if(!ConfigUtils::IsStringSetting(setting)) { LOG_ERROR("Invalid string setting"); return cache.strings.emplace("invalid", "").first->second; } std::string sSetting = ConfigUtils::GetSettingName(setting); auto it = cache.strings.find(sSetting); if(it != cache.strings.end()) return it->second; const std::vector* values = GetConfiguration().GetObjectPtr(sSetting); // No value found, using default if(values == nullptr) return cache.strings.emplace(sSetting, ConfigUtils::GetDefaultSettingString(setting, configPath)).first->second; if(values->size() != 1) { LOG_ERROR("To many arguments for setting using first: ", (int)setting, "=", (*values)[0].GetText()); } std::string s = (*values)[0].GetText(); if(ConfigUtils::IsDirectory(setting) && s[s.size()-1] != '/') s += '/'; return cache.strings.emplace(sSetting, s).first->second; } bool ConfigFile::GetSettingBool(ConfigSetting setting) { if(setting == ConfigSetting::Invalid) { LOG_ERROR("Invalid config setting"); return false; } std::string sSetting = ConfigUtils::GetSettingName(setting); auto it = cache.bools.find(sSetting); if(it != cache.bools.end()) return it->second; const std::vector* values = GetConfiguration().GetObjectPtr(sSetting);//, if(values == nullptr) return cache.bools.emplace(sSetting, ConfigUtils::GetDefaultSettingBool(setting)).first->second; if(values->size() != 1) { LOG_ERROR("To many arguments for setting using first: ", (int)setting, "=", (*values)[0].GetText()); } return cache.bools.emplace(sSetting, (*values)[0].GetText() == "true").first->second; } std::vector& ConfigFile::GetSettingVectorString(ConfigSetting setting) { std::string sSetting = ConfigUtils::GetSettingName(setting); auto it = cache.vecStrings.find(sSetting); if(it != cache.vecStrings.end()) return it->second; const std::vector* values = GetConfiguration().GetObjectPtr(sSetting); if(values == nullptr) return cache.vecStrings.emplace(sSetting, std::vector{}).first->second; std::vector strings; strings.reserve(values->size()); for(auto it = values->begin(); it != values->end(); ++it) { if(it->GetText() == "") continue; std::string s = it->GetText(); if(ConfigUtils::IsDirectory(setting) && s[s.size()-1] != '/') s += '/'; strings.push_back(s); } return cache.vecStrings.emplace(sSetting, strings).first->second; } std::vector ConfigFile::GetSetting(ConfigSetting setting) { if(ConfigUtils::IsStringSetting(setting)) return {GetSettingString(setting)}; else if(ConfigUtils::IsVectorSetting(setting)) return GetSettingVectorString(setting); else if(ConfigUtils::IsBoolSetting(setting)) return {GetSettingBool(setting) ? "true" : "false"}; else { LOG_ERROR("Invalid config setting"); return {}; } } bool ConfigFile::SetSettingString(ConfigSetting setting, const std::string& value) { // Check if valid enum std::string s = value; std::string sSetting = ConfigUtils::GetSettingName(setting); if(ConfigUtils::IsStringSetting(setting)) { if(ConfigUtils::IsDirectory(setting) && s[s.size()-1] != '/') { s += '/'; } auto it = cache.strings.find(sSetting); // Update cache if(it != cache.strings.end()) it->second = s; else cache.strings.emplace(sSetting, s); } else if(ConfigUtils::IsBoolSetting(setting)) { if(s == "true" || s == "t" || s == "yes" || s == "y") s = "true"; else if(s == "false" || s == "f" || s == "no" || s == "n") s = "false"; else { LOG_ERROR("Invalid boolean value: ", s); return false; } auto it = cache.bools.find(sSetting); // Update cache if(it != cache.bools.end()) it->second = s == "true"; else cache.bools.emplace(sSetting, value == "true"); } else { LOG_ERROR("Not a string setting"); return false; } XMLObject& configuration = GetConfiguration(); std::vector* values = configuration.GetObjectPtr(sSetting); if(values == nullptr) configuration.AddXMLObject({sSetting, {}, s}); else if(values->size() > 1) LOG_ERROR("Multiple values of setting, changing first: ", sSetting, "=", s); else (*values)[0].SetText(s); return true; } bool ConfigFile::AddSettingVectorString(ConfigSetting setting, const std::string& value) { // Check if valid enum if(ConfigUtils::IsVectorSetting(setting)) { std::string s = value; if(ConfigUtils::IsDirectory(setting) && s[s.size()-1] != '/') { s += '/'; } std::string sSetting = ConfigUtils::GetSettingName(setting); auto it = cache.vecStrings.find(sSetting); // Update cache if(it != cache.vecStrings.end()) it->second.push_back(s); else cache.vecStrings.emplace(sSetting, std::vector{s}); GetConfiguration().AddXMLObject({sSetting, {}, s}); } else { LOG_ERROR("Not a vector setting"); return false; } return true; } bool ConfigFile::RemoveSettingVectorString(ConfigSetting setting, const std::string& value) { // Check if valid enum if(ConfigUtils::IsVectorSetting(setting)) { std::string s = value; if(ConfigUtils::IsDirectory(setting) && s[s.size()-1] != '/') { s += '/'; } std::string sSetting = ConfigUtils::GetSettingName(setting); auto it = cache.vecStrings.find(sSetting); if(it != cache.vecStrings.end()) { // Update cache for(auto itVec = it->second.begin(); itVec != it->second.end(); ++itVec) { if(*itVec == s) { it->second.erase(itVec); } } } std::vector* values = GetConfiguration().GetObjectPtr(sSetting); bool found = false; for(auto it = values->begin(); it != values->end();++it) { if(it->GetText() == s) { values->erase(it); found = true; break; } } if(!found) { LOG_ERROR("Couldn't find value: ", s); return false; } } else { LOG_ERROR("Not a vector setting"); return false; } return false; } XMLObject& ConfigFile::GetConfiguration() { std::vector* configurations = config.GetObjectPtr("configuration"); if(configurations == nullptr || configurations->size() == 0) { LOG_ERROR("No configuration in makegen.xml"); assert(false); } for(auto it = configurations->begin(); it != configurations->end(); ++it) { if(!it->HasAttribute("name")) { LOG_ERROR("No name attribute in configuration tag"); continue; } if(it->GetAttribute("name") == target) { return *it; } } LOG_ERROR("Couldn\'t find given target in config file. Using target=", (*configurations)[0].HasAttribute("name") ? (*configurations)[0].GetAttribute("name") : ""); return (*configurations)[0]; } const std::string& ConfigFile::GetConfigPath() const { return configPath;; } ConfigFile& ConfigFile::GetDependencyConfig(size_t i) { return dependencyConfigs[i]; } std::optional ConfigFile::GetConfigFile(const std::string& filepath) { std::map loadedConfigs; return GetConfigFile(filepath, loadedConfigs); } std::optional ConfigFile::GetConfigFile(const std::string& filepath, std::map& loadedConfigs) { std::string realPath = FileUtils::GetRealPath(filepath); if(realPath == "") return {}; auto it = loadedConfigs.find(realPath); if(it != loadedConfigs.end()) { return {}; } bool oldFile = false; std::ifstream f(filepath + CONFIG_FILENAME); if(!f.good()) { ConfigFileConf::CreateXMLFile(realPath); // try to read an old config file f.close(); f = std::ifstream(filepath + CONFIG_FILENAME); } // Check if the file exists if(f.good()) { f.close(); ConfigFile conf = ConfigFile(filepath); if(conf.hasInitError) return {}; loadedConfigs.emplace(realPath, conf); std::vector& dependencies = conf.GetSettingVectorString(ConfigSetting::Dependency); // Create dependency config files. for(size_t i = 0; i < dependencies.size();++i) { std::optional dep = GetConfigFile(conf.configPath + dependencies[i], loadedConfigs); if(dep) { conf.dependencyConfigs.push_back(*dep); dependencies[i] = dep->configPath; } else { // Remove the dependency since it is already accounted for dependencies.erase(dependencies.begin() + i); --i; } } return conf; } return {}; } void ConfigFile::InputBoolean(const std::string& inputText, bool& b) { std::string input; while(true) { LOG_INFO(inputText); std::getline(std::cin, input); if(input.length() > 0) { if(input[0] == 'y' || input[0] == 'n') { b = input[0] == 'y'; return; } } } } void ConfigFile::InputString(const std::string& inputText, std::string& str, bool needEnding, bool allowEmpty) { str = ""; while(true) { LOG_INFO(inputText); std::getline(std::cin, str); if(needEnding && str[str.length()-1] != '/' && !str.empty()) str += '/'; if(allowEmpty || !str.empty()) return; } } void ConfigFile::InputMultiple(const std::string& inputText, std::vector& vec, bool needEnding) { std::string input; while(true) { InputString(inputText, input, needEnding, true); if(input == "") break; vec.push_back(input); } } ConfigFile ConfigFile::Gen() { bool executable, shared, generateHFile; std::vector libs, libdirs, includedirs, defines, compileFlags, dependencies; std::string srcdir, outputdir, projectname, outputname, hFile; InputBoolean("Should it be compiled as an executable? (y/n)", executable); // If it isn't an executable there is not need to have libraries if(executable) { InputMultiple("Enter library:", libs,false); InputMultiple("Enter library directory:", libdirs,true); InputMultiple("Enter project dependencies:", dependencies,true); } else { InputBoolean("Should it be compiled as a shared library? (y/n)", shared); InputBoolean("Should it compile a project h-file? (y/n):", generateHFile); if(generateHFile) { InputString("Enter the project h-file name (relative to source directory): ", hFile, false, false); } } InputMultiple("Enter include directory:", includedirs, true); InputString("Enter source directories:", srcdir, true, false); InputMultiple("Enter preprocessor definitions:", defines, false); InputMultiple("Enter compile flags:", compileFlags, false); InputString("Enter output directory (default: bin):", outputdir, true, true); if(outputdir == "") outputdir = "bin/"; InputString("Enter a name for the project:", projectname, false, false); InputString("Enter a name for the output file:", outputname, false, false); // Create xml XMLObject makegen("makegen", {}, std::map>{}); // Version, target and configuration is probably going to be used in the future makegen.AddXMLObject(XMLObject("version", {}, "v1.3.0")); makegen.AddXMLObject(XMLObject("target", {}, "Release")); XMLObject configuration("configuration", {{"name", "Release"}}, std::map>{}); configuration.AddXMLObject(XMLObject("projectname", {}, projectname)); configuration.AddXMLObject(XMLObject("outputname", {}, outputname)); configuration.AddXMLObject(XMLObject("srcdir", {}, srcdir)); configuration.AddXMLObject(XMLObject("outputdir", {}, outputdir)); configuration.AddXMLObject(XMLObject("hfilename", {}, hFile)); configuration.AddXMLObject(XMLObject("outputtype", {}, executable ? "executable" : (shared ? "sharedlibrary" : "staticlibrary"))); configuration.AddXMLObject(XMLObject("generatehfile", {}, generateHFile ? "true" : "false")); for(auto it = libs.begin();it != libs.end(); ++it) configuration.AddXMLObject({"library",{},*it}); for(auto it = libdirs.begin();it != libdirs.end(); ++it) configuration.AddXMLObject({"librarydir",{},*it}); for(auto it = includedirs.begin();it != includedirs.end(); ++it) configuration.AddXMLObject({"includedir",{},*it}); for(auto it = defines.begin();it != defines.end(); ++it) configuration.AddXMLObject({"define",{},*it}); for(auto it = compileFlags.begin();it != compileFlags.end(); ++it) configuration.AddXMLObject({"cflag",{},*it}); for(auto it = dependencies.begin();it != dependencies.end(); ++it) configuration.AddXMLObject({"dependency",{},*it}); makegen.AddXMLObject(configuration); return ConfigFile{makegen, FileUtils::GetRealPath(".")}; } void ConfigFile::Save() const { std::ofstream file("makegen.xml"); file << config; }