mirror of
https://gitlab.com/niansa/commoncpp.git
synced 2025-03-06 20:48:30 +01:00
Initial commit
This commit is contained in:
commit
ec148c3c34
5 changed files with 240 additions and 0 deletions
11
CMakeLists.txt
Normal file
11
CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
project(commoncpp LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(commoncpp STATIC
|
||||
config.cpp include/commoncpp/config.hpp
|
||||
utils.cpp include/commoncpp/utils.hpp)
|
||||
|
||||
target_include_directories(commoncpp PUBLIC include/)
|
||||
target_include_directories(commoncpp PRIVATE include/commoncpp/)
|
106
config.cpp
Normal file
106
config.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
#include "config.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
|
||||
namespace common {
|
||||
bool Configuration::parse_bool(const std::string &value) {
|
||||
if (value == "true")
|
||||
return true;
|
||||
if (value == "false")
|
||||
return false;
|
||||
throw Exception("Error: Failed to parse configuration file: Unknown bool (true/false): "+value);
|
||||
}
|
||||
|
||||
std::string Configuration::parse_string(const std::string &value) {
|
||||
std::string fres;
|
||||
fres.reserve(value.size());
|
||||
bool escaped = false;
|
||||
for (const char c : value) {
|
||||
if (!escaped) {
|
||||
if (c == '\\') {
|
||||
escaped = true;
|
||||
continue;
|
||||
} else
|
||||
fres.push_back(c);
|
||||
} else {
|
||||
char nc;
|
||||
switch (c) {
|
||||
case 'n':
|
||||
nc = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
nc = '\r';
|
||||
break;
|
||||
case 't':
|
||||
nc = '\t';
|
||||
break;
|
||||
case 'b':
|
||||
nc = '\b';
|
||||
break;
|
||||
case '\\':
|
||||
nc = '\\';
|
||||
break;
|
||||
default:
|
||||
throw Exception("Unknown escape sequence: \\"+std::string(1, c));
|
||||
}
|
||||
fres.push_back(nc);
|
||||
escaped = false;
|
||||
}
|
||||
}
|
||||
return fres;
|
||||
}
|
||||
|
||||
bool Configuration::file_exists(std::string_view path) {
|
||||
// Make sure we don't respond to some file that is actually called "none"...
|
||||
if (path.empty() || path == "none") return false;
|
||||
return std::filesystem::exists(path);
|
||||
}
|
||||
|
||||
Configuration::KeyValueMap Configuration::file_parser(const std::string& path) {
|
||||
std::unordered_map<std::string, std::string> fres;
|
||||
// Open file
|
||||
std::ifstream cfgf(path);
|
||||
if (!cfgf) {
|
||||
throw Exception("Failed to open configuration file: "+path);
|
||||
}
|
||||
// Read each entry
|
||||
for (std::string key; cfgf >> key;) {
|
||||
// Read value
|
||||
std::string value;
|
||||
std::getline(cfgf, value);
|
||||
// Ignore comment and empty lines
|
||||
if (key.empty() || key[0] == '#') continue;
|
||||
// Erase all leading spaces in value
|
||||
while (!value.empty() && (value[0] == ' ' || value[0] == '\t')) value.erase(0, 1);
|
||||
// Add to function result
|
||||
fres.emplace(std::move(key), std::move(value));
|
||||
}
|
||||
// Return final result
|
||||
return fres;
|
||||
}
|
||||
|
||||
|
||||
extern "C" char **environ;
|
||||
Configuration::KeyValueMap Configuration::environment_parser() {
|
||||
std::unordered_map<std::string, std::string> fres;
|
||||
for (char **s = environ; *s; s++) {
|
||||
const auto pair = utils::str_split(*s, '=', 1);
|
||||
fres.emplace(pair[0], pair[1]);
|
||||
}
|
||||
return fres;
|
||||
}
|
||||
|
||||
void Configuration::parse(const std::string &file) {
|
||||
const auto file_location = file.empty()?
|
||||
std::filesystem::current_path():
|
||||
std::filesystem::path(file).parent_path();
|
||||
if (!ignore_environment)
|
||||
fill(environment_parser());
|
||||
if (!file.empty())
|
||||
fill(file_parser(file), true);
|
||||
}
|
||||
}
|
48
include/commoncpp/config.hpp
Normal file
48
include/commoncpp/config.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef CONFIG_HPP
|
||||
#define CONFIG_HPP
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace common {
|
||||
class Configuration {
|
||||
public:
|
||||
struct Exception : public std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
protected:
|
||||
using KeyValueMap = std::unordered_map<std::string, std::string>;
|
||||
|
||||
bool ignore_environment = false;
|
||||
|
||||
static
|
||||
bool parse_bool(const std::string& value);
|
||||
|
||||
static
|
||||
std::string parse_string(const std::string& value); // Unescapes string
|
||||
|
||||
static
|
||||
bool file_exists(std::string_view path);
|
||||
|
||||
static
|
||||
KeyValueMap file_parser(const std::string& path);
|
||||
static
|
||||
KeyValueMap environment_parser();
|
||||
|
||||
virtual void fill(KeyValueMap&&, bool ignore_extra = false) = 0;
|
||||
virtual void check() const {} // Should throw on error
|
||||
|
||||
public:
|
||||
Configuration() {}
|
||||
Configuration(Configuration&) = delete;
|
||||
Configuration(const Configuration&) = delete;
|
||||
Configuration(Configuration&&) = delete;
|
||||
Configuration(const Configuration&&) = delete;
|
||||
virtual ~Configuration() {}
|
||||
|
||||
virtual void parse(const std::string& file = "");
|
||||
};
|
||||
}
|
||||
#endif // CONFIG_HPP
|
26
include/commoncpp/utils.hpp
Normal file
26
include/commoncpp/utils.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef UTILS_HPP
|
||||
#define UTILS_HPP
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace common {
|
||||
namespace utils {
|
||||
std::vector<std::string_view> str_split(std::string_view s, char delimiter, size_t times = -1);
|
||||
void str_replace_in_place(std::string& subject, std::string_view search, const std::string& replace);
|
||||
std::string_view max_words(std::string_view text, unsigned count);
|
||||
|
||||
[[noreturn]] inline void unreachable() {
|
||||
// Uses compiler specific extensions if possible.
|
||||
// Even if no extension is used, undefined behavior is still raised by
|
||||
// an empty function body and the noreturn attribute.
|
||||
#ifdef __GNUC__ // GCC, Clang, ICC
|
||||
__builtin_unreachable();
|
||||
#elifdef _MSC_VER // MSVC
|
||||
__assume(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UTILS_HPP
|
49
utils.cpp
Normal file
49
utils.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include "utils.hpp"
|
||||
|
||||
|
||||
|
||||
namespace common {
|
||||
namespace utils {
|
||||
std::vector<std::string_view> str_split(std::string_view s, char delimiter, size_t times) {
|
||||
std::vector<std::string_view> to_return;
|
||||
decltype(s.size()) start = 0, finish = 0;
|
||||
while ((finish = s.find_first_of(delimiter, start)) != std::string_view::npos) {
|
||||
to_return.emplace_back(s.substr(start, finish - start));
|
||||
start = finish + 1;
|
||||
if (to_return.size() == times) { break; }
|
||||
}
|
||||
to_return.emplace_back(s.substr(start));
|
||||
return to_return;
|
||||
}
|
||||
|
||||
void str_replace_in_place(std::string& subject, std::string_view search,
|
||||
const std::string& replace) {
|
||||
if (search.empty()) return;
|
||||
size_t pos = 0;
|
||||
while ((pos = subject.find(search, pos)) != std::string::npos) {
|
||||
subject.replace(pos, search.length(), replace);
|
||||
pos += replace.length();
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view max_words(std::string_view text, unsigned count) {
|
||||
unsigned word_len = 0,
|
||||
word_count = 0,
|
||||
idx;
|
||||
// Get idx after last word
|
||||
for (idx = 0; idx != text.size() && word_count != count; idx++) {
|
||||
char c = text[idx];
|
||||
if (c == ' ' || word_len == 8) {
|
||||
if (word_len != 0) {
|
||||
word_count++;
|
||||
word_len = 0;
|
||||
}
|
||||
} else {
|
||||
word_len++;
|
||||
}
|
||||
}
|
||||
// Return resulting string
|
||||
return {text.data(), idx};
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue