1
0
Fork 0
mirror of https://gitlab.com/niansa/commoncpp.git synced 2025-03-06 20:48:30 +01:00

Initial commit

This commit is contained in:
niansa 2023-06-09 23:23:22 +02:00
commit ec148c3c34
5 changed files with 240 additions and 0 deletions

11
CMakeLists.txt Normal file
View 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
View 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);
}
}

View 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

View 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
View 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};
}
}
}