Skip to content

Commit

Permalink
[#5] ConfigFile implementation
Browse files Browse the repository at this point in the history
General handling of config files.
Allows saving and loading lists of data of various types.
Simple with no nesting etc.
  • Loading branch information
kmicki committed Jun 5, 2022
1 parent d40da4b commit 7d719b5
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 0 deletions.
43 changes: 43 additions & 0 deletions inc/config/configfile.h
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
213 changes: 213 additions & 0 deletions src/config/configfile.cpp
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;
}
}

0 comments on commit 7d719b5

Please sign in to comment.