#ifndef XGBOOST_COMMON_CONFIG_H_
#define XGBOOST_COMMON_CONFIG_H_
#include <string>
#include <fstream>
#include <istream>
#include <sstream>
#include <vector>
#include <regex>
#include <iterator>
#include <utility>
#include "xgboost/logging.h"
namespace xgboost {
namespace common {
class ConfigParser {
public:
explicit ConfigParser(const std::string path)
: path_(std::move(path)),
line_comment_regex_("^#"),
key_regex_(R"rx(^([^#"'=\r\n\t ]+)[\t ]*=)rx"),
key_regex_escaped_(R"rx(^(["'])([^"'=\r\n]+)\1[\t ]*=)rx"),
value_regex_(R"rx(^([^#"'\r\n\t ]+)[\t ]*(?:#.*){0,1}$)rx"),
value_regex_escaped_(R"rx(^(["'])([^"'\r\n]+)\1[\t ]*(?:#.*){0,1}$)rx")
{}
std::string LoadConfigFile(const std::string& path) {
std::ifstream fin(path, std::ios_base::in | std::ios_base::binary);
CHECK(fin) << "Failed to open config file: \"" << path << "\"";
try {
std::string content{std::istreambuf_iterator<char>(fin),
std::istreambuf_iterator<char>()};
return content;
} catch (std::ios_base::failure const &e) {
LOG(FATAL) << "Failed to read config file: \"" << path << "\"\n"
<< e.what();
}
return "";
}
std::string NormalizeConfigEOL(std::string const& config_str) {
std::string result;
std::stringstream ss(config_str);
for (auto c : config_str) {
if (c == '\r') {
result.push_back('\n');
continue;
}
result.push_back(c);
}
return result;
}
std::vector<std::pair<std::string, std::string>> Parse() {
std::string content { LoadConfigFile(path_) };
content = NormalizeConfigEOL(content);
std::stringstream ss { content };
std::vector<std::pair<std::string, std::string>> results;
std::string line;
std::string key, value;
while (std::getline(ss, line)) {
if (ParseKeyValuePair(line, &key, &value)) {
results.emplace_back(key, value);
}
}
return results;
}
private:
std::string path_;
const std::regex line_comment_regex_, key_regex_, key_regex_escaped_,
value_regex_, value_regex_escaped_;
public:
static std::string TrimWhitespace(const std::string& str) {
const auto first_char = str.find_first_not_of(" \t\n\r");
const auto last_char = str.find_last_not_of(" \t\n\r");
if (first_char == std::string::npos) {
return {};
}
CHECK_NE(last_char, std::string::npos);
const auto substr_len = last_char + 1 - first_char;
return str.substr(first_char, substr_len);
}
bool ParseKeyValuePair(const std::string& str, std::string* key,
std::string* value) {
std::string buf = TrimWhitespace(str);
if (buf.empty()) {
return false;
}
std::smatch m;
if (std::regex_search(buf, m, line_comment_regex_)) {
return false;
} else if (std::regex_search(buf, m, key_regex_)) {
CHECK_EQ(m.size(), 2);
*key = m[1].str();
} else if (std::regex_search(buf, m, key_regex_escaped_)) {
CHECK_EQ(m.size(), 3);
*key = m[2].str();
} else {
LOG(FATAL) << "This line is not a valid key-value pair: " << str;
}
buf = m.suffix().str();
buf = TrimWhitespace(buf);
if (std::regex_search(buf, m, value_regex_)) {
CHECK_EQ(m.size(), 2);
*value = m[1].str();
} else if (std::regex_search(buf, m, value_regex_escaped_)) {
CHECK_EQ(m.size(), 3);
*value = m[2].str();
} else {
LOG(FATAL) << "This line is not a valid key-value pair: " << str;
}
return true;
}
};
} } #endif