diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f925daa97dabfd7bbc0990665014856e677864f..4b632f04cebdae375592bdf19a2f72dd74f82564 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,7 +8,7 @@ include(FindThreads) #---------------------------------------------------------------------------- # Sources and headers -file(GLOB src "*.cxx") +file(GLOB src "*.cxx" "*.cpp") file(GLOB allheaders RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.h") list(REMOVE_ITEM allheaders "${CMAKE_CURRENT_SOURCE_DIR}/${LIBNAME}_LinkDef.h" diff --git a/src/ConfigObject.cpp b/src/ConfigObject.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ee18cd36afcc2b782b33a642e35618379256f7c6 --- /dev/null +++ b/src/ConfigObject.cpp @@ -0,0 +1,332 @@ +//============================================================================// +// A class based on the support from ConfigParser and ConfigValue // +// It provides a simple way to read text file as configuration file, and read // +// or modify a parameter in the inherited class // +// The Configure() function should be overloaded according to specialized // +// requirements, and be called after the parameters being configured // +// // +// Chao Peng // +// 10/31/2016 // +//============================================================================// + +#include <fstream> +#include <iostream> +#include <iomanip> +#include <algorithm> +#include "ConfigObject.h" + + + +//============================================================================// +// Constructor, Destructor // +//============================================================================// + +// constructor +ConfigObject::ConfigObject(const std::string &splitter, const std::string &ignore, bool case_ins) +: split_chars(splitter), ignore_chars(ignore), case_insensitive(case_ins), __empty_value("") +{ + // set default replace bracket + replace_pair = std::make_pair("{", "}"); +} + +// destructor +ConfigObject::~ConfigObject() +{ + // place holder +} + + +//============================================================================// +// Public Member Function // +//============================================================================// + +// configure the cluster method +void ConfigObject::Configure(const std::string &path) +{ + // save the path + config_path = path; + + // clear the map + config_map.clear(); + + // read configuration file in + ReadConfigFile(path); +} + +// clear all the loaded configuration values +void ConfigObject::ClearConfig() +{ + config_path = ""; + config_map.clear(); +} + +// read configuration file and build the configuration map +bool ConfigObject::ReadConfigFile(const std::string &path) +{ + ConfigParser c_parser; + c_parser.SetSplitters(split_chars); // self-defined splitters + + if (c_parser.ReadFile(path)) { + // current directory + parserProcess(c_parser, path); + return true; + } else { + std::cerr << "Cannot open configuration file " + << "\"" << path << "\"" + << std::endl; + return false; + } +} + +// read the configuration string directly +void ConfigObject::ReadConfigString(const std::string &content) +{ + ConfigParser c_parser; + c_parser.SetSplitters(split_chars); + + c_parser.ReadBuffer(content.c_str()); + + parserProcess(c_parser, "buffer_string"); +} + +// continue parse the terms +void ConfigObject::parserProcess(ConfigParser &c_parser, const std::string &source) +{ + std::string cur_dir = ConfigParser::decompose_path(source).dir; + + while (c_parser.ParseLine()) { + // possible control words + if (c_parser.NbofElements() == 1) { + std::string control = c_parser.TakeFirst(); + size_t pos = control.find("{THIS_DIR}"); + if(pos != std::string::npos) + control.replace(pos, 10, cur_dir); + parseControl(control); + // var_name and var_value + } else if (c_parser.NbofElements() == 2) { + std::string var_name, key, var_value; + c_parser >> var_name >> var_value; + size_t pos = var_value.find("{THIS_DIR}"); + if(pos != std::string::npos) + var_value.replace(pos, 10, cur_dir); + parseTerm(std::move(var_name), std::move(var_value)); + // unsupported format + } else { + std::cout << "Warning: Unsupported format at line " + << c_parser.LineNumber() << " from " << source << "\n" + << "\"" << c_parser.CurrentLine() << "\"" + << std::endl; + } + } +} + +// check if a certain term is configured +bool ConfigObject::HasKey(const std::string &var_name) +const +{ + auto key = formKey(var_name); + + if (config_map.find(key) != config_map.end()) + return true; + return false; +} + +// list all the existing configuration keys +void ConfigObject::ListKeys() +const +{ + for (auto &it : config_map) { + std::cout << it.first << std::endl; + } +} + +// get all the existing configuration keys +std::vector<std::string> ConfigObject::GetKeyList() +const +{ + std::vector<std::string> res; + + for (auto &it : config_map) { + res.push_back(it.first); + } + + return res; +} + +// save current configuration into a file +void ConfigObject::SaveConfig(const std::string &path) +const +{ + std::string save_path; + + if (path.empty()) + save_path = config_path; + if(save_path.empty()) + save_path = "latest.conf"; + + std::ofstream save(save_path); + for (auto &it : config_map) { + save << it.first + << " " << split_chars.front() << " " + << it.second + << std::endl; + } +} + +// get configuration value by its name/key +ConfigValue ConfigObject::GetConfigValue(const std::string &var_name) +const +{ + // convert to lower case and remove uninterested characters + auto key = formKey(var_name); + + auto it = config_map.find(key); + if (it == config_map.end()) { + return __empty_value; + } else { + ConfigValue result(it->second); + reform(result._value, replace_pair.first, replace_pair.second); + return result; + } +} + +// set configuration value by its name/key +void ConfigObject::SetConfigValue(const std::string &var_name, const ConfigValue &c_value) +{ + // convert to lower case and remove uninterested characters + auto key = formKey(var_name); + + config_map[key] = c_value; +} + + +// get configuration value from the map +// if no such config value exists, it will fill the default value in +ConfigValue ConfigObject::GetConfigValue(const std::string &var_name, const ConfigValue &def_value, bool verbose) +{ + auto key = formKey(var_name); + + auto it = config_map.find(key); + if (it == config_map.end()) { + if (def_value.IsEmpty()) + return __empty_value; + + if (verbose) { + std::cout << var_name << " (key: " << key << ")" + << " not defined in configuration file, set to default value " + << def_value + << std::endl; + } + config_map[key] = def_value; + return def_value; + } + + ConfigValue result(it->second); + reform(result._value, replace_pair.first, replace_pair.second); + return result; +} + + + +//============================================================================// +// Protected Member Function // +//============================================================================// + +// build the key +std::string ConfigObject::formKey(const std::string &rawKey) +const +{ + std::string key = ConfigParser::str_remove(rawKey, ignore_chars); + if (case_insensitive) { + key = ConfigParser::str_lower(key); + } + return key; +} + +// replace the contents inside replace_pair with the configuration value +void ConfigObject::reform(std::string &input, const std::string &op, const std::string &cl) +const +{ + // loop until no pair found + while (true) { + auto rpair = ConfigParser::find_pair(input, op, cl); + if (rpair.first != std::string::npos && rpair.second != std::string::npos) { + // get content inside the bracket + std::string var = input.substr(rpair.first + op.size(), + rpair.second - op.size() - rpair.first); + // deal with nested structure + reform(var, op, cl); + + // replace content + std::string val; + + // environment variable + if (rpair.first > 0 && input.at(rpair.first - 1) == '$') { + val = std::getenv(var.c_str()); + // replace $ mark also + rpair.first--; + // ConfigObject variable + } else { + val = GetConfigValue(var)._value; + } + + // replace variable with configuration value + input.replace(rpair.first, rpair.second - rpair.first + cl.size(), val); + } else { + // no pair found any more + return; + } + } +} + + + + +//============================================================================// +// Private Member Function // +//============================================================================// + +// parse the control word and respond +void ConfigObject::parseControl(const std::string &word) +{ + if (ConfigParser::str_upper(word.substr(0, 7)) == "INCLUDE") { + // need the most outer pair + auto p = ConfigParser::find_pair(word, "(", ")"); + // not find pair + if (p.first == std::string::npos || p.second == std::string::npos) { + std::cout << "Unsupported control word format: " << word << "." + << "Expected: INCLUDE(path)" + << std::endl; + return; + } + + int begin = p.first + 1; + int length = p.second - begin; + + std::string new_path = word.substr(begin, length); + reform(new_path, replace_pair.first, replace_pair.second); + + ReadConfigFile(new_path); + } + else { + std::cout << "Unsupported control word: " << word << std::endl; + } +} + +// parse the configuration term +void ConfigObject::parseTerm(std::string &&var_name, std::string &&var_value) +{ + // convert to lower case and remove uninterested characters + auto key = formKey(var_name); + + if (key.back() == '+') { + key.pop_back(); + auto it = config_map.find(key); + if (it != config_map.end()) + it->second += var_value; + else + config_map[key] = var_value; + } else { + config_map[key] = var_value; + } +} diff --git a/src/ConfigObject.h b/src/ConfigObject.h new file mode 100644 index 0000000000000000000000000000000000000000..aa3bd1c82e8582cd994dca214d910b6edaf9076c --- /dev/null +++ b/src/ConfigObject.h @@ -0,0 +1,89 @@ +#ifndef CONFIG_OBJECT_H +#define CONFIG_OBJECT_H + +#include <string> +#include <utility> +#include <vector> +#include <unordered_map> +#include "ConfigParser.h" + + +#define CONF_CONN(val, str, def, warn) val=GetConfigValue<decltype(val)>(str, def, warn) +#define CONF_CONN2(val, def, warn) val=GetConfiValue<decltype(val)>(str, def, warn) +#define GET_CONF(obj, val, str, def, warn) val=obj.GetConfiValue<decltype(val)>(str, def, warn) +#define GET_CONF2(obj, val, def, warn) val=GetConfiValue<decltype(val)>(#val, def, warn) + + +class ConfigObject +{ +public: + // constructor, desctructor + ConfigObject(const std::string &spliiter = ":=", const std::string &ignore = " _\t", bool case_ins = true); + + virtual ~ConfigObject(); + + // public member functions + void ClearConfig(); + void SaveConfig(const std::string &path = "") const; + void ListKeys() const; + bool HasKey(const std::string &name) const; + + bool ReadConfigFile(const std::string &path); + void ReadConfigString(const std::string &content); + void SetConfigValue(const std::string &var_name, const ConfigValue &c_value); + void SetIgnoreChars(const std::string &ignore) {ignore_chars = ignore;} + void SetSplitChars(const std::string &splitter) {split_chars = splitter;} + void SetReplacePair(const std::string &open, const std::string &close) + { + replace_pair = std::make_pair(open, close); + } + + // get members + ConfigValue GetConfigValue(const std::string &var_name) const; + template<typename T> + T GetConfigValue(const std::string &var_name) + const + { + return GetConfigValue(var_name).Convert<T>(); + } + + ConfigValue GetConfigValue(const std::string &var_name, const ConfigValue &def_value, bool verbose = true); + template<typename T> + T GetConfigValue(const std::string &var_name, const T &val, bool verbose = true) + { + return GetConfigValue(var_name, ConfigValue(val), verbose).Convert<T>(); + } + + const std::string &GetConfigPath() const {return config_path;} + const std::string &GetSplitChars() const {return split_chars;} + const std::string &GetSpaceChars() const {return ignore_chars;} + const std::pair<std::string, std::string> &GetReplacePair() const {return replace_pair;} + std::vector<std::string> GetKeyList() const; + + + // functions that to be overloaded + virtual void Configure(const std::string &path = ""); + +protected: + // protected member functions + void reform(std::string &input, const std::string &open, const std::string &close) const; + std::string formKey(const std::string &raw_key) const; + +private: + void parserProcess(ConfigParser &p, const std::string &source); + void parseControl(const std::string &control_word); + void parseTerm(std::string &&var_name, std::string &&var_value); + +protected: + std::string split_chars; + std::string ignore_chars; + bool case_insensitive; + std::pair<std::string, std::string> replace_pair; + std::string config_path; + std::unordered_map<std::string, std::string> config_map; + + // return this reference when there is no value found in the map + ConfigValue __empty_value; +}; + +#endif diff --git a/src/ConfigOption.cpp b/src/ConfigOption.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9b6744d82738bf93020b6c3d1c1cc8d91a02cb8 --- /dev/null +++ b/src/ConfigOption.cpp @@ -0,0 +1,218 @@ +//============================================================================// +// A class to simplify the argument parsing procedure // +// // +// Chao Peng // +// 07/26/2017 // +//============================================================================// + +#include "ConfigOption.h" +#include "ConfigParser.h" +#include <iostream> +#include <cstring> + + + +ConfigOption::ConfigOption() +{ + // place holder +} + +ConfigOption::~ConfigOption() +{ + // place holder +} + +void ConfigOption::AddOpt(OptType type, char s_term) +{ + AddOpt(type, s_term, s_term); +} + +void ConfigOption::AddLongOpt(OptType type, const char *l_term) +{ + AddLongOpt(type, l_term, *l_term); +} + +void ConfigOption::AddOpts(OptType type, char s_term, const char *l_term) +{ + AddOpts(type, s_term, l_term, s_term); +} + +void ConfigOption::AddOpt(OptType type, char s_term, char mark) +{ + if(s_opt_map.find(s_term) != s_opt_map.end()) { + std::cerr << "ConfigOption: Short option " << s_term + << " has already been added, abort AddOpt." + << std::endl; + return; + } + + s_opt_map[s_term] = Opt(mark, type); +} + +void ConfigOption::AddLongOpt(OptType type, const char *l_term, char mark) +{ + if(l_opt_map.find(l_term) != l_opt_map.end()) { + std::cerr << "ConfigOption: Long option " << l_term + << " has already been added, abort AddOpt." + << std::endl; + return; + } + + l_opt_map[l_term] = Opt(mark, type); +} + +void ConfigOption::AddOpts(OptType type, char s_term, const char *l_term, char mark) +{ + AddOpt(type, s_term, mark); + AddLongOpt(type, l_term, mark); +} + +bool ConfigOption::parseLongOpt(const char *arg) +{ + auto vars = ConfigParser::split(arg, strlen(arg), "="); + + std::string key = std::move(vars.front()); + vars.pop_front(); + auto it = l_opt_map.find(key); + + if(it == l_opt_map.end()) { + std::cerr << "Unknown option --" << key << std::endl; + return false; + } + + switch(it->second.type) + { + case arg_require: + if(vars.empty()) { + std::cerr << "Lack of argument for option --" << key << std::endl; + return false; + } + opt_pack.emplace_back(it->second.mark, it->second.type, std::move(vars.front())); + break; + case arg_none: + opt_pack.emplace_back(it->second.mark, it->second.type); + break; + case help_message: + return false; + } + + return true; +} + +bool ConfigOption::parseShortOpt(char key, int argc, char *argv[], int &idx) +{ + auto it = s_opt_map.find(key); + if(it == s_opt_map.end()) { + std::cerr << "Unknown option -" << key << std::endl; + return false; + } + + switch(it->second.type) + { + case arg_require: + if((idx + 1) >= argc || *argv[idx + 1] == '-') { + std::cerr << "Lack of argument for option -" << key << std::endl; + return false; + } + opt_pack.emplace_back(it->second.mark, it->second.type, ConfigValue(argv[++idx])); + break; + case arg_none: + opt_pack.emplace_back(it->second.mark, it->second.type); + break; + case help_message: + return false; + } + + return true; +} + +bool ConfigOption::ParseArgs(int argc, char *argv[]) +{ + if (argc < 1) + return false; + + argv0 = argv[0]; + + bool success = true; + arg_pack.clear(); + opt_pack.clear(); + + for(int i = 1; i < argc; ++i) + { + char* ptr = argv[i]; + // option + if(*ptr == '-') { + // long option + if(*(ptr + 1) == '-') { + success &= parseLongOpt(ptr + 2); + // short option + } else { + success &= parseShortOpt(*(ptr + 1), argc, argv, i); + } + // arguments + } else { + arg_pack.emplace_back(ptr); + } + } + + return success; +} + +void ConfigOption::SetDesc(const char *desc) +{ + base_desc = desc; +} + +void ConfigOption::SetDesc(char mark, const char *desc) +{ + std::string option; + bool found_mark = false; + for(auto &it : s_opt_map) + { + if(it.second.mark != mark) continue; + option = " - "; + option.back() = it.first; + if(it.second.type == arg_require) + option += " <arg>"; + option += ","; + found_mark = true; + } + + for(auto &it : l_opt_map) + { + if(it.second.mark != mark) continue; + option += " --" + it.first; + if(it.second.type == arg_require) + option += "=<arg>"; + option += ","; + found_mark = true; + } + + if(!found_mark) { + std::cerr << "Config Option: Cannot find any option with mark <" + << mark << ">, abort adding description." << std::endl; + return; + } + + option.back() = ':'; + option += " "; + option += desc; + option_desc.emplace_back(option); +} + +std::string ConfigOption::GetInstruction() +{ + std::string res = base_desc + "\noptions:\n"; + + auto pvar = res.find("%0"); + if (pvar != std::string::npos) { + res.replace(pvar, 2, argv0); + } + + for(auto &option : option_desc) + { + res += "\t" + option + "\n"; + } + return res; +} + diff --git a/src/ConfigOption.h b/src/ConfigOption.h new file mode 100644 index 0000000000000000000000000000000000000000..1ca1f7c158b6ccbc853f3226ad079dd08dc63877 --- /dev/null +++ b/src/ConfigOption.h @@ -0,0 +1,66 @@ +#ifndef CONFIG_OPTION_H +#define CONFIG_OPTION_H + +#include "ConfigValue.h" +#include <vector> +#include <string> +#include <unordered_map> + +class ConfigOption +{ +public: + enum OptType : int + { + arg_none = 0, + arg_require, + help_message, + }; + + struct Opt + { + char mark; + OptType type; + ConfigValue var; + + Opt() : mark(-1), type(arg_none) {} + Opt(char m, OptType t) : mark(m), type(t) {} + Opt(char m, OptType t, ConfigValue v) : mark(m), type(t), var(v) {} + }; + +public: + ConfigOption(); + virtual ~ConfigOption(); + + void AddOpt(OptType type, char s_term); + void AddOpt(OptType type, char s_term, char mark); + void AddLongOpt(OptType type, const char *l_term); + void AddLongOpt(OptType type, const char *l_term, char mark); + void AddOpts(OptType type, char s_term, const char *l_term); + void AddOpts(OptType type, char s_term, const char *l_term, char mark); + void SetDesc(const char *desc); + void SetDesc(char mark, const char *desc); + std::string GetInstruction(); + bool ParseArgs(int argc, char *argv[]); + size_t NbofArgs() const {return arg_pack.size();} + size_t NbofOpts() const {return opt_pack.size();} + const std::string &GetArgv0() {return argv0;} + const ConfigValue &GetArgument(size_t i) {return arg_pack.at(i);} + const std::vector<ConfigValue> &GetArguments() {return arg_pack;} + const std::vector<Opt> &GetOptions() {return opt_pack;} + +private: + bool parseLongOpt(const char *arg); + bool parseShortOpt(char key, int argc, char *argv[], int &idx); + +private: + std::unordered_map<char, Opt> s_opt_map; + std::unordered_map<std::string, Opt> l_opt_map; + std::vector<Opt> opt_pack; + std::vector<ConfigValue> arg_pack; + std::string argv0; + std::string base_desc; + std::vector<std::string> option_desc; +}; + + +#endif // CONFIG_OPTION_H diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad496d48e13bfeacdfda01dd92178482dd778c69 --- /dev/null +++ b/src/ConfigParser.cpp @@ -0,0 +1,966 @@ +//============================================================================// +// A simple parser class to read text file and deal with strings // +// // +// Chao Peng // +// 06/07/2016 // +//============================================================================// + +#include "ConfigParser.h" +#include <cstring> +#include <climits> +#include <algorithm> + +using namespace std; + + + +//============================================================================// +// Constructors, Destructor, Assignment Operators // +//============================================================================// + +// constructor, with format input +ConfigParser::ConfigParser(Format f) +: form(f), line_number(0) +{ + // place holder +} + +// copy constructor, only copy format +ConfigParser::ConfigParser(const ConfigParser &that) +: form(that.form), line_number(0) +{ + // place holder +} + +// move constructor, only move format +ConfigParser::ConfigParser(ConfigParser &&that) +: form(that.form), line_number(0) +{ + // place holder +} + +// desctructor +ConfigParser::~ConfigParser() +{ + CloseFile(); +} + +// copy assignment operator +ConfigParser &ConfigParser::operator = (const ConfigParser &rhs) +{ + form = rhs.form; + return *this; +} + +// move assignment operator +ConfigParser &ConfigParser::operator = (ConfigParser &&rhs) +{ + form = rhs.form; + return *this; +} + +//============================================================================// +// Public Member Function // +//============================================================================// + +// open a file for future parsing +bool ConfigParser::OpenFile(const string &path, size_t cap) +{ + Clear(); + + infile.open(path); + + if(!infile.is_open()) + return false; + + buf.data.resize(cap); + + // success! + return true; +} + +// read the whole file into a buffer and break it into lines +bool ConfigParser::ReadFile(const string &path) +{ + Clear(); + + infile.open(path); + + if(!infile.is_open()) + return false; + + infile.seekg(0, ios::end); + buf.end = infile.tellg(); + buf.data.resize(buf.end); + infile.seekg(0, ios::beg); + + infile.read(&buf.data[0], buf.end); + infile.close(); + + return true; +} + +// close file +void ConfigParser::CloseFile() +{ + return infile.close(); +} + +// clear stored lines +void ConfigParser::Clear() +{ + buf.Reset(); + + // reset line + line_number = 0; + cur_line.Reset(); + + // close file + CloseFile(); +} + +// read a buffer in +void ConfigParser::ReadBuffer(const char *buf_in) +{ + Clear(); + + buf.end = strlen(buf_in); + buf.data.resize(buf.end + 2); + + strncpy(&buf.data[0], buf_in, buf.end); +} + +// parse a line from the file or buffer +// if the line is empty (all white spaces or comments), it will be skipped +// return false if reached the end +bool ConfigParser::ParseLine() +{ + elements.clear(); + + while(elements.empty()) + { + if(!getLine(cur_line)) + return false; + + // count the line number + ++line_number; + + parseBuffer(cur_line); + } + + return true; +} + +// parse the whole file or buffer +// return false if nothing was found +bool ConfigParser::ParseAll() +{ + elements.clear(); + + while(true) + { + if(!getLine(cur_line)) + return !elements.empty(); + + // count the line number + ++line_number; + + parseBuffer(cur_line); + } +} + +// parse an input string, split the string into elements +// the trail white spaces in the elements will be trimmed +int ConfigParser::ParseString(const string &line) +{ + deque<string> eles = split(line.c_str(), line.size(), form.split); + + int count = 0; + for(auto &ele : eles) + { + string trim_ele = trim(ele, form.white); + if(!trim_ele.empty()) { + elements.emplace_back(move(trim_ele)); + count++; + } + } + + return count; +} + +// check if the current elemtns number is in the range [num, num + optional] +// output a warning message if not +bool ConfigParser::CheckElements(int num, int optional) +{ + string num_str; + + if(optional > 0) { + if((elements.size() >= (size_t)num) && + (elements.size() <= (size_t)(num + optional))) { + return true; + } + + num_str = to_string(num) + " - " + to_string(num + optional); + + } else if(optional == 0) { + + if(elements.size() == (size_t)num) { + return true; + } + + num_str = to_string(num); + + } else { // optional < 0 + if(elements.size() >= (size_t)num) { + return true; + } + + num_str = " >= " + to_string(num); + } + + + cout << "Config Parser Warning: Wrong format at line " + << line_number + << ", expecting " << num_str << " elements. " + << endl + << "\"" << cur_line.String() << "\"" + << endl; + return false; +} + + +// take the first element +ConfigValue ConfigParser::TakeFirst() +{ + if(elements.empty()) { + cout << "Config Parser Warning: Trying to take elements while there is " + << "nothing, 0 value returned." << endl; + return ConfigValue("0"); + } + + ConfigValue output(move(elements.front())); + elements.pop_front(); + + return output; +} + + + +//============================================================================// +// Private Member Function // +//============================================================================// + +// get buffer from the file or the input buffer +// return false if reached input end +bool ConfigParser::getBuffer() +{ + if(buf.begin < buf.end) + return true; + + if(!infile.is_open() || infile.bad() || infile.eof()) + return false; + + infile.read(&buf.data[0], buf.data.size()); + + buf.begin = 0; + buf.end = infile.gcount(); + + return true; +} + +// trim white spaces +inline void trimbuf(const vector<char> &buf, size_t &begin, size_t &end, const string &w) +{ + while(begin < end) + { + if(w.find(buf[begin]) == string::npos) + break; + + begin++; + } + + while(end > begin) + { + if(w.find(buf[end - 1]) == string::npos) + break; + + end--; + } +} + +inline bool compare(const char ch, const string &str, size_t &c1) +{ + if(str.empty()) + return false; + + c1 = (ch == str[c1]) ? (c1 + 1) : 0; + + return (c1 >= str.size()); +} + +inline bool rcompare(const ConfigParser::CharBuffer &buf, const string &str) +{ + if(str.empty() || buf.end <= buf.begin || buf.end - buf.begin < str.size()) + return false; + + for(size_t i = 1; i <= str.size(); ++i) + { + if(str[str.size() - i] != buf[buf.end - i]) + return false; + } + + return true; +} + +// a helper structure to check context status +struct TextStatus +{ + int val; + size_t cmt1, cmt2, delim; + + TextStatus() : val(0), cmt1(0), cmt2(0), delim(0) {} + inline void Set(int i) {val = i; cmt1 = 0; cmt2 = 0;} +}; + +// get a line from the file or buffer +// it deals with comments, white spaces +// return false if reached the end +bool ConfigParser::getLine(CharBuffer &line_buf, bool recursive) +{ + if(!recursive) + line_buf.Reset(); + bool success = false; + TextStatus stat; + + while(getBuffer()) + { + success = true; + + while(buf.begin < buf.end) + { + auto &ch = buf[buf.begin++]; + switch(stat.val) + { + default: + case 0: + line_buf.Add(ch); + // check if it is the end + if(compare(ch, form.delim, stat.delim)) { + line_buf.end -= form.delim.size(); + trimbuf(line_buf.data, line_buf.begin, line_buf.end, form.white); + // glue lines + if(rcompare(line_buf, form.glue)) { + line_buf.end -= form.glue.size(); + return getLine(line_buf, true); + } else { + return success; + } + } else if(compare(ch, form.cmtopen, stat.cmt1)) { + stat.Set(1); + line_buf.end -= form.cmtopen.size(); + } else if(compare(ch, form.cmtmark, stat.cmt2)) { + stat.Set(2); + line_buf.end -= form.cmtmark.size(); + } + break; + case 1: + if(compare(ch, form.cmtclose, stat.cmt1)) { + stat.Set(0); + } + break; + case 2: + if(ch == '\n') { + stat.Set(0); + buf.begin -= 1; + } + break; + } + } + } + + trimbuf(line_buf.data, line_buf.begin, line_buf.end, form.white); + return success; +} + +// parse an input char buffer, split the string into elements +// the trail white spaces in the elements will be trimmed +int ConfigParser::parseBuffer(const CharBuffer &line) +{ + if(line.begin >= line.end) + return 0; + + size_t ele_begin = line.begin; + int count = 0; + + // intended to visit i == line.end, so the rest of the string get parsed + for(size_t i = 0; i <= line.end; ++i) + { + if(i == line.end || form.split.find(line[i]) != string::npos) { + size_t ele_end = i; + trimbuf(line.data, ele_begin, ele_end, form.white); + if(ele_begin < ele_end) { + elements.emplace_back(&line[ele_begin], ele_end - ele_begin); + count++; + } + ele_begin = i + 1; + } + } + + return count; +} + +//============================================================================// +// Public Static Function // +//============================================================================// + +// comment out a string, remove chars from the comment mark to the line break +void ConfigParser::comment_line(string &str, const string &c, const string &b) +{ + // no need to continue + if(str.empty() || c.empty() || b.empty()) + return; + + // loop until no marks found + while(true) + { + size_t c_begin = str.find(c); + if(c_begin != string::npos) { + size_t c_end = str.find(b, c_begin + c.size()); + // found, comment out until the line break + if(c_end != string::npos) { + // do not remove line break + str.erase(c_begin, c_end - c_begin); + // not found, comment out until the end + } else { + str.erase(c_begin); + // can stop now, since everything afterwards is removed + return; + } + } else { + // comment marks not found + return; + } + } +} + +// comment out between a pair of comment marks +// NOTICE: does not support nested structure of comment marks +void ConfigParser::comment_between(string &str, const string &open, const string &close) +{ + // no need to continue + if(str.empty() || open.empty() || close.empty()) + return; + + while(true) + { + // find the openning comment mark + size_t pos1 = str.find(open); + if(pos1 != string::npos) { + size_t pos2 = str.find(close, pos1 + open.size()); + // found pair + if(pos2 != string::npos) { + // remove everything between, including this pair + str.erase(pos1, pos2 + close.size() - pos1); + // comment pair not found + } else { + return; + } + } else { + // comment pair not found + return; + } + } +} + +// trim all the characters defined as white space at both ends +string ConfigParser::trim(const string &str, const string &w) +{ + + const auto strBegin = str.find_first_not_of(w); + if (strBegin == string::npos) + return ""; // no content + + const auto strEnd = str.find_last_not_of(w); + + const auto strRange = strEnd - strBegin + 1; + return str.substr(strBegin, strRange); +} + +// split a string into several pieces by all the characters defined as splitter +deque<string> ConfigParser::split(const string &str, const string &s) +{ + deque<string> eles; + + char *cstr = new char[str.length() + 1]; + + strcpy(cstr, str.c_str()); + + char *pch = strtok(cstr, s.c_str()); + + while(pch != nullptr) + { + eles.emplace_back(pch); + pch = strtok(nullptr, s.c_str()); + } + + delete[] cstr; + + return eles; +} + +// split a char array into several pieces +deque<string> ConfigParser::split(const char* str, const size_t &len, const string &s) +{ + deque<string> eles; + + char *str_cpy = new char[len + 1]; + + strncpy(str_cpy, str, len); + // end of C string + str_cpy[len] = '\0'; + + char *pch = strtok(str_cpy, s.c_str()); + + while(pch != nullptr) + { + eles.emplace_back(pch); + pch = strtok(nullptr, s.c_str()); + } + + delete[] str_cpy; + + return eles; +} + +// split a string and convert all parts to float numbers +vector<int> ConfigParser::stois(const string &str, const string &s, const string &w) +{ + vector<int> res; + for(auto &val : split(str, s)) + { + res.push_back(stoi(trim(val, w))); + } + return res; +} + +// split a string and convert all parts to float numbers +vector<float> ConfigParser::stofs(const string &str, const string &s, const string &w) +{ + vector<float> res; + for(auto &val : split(str, s)) + { + res.push_back(stof(trim(val, w))); + } + return res; +} + +// split a string and convert all parts to double numbers +vector<double> ConfigParser::stods(const string &str, const string &s, const string &w) +{ + vector<double> res; + for(auto &val : split(str, s)) + { + res.push_back(stod(trim(val, w))); + } + return res; +} + +// get the split part at num +string ConfigParser::get_split_part(int num, const char *str, const char &s) +{ + // unavailable + if(num < 0) return ""; + + int beg = 0, cur = 0; + while(str[cur] != '\0') + { + if(str[cur] == s) { + // number macthed + if(num-- == 0) { + return string(&str[beg], cur - beg); + // update segment + } else { + beg = cur + 1; + } + } + ++cur; + } + + // last element + if(num == 0) + return string(&str[beg], cur - beg); + + return ""; +} + +// check if the short string is the same with the first part of a long string +bool cstr_cmp_helper(const char *cmp, const char *str, int size) +{ + for(int i = 0; i < size; ++i) + { + if(cmp[i] != str[i] || cmp[i] == '\0' || str[i] == '\0') + return false; + } + + if(cmp[size] != '\0') + return false; + + return true; +} + +// split a long string and find if a short string is belong to its elements +int ConfigParser::get_part_count(const char *cmp, const char *str, const char &s) +{ + int cnt = 0, beg = 0, cur = 0; + while(str[cur] != '\0') + { + if(str[cur] == s) { + if(cstr_cmp_helper(cmp, &str[beg], cur - beg)) { + return cnt; + } + + ++cnt; + beg = cur + 1; + } + ++cur; + } + + if(cstr_cmp_helper(cmp, &str[beg], cur-beg)) + return cnt; + + return -1; +} + +// find the integer in a string +int ConfigParser::find_integer(const string &str, const size_t &pos) +{ + vector<int> integers = find_integers(str); + if(pos >= integers.size()) + { + cerr << "Config Parser: Cannot find " << pos + 1 << " integers from " + << "\"" << str << "\"." + << endl; + return 0; + } + + return integers.at(pos); +} + +// find all the integers in a string +vector<int> ConfigParser::find_integers(const string &str) +{ + vector<int> result; + + find_integer_helper(str, result); + + return result; +} + +// helper function for finding a integer +void ConfigParser::find_integer_helper(const string &str, vector<int> &result) +{ + if(str.empty()) + return; + + int negative = 1; + auto numBeg = str.find_first_of("-0123456789"); + if(numBeg == string::npos) + return; + + // check negative sign + string str2 = str.substr(numBeg); + + if(str2.at(0) == '-') + { + negative = -1; + int num_check; + + do { + str2.erase(0, 1); + + if(str2.empty()) + return; + + num_check = str2.at(0) - '0'; + } while (num_check > 9 || num_check < 0); + } + + auto numEnd = str2.find_first_not_of("0123456789"); + if(numEnd == string::npos) + numEnd = str2.size(); + + int num = 0; + size_t i = 0; + + for(; i < numEnd; ++i) + { + if( (num > INT_MAX/10) || + (num == INT_MAX/10 && ((str2.at(i) - '0') > (INT_MAX - num*10))) ) + { + ++i; + break; + } + + num = num*10 + str2.at(i) - '0'; + } + + result.push_back(negative*num); + find_integer_helper(str2.substr(i), result); +} + +// return the lower case of this string +string ConfigParser::str_lower(const string &str) +{ + string res = str; + for(auto &c : res) + { + c = tolower(c); + } + return res; +} + +// return the upper case of this string +string ConfigParser::str_upper(const string &str) +{ + string res = str; + for(auto &c : res) + { + c = toupper(c); + } + return res; +} + +// remove characters in ignore list +string ConfigParser::str_remove(const string &str, const string &iignore) +{ + string res = str; + + for(auto &c : iignore) + { + res.erase(remove(res.begin(), res.end(), c), res.end()); + } + return res; +} + +// replace characters in the list with certain char +string ConfigParser::str_replace(const string &str, const string &list, const char &rc) +{ + if(list.empty()) + return str; + + string res = str; + + for(auto &c : res) + { + if(list.find(c) != string::npos) + c = rc; + } + + return res; +} + +// compare two strings, can be case insensitive +bool ConfigParser::case_ins_equal(const string &str1, const string &str2) +{ + if(str1.size() != str2.size()) { + return false; + } + + for(auto c1 = str1.begin(), c2 = str2.begin(); c1 != str1.end(); ++c1, ++c2) + { + if(tolower(*c1) != tolower(*c2)) { + return false; + } + } + + return true; +} + +// find the first pair position in a string +// it will return the most outer pair if the first pair was in a nested structure +pair<size_t, size_t> ConfigParser::find_pair(const string &str, + const string &open, + const string &close, + size_t pos) +{ + pair<size_t, size_t> res(string::npos, string::npos); + + if(open.empty() || close.empty() || str.size() <= pos) + return res; + + res.first = str.find(open, pos); + + // pair not found + if(res.first == string::npos) { + return res; + } + + int open_bracket = 1; + size_t search_beg = res.first + open.size(); + + // loop for nested structure + while(open_bracket > 0) + { + size_t next_close = str.find(close, search_beg); + + // pair not found + if(next_close == string::npos) { + // change back to npos for the not-found indication + res.first = string::npos; + return res; + } + + // check for nested structure + size_t next_open = str.find(open, search_beg); + + // the comparison is based on the definition of string::npos + // npos for not found is size_t = -1, which is the biggest size_t value + // find another open before close + if(next_open < next_close) { + open_bracket++; + search_beg = next_open + open.size(); + // else cases + // 1. close mark found before open mark + // 2. close mark found, open mark not + // 3. close mark is the same as open mark, so the position is the same + } else { + open_bracket--; + search_beg = next_close + close.size(); + res.second = next_close; + } + } + + return res; +} + +// get file name and directory from a path +ConfigParser::PathInfo ConfigParser::decompose_path(const string &path) +{ + PathInfo res; + if(path.empty()) return res; + + // find directory + auto dir_pos = path.find_last_of("/"); + + if(dir_pos != string::npos) { + res.dir = path.substr(0, dir_pos); + res.name = path.substr(dir_pos + 1); + } else { + res.name = path; + } + + // find extension + auto ext_pos = res.name.find_last_of("."); + if(ext_pos != string::npos) { + res.ext = res.name.substr(ext_pos + 1); + res.name = res.name.substr(0, ext_pos); + } + + return res; +} + +// form the path +string ConfigParser::compose_path(const ConfigParser::PathInfo &path) +{ + string res(path.dir); + res.reserve(path.dir.size() + path.name.size() + path.ext.size() + 2); + + if(!res.empty() && res.back() != '/') + res += '/'; + + res += path.name; + + if(!path.ext.empty()) + res += "." + path.ext; + + return res; +} + +// form a path from given directory and file name, automatically add / if it is missing +string ConfigParser::form_path(const string &dir, const string &file) +{ + string file_path; + file_path.reserve(dir.size() + file.size() + 1); + + file_path = dir; + if(file_path.size() && file_path.back() != '/') file_path += "/"; + file_path += file; + + return file_path; +} + +// read a file and return its content in a char string +string ConfigParser::file_to_string(const string &path) +{ + ifstream inf(path); + + if(!inf.is_open()) + return ""; + + // read the whole file in + string str; + + inf.seekg(0, ios::end); + str.reserve(inf.tellg()); + inf.seekg(0, ios::beg); + + str.assign((istreambuf_iterator<char>(inf)), istreambuf_iterator<char>()); + inf.close(); + + return str; +} + +// break text file into several blocks in the format +// <label> <open_mark> <content> <close_mark> +// return extracted <residual> {<label> <content>} +ConfigParser::TextBlocks ConfigParser::break_into_blocks(const string &buf, + const string &open, + const string &close, + const string &seps) +{ + TextBlocks result; + + if(buf.empty() || open.empty() || close.empty()) + return result; + + size_t last_end = 0; + // loop until no blocks found + while(true) + { + // find the contents in block brackets + auto p = find_pair(buf, open, close, last_end); + + // no pair found anymore + if(p.first == string::npos || p.second == string::npos) + break; + + // add content + TextBlock block; + block.content = trim(buf.substr(p.first + open.size(), p.second - p.first - open.size()), seps); + + // find label + string head = buf.substr(last_end, p.first - last_end); + if(head.empty()) { + block.label = ""; + } else { + // find end of label + auto end = head.find_last_not_of(seps); + if(end == string::npos) end = head.size() - 1; + // find begin of label + auto beg = head.find_last_of(seps, end); + if(beg == string::npos) beg = 0; + // add label + block.label = trim(head.substr(beg, end - beg + 1), seps); + // other content goes to residual + result.residual += head.substr(0, beg); + } + // combine blocks + result.blocks.emplace_back(move(block)); + last_end = p.second + close.size(); + } + + // trim + result.residual = trim(result.residual, seps); + + return result; +} diff --git a/src/ConfigParser.h b/src/ConfigParser.h new file mode 100644 index 0000000000000000000000000000000000000000..5e2810b4b0ea14d3f0167fa05e9089c5542eed1d --- /dev/null +++ b/src/ConfigParser.h @@ -0,0 +1,235 @@ +#ifndef CONFIG_PARSER_H +#define CONFIG_PARSER_H + +#include <string> +#include <vector> +#include <deque> +#include <fstream> +#include "ConfigValue.h" + +// a macro to auto generate enum2str and str2enum +// name mapping begins at bias and continuously increase, split by '|' +// an example: +// enum ABC {a = 3, b, c}; +// ENUM_MAP(ABC, 3, "a|b|c") +// ABC2str(3) = "a" +// str2ABC("b") = 4 +#define ENUM_MAP(type, bias, strings) \ + static std::string type ## 2str(int T) \ + { \ + return ConfigParser::get_split_part(T - (bias), strings, '|'); \ + }; \ + static type str2 ## type(const char *str) \ + { \ + return static_cast<type>(bias + ConfigParser::get_part_count(str, strings, '|')); \ + } + +// config parser class +class ConfigParser +{ + typedef std::pair<std::string, std::string> string_pair; + +public: + struct Format + { + std::string split; // element splitters (chars) + std::string white; // white spaces (chars) + std::string delim; // line delimiter (string) + std::string glue; // line glue (string) + std::string cmtmark; // comment marks (string), until the line breaker '\n' + std::string cmtopen; // comment-block opening mark (string) + std::string cmtclose; // comment-block closing mark (string) + + static Format Basic() {return {" \t,", " \t", "\n", "", "", "", ""};} + static Format BashLike() {return {" \t,", " \t", "\n", "\\", "#", "\'", "\'"};} + static Format CLike() {return {" \t,\n", " \t\n", ";", "", "//", "/*", "*/"};} + }; + + struct CharBuffer + { + std::vector<char> data; + size_t begin, end; + + CharBuffer(size_t cap = 256) : begin(0), end(0) + { + data.resize(cap); + } + + void Reset() {begin = 0; end = 0; data.clear();} + void Add(char ch) + { + if(data.size() <= end) + data.resize(2*data.size()); + + data[end++] = ch; + } + + std::string String() + const + { + std::string str; + if(end > begin) + str.assign(&data[begin], end - begin); + return str; + } + + inline const char &operator [] (size_t idx) const {return data[idx];} + inline char &operator [] (size_t idx) {return data[idx];} + }; + +public: + ConfigParser(Format f = Format::BashLike()); + + ConfigParser(ConfigParser &&that); + ConfigParser(const ConfigParser &that); + + virtual ~ConfigParser(); + + ConfigParser &operator = (ConfigParser &&rhs); + ConfigParser &operator = (const ConfigParser &rhs); + + // format related + inline void SetFormat(Format &&f) {form = f;} + inline void SetFormat(const Format &f) {form = f;} + inline void SetSplitters(std::string s) {form.split = s;} + inline void SetWhiteSpaces(std::string w) {form.white = w;} + inline void SetCommentMark(std::string c) {form.cmtmark = c;} + inline void SetCommentPair(std::string o, std::string c) {form.cmtopen = o; form.cmtclose = c;} + inline void SetLineGlues(std::string g) {form.glue = g;} + inline void SetLineBreaks(std::string b) {form.delim = b;} + + const Format &GetFormat() const {return form;} + + // dealing with file/buffer + bool OpenFile(const std::string &path, size_t cap = 64*1024); + bool ReadFile(const std::string &path); + void ReadBuffer(const char*); + void CloseFile(); + void Clear(); + + // parse line, return false if no more line to parse + bool ParseLine(); + // parse the whole file or buffer, return false if no elements found + bool ParseAll(); + // parse a string, trim and split it into elements + int ParseString(const std::string &line); + + // get current parsing status + bool CheckElements(int num, int optional = 0); + int NbofElements() const {return elements.size();} + int LineNumber() const {return line_number;} + std::string CurrentLine() const {return cur_line.String();} + + // take the elements + ConfigValue TakeFirst(); + + template<typename T> + T TakeFirst() + { + return TakeFirst().Convert<T>(); + } + + template<typename T> + ConfigParser &operator >>(T &t) + { + t = (*this).TakeFirst().Convert<T>(); + return *this; + } + + template<class BidirIt> + int Take(BidirIt first, BidirIt last) + { + int count = 0; + for(auto it = first; it != last; ++it, ++count) + { + if(elements.empty()) + break; + + *it = elements.front(); + elements.pop_front(); + } + return count; + } + + template<template<class, class> class Container> + Container<ConfigValue, std::allocator<ConfigValue>> TakeAll() + { + Container<ConfigValue, std::allocator<ConfigValue>> res; + while(elements.size()) + { + res.emplace_back(std::move(elements.front())); + elements.pop_front(); + } + return res; + } + + template<template<class, class> class Container, class T> + Container<T, std::allocator<T>> TakeAll() + { + Container<T, std::allocator<T>> res; + while(elements.size()) + { + ConfigValue tmp(std::move(elements.front())); + elements.pop_front(); + res.emplace_back(tmp.Convert<T>()); + } + return res; + } + + +private: + // private functions + bool getBuffer(); + bool getLine(CharBuffer &line_buf, bool recursive = false); + int parseBuffer(const CharBuffer &line); + +private: + // private members + Format form; + std::ifstream infile; + CharBuffer buf, cur_line; + int line_number; + std::deque<std::string> elements; + +public: + // static functions + static void comment_line(std::string &str, const std::string &cmt, const std::string &brk); + static void comment_between(std::string &str, const std::string &open, const std::string &close); + static std::string trim(const std::string &str, const std::string &w); + static std::deque<std::string> split(const std::string &str, const std::string &s); + static std::deque<std::string> split(const char* str, const size_t &len, const std::string &s); + static std::string get_split_part(int num, const char *str, const char &s); + static int get_part_count(const char *cmp, const char *str, const char &s); + static std::vector<int> stois(const std::string &str, const std::string &s, const std::string &w); + static std::vector<float> stofs(const std::string &str, const std::string &s, const std::string &w); + static std::vector<double> stods(const std::string &str, const std::string &s, const std::string &w); + static std::string str_remove(const std::string &str, const std::string &ignore); + static std::string str_replace(const std::string &str, const std::string &ignore, const char &rc = ' '); + static std::string str_lower(const std::string &str); + static std::string str_upper(const std::string &str); + static std::pair<size_t, size_t> find_pair(const std::string &str, + const std::string &open, + const std::string &close, + size_t pos = 0); + static bool case_ins_equal(const std::string &str1, const std::string &str2); + static int find_integer(const std::string &str, const size_t &pos = 0); + static std::vector<int> find_integers(const std::string &str); + static void find_integer_helper(const std::string &str, std::vector<int> &result); + struct PathInfo { std::string dir, name, ext; }; + static PathInfo decompose_path(const std::string &path); + static std::string compose_path(const PathInfo &path); + static std::string form_path(const std::string &dir, const std::string &file); + static std::string file_to_string(const std::string &path); + // break text file into several blocks in the format + // <label> <open_mark> <content> <close_mark>, this structure can be separated by sep characters + // return extracted <residual> {<label> <content>} with white characters trimmed + struct TextBlock {std::string label, content;}; + struct TextBlocks {std::string residual; std::vector<TextBlock> blocks;}; + static TextBlocks break_into_blocks(const std::string &buf, + const std::string &open = "{", + const std::string &close = "}", + const std::string &seps = " \t\n"); + +}; + +#endif diff --git a/src/ConfigValue.cpp b/src/ConfigValue.cpp new file mode 100644 index 0000000000000000000000000000000000000000..96bd4b2ead9db0fe957b8cb11cb1c260f4e75fae --- /dev/null +++ b/src/ConfigValue.cpp @@ -0,0 +1,317 @@ +//============================================================================// +// A string wrapper class that convert string to other data types // +// // +// Chao Peng // +// 06/07/2016 // +//============================================================================// + +#include "ConfigValue.h" +#include "ConfigParser.h" +#include <climits> +#include <algorithm> + +using namespace std; + + + +//============================================================================// +// Constructors, Destructor and Assignment Operators // +//============================================================================// + +ConfigValue::ConfigValue(const string &value) +: _value(value) +{} + +ConfigValue::ConfigValue(string &&value) +: _value(move(value)) +{} + +ConfigValue::ConfigValue(const char *value) +: _value(value) +{} + +ConfigValue::ConfigValue(const bool &value) +{ + if(value) + _value = "1"; + else + _value = "0"; +} + +ConfigValue::ConfigValue(const int &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const long &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const long long &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const unsigned &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const unsigned long &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const unsigned long long &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const float &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const double &value) +: _value(to_string(value)) +{} + +ConfigValue::ConfigValue(const long double &value) +: _value(to_string(value)) +{} + +ConfigValue &ConfigValue::operator =(const string &str) +{ + (*this)._value = str; + return *this; +} + +ConfigValue &ConfigValue::operator =(string &&str) +{ + (*this)._value = move(str); + return *this; +} + + +//============================================================================// +// Public Member functions // +//============================================================================// + +bool ConfigValue::Bool() +const +{ + if((_value == "1") || + (ConfigParser::case_ins_equal(_value, "T")) || + (ConfigParser::case_ins_equal(_value, "True")) || + (ConfigParser::case_ins_equal(_value, "Y")) || + (ConfigParser::case_ins_equal(_value, "Yes"))) + return true; + + if((_value == "0") || + (ConfigParser::case_ins_equal(_value, "F")) || + (ConfigParser::case_ins_equal(_value, "False")) || + (ConfigParser::case_ins_equal(_value, "N")) || + (ConfigParser::case_ins_equal(_value, "No"))) + return false; + + cout << "Config Value: Failed to convert " + << _value << " to bool type. Return false." + << endl; + return false; +} + +char ConfigValue::Char() +const +{ + try { + int value = stoi(_value); + if(value > CHAR_MAX) + cout << "Config Value: Limit exceeded while converting " + << _value << " to char." << endl; + return (char) value; + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to char. 0 returned." << endl; + return 0; + } +} + +unsigned char ConfigValue::UChar() +const +{ + try { + unsigned long value = stoul(_value); + if(value > UCHAR_MAX) + cout << "Config Value: Limit exceeded while converting " + << _value << " to unsigned char." << endl; + return (unsigned char) value; + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to unsigned char. 0 returned." << endl; + return 0; + } +} + +short ConfigValue::Short() +const +{ + try { + int value = stoi(_value); + if(value > SHRT_MAX) + cout << "Config Value: Limit exceeded while converting " + << _value << " to short." << endl; + return (short) value; + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to short. 0 returned." << endl; + return 0; + } +} + +unsigned short ConfigValue::UShort() +const +{ + try { + unsigned long value = stoul(_value); + if(value > USHRT_MAX) + cout << "Config Value: Limit exceeded while converting " + << _value << " to unsigned short." << endl; + return (unsigned short) value; + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to unsigned short. 0 returned." << endl; + return 0; + } +} + +int ConfigValue::Int() +const +{ + try { + return stoi(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to int. 0 returned." << endl; + return 0; + } +} + +unsigned int ConfigValue::UInt() +const +{ + try { + return (unsigned int)stoul(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to unsigned int. 0 returned." << endl; + return 0; + } +} + +long ConfigValue::Long() +const +{ + try { + return stol(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to long. 0 returned." << endl; + return 0; + } +} + +long long ConfigValue::LongLong() +const +{ + try { + return stoll(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to long long. 0 returned." << endl; + return 0; + } +} + +unsigned long ConfigValue::ULong() +const +{ + try { + return stoul(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to unsigned long. 0 returned." << endl; + return 0; + } +} + +unsigned long long ConfigValue::ULongLong() +const +{ + try { + return stoull(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to unsigned long long. 0 returned." << endl; + return 0; + } +} + +float ConfigValue::Float() +const +{ + try { + return stof(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to float. 0 returned." << endl; + return 0; + } +} + +double ConfigValue::Double() +const +{ + try { + return stod(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to double. 0 returned." << endl; + return 0; + } +} + +long double ConfigValue::LongDouble() +const +{ + try { + return stold(_value); + } catch (exception &e) { + cerr << e.what() << endl; + cerr << "Config Value: Failed to convert " + << _value << " to long double. 0 returned." << endl; + return 0; + } +} + +const char *ConfigValue::c_str() +const +{ + return _value.c_str(); +} + + + +//============================================================================// +// Other functions // +//============================================================================// + +ostream &operator << (ostream &os, const ConfigValue &b) +{ + return os << b.c_str(); +} diff --git a/src/ConfigValue.h b/src/ConfigValue.h new file mode 100644 index 0000000000000000000000000000000000000000..aaf3a36103cf96af200f2b68f1f330278a8ae46a --- /dev/null +++ b/src/ConfigValue.h @@ -0,0 +1,138 @@ +#ifndef CONFIG_VALUE_H +#define CONFIG_VALUE_H + +#include <string> +#include <iostream> +#include <sstream> +#include <utility> +#include <typeinfo> + +// demangle type name +#ifdef __GNUG__ +#include <cstdlib> +#include <memory> +#include <cxxabi.h> +// gnu compiler needs to demangle type info +static inline std::string demangle(const char* name) +{ + + int status = 0; + + //enable c++11 by passing the flag -std=c++11 to g++ + std::unique_ptr<char, void(*)(void*)> res { + abi::__cxa_demangle(name, NULL, NULL, &status), + std::free + }; + + return (status==0) ? res.get() : name ; +} +#else +// do nothing if not gnu compiler +static inline std::string demangle(const char* name) +{ + return name; +} +#endif + +// this helps template specialization in class +template <typename T> +struct __cv_id { typedef T type; }; + +class ConfigValue +{ +public: + friend class ConfigParser; + friend class ConfigObject; + +public: + ConfigValue() {} + + ConfigValue(const std::string &value); + ConfigValue(std::string &&value); + ConfigValue(const int &value); + ConfigValue(const double &value); + explicit ConfigValue(const char *value); + explicit ConfigValue(const bool &value); + explicit ConfigValue(const long &value); + explicit ConfigValue(const long long &value); + explicit ConfigValue(const unsigned &value); + explicit ConfigValue(const unsigned long &value); + explicit ConfigValue(const unsigned long long &value); + explicit ConfigValue(const float &value); + explicit ConfigValue(const long double &value); + + ConfigValue &operator =(const std::string &str); + ConfigValue &operator =(std::string &&str); + + bool Bool() const; + char Char() const; + unsigned char UChar() const; + short Short() const; + unsigned short UShort() const; + int Int() const; + unsigned int UInt() const; + long Long() const; + long long LongLong() const; + unsigned long ULong() const; + unsigned long long ULongLong() const; + float Float() const; + double Double() const; + long double LongDouble() const; + const char *c_str() const; + const std::string &String() const {return _value;} + bool IsEmpty() const {return _value.empty();} + + operator std::string() + const + { + return _value; + } + + bool operator ==(const std::string &rhs) + const + { + return _value == rhs; + } + + template<typename T> + T Convert() + const + { + return convert( __cv_id<T>()); + } + +private: + std::string _value; + + template<typename T> + T convert(__cv_id<T> &&) + const + { + std::stringstream iss(_value); + T _cvalue; + + if(!(iss >> _cvalue)) { + std::cerr << "Config Value Warning: Undefined value returned, failed to convert " + << _value + << " to " + << demangle(typeid(T).name()) + << std::endl; + } + + return _cvalue; + } + + ConfigValue convert(__cv_id<ConfigValue>) const {return *this;} + bool convert(__cv_id<bool> &&) const {return (*this).Bool();} + float convert(__cv_id<float> &&) const {return (*this).Float();} + double convert(__cv_id<double> &&) const {return (*this).Double();} + long double convert(__cv_id<long double> &&) const {return (*this).LongDouble();} + std::string convert(__cv_id<std::string> &&) const {return (*this)._value;} + const char* convert(__cv_id<const char*> &&) const {return (*this)._value.c_str();} + +}; + +// show string content of the config value to ostream +std::ostream &operator << (std::ostream &os, const ConfigValue &b); + +#endif