Read and write config as xml
This commit is contained in:
@@ -7,7 +7,7 @@ MKDIR_P=mkdir -p
|
|||||||
BIN=bin/
|
BIN=bin/
|
||||||
OBJPATH=$(BIN)intermediates
|
OBJPATH=$(BIN)intermediates
|
||||||
INCLUDES=
|
INCLUDES=
|
||||||
OBJECTS=$(OBJPATH)/ConfigCLI.o $(OBJPATH)/ConfigFile.o $(OBJPATH)/HFileGen.o $(OBJPATH)/IncludeDeps.o $(OBJPATH)/Makefile.o $(OBJPATH)/Utils.o $(OBJPATH)/main.o $(OBJPATH)/XML.o $(OBJPATH)/XMLObject.o
|
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 -D_DEBUG
|
CFLAGS=$(INCLUDES) -std=c++17 -c -w -g3 -D_DEBUG
|
||||||
LIBDIR=
|
LIBDIR=
|
||||||
LDFLAGS=
|
LDFLAGS=
|
||||||
@@ -33,29 +33,32 @@ $(OUTPUT): $(OBJECTS)
|
|||||||
install: all
|
install: all
|
||||||
$(info Installing MakeGen to /usr/bin/)
|
$(info Installing MakeGen to /usr/bin/)
|
||||||
@cp $(OUTPUT) /usr/bin/makegen
|
@cp $(OUTPUT) /usr/bin/makegen
|
||||||
$(OBJPATH)/ConfigCLI.o : src/ConfigCLI.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h
|
$(OBJPATH)/ConfigCLI.o : src/ConfigCLI.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h src/xml/XMLObject.h
|
||||||
$(info -[11%]- $<)
|
$(info -[10%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
$(OBJPATH)/ConfigFile.o : src/ConfigFile.cpp src/ConfigFile.h src/FileUtils.h src/Common.h src/Utils.h
|
$(OBJPATH)/ConfigFile.o : src/ConfigFile.cpp src/ConfigFile.h src/xml/XMLObject.h src/FileUtils.h src/Common.h src/Utils.h src/compatibility/ConfigFileConf.h src/xml/XML.h
|
||||||
$(info -[22%]- $<)
|
$(info -[20%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
$(OBJPATH)/HFileGen.o : src/HFileGen.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h src/HFileGen.h
|
$(OBJPATH)/HFileGen.o : src/HFileGen.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h src/xml/XMLObject.h src/HFileGen.h
|
||||||
$(info -[33%]- $<)
|
$(info -[30%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
$(OBJPATH)/IncludeDeps.o : src/IncludeDeps.cpp src/Common.h src/IncludeDeps.h src/ConfigFile.h src/FileUtils.h src/Utils.h
|
$(OBJPATH)/IncludeDeps.o : src/IncludeDeps.cpp src/Common.h src/IncludeDeps.h src/ConfigFile.h src/xml/XMLObject.h src/FileUtils.h src/Utils.h
|
||||||
$(info -[44%]- $<)
|
$(info -[40%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(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
|
$(OBJPATH)/Makefile.o : src/Makefile.cpp src/IncludeDeps.h src/ConfigFile.h src/xml/XMLObject.h src/FileUtils.h src/Common.h src/Utils.h src/Makefile.h
|
||||||
$(info -[55%]- $<)
|
$(info -[50%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
$(OBJPATH)/Utils.o : src/Utils.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h
|
$(OBJPATH)/Utils.o : src/Utils.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h src/xml/XMLObject.h
|
||||||
$(info -[66%]- $<)
|
$(info -[60%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(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 -[77%]- $<)
|
$(info -[70%]- $<)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
$(OBJPATH)/main.o : src/main.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h src/xml/XMLObject.h src/FileUtils.h src/Utils.h src/HFileGen.h src/Makefile.h src/Timer.h
|
||||||
|
$(info -[80%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
$(OBJPATH)/XML.o : src/xml/XML.cpp src/xml/XML.h src/xml/XMLObject.h src/xml/XMLException.h
|
$(OBJPATH)/XML.o : src/xml/XML.cpp src/xml/XML.h src/xml/XMLObject.h src/xml/XMLException.h
|
||||||
$(info -[88%]- $<)
|
$(info -[90%]- $<)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
$(OBJPATH)/XMLObject.o : src/xml/XMLObject.cpp src/xml/XMLException.h src/xml/XMLObject.h
|
$(OBJPATH)/XMLObject.o : src/xml/XMLObject.cpp src/xml/XMLException.h src/xml/XMLObject.h
|
||||||
$(info -[100%]- $<)
|
$(info -[100%]- $<)
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
#libs
|
|
||||||
#libdirs
|
|
||||||
#includedirs
|
|
||||||
#defines
|
|
||||||
_DEBUG
|
|
||||||
#compileflags
|
|
||||||
#dependencies
|
|
||||||
#srcdir
|
|
||||||
src/
|
|
||||||
#outputdir
|
|
||||||
bin/
|
|
||||||
#projectname
|
|
||||||
MakeGen
|
|
||||||
#outputname
|
|
||||||
makegen
|
|
||||||
#executable
|
|
||||||
true
|
|
||||||
#generatehfile
|
|
||||||
false
|
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<makegen>
|
||||||
|
<configuration name="Release">
|
||||||
|
<define>_DEBUG</define>
|
||||||
|
<generatehfile>false</generatehfile>
|
||||||
|
<hfile>MakeGen.h</hfile>
|
||||||
|
<outputdir>bin/</outputdir>
|
||||||
|
<outputname>makegen</outputname>
|
||||||
|
<outputtype>executable</outputtype>
|
||||||
|
<projectname>MakeGen</projectname>
|
||||||
|
<srcdir>src/</srcdir>
|
||||||
|
</configuration>
|
||||||
|
<target>Release</target>
|
||||||
|
<version>v1.3.0</version>
|
||||||
|
</makegen>
|
||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
// Major changes, probably not be backwards compatible
|
// Major changes, probably not be backwards compatible
|
||||||
#define MAKEGEN_VERSION_MAJOR 1
|
#define MAKEGEN_VERSION_MAJOR 1
|
||||||
// Release, should be backwards compatible with any minor version
|
// Release, should be backwards compatible with any minor version
|
||||||
#define MAKEGEN_VERSION_RELEASE 2
|
#define MAKEGEN_VERSION_RELEASE 3
|
||||||
// Minor changes, should be compatible with any other minor version with same major and release.
|
// Minor changes, should be compatible with any other minor version with same major and release.
|
||||||
#define MAKEGEN_VERSION_MINOR 0
|
#define MAKEGEN_VERSION_MINOR 0
|
||||||
#define MAKEGEN_VERSION ("v" STR(MAKEGEN_VERSION_MAJOR) "." STR(MAKEGEN_VERSION_RELEASE) "." STR(MAKEGEN_VERSION_MINOR))
|
#define MAKEGEN_VERSION ("v" STR(MAKEGEN_VERSION_MAJOR) "." STR(MAKEGEN_VERSION_RELEASE) "." STR(MAKEGEN_VERSION_MINOR))
|
||||||
|
|||||||
+141
-172
@@ -1,17 +1,14 @@
|
|||||||
#include "ConfigFile.h"
|
#include "ConfigFile.h"
|
||||||
|
|
||||||
#include "FileUtils.h"
|
#include "FileUtils.h"
|
||||||
|
#include "compatibility/ConfigFileConf.h"
|
||||||
|
#include "xml/XML.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#define FLAG_NONE 0
|
|
||||||
#define FLAG_VECTOR 1
|
|
||||||
#define FLAG_STRING 2
|
|
||||||
#define FLAG_BOOL 3
|
|
||||||
|
|
||||||
ConfigFile::ConfigFile()
|
ConfigFile::ConfigFile()
|
||||||
: outputdir("bin/"), srcdir("src/"), outputname(""), projectname(FileUtils::GetCurrentDirectory()), hFile(""), executable(true), shared(true), generateHFile(false)
|
: outputdir("bin/"), srcdir("src/"), outputname(""), projectname(FileUtils::GetCurrentDirectory()), hFile(projectname+".h"), executable(true), shared(true), generateHFile(false)
|
||||||
{
|
{
|
||||||
// Converts project name (current directory) to lowercase
|
// Converts project name (current directory) to lowercase
|
||||||
// and replace whitespace with underscore
|
// and replace whitespace with underscore
|
||||||
@@ -54,27 +51,42 @@ std::optional<ConfigFile> ConfigFile::GetConfigFile(const std::string& filepath,
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool oldFile = false;
|
||||||
std::ifstream f(filepath + CONFIG_FILENAME);
|
std::ifstream f(filepath + CONFIG_FILENAME);
|
||||||
|
if(!f.good())
|
||||||
|
{
|
||||||
|
// try to read an old config file
|
||||||
|
f.close();
|
||||||
|
f = std::ifstream(filepath + "makegen.conf");
|
||||||
|
oldFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
if(f.good())
|
if(f.good())
|
||||||
{
|
{
|
||||||
f.close();
|
f.close();
|
||||||
ConfigFile conf = ConfigFile::Load(realPath);
|
std::optional<ConfigFile> conf;
|
||||||
loadedConfigs.emplace(realPath, conf);
|
if(oldFile)
|
||||||
|
conf = ConfigFileConf::Load(realPath);
|
||||||
|
else
|
||||||
|
conf = ConfigFile::Load(realPath);
|
||||||
|
if(!conf)
|
||||||
|
return {};
|
||||||
|
loadedConfigs.emplace(realPath, *conf);
|
||||||
|
|
||||||
// Create dependency config files.
|
// Create dependency config files.
|
||||||
for(size_t i = 0; i < conf.dependencies.size();++i)
|
for(size_t i = 0; i < conf->dependencies.size();++i)
|
||||||
{
|
{
|
||||||
std::optional<ConfigFile> dep = GetConfigFile(conf.configPath + conf.dependencies[i], loadedConfigs);
|
std::optional<ConfigFile> dep = GetConfigFile(conf->configPath + conf->dependencies[i], loadedConfigs);
|
||||||
if(dep)
|
if(dep)
|
||||||
{
|
{
|
||||||
conf.dependencyConfigs.push_back(*dep);
|
conf->dependencyConfigs.push_back(*dep);
|
||||||
conf.dependencies[i] = dep->configPath;
|
conf->dependencies[i] = dep->configPath;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Remove the dependency since it is already accounted for
|
// Remove the dependency since it is already accounted for
|
||||||
conf.dependencies.erase(conf.dependencies.begin() + i);
|
conf->dependencies.erase(conf->dependencies.begin() + i);
|
||||||
--i;
|
--i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,120 +96,99 @@ std::optional<ConfigFile> ConfigFile::GetConfigFile(const std::string& filepath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ConfigFile ConfigFile::Load(const std::string& filepath)
|
std::optional<ConfigFile> ConfigFile::Load(const std::string& filedir)
|
||||||
{
|
{
|
||||||
ConfigFile conf;
|
XMLObject object{XML::FromFile(filedir + CONFIG_FILENAME)};
|
||||||
conf.configPath = filepath;
|
|
||||||
unsigned int loadFlag = 0;
|
|
||||||
|
|
||||||
std::vector<std::string>* vec;
|
const std::string& target = object.GetObject("target", {XMLObject{"target", {}, "Release"}})[0].GetText();
|
||||||
std::string* s;
|
const std::vector<XMLObject>& configurations = object.GetObject("configuration");
|
||||||
bool* b;
|
const XMLObject* configuration = nullptr;
|
||||||
|
for(auto it = configurations.begin(); it != configurations.end(); ++it)
|
||||||
bool isDirectory = false;
|
|
||||||
|
|
||||||
std::ifstream file(filepath+CONFIG_FILENAME);
|
|
||||||
std::string line;
|
|
||||||
|
|
||||||
if(file.is_open())
|
|
||||||
{
|
{
|
||||||
// config name, { pointer to memory, isDirectory}
|
if(!it->HasAttribute("name"))
|
||||||
std::map<std::string, std::pair<std::string*, bool>> strings =
|
|
||||||
{
|
{
|
||||||
{"#srcdir", {&conf.srcdir, true}},
|
LOG_ERROR("No name attribute in configuration tag");
|
||||||
{"#outputdir", {&conf.outputdir, true}},
|
|
||||||
{"#outputname", {&conf.outputname, false}},
|
|
||||||
{"#projectname", {&conf.projectname, false}},
|
|
||||||
{"#hfile", {&conf.hFile, false}},
|
|
||||||
};
|
|
||||||
|
|
||||||
// config name, { pointer to memory, isDirectory}
|
|
||||||
std::map<std::string, std::pair<std::vector<std::string>*, 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<std::string, bool*> booleans =
|
|
||||||
{
|
|
||||||
{"#executable", &conf.executable},
|
|
||||||
{"#shared", &conf.shared},
|
|
||||||
{"#generatehfile", &conf.generateHFile},
|
|
||||||
};
|
|
||||||
|
|
||||||
while(std::getline(file,line))
|
|
||||||
{
|
|
||||||
if(line == "")
|
|
||||||
continue;
|
continue;
|
||||||
if(line[0]=='#')
|
}
|
||||||
|
if(it->GetAttribute("name") == target)
|
||||||
{
|
{
|
||||||
// The format is a bit wacky, but it is this way since we do not want
|
configuration = &(*it);
|
||||||
// to use map::find for all maps. This way we gain some optimization.
|
break;
|
||||||
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";
|
|
||||||
|
|
||||||
|
if(configuration == nullptr)
|
||||||
|
{
|
||||||
|
LOG_ERROR("No configuration matching target: ", target);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFile conf;
|
||||||
|
conf.configPath = filedir;
|
||||||
|
conf.projectname = configuration->GetObject("projectname",
|
||||||
|
{XMLObject{"projectname", {}, conf.projectname}})[0].GetText();
|
||||||
|
conf.outputname = configuration->GetObject("outputname",
|
||||||
|
{XMLObject{"outputname", {}, conf.outputname}})[0].GetText();
|
||||||
|
conf.srcdir = configuration->GetObject("srcdir",
|
||||||
|
{XMLObject{"srcdir", {}, conf.srcdir}})[0].GetText();
|
||||||
|
conf.outputdir = configuration->GetObject("outputdir",
|
||||||
|
{XMLObject{"outputdir", {}, conf.outputdir}})[0].GetText();
|
||||||
|
conf.hFile = configuration->GetObject("hfile",
|
||||||
|
{XMLObject{"hfile", {}, conf.hFile}})[0].GetText();
|
||||||
|
std::string outputtype = configuration->GetObject("outputtype",
|
||||||
|
{XMLObject{"outputtype", {}, "executable"}})[0].GetText();
|
||||||
|
|
||||||
|
if(conf.srcdir[conf.srcdir.size()-1] != '/')
|
||||||
|
conf.srcdir += '/';
|
||||||
|
if(conf.outputdir[conf.srcdir.size()-1] != '/')
|
||||||
|
conf.outputdir += '/';
|
||||||
|
|
||||||
|
if(outputtype == "executable")
|
||||||
|
conf.executable = true;
|
||||||
|
else if(outputtype == "staticlibrary")
|
||||||
|
{
|
||||||
|
conf.executable = false;
|
||||||
|
conf.shared = false;
|
||||||
|
}
|
||||||
|
else if(outputtype == "sharedlibrary")
|
||||||
|
{
|
||||||
|
conf.executable = false;
|
||||||
|
conf.shared = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_ERROR("Invalid outputtype: ", outputtype);
|
||||||
|
LOG_ERROR("Valid arguments are executable, staticlibrary and sharedlibrary");
|
||||||
|
}
|
||||||
|
conf.generateHFile = configuration->GetObject("generatehfile",
|
||||||
|
{XMLObject{"generatehfile", {}, conf.generateHFile ? "true" : "false"}})[0].GetText() == "true";
|
||||||
|
|
||||||
|
const int vectorCount = 6;
|
||||||
|
std::tuple<std::vector<XMLObject>, std::vector<std::string>*, bool> vectors[vectorCount] = {
|
||||||
|
{configuration->GetObject("library"), &conf.libs, false},
|
||||||
|
{configuration->GetObject("libdir"), &conf.libdirs, true},
|
||||||
|
{configuration->GetObject("includedir"), &conf.includedirs, true},
|
||||||
|
{configuration->GetObject("define"), &conf.defines, false},
|
||||||
|
{configuration->GetObject("compileflag"), &conf.flags, false},
|
||||||
|
{configuration->GetObject("dependency"), &conf.dependencies, true}
|
||||||
|
};
|
||||||
|
|
||||||
|
for(int i = 0;i<vectorCount;++i)
|
||||||
|
{
|
||||||
|
const std::vector<XMLObject>& xmls = std::get<0>(vectors[i]);
|
||||||
|
std::vector<std::string>* vec = std::get<1>(vectors[i]);
|
||||||
|
bool isDir = std::get<2>(vectors[i]);
|
||||||
|
for(auto it = xmls.begin(); it != xmls.end();++it)
|
||||||
|
{
|
||||||
|
if(it->GetText() != "")
|
||||||
|
{
|
||||||
|
std::string s = it->GetText();
|
||||||
|
if(isDir && s[s.size()-1] != '/')
|
||||||
|
s += '/';
|
||||||
|
vec->push_back(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,58 +270,36 @@ ConfigFile ConfigFile::Gen()
|
|||||||
|
|
||||||
void ConfigFile::Save() const
|
void ConfigFile::Save() const
|
||||||
{
|
{
|
||||||
std::ofstream file("makegen.conf");
|
XMLObject makegen("makegen", {}, std::map<std::string, std::vector<XMLObject>>{});
|
||||||
file << "#libs" << std::endl;
|
|
||||||
for(auto it = libs.begin();it!=libs.end();++it)
|
// Version, target and configuration is probably going to be used in the future
|
||||||
{
|
makegen.AddXMLObject(XMLObject("version", {}, "v1.3.0"));
|
||||||
file << *it << std::endl;
|
makegen.AddXMLObject(XMLObject("target", {}, "Release"));
|
||||||
}
|
|
||||||
file << "#libdirs" << std::endl;
|
XMLObject configuration("configuration", {{"name", "Release"}}, std::map<std::string, std::vector<XMLObject>>{});
|
||||||
for(auto it = libdirs.begin();it!=libdirs.end();++it)
|
configuration.AddXMLObject(XMLObject("projectname", {}, projectname));
|
||||||
{
|
configuration.AddXMLObject(XMLObject("outputname", {}, outputname));
|
||||||
file << *it << std::endl;
|
configuration.AddXMLObject(XMLObject("srcdir", {}, srcdir));
|
||||||
}
|
configuration.AddXMLObject(XMLObject("outputdir", {}, outputdir));
|
||||||
file << "#includedirs" << std::endl;
|
configuration.AddXMLObject(XMLObject("hfile", {}, hFile));
|
||||||
for(auto it = includedirs.begin();it!=includedirs.end();++it)
|
configuration.AddXMLObject(XMLObject("outputtype", {},
|
||||||
{
|
executable ? "executable" : (shared ? "sharedlibrary" : "staticlibrary")));
|
||||||
file << *it << std::endl;
|
configuration.AddXMLObject(XMLObject("generatehfile", {}, generateHFile ? "true" : "false"));
|
||||||
}
|
|
||||||
file << "#defines" << std::endl;
|
for(auto it = libs.begin();it != libs.end(); ++it)
|
||||||
for(auto it = defines.begin();it!=defines.end();++it)
|
configuration.AddXMLObject({"library",{},*it});
|
||||||
{
|
for(auto it = libdirs.begin();it != libdirs.end(); ++it)
|
||||||
file << *it << std::endl;
|
configuration.AddXMLObject({"libdir",{},*it});
|
||||||
}
|
for(auto it = includedirs.begin();it != includedirs.end(); ++it)
|
||||||
file << "#compileflags" << std::endl;
|
configuration.AddXMLObject({"includedir",{},*it});
|
||||||
for(auto it = flags.begin();it!=flags.end();++it)
|
for(auto it = defines.begin();it != defines.end(); ++it)
|
||||||
{
|
configuration.AddXMLObject({"define",{},*it});
|
||||||
file << *it << std::endl;
|
for(auto it = flags.begin();it != flags.end(); ++it)
|
||||||
}
|
configuration.AddXMLObject({"compileflag",{},*it});
|
||||||
file << "#dependencies" << std::endl;
|
for(auto it = dependencies.begin();it != dependencies.end(); ++it)
|
||||||
for(auto it = dependencies.begin();it!=dependencies.end();++it)
|
configuration.AddXMLObject({"dependency",{},*it});
|
||||||
{
|
|
||||||
file << *it << std::endl;
|
makegen.AddXMLObject(configuration);
|
||||||
}
|
std::ofstream file("makegen.xml");
|
||||||
file << "#srcdir" << std::endl;
|
file << makegen;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -1,16 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "xml/XMLObject.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
static const std::string CONFIG_FILENAME = "makegen.conf";
|
static const std::string CONFIG_FILENAME = "makegen.xml";
|
||||||
|
|
||||||
class ConfigFile
|
class ConfigFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string configPath;
|
std::string configPath;
|
||||||
|
|
||||||
std::vector<std::string> libs;
|
std::vector<std::string> libs;
|
||||||
std::vector<std::string> libdirs;
|
std::vector<std::string> libdirs;
|
||||||
std::vector<std::string> includedirs;
|
std::vector<std::string> includedirs;
|
||||||
@@ -26,6 +29,7 @@ class ConfigFile
|
|||||||
bool executable;
|
bool executable;
|
||||||
bool shared;
|
bool shared;
|
||||||
bool generateHFile;
|
bool generateHFile;
|
||||||
|
|
||||||
std::vector<ConfigFile> dependencyConfigs;
|
std::vector<ConfigFile> dependencyConfigs;
|
||||||
public:
|
public:
|
||||||
ConfigFile();
|
ConfigFile();
|
||||||
@@ -34,7 +38,7 @@ class ConfigFile
|
|||||||
static std::optional<ConfigFile> GetConfigFile(const std::string& filepath = "./");
|
static std::optional<ConfigFile> GetConfigFile(const std::string& filepath = "./");
|
||||||
private:
|
private:
|
||||||
static std::optional<ConfigFile> GetConfigFile(const std::string& filepath, std::map<std::string, ConfigFile>& loadedConfigs);
|
static std::optional<ConfigFile> GetConfigFile(const std::string& filepath, std::map<std::string, ConfigFile>& loadedConfigs);
|
||||||
static ConfigFile Load(const std::string& filename);
|
static std::optional<ConfigFile> Load(const std::string& filename);
|
||||||
static void InputBoolean(const std::string& inputText, bool& b);
|
static void InputBoolean(const std::string& inputText, bool& b);
|
||||||
static void InputMultiple(const std::string& inputText, std::vector<std::string>& vec, bool needEnding);
|
static void InputMultiple(const std::string& inputText, std::vector<std::string>& vec, bool needEnding);
|
||||||
static void InputString(const std::string& inputText, std::string& vec, bool needEnding, bool allowEmpty);
|
static void InputString(const std::string& inputText, std::string& vec, bool needEnding, bool allowEmpty);
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
#include "ConfigFileConf.h"
|
||||||
|
|
||||||
|
const std::string CONFIG_FILENAME_CONF = "makegen.conf";
|
||||||
|
|
||||||
|
#include "../ConfigFile.h"
|
||||||
|
#include "../FileUtils.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#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";
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFile ConfigFileConf::Load(const std::string& filepath)
|
||||||
|
{
|
||||||
|
ConfigFile conf;
|
||||||
|
conf.configPath = filepath;
|
||||||
|
unsigned int loadFlag = 0;
|
||||||
|
|
||||||
|
std::vector<std::string>* 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<std::string, std::pair<std::string*, bool>> 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<std::string, std::pair<std::vector<std::string>*, 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<std::string, bool*> 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";
|
||||||
|
|
||||||
|
LOG_INFO("------ COULDN\'T FIND makegen.xml. BUT FOUND OLD makegen.conf.");
|
||||||
|
LOG_INFO("------ GENERATING NEW CONFIGURATION FILE");
|
||||||
|
|
||||||
|
conf.Save();
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ConfigFile;
|
||||||
|
|
||||||
|
class ConfigFileConf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string configPath;
|
||||||
|
std::vector<std::string> libs;
|
||||||
|
std::vector<std::string> libdirs;
|
||||||
|
std::vector<std::string> includedirs;
|
||||||
|
std::vector<std::string> defines;
|
||||||
|
std::vector<std::string> flags;
|
||||||
|
std::vector<std::string> 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 ConfigFile Load(const std::string& filename);
|
||||||
|
private:
|
||||||
|
};
|
||||||
+59
-9
@@ -34,7 +34,7 @@ XMLObject::XMLObject(const std::string& name, const std::map<std::string, std::s
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLObject::XMLObject(const std::string& name, const std::map<std::string, std::string>& attributes, const std::vector<XMLObject>& objects)
|
XMLObject::XMLObject(const std::string& name, const std::map<std::string, std::string>& attributes, const std::map<std::string,std::vector<XMLObject>>& objects)
|
||||||
: name(name), attributes(attributes), objects(objects)
|
: name(name), attributes(attributes), objects(objects)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -66,15 +66,16 @@ unsigned int XMLObject::GetObjectCount() const
|
|||||||
return objects.size();
|
return objects.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
const XMLObject& XMLObject::GetObject(unsigned int i) const
|
const std::vector<XMLObject>& XMLObject::GetObject(const std::string& name, const std::vector<XMLObject>& defaults) const
|
||||||
{
|
{
|
||||||
if (i >= objects.size())
|
auto it = objects.find(name);
|
||||||
throw XMLException((std::string("XML index out of bounds \"") + std::to_string(i) + "\".").c_str());
|
if(it == objects.end())
|
||||||
|
return defaults;
|
||||||
|
|
||||||
return objects[i];
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<XMLObject>& XMLObject::GetObjects() const
|
const std::map<std::string, std::vector<XMLObject>>& XMLObject::GetObjects() const
|
||||||
{
|
{
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
@@ -108,7 +109,14 @@ void XMLObject::AddAttribute(const std::string& property, const std::string& val
|
|||||||
attributes.emplace(property, value);
|
attributes.emplace(property, value);
|
||||||
else
|
else
|
||||||
LOG_ERROR("XML property name can only be made up of letters");
|
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<XMLObject>{object});
|
||||||
|
else
|
||||||
|
it->second.push_back(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLObject XMLObject::GetStrippedXMLObject() const
|
XMLObject XMLObject::GetStrippedXMLObject() const
|
||||||
@@ -234,8 +242,7 @@ void XMLObject::ReadBodyTail(const std::string& string, XMLLoadData& data)
|
|||||||
std::string closeTag = GetClosingTag(string, data);
|
std::string closeTag = GetClosingTag(string, data);
|
||||||
while (closeTag.length() == 0)
|
while (closeTag.length() == 0)
|
||||||
{
|
{
|
||||||
XMLObject object = XMLObject(string, data);
|
AddXMLObject(XMLObject(string, data));
|
||||||
objects.push_back(object);
|
|
||||||
ReadWhiteSpace(string, data);
|
ReadWhiteSpace(string, data);
|
||||||
closeTag = GetClosingTag(string, data);
|
closeTag = GetClosingTag(string, data);
|
||||||
}
|
}
|
||||||
@@ -333,3 +340,46 @@ std::string XMLObject::ReadXMLName(const std::string& string, XMLLoadData& data)
|
|||||||
endPos++;
|
endPos++;
|
||||||
return string.substr(data.pos, endPos - data.pos);
|
return string.substr(data.pos, endPos - data.pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream& XMLObject::WriteToStream(std::ostream& stream, int indent) const
|
||||||
|
{
|
||||||
|
for(int i = 0;i<indent;i++)
|
||||||
|
{
|
||||||
|
stream << " ";
|
||||||
|
}
|
||||||
|
stream << "<" << name;
|
||||||
|
for(auto it = attributes.begin();it!=attributes.end();++it)
|
||||||
|
{
|
||||||
|
stream << " " << it->first << "=\"" << 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<indent;i++)
|
||||||
|
{
|
||||||
|
stream << "\t";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream << "</" << name << ">";
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-4
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class XMLObject
|
class XMLObject
|
||||||
@@ -19,7 +20,7 @@ class XMLObject
|
|||||||
std::string text;
|
std::string text;
|
||||||
|
|
||||||
std::map<std::string, std::string> attributes;
|
std::map<std::string, std::string> attributes;
|
||||||
std::vector<XMLObject> objects;
|
std::map<std::string, std::vector<XMLObject>> objects;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
XMLObject() {}
|
XMLObject() {}
|
||||||
@@ -27,15 +28,15 @@ class XMLObject
|
|||||||
XMLObject(const std::string& string, int pos, int line, const std::string& file);
|
XMLObject(const std::string& string, int pos, int line, const std::string& file);
|
||||||
XMLObject(const std::string& string, XMLLoadData& data);
|
XMLObject(const std::string& string, XMLLoadData& data);
|
||||||
XMLObject(const std::string& name, const std::map<std::string, std::string>& properties, const std::string& text);
|
XMLObject(const std::string& name, const std::map<std::string, std::string>& properties, const std::string& text);
|
||||||
XMLObject(const std::string& name, const std::map<std::string, std::string>& properties, const std::vector<XMLObject>& objects);
|
XMLObject(const std::string& name, const std::map<std::string, std::string>& properties, const std::map<std::string, std::vector<XMLObject>>& objects);
|
||||||
|
|
||||||
bool HasAttribute(const std::string& property) const;
|
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;
|
||||||
const std::string& GetAttribute(const std::string& property, const std::string& defaultValue) const;
|
const std::string& GetAttribute(const std::string& property, const std::string& defaultValue) const;
|
||||||
|
|
||||||
unsigned int GetObjectCount() const;
|
unsigned int GetObjectCount() const;
|
||||||
const XMLObject& GetObject(unsigned int i) const;
|
const std::vector<XMLObject>& GetObject(const std::string& name, const std::vector<XMLObject>& defaults = {}) const;
|
||||||
const std::vector<XMLObject>& GetObjects() const;
|
const std::map<std::string, std::vector<XMLObject>>& GetObjects() const;
|
||||||
const std::string& GetName() const;
|
const std::string& GetName() const;
|
||||||
const std::string& GetText() const;
|
const std::string& GetText() const;
|
||||||
XMLObject GetStrippedXMLObject() const;
|
XMLObject GetStrippedXMLObject() const;
|
||||||
@@ -43,6 +44,18 @@ class XMLObject
|
|||||||
void SetName(const std::string& name);
|
void SetName(const std::string& name);
|
||||||
void SetText(const std::string& text);
|
void SetText(const std::string& text);
|
||||||
void AddAttribute(const std::string& property, const std::string& value);
|
void AddAttribute(const std::string& property, const std::string& value);
|
||||||
|
void AddXMLObject(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);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string GetClosingTag(const std::string& string, XMLLoadData& data);
|
std::string GetClosingTag(const std::string& string, XMLLoadData& data);
|
||||||
|
|||||||
Reference in New Issue
Block a user