diff --git a/Makefile b/Makefile index bc32c18..a1a1d21 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# This Makefile was generated using MakeGen v1.1.8 made by Tim Håkansson +# This Makefile was generated using MakeGen v1.3.0 made by Tim Håkansson # and is licensed under MIT. Full source of the project can be found at # https://github.com/Thraix/MakeGen CC=@g++ @@ -7,8 +7,8 @@ MKDIR_P=mkdir -p BIN=bin/ OBJPATH=$(BIN)intermediates INCLUDES= -OBJECTS=$(OBJPATH)/ConfigCLI.o $(OBJPATH)/ConfigFile.o $(OBJPATH)/HFileGen.o $(OBJPATH)/IncludeDeps.o $(OBJPATH)/Makefile.o $(OBJPATH)/Utils.o $(OBJPATH)/main.o -CFLAGS=$(INCLUDES) -std=c++17 -c -w -g3 -D_DEBUG +OBJECTS=$(OBJPATH)/ConfigCLI.o $(OBJPATH)/ConfigFile.o $(OBJPATH)/HFileGen.o $(OBJPATH)/IncludeDeps.o $(OBJPATH)/Makefile.o $(OBJPATH)/Utils.o $(OBJPATH)/ConfigFileConf.o $(OBJPATH)/main.o $(OBJPATH)/XML.o $(OBJPATH)/XMLObject.o +CFLAGS=$(INCLUDES) -std=c++17 -c -w -g3 LIBDIR= LDFLAGS= LIBS=$(LIBDIR) @@ -33,24 +33,33 @@ $(OUTPUT): $(OBJECTS) install: all $(info Installing MakeGen to /usr/bin/) @cp $(OUTPUT) /usr/bin/makegen -$(OBJPATH)/ConfigCLI.o : src/ConfigCLI.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h - $(info -[14%]- $<) +$(OBJPATH)/ConfigCLI.o : src/ConfigCLI.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h src/ConfigUtils.h src/FileUtils.h src/Utils.h src/xml/XMLObject.h + $(info -[10%]- $<) $(CC) $(CFLAGS) -o $@ $< -$(OBJPATH)/ConfigFile.o : src/ConfigFile.cpp src/ConfigFile.h src/FileUtils.h src/Common.h src/Utils.h - $(info -[28%]- $<) +$(OBJPATH)/ConfigFile.o : src/ConfigFile.cpp src/ConfigFile.h src/ConfigUtils.h src/Common.h src/FileUtils.h src/Utils.h src/xml/XMLObject.h src/compatibility/ConfigFileConf.h src/xml/XML.h + $(info -[20%]- $<) $(CC) $(CFLAGS) -o $@ $< -$(OBJPATH)/HFileGen.o : src/HFileGen.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h src/HFileGen.h - $(info -[42%]- $<) +$(OBJPATH)/HFileGen.o : src/HFileGen.cpp src/FileUtils.h src/Common.h src/Utils.h src/HFileGen.h src/ConfigFile.h src/ConfigUtils.h src/xml/XMLObject.h + $(info -[30%]- $<) $(CC) $(CFLAGS) -o $@ $< -$(OBJPATH)/IncludeDeps.o : src/IncludeDeps.cpp src/Common.h src/IncludeDeps.h src/ConfigFile.h src/FileUtils.h src/Utils.h - $(info -[57%]- $<) +$(OBJPATH)/IncludeDeps.o : src/IncludeDeps.cpp src/Common.h src/IncludeDeps.h src/ConfigFile.h src/ConfigUtils.h src/FileUtils.h src/Utils.h src/xml/XMLObject.h + $(info -[40%]- $<) $(CC) $(CFLAGS) -o $@ $< -$(OBJPATH)/Makefile.o : src/Makefile.cpp src/IncludeDeps.h src/ConfigFile.h src/FileUtils.h src/Common.h src/Utils.h src/Makefile.h - $(info -[71%]- $<) +$(OBJPATH)/Makefile.o : src/Makefile.cpp src/IncludeDeps.h src/ConfigFile.h src/ConfigUtils.h src/Common.h src/FileUtils.h src/Utils.h src/xml/XMLObject.h src/Makefile.h + $(info -[50%]- $<) $(CC) $(CFLAGS) -o $@ $< -$(OBJPATH)/Utils.o : src/Utils.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h - $(info -[85%]- $<) +$(OBJPATH)/Utils.o : src/Utils.cpp src/ConfigFile.h src/ConfigUtils.h src/Common.h src/FileUtils.h src/Utils.h src/xml/XMLObject.h + $(info -[60%]- $<) $(CC) $(CFLAGS) -o $@ $< -$(OBJPATH)/main.o : src/main.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h src/FileUtils.h src/Utils.h src/HFileGen.h src/Makefile.h src/Timer.h +$(OBJPATH)/ConfigFileConf.o : src/compatibility/ConfigFileConf.cpp src/compatibility/ConfigFileConf.h + $(info -[70%]- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/main.o : src/main.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h src/ConfigUtils.h src/FileUtils.h src/Utils.h src/xml/XMLObject.h src/HFileGen.h src/Makefile.h src/Timer.h + $(info -[80%]- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/XML.o : src/xml/XML.cpp src/xml/XML.h src/xml/XMLObject.h src/xml/XMLException.h + $(info -[90%]- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/XMLObject.o : src/xml/XMLObject.cpp src/xml/XMLException.h src/xml/XMLObject.h $(info -[100%]- $<) $(CC) $(CFLAGS) -o $@ $< diff --git a/makegen.conf b/makegen.conf deleted file mode 100644 index e445d05..0000000 --- a/makegen.conf +++ /dev/null @@ -1,19 +0,0 @@ -#libs -#libdirs -#includedirs -#defines -_DEBUG -#compileflags -#dependencies -#srcdir -src/ -#outputdir -bin/ -#projectname -MakeGen -#outputname -makegen -#executable -true -#generatehfile -false diff --git a/makegen.xml b/makegen.xml new file mode 100644 index 0000000..c1f7e8d --- /dev/null +++ b/makegen.xml @@ -0,0 +1,22 @@ + + + false + MakeGen.h + bin/ + makegen + executable + MakeGen + src/ + + + _DEBUG + false + MakeGen.h + bin/ + makegen + executable + MakeGen + src/ + + v1.3.0 + diff --git a/src/Common.h b/src/Common.h index 84cf89b..5a91d5e 100755 --- a/src/Common.h +++ b/src/Common.h @@ -9,10 +9,10 @@ // Major changes, might not be backwards compatible #define MAKEGEN_VERSION_MAJOR 1 -// Release, should be backwards compatible -#define MAKEGEN_VERSION_RELEASE 2 +// Release, should be backwards compatible with any minor version +#define MAKEGEN_VERSION_RELEASE 3 // Minor changes, generally bug fixes -#define MAKEGEN_VERSION_MINOR 1 +#define MAKEGEN_VERSION_MINOR 0 #define MAKEGEN_VERSION ("v" STR(MAKEGEN_VERSION_MAJOR) "." STR(MAKEGEN_VERSION_RELEASE) "." STR(MAKEGEN_VERSION_MINOR)) @@ -29,9 +29,9 @@ const static unsigned int FLAG_SIMPLE = BIT(9); const static unsigned int FLAG_CONFIG = BIT(10); -#define LOG_INFO(...) Log(__VA_ARGS__); std::cout << std::endl -#define LOG_WARNING(...) Log(__VA_ARGS__); std::cout << std::endl -#define LOG_ERROR(...) Log(__VA_ARGS__); std::cout << std::endl +#define LOG_INFO(...) LogHelper(__VA_ARGS__) +#define LOG_WARNING(...) LogHelper(__VA_ARGS__) +#define LOG_ERROR(...) LogHelper(__VA_ARGS__) template void Log(const T& var) @@ -45,3 +45,18 @@ void Log(const T& var, const Ts& ...vars) Log(var); Log(vars...); } + +template +void LogHelper(const T& var) +{ + Log(var); + std::cout << std::endl; +} + +template +void LogHelper(const T& var, const Ts& ...vars) +{ + Log(var); + Log(vars...); + std::cout << std::endl; +} diff --git a/src/ConfigCLI.cpp b/src/ConfigCLI.cpp index b188905..2114971 100644 --- a/src/ConfigCLI.cpp +++ b/src/ConfigCLI.cpp @@ -8,7 +8,7 @@ void ConfigCLI::DisplayCLIHelp() { LOG_INFO(1+(char*)R"( -MakeGen conf is used to create, modify and query the makegen.conf file. +MakeGen conf is used to create, modify and query the makegen.xml file. Usage: makegen conf [] [--help] @@ -49,7 +49,7 @@ Usage: makegen conf add [] Valid settings are: library Library - libdir Library directory + librarydir Library directory includedir Include directory define Preprocessor define cflag g++ compiler flags @@ -65,7 +65,7 @@ Usage: makegen conf remove [< Valid settings are library Library name - libdir Library directory + librarydir Library directory includedir Include directory define Preprocessor define cflag g++ compiler flags @@ -81,13 +81,13 @@ Usage: makegen conf set Valid string settings are: outputdir Directory of the compiled output - output Name of the output executable/library - name Name of the project + outputname Name of the output executable/library + projectname Name of the project + outputtype Type of the output, valid values are executable, sharedlibrary + and staticlibrary hfile Name of the generated project h-file Valid boolean settings are: - executable Specifies if the project be compiled as executable or library - shared Specifies if the library should be compiled as shared. genhfile Specifies if MakeGen should generate a project h-file Boolean values can be set to either true/t/yes/y or false/f/no/n)"); @@ -102,50 +102,42 @@ Usage: makegen conf get Valid settings are: library Library name - libdir Library directory + librarydir Library directory includedir Include directory define Preprocessor define cflag g++ compiler flags dependency Project which current project depends on outputdir Directory of the compiled output - output Name of the output executable/library - name Name of the project + outputname Name of the output executable/library + outputtype Type of the output, valid values are executable, sharedlibrary + and staticlibrary + projectname Name of the project hfile Name of the generated project h-file - executable Specifies if the project be compiled as executable or library - shared Specifies if the library should be compiled as shared. genhfile Specifies if MakeGen should generate a project h-file)"); } -std::map*> ConfigCLI::GetSettingVectorMap(ConfigFile& config) -{ - return { - {"library",&config.libs}, - {"libdir",&config.libdirs}, - {"includedir",&config.includedirs}, - {"define",&config.defines}, - {"cflag",&config.flags}, - {"dependency",&config.dependencies} - }; -} - -std::map ConfigCLI::GetSettingStringMap(ConfigFile& config) +ConfigSetting ConfigCLI::CLIStringToSetting(const std::string& s) { - return { - {"outputdir", &config.outputdir}, - {"output", &config.outputname}, - {"name", &config.projectname}, - {"hfile", &config.hFile}, - }; -} - -std::map ConfigCLI::GetSettingBoolMap(ConfigFile& config) -{ - return { - {"executable", &config.executable}, - {"shared", &config.shared}, - {"genhfile", &config.generateHFile} + static std::map map{ + {"srcdir", ConfigSetting::SourceDir}, + {"outputdir", ConfigSetting::OutputDir}, + {"outputname", ConfigSetting::OutputName}, + {"outputtype", ConfigSetting::OutputType}, + {"projectname", ConfigSetting::ProjectName}, + {"hfile", ConfigSetting::HFileName}, + {"library", ConfigSetting::Library}, + {"librarydir", ConfigSetting::LibraryDir}, + {"includedir", ConfigSetting::IncludeDir}, + {"define", ConfigSetting::Define}, + {"cflag", ConfigSetting::CFlag}, + {"dependency", ConfigSetting::Dependency}, + {"genhfile", ConfigSetting::GenerateHFile}, }; + auto it = map.find(s); + if(it == map.end()) + return ConfigSetting::Invalid; + return it->second; } int ConfigCLI::Gen(int argc, char** argv) @@ -168,7 +160,7 @@ int ConfigCLI::Gen(int argc, char** argv) } if(option == "default") { - ConfigFile{}.Save(); + ConfigFile{FileUtils::GetRealPath("."),0}.Save(); return 0; } else @@ -191,25 +183,24 @@ int ConfigCLI::Add(int argc, char** argv, ConfigFile& config) return 1; } - auto settingMap = GetSettingVectorMap(config); - auto it = settingMap.find(argv[1]); - if(it == settingMap.end()) + ConfigSetting setting = CLIStringToSetting(argv[1]); + if(!ConfigUtils::IsVectorSetting(setting)) { - LOG_ERROR("Invalid setting: ", argv[1]); + if(setting == ConfigSetting::Invalid) + { + LOG_ERROR("No such setting: ", argv[1]); + } + else + { + LOG_ERROR("Cannot remove setting which only supports one argument"); + LOG_ERROR("use set instead."); + } return 1; } - - std::vector* setting = it->second; - std::set settingSet{setting->begin(), setting->end()}; for(int i = 2; i* setting = it->second; - std::set settingSet{setting->begin(), setting->end()}; for(int i = 2; isecond) = true; - else if(b == "false" || b == "f" || b == "no" || b == "n") - *it2->second = false; else { - LOG_ERROR("Invalid boolean value: ", argv[2]); - return 1; + LOG_ERROR("Cannot set setting which supports multiple arguments"); + LOG_ERROR("use add or remove instead."); } - config.Save(); - return 0; + return 1; } - *it1->second = argv[2]; + + config.SetSettingString(setting, argv[2]); config.Save(); return 0; } @@ -308,29 +288,9 @@ int ConfigCLI::Get(int argc, char** argv, ConfigFile& config) LOG_ERROR("get needs exactly one parameter"); return 1; } - auto settingVectorMap = GetSettingVectorMap(config); - auto itV = settingVectorMap.find(argv[1]); - if(itV == settingVectorMap.end()) - { - auto settingStringMap = GetSettingStringMap(config); - auto itS = settingStringMap.find(argv[1]); - if(itS == settingStringMap.end()) - { - auto settingBoolMap = GetSettingBoolMap(config); - auto itB = settingBoolMap.find(argv[1]); - if(itB == settingBoolMap.end()) - { - LOG_ERROR("Invalid setting: ", argv[1]); - return 1; - } - bool* t = itB->second; - LOG_INFO(*itB->second ? "true" : "false"); - return 0; - } - LOG_INFO(*itS->second); - return 0; - } - for(auto it = itV->second->begin(); it != itV->second->end(); ++it) + ConfigSetting setting = CLIStringToSetting(argv[1]); + std::vector vector = config.GetSetting(setting); + for(auto it = vector.begin(); it != vector.end(); ++it) { LOG_INFO(*it); } @@ -351,7 +311,7 @@ int ConfigCLI::Main(int argc, char** argv) { if(config) { - LOG_ERROR("Config file already exist (makegen.conf)"); + LOG_ERROR("Config file already exist (", CONFIG_FILENAME, ")"); return 1; } return Gen(argc-1, &argv[1]); diff --git a/src/ConfigCLI.h b/src/ConfigCLI.h index 88335a1..4335ae6 100644 --- a/src/ConfigCLI.h +++ b/src/ConfigCLI.h @@ -14,9 +14,7 @@ struct ConfigCLI static void DisplaySetHelp(); static void DisplayGetHelp(); - static std::map*> GetSettingVectorMap(ConfigFile& config); - static std::map GetSettingStringMap(ConfigFile& config); - static std::map GetSettingBoolMap(ConfigFile& config); + static ConfigSetting CLIStringToSetting(const std::string& s); static int Gen(int argc, char** argv); static int Add(int argc, char** argv, ConfigFile& config); diff --git a/src/ConfigFile.cpp b/src/ConfigFile.cpp index dbdf676..2b2ae32 100755 --- a/src/ConfigFile.cpp +++ b/src/ConfigFile.cpp @@ -1,43 +1,327 @@ #include "ConfigFile.h" #include "FileUtils.h" +#include "compatibility/ConfigFileConf.h" +#include "xml/XML.h" #include #include -#define FLAG_NONE 0 -#define FLAG_VECTOR 1 -#define FLAG_STRING 2 -#define FLAG_BOOL 3 - -ConfigFile::ConfigFile() - : outputdir("bin/"), srcdir("src/"), outputname(""), projectname(FileUtils::GetCurrentDirectory()), hFile(""), executable(true), shared(true), generateHFile(false) +ConfigFile::ConfigFile(const std::string& path, int) + : configPath{path} { // Converts project name (current directory) to lowercase // and replace whitespace with underscore - std::transform( - projectname.begin(), - projectname.end(), - std::back_inserter(outputname), - [](unsigned char c) - { - if(c == ' ') - return '_'; - return (char)std::tolower(c); - }); + // Create xml + XMLObject makegen("makegen", {}, std::map>{}); - // Removes all other characters - auto it = std::remove_if( - outputname.begin(), - outputname.end(), - [](unsigned char c) - { - return (c < '0' || c > '9') && (c < 'a' || c > 'z') && c != '_'; - }); - outputname.erase(it, outputname.end()); + // 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")); - // Add suffix - outputname += ".out"; + 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) @@ -55,27 +339,39 @@ std::optional ConfigFile::GetConfigFile(const std::string& filepath, 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::Load(realPath); + 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 < conf.dependencies.size();++i) + for(size_t i = 0; i < dependencies.size();++i) { - std::optional dep = GetConfigFile(conf.configPath + conf.dependencies[i], loadedConfigs); + std::optional dep = GetConfigFile(conf.configPath + dependencies[i], loadedConfigs); if(dep) { conf.dependencyConfigs.push_back(*dep); - conf.dependencies[i] = dep->configPath; + dependencies[i] = dep->configPath; } else { // Remove the dependency since it is already accounted for - conf.dependencies.erase(conf.dependencies.begin() + i); + dependencies.erase(dependencies.begin() + i); --i; } } @@ -84,124 +380,6 @@ std::optional ConfigFile::GetConfigFile(const std::string& filepath, return {}; } - -ConfigFile ConfigFile::Load(const std::string& filepath) -{ - ConfigFile conf; - conf.configPath = filepath; - unsigned int loadFlag = 0; - - std::vector* vec; - std::string* s; - bool* b; - - bool isDirectory = false; - - std::ifstream file(filepath+CONFIG_FILENAME); - std::string line; - - if(file.is_open()) - { - // config name, { pointer to memory, isDirectory} - std::map> strings = - { - {"#srcdir", {&conf.srcdir, true}}, - {"#outputdir", {&conf.outputdir, true}}, - {"#outputname", {&conf.outputname, false}}, - {"#projectname", {&conf.projectname, false}}, - {"#hfile", {&conf.hFile, false}}, - }; - - // config name, { pointer to memory, isDirectory} - std::map*, bool>> vectors = - { - {"#libs", {&conf.libs, false}}, - {"#libdirs", {&conf.libdirs, true}}, - {"#includedirs", {&conf.includedirs, true}}, - {"#compileflags", {&conf.flags, false}}, - {"#defines", {&conf.defines, false}}, - {"#dependencies", {&conf.dependencies, true}}, - }; - - std::map booleans = - { - {"#executable", &conf.executable}, - {"#shared", &conf.shared}, - {"#generatehfile", &conf.generateHFile}, - }; - - while(std::getline(file,line)) - { - if(line == "") - continue; - if(line[0]=='#') - { - // The format is a bit wacky, but it is this way since we do not want - // to use map::find for all maps. This way we gain some optimization. - auto&& itStr{strings.find(line)}; - if(itStr != strings.end()) - { - s = itStr->second.first; - isDirectory = itStr->second.second; - loadFlag = FLAG_STRING; - } - else - { - auto&& itVec{vectors.find(line)}; - if(itVec != vectors.end()) - { - vec = itVec->second.first; - isDirectory = itVec->second.second; - loadFlag = FLAG_VECTOR; - } - else - { - auto&& itBool{booleans.find(line)}; - if(itBool != booleans.end()) - { - b = itBool->second; - loadFlag = FLAG_BOOL; - } - else - { - LOG_ERROR("Invalid flag: ", line); - loadFlag = FLAG_NONE; - } - } - } - } - else - { - if(loadFlag == FLAG_STRING) - { - if(isDirectory && line[line.size()-1] != '/') - line += '/'; - *s = line; - } - else if(loadFlag == FLAG_VECTOR) - { - if(isDirectory && line[line.size()-1] != '/') - {; - line += '/'; - } - vec->push_back(line); - } - else if(loadFlag == FLAG_BOOL) - { - if(line == "true") - *b = true; - else - *b = false; - } - } - } - } - if(conf.hFile == "") - conf.hFile = conf.projectname+".h"; - - return conf; -} - void ConfigFile::InputBoolean(const std::string& inputText, bool& b) { std::string input; @@ -248,90 +426,74 @@ void ConfigFile::InputMultiple(const std::string& inputText, 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(conf.executable) + if(executable) { - InputMultiple("Enter library:", conf.libs,false); - InputMultiple("Enter library directory:", conf.libdirs,true); - InputMultiple("Enter project dependencies:", conf.dependencies,true); + 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)", conf.shared); - InputBoolean("Should it compile a project h-file? (y/n):", conf.generateHFile); - if(conf.generateHFile) + 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): ", conf.hFile, false, false); + InputString("Enter the project h-file name (relative to source directory): ", hFile, false, false); } } - InputMultiple("Enter include directory:", conf.includedirs, true); - InputString("Enter source directories:", conf.srcdir, true, false); - InputMultiple("Enter preprocessor definitions:", conf.defines, false); - InputMultiple("Enter compile flags:", conf.flags, false); - InputString("Enter output directory (default: bin):", conf.outputdir, true, true); - if(conf.outputdir == "") - conf.outputdir = "bin/"; - InputString("Enter a name for the project:", conf.projectname, false, false); - InputString("Enter a name for the output file:", conf.outputname, false, false); - return conf; + 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.conf"); - file << "#libs" << std::endl; - for(auto it = libs.begin();it!=libs.end();++it) - { - file << *it << std::endl; - } - file << "#libdirs" << std::endl; - for(auto it = libdirs.begin();it!=libdirs.end();++it) - { - file << *it << std::endl; - } - file << "#includedirs" << std::endl; - for(auto it = includedirs.begin();it!=includedirs.end();++it) - { - file << *it << std::endl; - } - file << "#defines" << std::endl; - for(auto it = defines.begin();it!=defines.end();++it) - { - file << *it << std::endl; - } - file << "#compileflags" << std::endl; - for(auto it = flags.begin();it!=flags.end();++it) - { - file << *it << std::endl; - } - file << "#dependencies" << std::endl; - for(auto it = dependencies.begin();it!=dependencies.end();++it) - { - file << *it << std::endl; - } - file << "#srcdir" << std::endl; - file << srcdir << std::endl; - file << "#outputdir" << std::endl; - file << outputdir << std::endl; - file << "#projectname" << std::endl; - file << projectname << std::endl; - file << "#outputname" << std::endl; - file << outputname << std::endl; - file << "#executable" << std::endl; - file << (executable ? "true" : "false") << std::endl; - file << "#generatehfile" << std::endl; - file << (generateHFile ? "true" : "false") << std::endl; - if(generateHFile) - { - file << "#hfile" << std::endl; - file << hFile << std::endl; - } - if(!executable) - { - file << "#shared" << std::endl; - file << (shared ? "true" : "false") << std::endl; - } - file.close(); + std::ofstream file("makegen.xml"); + file << config; } diff --git a/src/ConfigFile.h b/src/ConfigFile.h index 3d9b36b..e058497 100755 --- a/src/ConfigFile.h +++ b/src/ConfigFile.h @@ -1,40 +1,58 @@ #pragma once +#include "ConfigUtils.h" +#include "xml/XMLObject.h" + #include #include #include #include -static const std::string CONFIG_FILENAME = "makegen.conf"; +static const std::string CONFIG_FILENAME = "makegen.xml"; class ConfigFile { - public: - std::string configPath; - std::vector libs; - std::vector libdirs; - std::vector includedirs; - std::vector defines; - std::vector flags; - std::vector dependencies; + private: + ConfigCache cache; - std::string outputdir; - std::string srcdir; - std::string outputname; - std::string projectname; - std::string hFile; - bool executable; - bool shared; - bool generateHFile; + XMLObject config; + // Current configuration + std::string target; + + std::string configPath; std::vector dependencyConfigs; + + bool hasInitError = false; + public: - ConfigFile(); + // Generates a new default config file + ConfigFile(const std::string& path, int); + ConfigFile(const std::string& path); + ConfigFile(XMLObject& config, const std::string& path); + void Save() const; + + std::string& GetSettingString(ConfigSetting setting); + bool GetSettingBool(ConfigSetting setting); + std::vector& GetSettingVectorString(ConfigSetting setting); + std::vector GetSetting(ConfigSetting setting); + + bool SetSettingString(ConfigSetting setting, const std::string& value); + bool AddSettingVectorString(ConfigSetting setting, const std::string& value); + bool RemoveSettingVectorString(ConfigSetting setting, const std::string& value); + + XMLObject& GetConfiguration(); + const std::string& GetConfigPath() const; + ConfigFile& GetDependencyConfig(size_t i); + private: + void Init(); + + public: static ConfigFile Gen(); static std::optional GetConfigFile(const std::string& filepath = "./"); private: static std::optional GetConfigFile(const std::string& filepath, std::map& loadedConfigs); - static ConfigFile Load(const std::string& filename); + static std::optional Load(const std::string& filename); static void InputBoolean(const std::string& inputText, bool& b); static void InputMultiple(const std::string& inputText, std::vector& vec, bool needEnding); static void InputString(const std::string& inputText, std::string& vec, bool needEnding, bool allowEmpty); diff --git a/src/ConfigUtils.h b/src/ConfigUtils.h new file mode 100644 index 0000000..bfcf066 --- /dev/null +++ b/src/ConfigUtils.h @@ -0,0 +1,242 @@ +#pragma once + +#include "Common.h" +#include "FileUtils.h" + +#include +#include +#include +#include + +struct ConfigCache +{ + std::map strings; + std::map> vecStrings; + std::map bools; +}; + +enum class ConfigSetting +{ + // vectors + Library = 0, LibraryDir = 1, IncludeDir = 2, Define = 3, CFlag = 4, Dependency = 5, + // Strings + SourceDir = 32, OutputDir = 33, OutputName = 34, OutputType = 35, ProjectName = 36, HFileName = 37, + // Bools + GenerateHFile = 64, + // Other + Invalid = 1024 +}; + +struct ConfigUtils +{ + static std::string GetSettingName(ConfigSetting setting) + { + switch(setting) + { + case ConfigSetting::SourceDir: + return "srcdir"; + case ConfigSetting::OutputDir: + return "outputdir"; + case ConfigSetting::OutputName: + return "outputname"; + case ConfigSetting::OutputType: + return "outputtype"; + case ConfigSetting::ProjectName: + return "projectname"; + case ConfigSetting::HFileName: + return "hfilename"; + case ConfigSetting::LibraryDir: + return "librarydir"; + case ConfigSetting::IncludeDir: + return "includedir"; + case ConfigSetting::Dependency: + return "dependency"; + case ConfigSetting::Library: + return "library"; + case ConfigSetting::Define: + return "define"; + case ConfigSetting::CFlag: + return "cflag"; + case ConfigSetting::GenerateHFile: + return "generatehfile"; + case ConfigSetting::Invalid: + return "invalid"; + } + } + + static bool IsDirectory(ConfigSetting setting) + { + // Library, LibraryDir, IncludeDir, Define, CFlag, Dependency, + switch(setting) + { + case ConfigSetting::SourceDir: + case ConfigSetting::OutputDir: + case ConfigSetting::LibraryDir: + case ConfigSetting::IncludeDir: + case ConfigSetting::Dependency: + return true; + case ConfigSetting::OutputName: + case ConfigSetting::OutputType: + case ConfigSetting::ProjectName: + case ConfigSetting::HFileName: + case ConfigSetting::Library: + case ConfigSetting::Define: + case ConfigSetting::CFlag: + case ConfigSetting::GenerateHFile: + return false; + default: + LOG_ERROR("INVALID ENUM: ", (int)setting); + assert(false); + } + } + + static bool IsStringSetting(ConfigSetting setting) + { + switch(setting) + { + case ConfigSetting::SourceDir: + case ConfigSetting::OutputDir: + case ConfigSetting::OutputName: + case ConfigSetting::OutputType: + case ConfigSetting::ProjectName: + case ConfigSetting::HFileName: + return true; + case ConfigSetting::LibraryDir: + case ConfigSetting::IncludeDir: + case ConfigSetting::Dependency: + case ConfigSetting::Library: + case ConfigSetting::Define: + case ConfigSetting::CFlag: + case ConfigSetting::GenerateHFile: + case ConfigSetting::Invalid: + return false; + } + } + + static bool IsVectorSetting(ConfigSetting setting) + { + switch(setting) + { + case ConfigSetting::LibraryDir: + case ConfigSetting::IncludeDir: + case ConfigSetting::Dependency: + case ConfigSetting::Library: + case ConfigSetting::Define: + case ConfigSetting::CFlag: + return true; + case ConfigSetting::SourceDir: + case ConfigSetting::OutputDir: + case ConfigSetting::OutputName: + case ConfigSetting::OutputType: + case ConfigSetting::ProjectName: + case ConfigSetting::HFileName: + case ConfigSetting::GenerateHFile: + case ConfigSetting::Invalid: + return false; + } + } + static bool IsBoolSetting(ConfigSetting setting) + { + switch(setting) + { + case ConfigSetting::GenerateHFile: + return true; + case ConfigSetting::SourceDir: + case ConfigSetting::OutputDir: + case ConfigSetting::OutputName: + case ConfigSetting::OutputType: + case ConfigSetting::ProjectName: + case ConfigSetting::HFileName: + case ConfigSetting::LibraryDir: + case ConfigSetting::IncludeDir: + case ConfigSetting::Dependency: + case ConfigSetting::Library: + case ConfigSetting::Define: + case ConfigSetting::CFlag: + case ConfigSetting::Invalid: + return false; + } + } + + static std::string GetDefaultSettingString(ConfigSetting setting, const std::string& path) + { + switch(setting) + { + case ConfigSetting::SourceDir: + return "src/"; + case ConfigSetting::OutputDir: + return "bin/"; + case ConfigSetting::OutputName: + return GetDefaultOutputName(path); + case ConfigSetting::OutputType: + return "executable"; + case ConfigSetting::ProjectName: + return GetDefaultProjectName(path); + case ConfigSetting::HFileName: + return GetDefaultHFileName(path); + case ConfigSetting::GenerateHFile: + return GetDefaultSettingBool(setting) ? "true" : "false"; + default: + LOG_ERROR("INVALID STRING ENUM: ", (int)setting); + assert(false); + } + } + + static bool GetDefaultSettingBool(ConfigSetting setting) + { + switch(setting) + { + case ConfigSetting::GenerateHFile: + return false; + default: + LOG_ERROR("NOT BOOLEAN VALUE: ", (int)setting); + assert(false); + } + } + + static std::string GetDefaultProjectName(const std::string& path) + { + return FileUtils::GetTopDirectory(path); + } + + static std::string GetDefaultOutputName(const std::string& path) + { + std::string projectname = GetDefaultProjectName(path); + std::string outputname; + std::transform( + projectname.begin(), + projectname.end(), + std::back_inserter(outputname), + [](unsigned char c) + { + if(c == ' ') + return '_'; + return (char)std::tolower(c); + }); + auto it = std::remove_if( + outputname.begin(), + outputname.end(), + [](unsigned char c) + { + return (c < '0' || c > '9') && (c < 'a' || c > 'z') && c != '_'; + }); + outputname.erase(it, outputname.end()); + outputname += ".out"; + return outputname; + } + + static std::string GetDefaultHFileName(const std::string& path) + { + std::string hfile = GetDefaultProjectName(path); + auto it = std::remove_if( + hfile.begin(), + hfile.end(), + [](unsigned char c) + { + return (c < 'a' || c > 'z') && (c < 'A' || c > 'Z'); + }); + hfile.erase(it, hfile.end()); + hfile += ".h"; + return hfile; + } +}; diff --git a/src/FileUtils.h b/src/FileUtils.h index 89fb867..e5905d8 100644 --- a/src/FileUtils.h +++ b/src/FileUtils.h @@ -34,14 +34,26 @@ struct FileUtils { static char path[256]; // Usual maximum filename getcwd(path, sizeof(path)); - std::string dir = path; - size_t pos = dir.find_last_of("/"); + return GetTopDirectory(path); + } + + static std::string GetTopDirectory(const std::string& dir) + { + if(dir.size() == 0) + { + LOG_ERROR("Cannot send empty string to FileUtils::GetTopDirectory()"); + assert(false); + } + size_t dirEnd = std::string::npos; + if(dir[dir.size()-1] == '/') + dirEnd = dir.size()-2; + size_t pos = dir.find_last_of("/", dirEnd); if(pos == std::string::npos) { LOG_ERROR("Couldn't find / (slash) in directory. This shouldn't occur."); assert(false); } - return dir.substr(pos+1); + return dir.substr(pos + 1, dirEnd - pos); } static std::string GetRealPath(const std::string& filename) diff --git a/src/HFileGen.cpp b/src/HFileGen.cpp index da6be3d..bf41b12 100644 --- a/src/HFileGen.cpp +++ b/src/HFileGen.cpp @@ -3,11 +3,11 @@ #include "FileUtils.h" #include -void HFileGen::Create(const ConfigFile& conf) +void HFileGen::Create(ConfigFile& conf) { std::set hFiles; std::vector files; - std::string path = conf.configPath + conf.srcdir; + std::string path = conf.GetConfigPath() + conf.GetSettingString(ConfigSetting::SourceDir); FileUtils::GetAllFiles(path,files); // include paramenter with the path of the file // For example src/graphics/Window.h -> graphics/Window.h if src is a src folder @@ -17,7 +17,7 @@ void HFileGen::Create(const ConfigFile& conf) if(extensionPos != std::string::npos) { std::string filename = it->substr(path.length()); - if(it->substr(extensionPos+1) == "h" && filename != conf.configPath + conf.hFile) + if(it->substr(extensionPos+1) == "h" && filename != conf.GetConfigPath() + conf.GetSettingString(ConfigSetting::HFileName)) { // Make files sorted in alphabetical order hFiles.emplace(filename); @@ -25,7 +25,7 @@ void HFileGen::Create(const ConfigFile& conf) } } - std::ofstream os(conf.configPath + conf.srcdir+"/"+conf.hFile); + std::ofstream os(path + "/" + conf.GetSettingString(ConfigSetting::HFileName)); os << "#pragma once" << std::endl << std::endl; for(auto&& hFile : hFiles) os << "#include <" << hFile << ">" << std::endl; diff --git a/src/HFileGen.h b/src/HFileGen.h index 03585a6..e0ce193 100644 --- a/src/HFileGen.h +++ b/src/HFileGen.h @@ -5,5 +5,5 @@ class HFileGen { public: - static void Create(const ConfigFile& conf); + static void Create(ConfigFile& conf); }; diff --git a/src/IncludeDeps.h b/src/IncludeDeps.h index 065b9b9..3ba496c 100755 --- a/src/IncludeDeps.h +++ b/src/IncludeDeps.h @@ -32,7 +32,7 @@ class IncludeDeps printCounter++; printSet.emplace(filepath); if(!projectHFile) - stream << FileUtils::GetRelativePath(conf.configPath, filepath); + stream << FileUtils::GetRelativePath(conf.GetConfigPath(), filepath); for(auto it = dependencies.begin();it!=dependencies.end();++it) { stream << " "; diff --git a/src/Makefile.cpp b/src/Makefile.cpp index 6243afe..ecc13b9 100755 --- a/src/Makefile.cpp +++ b/src/Makefile.cpp @@ -6,7 +6,7 @@ #include #include -void Makefile::Save(const ConfigFile& conf, unsigned int flags) +void Makefile::Save(ConfigFile& conf, unsigned int flags) { std::set hFiles; // hFile, directory std::set cppFiles; @@ -15,14 +15,15 @@ void Makefile::Save(const ConfigFile& conf, unsigned int flags) else Utils::GetCppAndHFiles(conf, hFiles, cppFiles); - std::ofstream outputFile(conf.configPath + "Makefile"); - outputFile << "# This Makefile was generated using MakeGen "<< MAKEGEN_VERSION<< " made by Tim Håkansson" << std::endl; + std::ofstream outputFile(conf.GetConfigPath()+ "Makefile"); + outputFile << "# This Makefile was generated using MakeGen "<< MAKEGEN_VERSION << " made by Tim Håkansson" << std::endl; outputFile << "# and is licensed under MIT. Full source of the project can be found at" << std::endl; outputFile << "# https://github.com/Thraix/MakeGen" << std::endl; outputFile << "CC=@g++" << std::endl; - if(!conf.executable) + std::string outputtype = conf.GetSettingString(ConfigSetting::OutputType); + if(outputtype != "executable") { - if(conf.shared) + if(outputtype == "sharedlibrary") outputFile << "CO=@g++ -shared -o" << std::endl; else outputFile << "CO=@g++ -o" << std::endl; @@ -31,10 +32,11 @@ void Makefile::Save(const ConfigFile& conf, unsigned int flags) outputFile << "CO=@g++ -o" << std::endl; outputFile << "MKDIR_P=mkdir -p" << std::endl; - outputFile << "BIN=" << conf.outputdir << std::endl; + outputFile << "BIN=" << conf.GetSettingString(ConfigSetting::OutputDir) << std::endl; outputFile << "OBJPATH=$(BIN)intermediates" << std::endl; outputFile << "INCLUDES="; - for(auto it = conf.includedirs.begin();it!=conf.includedirs.end();++it) + std::vector& includedirs = conf.GetSettingVectorString(ConfigSetting::IncludeDir); + for(auto it = includedirs.begin(); it != includedirs.end(); ++it) { outputFile << "-I./" << *it << " "; } @@ -47,7 +49,7 @@ void Makefile::Save(const ConfigFile& conf, unsigned int flags) outputFile << "$(OBJPATH)/" << it->substr(slash, extensionPos - slash) << ".o "; } outputFile << std::endl; - if(conf.executable || !conf.shared) + if(outputtype == "executable" || outputtype != "sharedlibrary") { outputFile << "CFLAGS=$(INCLUDES) -std=c++17 -c -w -g3 "; } @@ -55,46 +57,51 @@ void Makefile::Save(const ConfigFile& conf, unsigned int flags) { outputFile << "CFLAGS=$(INCLUDES) -fPIC -std=c++17 -c -w -g3 "; } - for(auto it = conf.defines.begin();it!=conf.defines.end();++it) + std::vector& defines = conf.GetSettingVectorString(ConfigSetting::Define); + for(auto it = defines.begin(); it != defines.end(); ++it) { outputFile << "-D" << *it << " "; } - for(auto it = conf.flags.begin();it!=conf.flags.end();++it) + std::vector& cflags = conf.GetSettingVectorString(ConfigSetting::CFlag); + for(auto it = cflags.begin(); it != cflags.end(); ++it) { outputFile << *it << " "; } outputFile << std::endl; - if(conf.executable) + if(outputtype == "executable") { + std::vector& libdirs= conf.GetSettingVectorString(ConfigSetting::LibraryDir); outputFile << "LIBDIR="; - for(auto it = conf.libdirs.begin();it!=conf.libdirs.end();++it) + for(auto it = libdirs.begin();it!=libdirs.end();++it) { outputFile << "-L./" << *it << " "; } outputFile << std::endl; outputFile << "LDFLAGS="; - for(auto it = conf.libdirs.begin();it!=conf.libdirs.end();++it) + for(auto it = libdirs.begin(); it != libdirs.end(); ++it) { outputFile << "-Wl,-rpath=" << *it << " "; } outputFile << std::endl; + std::vector& libs = conf.GetSettingVectorString(ConfigSetting::Library); outputFile << "LIBS=$(LIBDIR) "; - for(auto it = conf.libs.begin();it!=conf.libs.end();++it) + for(auto it = libs.begin(); it != libs.end(); ++it) { outputFile << "-l" << *it << " "; } outputFile << std::endl; - if(!conf.dependencies.empty()) + std::vector& dependencies = conf.GetSettingVectorString(ConfigSetting::Dependency); + if(!dependencies.empty()) { outputFile << "DEPENDENCIES="; - for(auto it = conf.dependencies.begin();it!=conf.dependencies.end();++it) + for(auto it = dependencies.begin();it!=dependencies.end();++it) { outputFile << *it << " "; } outputFile << std::endl; } } - outputFile << "OUTPUT=$(BIN)" << conf.outputname << std::endl; + outputFile << "OUTPUT=$(BIN)" << conf.GetSettingString(ConfigSetting::OutputName) << std::endl; outputFile << ".PHONY: all directories rebuild clean run" << std::endl; // All @@ -114,7 +121,7 @@ void Makefile::Save(const ConfigFile& conf, unsigned int flags) // Run outputFile << "run: all" << std::endl; - if(conf.executable) + if(outputtype == "executable") { outputFile << "\t@./$(OUTPUT)" << std::endl; } @@ -130,25 +137,26 @@ void Makefile::Save(const ConfigFile& conf, unsigned int flags) // Output file outputFile << "$(OUTPUT): $(OBJECTS)" << std::endl; outputFile << "\t$(info Generating output file)" << std::endl; - if(conf.executable) + if(outputtype == "executable") outputFile << "\t$(CO) $(OUTPUT) $(OBJECTS) $(LDFLAGS) $(LIBS)" << std::endl; else outputFile << "\t$(CO) $(OUTPUT) $(OBJECTS)" << std::endl; // Install outputFile << "install: all" << std::endl; - outputFile << "\t$(info Installing " << conf.projectname <<" to /usr/bin/)" << std::endl; - outputFile << "\t@cp $(OUTPUT) /usr/bin/" << conf.outputname << std::endl; + outputFile << "\t$(info Installing " << conf.GetSettingString(ConfigSetting::ProjectName) <<" to /usr/bin/)" << std::endl; + outputFile << "\t@cp $(OUTPUT) /usr/bin/" << conf.GetSettingString(ConfigSetting::OutputName) << std::endl; std::map dependencies; size_t i = 0; - for(auto it = cppFiles.begin(); it!= cppFiles.end();++it) + for(auto it = cppFiles.begin(); it != cppFiles.end();++it) { i++; - auto itD = dependencies.find(conf.srcdir + *it); + std::string& srcdir = conf.GetSettingString(ConfigSetting::SourceDir); + auto itD = dependencies.find(srcdir + *it); if(itD == dependencies.end()) { - IncludeDeps* deps = new IncludeDeps(*it, conf.configPath+conf.srcdir,hFiles,dependencies); + IncludeDeps* deps = new IncludeDeps(*it, conf.GetConfigPath() + srcdir,hFiles,dependencies); size_t extensionPos = it->find_last_of("."); size_t slash = it->find_last_of("/")+1; std::string oFile = it->substr(slash, extensionPos - slash)+".o "; diff --git a/src/Makefile.h b/src/Makefile.h index 6191711..36b589b 100755 --- a/src/Makefile.h +++ b/src/Makefile.h @@ -5,5 +5,5 @@ class Makefile { public: - static void Save(const ConfigFile& conf, unsigned int flags); + static void Save(ConfigFile& conf, unsigned int flags); }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 899559f..c4eace0 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -1,5 +1,6 @@ #include "Utils.h" +#include "ConfigFile.h" #include "FileUtils.h" std::string Utils::CommonPrefix(const std::string& s1, const std::string& s2) @@ -16,10 +17,10 @@ std::string Utils::CommonPrefix(const std::string& s1, const std::string& s2) return s1.substr(0, n); } -void Utils::GetCppFiles(const ConfigFile& conf, std::set& cppFiles) +void Utils::GetCppFiles(ConfigFile& conf, std::set& cppFiles) { std::vector files; - std::string path = conf.configPath + conf.srcdir; + std::string path = conf.GetConfigPath() + conf.GetSettingString(ConfigSetting::SourceDir); FileUtils::GetAllFiles(path, files); for(auto it = files.begin(); it!=files.end();++it) @@ -37,10 +38,10 @@ void Utils::GetCppFiles(const ConfigFile& conf, std::set& cppFiles) } } -void Utils::GetCppAndHFiles(const ConfigFile& conf, std::set& hFiles, std::set& cppFiles) +void Utils::GetCppAndHFiles(ConfigFile& conf, std::set& hFiles, std::set& cppFiles) { std::vector files; - std::string path = conf.configPath + conf.srcdir; + std::string path = conf.GetConfigPath() + conf.GetSettingString(ConfigSetting::SourceDir); FileUtils::GetAllFiles(path,files); // include paramenter with the path of the file // For example src/graphics/Window.h -> graphics/Window.h if src is a src folder @@ -62,24 +63,26 @@ void Utils::GetCppAndHFiles(const ConfigFile& conf, std::set& hFiles, std } } - for(size_t i = 0; i < conf.dependencies.size(); ++i) + std::vector& dependencies = conf.GetSettingVectorString(ConfigSetting::Dependency); + for(size_t i = 0; i < dependencies.size(); ++i) { - GetHFiles(conf.dependencies[i], conf.dependencyConfigs[i], hFiles); + GetHFiles(dependencies[i], conf.GetDependencyConfig(i), hFiles); } } -void Utils::GetHFiles(const std::string& dependencyDir, const ConfigFile& conf, std::set& hFiles) +void Utils::GetHFiles(const std::string& dependencyDir, ConfigFile& conf, std::set& hFiles) { // TODO: Fix so that cyclic dependencies doesn't crash the tool. // Cyclic dependencies probably shouldn't exist. // so just warn the user that it does and terminate. - for(size_t i = 0; i < conf.dependencies.size(); ++i) + std::vector& dependencies = conf.GetSettingVectorString(ConfigSetting::Dependency); + for(size_t i = 0; i < dependencies.size(); ++i) { - GetHFiles(conf.dependencies[i], conf.dependencyConfigs[i], hFiles); + GetHFiles(dependencies[i], conf.GetDependencyConfig(i), hFiles); } std::vector files; - std::string depSrcDir = dependencyDir + conf.srcdir; + std::string depSrcDir = dependencyDir + conf.GetSettingString(ConfigSetting::SourceDir); FileUtils::GetAllFiles(depSrcDir, files); for(auto it = files.begin(); it!=files.end();++it) { @@ -90,8 +93,37 @@ void Utils::GetHFiles(const std::string& dependencyDir, const ConfigFile& conf, if(extension == "hpp" || extension == "h") { std::string filename = it->substr(depSrcDir.length()); - hFiles.emplace(HFile{filename, depSrcDir, conf.generateHFile && filename == conf.hFile}); + hFiles.emplace(HFile{filename, depSrcDir, conf.GetSettingBool(ConfigSetting::GenerateHFile) && filename == conf.GetSettingString(ConfigSetting::HFileName)}); } } } } +bool Utils::IsWhiteSpace(char c) +{ + return c == '\n' || c == '\t' || c == '\r' || c == ' ' || c == '\t'; +} + +bool Utils::IsLetter(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +bool Utils::IsWord(const std::string& string) +{ + for(auto it{string.begin()}; it != string.end();++it) + { + if(!IsLetter(*it)) + return false; + } + return true; +} + +std::string Utils::GetWord(const std::string& string, int startPos) +{ + if (startPos >= string.length()) + throw std::runtime_error("start position out of bounds."); + + int endPos = startPos; + while (endPos < string.length() && IsLetter(string[endPos])) endPos++; + return string.substr(startPos, endPos - startPos); +} diff --git a/src/Utils.h b/src/Utils.h index cacedac..85e1ee4 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -1,7 +1,5 @@ #pragma once -#include "ConfigFile.h" - #include #include @@ -24,10 +22,18 @@ struct HFile } }; +class ConfigFile; + struct Utils { static std::string CommonPrefix(const std::string& s1, const std::string& s2); - static void GetCppFiles(const ConfigFile& conf, std::set& cppFiles); - static void GetCppAndHFiles(const ConfigFile& conf, std::set& hFiles, std::set& cppFiles); - static void GetHFiles(const std::string& dependencyDir, const ConfigFile& conf, std::set& hFiles); + static void GetCppFiles(ConfigFile& conf, std::set& cppFiles); + static void GetCppAndHFiles(ConfigFile& conf, std::set& hFiles, std::set& cppFiles); + static void GetHFiles(const std::string& dependencyDir, ConfigFile& conf, std::set& hFiles); + + // Used for parsing xml + static bool IsWhiteSpace(char c); + static bool IsLetter(char c); + static bool IsWord(const std::string& string); + static std::string GetWord(const std::string& string, int startPos = 0); }; diff --git a/src/compatibility/ConfigFileConf.cpp b/src/compatibility/ConfigFileConf.cpp new file mode 100644 index 0000000..f17d53b --- /dev/null +++ b/src/compatibility/ConfigFileConf.cpp @@ -0,0 +1,194 @@ +#include "ConfigFileConf.h" + +const std::string CONFIG_FILENAME_CONF = "makegen.conf"; + +#include "../ConfigFile.h" +#include "../FileUtils.h" + +#include +#include + +#define FLAG_NONE 0 +#define FLAG_VECTOR 1 +#define FLAG_STRING 2 +#define FLAG_BOOL 3 + +ConfigFileConf::ConfigFileConf() + : outputdir("bin/"), srcdir("src/"), outputname(""), projectname(FileUtils::GetCurrentDirectory()), hFile(""), executable(true), shared(true), generateHFile(false) +{ + // Converts project name (current directory) to lowercase + // and replace whitespace with underscore + std::transform( + projectname.begin(), + projectname.end(), + std::back_inserter(outputname), + [](unsigned char c) + { + if(c == ' ') + return '_'; + return (char)std::tolower(c); + }); + + // Removes all other characters + std::remove_if( + outputdir.begin(), + outputdir.end(), + [](unsigned char c) + { + return (c < 'a' || c > 'z') && c != '_'; + }); + + // Add suffix + outputname += ".out"; +} + +void ConfigFileConf::CreateXMLFile(const std::string& filepath) +{ + ConfigFileConf conf; + conf.configPath = filepath; + unsigned int loadFlag = 0; + + std::vector* vec; + std::string* s; + bool* b; + + bool isDirectory = false; + + std::ifstream file(filepath + CONFIG_FILENAME_CONF); + std::string line; + + if(file.is_open()) + { + // config name, { pointer to memory, isDirectory} + std::map> strings = + { + {"#srcdir", {&conf.srcdir, true}}, + {"#outputdir", {&conf.outputdir, true}}, + {"#outputname", {&conf.outputname, false}}, + {"#projectname", {&conf.projectname, false}}, + {"#hfile", {&conf.hFile, false}}, + }; + + // config name, { pointer to memory, isDirectory} + std::map*, bool>> vectors = + { + {"#libs", {&conf.libs, false}}, + {"#libdirs", {&conf.libdirs, true}}, + {"#includedirs", {&conf.includedirs, true}}, + {"#compileflags", {&conf.flags, false}}, + {"#defines", {&conf.defines, false}}, + {"#dependencies", {&conf.dependencies, true}}, + }; + + std::map booleans = + { + {"#executable", &conf.executable}, + {"#shared", &conf.shared}, + {"#generatehfile", &conf.generateHFile}, + }; + + while(std::getline(file,line)) + { + if(line == "") + continue; + if(line[0]=='#') + { + // The format is a bit wacky, but it is this way since we do not want + // to use map::find for all maps. This way we gain some optimization. + auto&& itStr{strings.find(line)}; + if(itStr != strings.end()) + { + s = itStr->second.first; + isDirectory = itStr->second.second; + loadFlag = FLAG_STRING; + } + else + { + auto&& itVec{vectors.find(line)}; + if(itVec != vectors.end()) + { + vec = itVec->second.first; + isDirectory = itVec->second.second; + loadFlag = FLAG_VECTOR; + } + else + { + auto&& itBool{booleans.find(line)}; + if(itBool != booleans.end()) + { + b = itBool->second; + loadFlag = FLAG_BOOL; + } + else + { + LOG_ERROR("Invalid flag: ", line); + loadFlag = FLAG_NONE; + } + } + } + } + else + { + if(loadFlag == FLAG_STRING) + { + if(isDirectory && line[line.size()-1] != '/') + line += '/'; + *s = line; + } + else if(loadFlag == FLAG_VECTOR) + { + if(isDirectory && line[line.size()-1] != '/') + {; + line += '/'; + } + vec->push_back(line); + } + else if(loadFlag == FLAG_BOOL) + { + if(line == "true") + *b = true; + else + *b = false; + } + } + } + + LOG_INFO("------ COULDN\'T FIND makegen.xml. BUT FOUND OLD makegen.conf."); + LOG_INFO("------ GENERATING NEW CONFIGURATION FILE"); + if(conf.hFile == "") + conf.hFile = conf.projectname+".h"; + + 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", {}, conf.projectname)); + configuration.AddXMLObject(XMLObject("outputname", {}, conf.outputname)); + configuration.AddXMLObject(XMLObject("srcdir", {}, conf.srcdir)); + configuration.AddXMLObject(XMLObject("outputdir", {}, conf.outputdir)); + configuration.AddXMLObject(XMLObject("hfilename", {}, conf.hFile)); + configuration.AddXMLObject(XMLObject("outputtype", {}, + conf.executable ? "executable" : (conf.shared ? "sharedlibrary" : "staticlibrary"))); + configuration.AddXMLObject(XMLObject("generatehfile", {}, conf.generateHFile ? "true" : "false")); + + for(auto it = conf.libs.begin();it != conf.libs.end(); ++it) + configuration.AddXMLObject({"library",{},*it}); + for(auto it = conf.libdirs.begin();it != conf.libdirs.end(); ++it) + configuration.AddXMLObject({"librarydir",{},*it}); + for(auto it = conf.includedirs.begin();it != conf.includedirs.end(); ++it) + configuration.AddXMLObject({"includedir",{},*it}); + for(auto it = conf.defines.begin();it != conf.defines.end(); ++it) + configuration.AddXMLObject({"define",{},*it}); + for(auto it = conf.flags.begin();it != conf.flags.end(); ++it) + configuration.AddXMLObject({"cflag",{},*it}); + for(auto it = conf.dependencies.begin();it != conf.dependencies.end(); ++it) + configuration.AddXMLObject({"dependency",{},*it}); + + makegen.AddXMLObject(configuration); + std::ofstream xmlFile(conf.configPath + "makegen.xml"); + xmlFile << makegen; + } +} diff --git a/src/compatibility/ConfigFileConf.h b/src/compatibility/ConfigFileConf.h new file mode 100644 index 0000000..cd0a7f8 --- /dev/null +++ b/src/compatibility/ConfigFileConf.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +class ConfigFile; + +class ConfigFileConf +{ + public: + std::string configPath; + std::vector libs; + std::vector libdirs; + std::vector includedirs; + std::vector defines; + std::vector flags; + std::vector dependencies; + + std::string outputdir; + std::string srcdir; + std::string outputname; + std::string projectname; + std::string hFile; + bool executable; + bool shared; + bool generateHFile; + public: + ConfigFileConf(); + + static void CreateXMLFile(const std::string& filename); + private: +}; diff --git a/src/main.cpp b/src/main.cpp index ea1f7ae..e29877e 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,9 +47,9 @@ Usage: makegen [options] clean all install run, rebuild will be translated to \"clean make\")"); } -void GenMakefile(const ConfigFile& conf, unsigned int flags) +void GenMakefile(ConfigFile& conf, unsigned int flags) { - if(conf.generateHFile) + if(conf.GetSettingBool(ConfigSetting::GenerateHFile)) HFileGen::Create(conf); Makefile::Save(conf, flags); } @@ -115,7 +115,7 @@ unsigned int ReadFlags(int argc, char** argv) return flags; } -bool RunMake(const std::string& filepath, unsigned int flags, const ConfigFile& conf) +bool RunMake(const std::string& filepath, unsigned int flags, ConfigFile& conf) { std::string make = "make --no-print-directory -C " + filepath; if(!(flags & FLAG_SINGLE_THREAD)) @@ -132,33 +132,37 @@ bool RunMake(const std::string& filepath, unsigned int flags, const ConfigFile& { RETURN_IF(system(std::string(make + " install").c_str()) != 0, false); } - if(flags & FLAG_RUN && conf.executable) + if(flags & FLAG_RUN && conf.GetSettingString(ConfigSetting::OutputType) == "executable") { RETURN_IF(system(std::string(make + " run").c_str()) != 0, false); } return true; } -bool MakeGen(const std::string& filepath, unsigned int flags, const ConfigFile& conf) +bool MakeGen(const std::string& filepath, unsigned int flags, ConfigFile& conf) { - for(size_t i = 0;i& dependencies = conf.GetSettingVectorString(ConfigSetting::Dependency); + for(size_t i = 0;i +#include + +XMLObject XML::FromString(const std::string& string, const std::string& filename="") +{ + int startLine = 1; + int startPos = 0; + // Remove version tag. + if(string.find("") + 3; + startLine = std::count(string.begin(), string.begin()+startPos, '\n') + 1; + } + return XMLObject(string, startPos, startLine,filename); +} + +XMLObject XML::FromFile(const std::string& filename) +{ + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if(!file) + throw XMLException("Could not read file \""+filename+"\""); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::string buffer; + buffer.reserve(size); + while (!file.eof()) + { + buffer += file.get(); + } + return FromString(buffer, filename); +} diff --git a/src/xml/XML.h b/src/xml/XML.h new file mode 100644 index 0000000..2bf9b5c --- /dev/null +++ b/src/xml/XML.h @@ -0,0 +1,9 @@ +#pragma once + +#include "XMLObject.h" + +struct XML +{ + static XMLObject FromString(const std::string& string, const std::string& filename); + static XMLObject FromFile(const std::string& fileName); +}; diff --git a/src/xml/XMLException.h b/src/xml/XMLException.h new file mode 100644 index 0000000..ade26f9 --- /dev/null +++ b/src/xml/XMLException.h @@ -0,0 +1,22 @@ +#pragma once + +#include "XMLObject.h" + +#include +#include + +class XMLException : public std::exception +{ + private: + std::string m_message; + public: + explicit XMLException(const std::string& message) : m_message("XMLException: " + message) {} + explicit XMLException(const std::string& message, const XMLObject::XMLLoadData& data) + : m_message("XMLException(" + data.file + ":" + std::to_string(data.line) + "): " + message) + {} + + virtual const char* what() const throw() + { + return m_message.c_str(); + } +}; diff --git a/src/xml/XMLObject.cpp b/src/xml/XMLObject.cpp new file mode 100644 index 0000000..8e8dea0 --- /dev/null +++ b/src/xml/XMLObject.cpp @@ -0,0 +1,406 @@ +#include "XMLObject.h" + +#include "../Common.h" +#include "../Utils.h" + +#include +#include "XMLException.h" + +XMLObject::XMLObject(const std::string& string) +{ + int pos = 0; + int line = 1; + XMLLoadData data{pos, line, ""}; + if(!ReadHead(string, data)) + ReadBodyTail(string, data); +} + +XMLObject::XMLObject(const std::string& string, int pos, int line, const std::string& file) +{ + XMLLoadData data{pos, line, file}; + if (!ReadHead(string, data)) + ReadBodyTail(string, data); +} + +XMLObject::XMLObject(const std::string& string, XMLLoadData& data) +{ + if (!ReadHead(string, data)) + ReadBodyTail(string, data); +} + +XMLObject::XMLObject(const std::string& name, const std::map& attributes, const std::string& text) + :name(name), attributes(attributes), text(text) +{ + +} + +XMLObject::XMLObject(const std::string& name, const std::map& attributes, const std::map>& objects) + : name(name), attributes(attributes), objects(objects) +{ + +} + +bool XMLObject::HasAttribute(const std::string& property) const +{ + return attributes.find(property) != attributes.end(); +} + +const std::string& XMLObject::GetAttribute(const std::string& property) const +{ + auto it = attributes.find(property); + if (it == attributes.end()) + throw XMLException((std::string("Attribute could not be found \"") + property + "\".").c_str()); + return it->second; +} + +const std::string& XMLObject::GetAttribute(const std::string& property, const std::string& defaultValue) const +{ + auto it = attributes.find(property); + if (it == attributes.end()) + return defaultValue; + return it->second; +} + +unsigned int XMLObject::GetObjectCount() const +{ + return objects.size(); +} + +std::vector* XMLObject::GetObjectPtr(const std::string& name) +{ + auto it = objects.find(name); + if(it == objects.end()) + return nullptr; + + return &it->second; +} + +const std::map>& XMLObject::GetObjects() const +{ + return objects; +} + +const std::string& XMLObject::GetName() const +{ + return name; +} + +const std::string& XMLObject::GetText() const +{ + return text; +} + +void XMLObject::SetName(const std::string& name) +{ + if(Utils::IsWord(name)) + this->name = name; + else + LOG_ERROR("XML Head can only be made up of letters"); +} + +void XMLObject::SetText(const std::string& text) +{ + this->text = text; +} + +void XMLObject::AddAttribute(const std::string& property, const std::string& value) +{ + if(Utils::IsWord(property)) + attributes.emplace(property, value); + else + LOG_ERROR("XML property name can only be made up of letters"); +} + +void XMLObject::AddXMLObject(const XMLObject& object) +{ + auto it = objects.find(object.name); + if(it == objects.end()) + objects.emplace(object.name, std::vector{object}); + else + it->second.push_back(object); +} + +bool XMLObject::RemoveXMLObject(const XMLObject& object) +{ + auto it = objects.find(object.name); + if(it == objects.end()) + return false; + + bool removed = false; + for(auto it2 = it->second.begin(); it2 != it->second.end();) + { + if(*it2 == object) + { + it2 = it->second.erase(it2); + removed = true; + } + else + ++it2; + } + return removed; +} + +XMLObject XMLObject::GetStrippedXMLObject() const +{ + if(text == "") + return XMLObject(name, attributes, objects); + else + return XMLObject(name, attributes, text); +} + +//////////////////////////////////////////////////////////// +// // +// Everything below here handles the reading of xml files // +// // +//////////////////////////////////////////////////////////// + + +bool XMLObject::ReadHead(const std::string& string, XMLLoadData& data) +{ + // Check if the first character is the start of and xml tag. + ReadWhiteSpace(string, data); + if (string[data.pos] != '<') + throw XMLException("Not an XML Object.", data); + + // Check if there is a closing tag + size_t closing = string.find('>'); + if (closing == std::string::npos) + throw XMLException("No enclosing > for opening tag.", data); + + // Read the name of the tag + ReadName(string, data); + + // Read all attributes of the xml tag + ReadAttributes(string, data); + + // Read opening tag + if (string[data.pos] == '/') + { + data.pos++; + ReadWhiteSpace(string, data); + if (string[data.pos] != '>') + throw XMLException((std::string("Invalid character proceeding / in opening XML Tag \"") + string[data.pos] + "\".").c_str(), data); + data.pos++; + // nothing more to read. + return true; + } + + ReadWhiteSpace(string, data); + if (string[data.pos] != '>') + throw XMLException((std::string("Invalid character proceeding attributes in opening XML Tag \"") + string[data.pos] + "\".").c_str(), data); + (data.pos)++; + return false; +} + +void XMLObject::ReadName(const std::string& string, XMLLoadData& data) +{ + data.pos++; + ReadWhiteSpace(string, data); + if (!Utils::IsLetter(string[data.pos])) + throw XMLException("Invalid XML name. Can only contain letters.", data); + name = Utils::GetWord(string, data.pos); + data.pos += name.length(); + ReadWhiteSpace(string, data); + if (string[data.pos] != '/' && string[data.pos] != '>' && Utils::IsWhiteSpace(string[data.pos])) + { + throw XMLException((std::string("Invalid character proceeding name in XML Tag \"") + string[data.pos] + "\".").c_str(), data); + } +} + +void XMLObject::ReadAttributes(const std::string& string, XMLLoadData& data) +{ + ReadWhiteSpace(string, data); + + while (string[data.pos] != '>' && string[data.pos] != '/') + { + ReadAttribute(string, data); + } +} + +void XMLObject::ReadAttribute(const std::string& string, XMLLoadData& data) +{ + // Read property name + std::string property = ReadXMLName(string, data); + if (property.length() == 0) + throw XMLException((std::string("Invalid character proceeding name \"") + string[data.pos] + "\".").c_str(), data); + if (attributes.count(property) > 0) + throw XMLException((std::string("Duplicate property in XML tag \"") + property + "\".").c_str(), data); + data.pos += property.length(); + ReadWhiteSpace(string, data); + + // Read = + if (string[data.pos] != '=') + throw XMLException((std::string("Invalid character proceeding property name in XML Tag \"") + string[data.pos] + "\".").c_str(), data); + (data.pos)++; + ReadWhiteSpace(string, data); + + // Read value + if (string[data.pos] != '\"') + throw XMLException("XML property value is not inside enclosing quotes.", data); + (data.pos)++; + int valueStart = data.pos; + while (string[data.pos] != '\"') (data.pos)++; + std::string value = string.substr(valueStart, (data.pos) - valueStart); + ReplacePredefinedEntities(value, data); + (data.pos)++; + attributes.emplace(property, value); + ReadWhiteSpace(string, data); +} + +void XMLObject::ReadBodyTail(const std::string& string, XMLLoadData& data) +{ + ReadWhiteSpace(string, data); + if (string[data.pos] != '<') + { + ReadText(string, data); + ReadWhiteSpace(string, data); + std::string closeTag = GetClosingTag(string, data); + if (closeTag.length() == 0) + throw XMLException("Tag after XML Test was not a closing tag. XMLObject doesn't support text and other XMLObjects at the same time.", data); + return; + } + // Check if we can read the closing tag. + std::string closeTag = GetClosingTag(string, data); + while (closeTag.length() == 0) + { + AddXMLObject(XMLObject(string, data)); + ReadWhiteSpace(string, data); + closeTag = GetClosingTag(string, data); + } +} + +void XMLObject::ReadText(const std::string& string, XMLLoadData& data) +{ + int startPos = data.pos; + while (string[data.pos] != '<') (data.pos)++; + text = string.substr(startPos, (data.pos) - startPos); + ReplacePredefinedEntities(text, data); +} + +void XMLObject::ReadWhiteSpace(const std::string& string, XMLLoadData& data) +{ + while (Utils::IsWhiteSpace(string[data.pos])) { + if (string[data.pos] == '\n') + (data.line)++; + (data.pos)++; + } +} + + +std::string XMLObject::GetClosingTag(const std::string& string, XMLLoadData& data) +{ + int startPos = data.pos; + int startLine = data.line; + if (string[(data.pos)++] != '<') + { + data.pos = startPos; + data.line = startLine; + return ""; + } + ReadWhiteSpace(string, data); + if (string[(data.pos)++] != '/') + { + data.pos = startPos; + data.line = startLine; + return ""; + } + ReadWhiteSpace(string, data); + std::string tag = Utils::GetWord(string, data.pos); + if (tag != name) + throw XMLException((std::string("Closing tag doesn't match opening tag. (\"") + name + "\" != \"" + tag+ "\")").c_str(), data); + data.pos += tag.length(); + ReadWhiteSpace(string, data); + if (string[data.pos] != '>') + throw XMLException((std::string("Invalid character in closing tag \"") + string[data.pos] + "\".").c_str(), data); + (data.pos)++; + return string.substr(startPos, (data.pos) - startPos); +} + +void XMLObject::ReplacePredefinedEntities(std::string& string, XMLLoadData& data) +{ + std::vector> entities + { + {""","\""}, + {"'", "\'"}, + {"<", "<"}, + {">",">"}, + {"&", "&"} + }; + size_t pos = string.find('&'); + while(pos != std::string::npos) + { + bool found = false; + for(auto entity : entities) + { + if(strncmp(&string[pos], entity.first.c_str(), entity.first.length()) == 0) + { + string.replace(pos, entity.first.length(), entity.second); + found = true; + } + } + if(!found) + LOG_ERROR("(" + data.file + ":" + std::to_string(data.line) + "): ""Ampersand found in xml but isn't a predefined entity."); + pos = string.find('&', pos+1); + } +} + +std::string XMLObject::ReadXMLName(const std::string& string, XMLLoadData& data) +{ + if(!(Utils::IsLetter(string[data.pos]) || + string[data.pos] == '_' || + string[data.pos] == ':')) + throw XMLException(std::string("Name doesn't start with a letter."), data); + + int endPos = data.pos + 1; + while (endPos < string.length() && ( + Utils::IsLetter(string[endPos]) || + string[endPos] == '_' || + string[endPos] == '-' || + string[endPos] == ':' || + string[endPos] == '.')) + endPos++; + return string.substr(data.pos, endPos - data.pos); +} + +std::ostream& XMLObject::WriteToStream(std::ostream& stream, int indent) const +{ + for(int i = 0;ifirst << "=\"" << it->second << "\""; + } + stream << ">"; + if(text != "") + { + stream << text; + } + else + { + bool hasChild = false; + for(auto it = objects.begin(); it != objects.end();++it) + { + for(auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) + { + stream << "\n"; + it2->WriteToStream(stream, indent+1); + hasChild = true; + } + } + if(hasChild) + { + stream << "\n"; + for(int i = 0;i"; + + return stream; +} + diff --git a/src/xml/XMLObject.h b/src/xml/XMLObject.h new file mode 100644 index 0000000..a3a1c0c --- /dev/null +++ b/src/xml/XMLObject.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include + +class XMLObject +{ + public: + friend class XMLexception; + struct XMLLoadData + { + int pos; + int line; + const std::string& file; + }; + private: + std::string name; + std::string text; + + std::map attributes; + std::map> objects; + + public: + XMLObject() {} + XMLObject(const std::string& string); + XMLObject(const std::string& string, int pos, int line, const std::string& file); + XMLObject(const std::string& string, XMLLoadData& data); + XMLObject(const std::string& name, const std::map& properties, const std::string& text); + XMLObject(const std::string& name, const std::map& properties, const std::map>& objects); + + bool HasAttribute(const std::string& property) const; + const std::string& GetAttribute(const std::string& property) const; + const std::string& GetAttribute(const std::string& property, const std::string& defaultValue) const; + + unsigned int GetObjectCount() const; + std::vector* GetObjectPtr(const std::string& name); + const std::map>& GetObjects() const; + const std::string& GetName() const; + const std::string& GetText() const; + XMLObject GetStrippedXMLObject() const; + + void SetName(const std::string& name); + void SetText(const std::string& text); + void AddAttribute(const std::string& property, const std::string& value); + void AddXMLObject(const XMLObject& object); + bool RemoveXMLObject(const XMLObject& object); + + friend bool operator<(const XMLObject& obj1, const XMLObject& obj2) + { + return obj1.name < obj2.name; + } + + std::ostream& WriteToStream(std::ostream& stream, int indent = 0) const; + friend std::ostream& operator<<(std::ostream& stream, const XMLObject& object) + { + return object.WriteToStream(stream); + } + + friend bool operator==(const XMLObject& object1, const XMLObject& object2) + { + if(object1.attributes.size() != object2.attributes.size()) + return false; + if(object1.objects.size() != object2.objects.size()) + return false; + if(object1.name != object2.name) + return false; + if(object1.GetText() != object2.GetText()) + return false; + + { + auto it1 = object1.attributes.begin(); + auto it2 = object2.attributes.begin(); + while(it1 != object1.attributes.end()) + { + if(it1->first != it2->first || it1->second != it1->second) + return false; + ++it1; + ++it2; + } + } + + { + auto it1 = object1.objects.begin(); + auto it2 = object2.objects.begin(); + while(it1 != object1.objects.end()) + { + if(it1->second != it2->second) + return false; + ++it1; + ++it2; + } + } + return true; + } + + private: + std::string GetClosingTag(const std::string& string, XMLLoadData& data); + // Returns true if the head contained closing tag. + bool ReadHead(const std::string& string, XMLLoadData& data); + void ReadName(const std::string& string, XMLLoadData& data); + void ReadAttribute(const std::string& string, XMLLoadData& data); + void ReadAttributes(const std::string& string, XMLLoadData& data); + void ReadBodyTail(const std::string& string, XMLLoadData& data); + void ReadText(const std::string& string, XMLLoadData& data); + void ReadWhiteSpace(const std::string& string, XMLLoadData& data); + void ReplacePredefinedEntities(std::string& string, XMLLoadData& data); + std::string ReadXMLName(const std::string& string, XMLLoadData& data); +};