-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
General handling of config files. Allows saving and loading lists of data of various types. Simple with no nesting etc.
- Loading branch information
Showing
2 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#ifndef _KMICKI_CONFIG_CONFIGFILE_H_ | ||
#define _KMICKI_CONFIG_CONFIGFILE_H_ | ||
|
||
#include <string> | ||
#include <vector> | ||
#include <memory> | ||
|
||
namespace kmicki::config | ||
{ | ||
class ConfigFile | ||
{ | ||
public: | ||
|
||
struct ConfigItemBase | ||
{ | ||
std::string Name; | ||
virtual bool Update(std::string const& value,std::string & message) = 0; | ||
virtual std::string ValToString() = 0; | ||
}; | ||
|
||
template<class T> | ||
struct ConfigItem : public ConfigItemBase | ||
{ | ||
ConfigItem() = delete; | ||
ConfigItem(std::string const& name, T const& val); | ||
T Val; //value | ||
bool Update(std::string const& value,std::string & message) override; | ||
std::string ValToString() override; | ||
}; | ||
|
||
ConfigFile() = delete; | ||
ConfigFile(std::string const& _filePath); | ||
|
||
bool LoadConfig(std::vector<std::unique_ptr<ConfigItemBase>> & configuration); | ||
bool SaveConfig(std::vector<std::unique_ptr<ConfigItemBase>> const& configuration, bool pretty = true); | ||
|
||
private: | ||
static const char cSeparator; | ||
std::string filePath; | ||
}; | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
#include "config/configfile.h" | ||
#include "log/log.h" | ||
|
||
#include <fstream> | ||
#include <algorithm> | ||
#include <iomanip> | ||
|
||
using namespace kmicki::log; | ||
|
||
namespace kmicki::config | ||
{ | ||
template<class T> | ||
ConfigFile::ConfigItem<T>::ConfigItem(std::string const& name, T const& val) | ||
{ | ||
Name = name; | ||
Val = val; | ||
} | ||
|
||
template<> | ||
bool ConfigFile::ConfigItem<std::string>::Update(std::string const& value,std::string & message) | ||
{ | ||
Val = value; | ||
message = static_cast<std::ostringstream&>(std::ostringstream() << "existing item value updated (type: string), Name=" << Name << " Value=" << value).str(); | ||
return true; | ||
} | ||
|
||
template<> | ||
bool ConfigFile::ConfigItem<int>::Update(std::string const& value,std::string & message) | ||
{ | ||
int val; | ||
try | ||
{ | ||
val = std::stoi(value); | ||
} | ||
catch(const std::invalid_argument& e) | ||
{ | ||
message = static_cast<std::ostringstream&>(std::ostringstream() << "invalid type (should be int), Name=" << Name << " Value=" << value).str(); | ||
return false; | ||
} | ||
catch(const std::out_of_range& e) | ||
{ | ||
message = static_cast<std::ostringstream&>(std::ostringstream() << "value out of range (type: int), Name=" << Name << " Value=" << value).str(); | ||
return false; | ||
} | ||
|
||
message = static_cast<std::ostringstream&>(std::ostringstream() << "existing item value updated (type: int), Name=" << Name << " Value=" << val).str(); | ||
Val = val; | ||
return true; | ||
} | ||
|
||
template<> | ||
bool ConfigFile::ConfigItem<bool>::Update(std::string const& value,std::string & message) | ||
{ | ||
std::string strVal(value.length(),' '); | ||
std::transform(value.begin(),value.end(),strVal.begin(),[](char c){ return std::tolower(c); }); | ||
bool val; | ||
if(value == "true" || value == "1" || value == "yes" || value == "t" || value == "y") | ||
val = true; | ||
else if(value == "false" || value == "0" || value == "no" || value == "f" || value == "n") | ||
val = false; | ||
else | ||
{ | ||
message = static_cast<std::ostringstream&>(std::ostringstream() << "invalid type (should be bool), Name=" << Name << " Value=" << value).str(); | ||
return false; | ||
} | ||
|
||
message = static_cast<std::ostringstream&>(std::ostringstream() << "existing item value updated (type: bool), Name=" << Name << " Value=" << val).str(); | ||
Val = val; | ||
return true; | ||
} | ||
|
||
template<> | ||
std::string ConfigFile::ConfigItem<std::string>::ValToString() | ||
{ | ||
return Val; | ||
} | ||
|
||
template<> | ||
std::string ConfigFile::ConfigItem<int>::ValToString() | ||
{ | ||
return std::to_string(Val); | ||
} | ||
|
||
template<> | ||
std::string ConfigFile::ConfigItem<bool>::ValToString() | ||
{ | ||
return Val?"true":"false"; | ||
} | ||
|
||
template struct ConfigFile::ConfigItem<std::string>; | ||
template struct ConfigFile::ConfigItem<int>; | ||
template struct ConfigFile::ConfigItem<bool>; | ||
|
||
ConfigFile::ConfigFile(std::string const& _filePath) | ||
{ | ||
filePath = _filePath; | ||
} | ||
|
||
const char ConfigFile::cSeparator = ':'; | ||
|
||
bool ConfigFile::LoadConfig(std::vector<std::unique_ptr<ConfigItemBase>> & configuration) | ||
{ | ||
static const int cBufLen = 500; | ||
|
||
{ LogF(LogLevelTrace) << "ConfigFile::LoadConfig: Loading configuration from file: " << filePath; } | ||
std::ifstream file(filePath); | ||
if(file.fail()) | ||
{ | ||
Log("ConfigFile::LoadConfig: File open failed.",LogLevelDebug); | ||
return false; | ||
} | ||
|
||
char buf[cBufLen]; | ||
|
||
int line = 0; | ||
|
||
while(!file.eof()) | ||
{ | ||
++line; | ||
file.getline(buf,cBufLen); | ||
std::string str(buf); | ||
|
||
// remove white space | ||
auto c = str.begin(); | ||
while(c != str.end()) | ||
if(std::isspace(*c)) | ||
c = str.erase(c); | ||
else | ||
++c; | ||
|
||
if(str.empty()) | ||
{ | ||
{ LogF(LogLevelTrace) << "ConfigFile::LoadConfig: Line " << line << " - empty"; } | ||
continue; | ||
} | ||
|
||
auto pos = str.find(cSeparator); | ||
if(pos == std::string::npos) | ||
{ | ||
{ LogF(LogLevelDebug) << "ConfigFile::LoadConfig: Line " << line << " - invalid, no separator found (" << cSeparator << ")"; } | ||
continue; | ||
} | ||
|
||
if(pos == 0) | ||
{ | ||
{ LogF(LogLevelDebug) << "ConfigFile::LoadConfig: Line " << line << " - invalid, no name found"; } | ||
continue; | ||
} | ||
|
||
auto name = str.substr(0,pos); | ||
auto item = std::find_if(configuration.begin(),configuration.end(), | ||
[&](std::unique_ptr<ConfigItemBase> & x) | ||
{ | ||
return x->Name == name; | ||
} | ||
); | ||
|
||
std::string value(""); | ||
if(pos < str.length()-1) | ||
value = str.substr(pos+1); | ||
|
||
if(item == configuration.end()) | ||
{ | ||
{ LogF(LogLevelTrace) << "ConfigFile::LoadConfig: Line " << line << " - new item, Name=" << name << " Value=" << value; } | ||
configuration.emplace_back(new ConfigItem<std::string>(name,value)); | ||
continue; | ||
} | ||
|
||
std::string msg; | ||
if((*item)->Update(value,msg)) | ||
{ LogF(LogLevelTrace) << "ConfigFile::LoadConfig: Line " << line << " - " << msg; } | ||
else | ||
{ LogF(LogLevelDebug) << "ConfigFile::LoadConfig: Line " << line << " - " << msg; } | ||
} | ||
|
||
Log("ConfigFile::LoadConfig: Configuration loaded.",LogLevelDebug); | ||
return true; | ||
} | ||
|
||
bool ConfigFile::SaveConfig(std::vector<std::unique_ptr<ConfigItemBase>> const& configuration,bool pretty) | ||
{ | ||
{ LogF(LogLevelTrace) << "ConfigFile::SaveConfig: Saving configuration to file: " << filePath; } | ||
std::ofstream file(filePath,std::ios_base::trunc); | ||
if(file.fail()) | ||
{ | ||
Log("ConfigFile::SaveConfig: File open failed.",LogLevelDebug); | ||
return false; | ||
} | ||
|
||
int max = 0; | ||
|
||
if(pretty) | ||
{ | ||
// Find longest name | ||
for(auto& item : configuration) | ||
if(item->Name.length() > max) | ||
max = item->Name.length(); | ||
{ LogF(LogLevelTrace) << "ConfigFile::SaveConfig: Pretty layout enabled. Found maximum name length: " << max; } | ||
} | ||
|
||
int line = 0; | ||
for(auto& item : configuration) | ||
{ | ||
std::string value = item->ValToString(); | ||
file << std::setw(max) << std::left << item->Name << ' ' << cSeparator << ' ' << value << std::endl; | ||
{ LogF(LogLevelTrace) << "ConfigFile::SaveConfig: Line " << ++line << " - saved item, Name=" << item->Name << " Value=" << value; } | ||
} | ||
|
||
Log("ConfigFile::SaveConfig: Configuration saved.",LogLevelDebug); | ||
|
||
return true; | ||
} | ||
} |