summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cfg.cc197
-rw-r--r--src/cfg.hh40
2 files changed, 237 insertions, 0 deletions
diff --git a/src/cfg.cc b/src/cfg.cc
new file mode 100644
index 0000000..0196bde
--- /dev/null
+++ b/src/cfg.cc
@@ -0,0 +1,197 @@
+#include "cfg.hh"
+
+#include "io.hh"
+#include "line.hh"
+#include "paths.hh"
+#include "str.hh"
+
+#include <charconv>
+#include <cstdint>
+#include <format>
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+namespace cfg {
+
+namespace {
+
+inline char ascii_lowercase(char c) {
+ if (c >= 'A' && c <= 'Z') {
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions)
+ return c | 0x20;
+ }
+ return c;
+}
+
+bool ascii_lowercase_eq(std::string_view a, std::string_view b) {
+ if (a.size() != b.size())
+ return false;
+ auto it_a = a.begin();
+ auto it_b = b.begin();
+ for (; it_a != a.end(); ++it_a, ++it_b) {
+ if (ascii_lowercase(*it_a) != *it_b)
+ return false;
+ }
+ return true;
+}
+
+class ConfigSingleImpl : public Config {
+ public:
+ ConfigSingleImpl() = default;
+
+ bool load(std::filesystem::path const& path,
+ std::vector<std::string>& errors) {
+ auto io_reader = io::open(std::string(path));
+ if (!io_reader.has_value()) {
+ errors.push_back(
+ std::format("Unable to open {} for reading", path.string()));
+ return false;
+ }
+ bool all_ok = true;
+ auto line_reader = line::open(std::move(io_reader.value()));
+ while (true) {
+ auto line = line_reader->read();
+ if (line.has_value()) {
+ auto trimmed = str::trim(line.value());
+ if (trimmed.empty() || trimmed.front() == '#')
+ continue;
+ auto eq = trimmed.find('=');
+ if (eq == std::string_view::npos) {
+ errors.push_back(
+ std::format("{}:{}: Invalid line, expected key = value.",
+ path.string(), line_reader->number()));
+ all_ok = false;
+ continue;
+ }
+ auto key = str::trim(trimmed.substr(0, eq));
+ auto value = str::trim(trimmed.substr(eq + 1));
+ auto ret = values_.emplace(key, value);
+ if (!ret.second) {
+ errors.push_back(std::format("{}:{}: Duplicate key {} ignored.",
+ path.string(), line_reader->number(),
+ key));
+ all_ok = false;
+ continue;
+ }
+ } else {
+ switch (line.error()) {
+ case io::ReadError::Eof:
+ break;
+ default:
+ errors.push_back(std::format("{}: Read error", path.string()));
+ all_ok = false;
+ break;
+ }
+ break;
+ }
+ }
+ return all_ok;
+ }
+
+ [[nodiscard]]
+ std::optional<std::string_view> get(std::string_view name) const override {
+ auto it = values_.find(name);
+ if (it == values_.end())
+ return std::nullopt;
+ return it->second;
+ }
+
+ private:
+ std::map<std::string, std::string, std::less<>> values_;
+};
+
+class ConfigXdgImpl : public Config {
+ public:
+ ConfigXdgImpl() = default;
+
+ bool load(std::string_view name, std::vector<std::string>& errors) {
+ bool all_ok = true;
+ for (auto const& dir : paths::config_dirs()) {
+ auto file = dir / name;
+ if (std::filesystem::exists(file)) {
+ auto cfg = std::make_unique<ConfigSingleImpl>();
+ if (!cfg->load(file, errors))
+ all_ok = false;
+ configs_.push_back(std::move(cfg));
+ }
+ }
+ return all_ok;
+ }
+
+ [[nodiscard]]
+ std::optional<std::string_view> get(std::string_view name) const override {
+ for (auto const& config : configs_) {
+ auto ret = config->get(name);
+ if (ret.has_value())
+ return ret;
+ }
+ return std::nullopt;
+ }
+
+ private:
+ std::vector<std::unique_ptr<ConfigSingleImpl>> configs_;
+};
+
+} // namespace
+
+bool Config::has(std::string_view name) const { return get(name).has_value(); }
+
+std::optional<int64_t> Config::get_int64(std::string_view name) const {
+ auto str = get(name);
+ if (str.has_value()) {
+ auto* const end = str->data() + str->size();
+ int64_t ret;
+ // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
+ auto [ptr, ec] = std::from_chars(str->data(), end, ret);
+ if (ec == std::errc() && ptr == end)
+ return ret;
+ }
+ return std::nullopt;
+}
+
+std::optional<uint64_t> Config::get_uint64(std::string_view name) const {
+ auto str = get(name);
+ if (str.has_value()) {
+ auto* const end = str->data() + str->size();
+ uint64_t ret;
+ // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
+ auto [ptr, ec] = std::from_chars(str->data(), end, ret);
+ if (ec == std::errc() && ptr == end)
+ return ret;
+ }
+ return std::nullopt;
+}
+
+std::optional<bool> Config::get_bool(std::string_view name) const {
+ auto str = get(name);
+ if (str.has_value()) {
+ if (ascii_lowercase_eq(str.value(), "true") ||
+ ascii_lowercase_eq(str.value(), "yes"))
+ return true;
+ if (ascii_lowercase_eq(str.value(), "false") ||
+ ascii_lowercase_eq(str.value(), "no"))
+ return false;
+ }
+ return std::nullopt;
+}
+
+std::unique_ptr<Config> Config::load(std::string_view name,
+ std::vector<std::string>& errors) {
+ if (name.empty() || name.front() == '/') {
+ auto ret = std::make_unique<ConfigSingleImpl>();
+ ret->load(name, errors);
+ return ret;
+ }
+ auto ret = std::make_unique<ConfigXdgImpl>();
+ ret->load(name, errors);
+ return ret;
+}
+
+} // namespace cfg
diff --git a/src/cfg.hh b/src/cfg.hh
new file mode 100644
index 0000000..a4a7865
--- /dev/null
+++ b/src/cfg.hh
@@ -0,0 +1,40 @@
+#ifndef CFG_HH
+#define CFG_HH
+
+#include <memory>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+namespace cfg {
+
+class Config {
+ public:
+ virtual ~Config() = default;
+
+ [[nodiscard]]
+ virtual std::optional<std::string_view> get(std::string_view name) const = 0;
+
+ [[nodiscard]]
+ bool has(std::string_view name) const;
+ [[nodiscard]]
+ std::optional<int64_t> get_int64(std::string_view name) const;
+ [[nodiscard]]
+ std::optional<uint64_t> get_uint64(std::string_view name) const;
+ [[nodiscard]]
+ std::optional<bool> get_bool(std::string_view name) const;
+
+ [[nodiscard]]
+ static std::unique_ptr<Config> load(std::string_view name,
+ std::vector<std::string>& errors);
+
+ protected:
+ Config() = default;
+
+ Config(Config const&) = delete;
+ Config& operator=(Config const&) = delete;
+};
+
+} // namespace cfg
+
+#endif // CFG_HH