#include "cfg.hh" #include "io.hh" #include "line.hh" #include "paths.hh" #include "str.hh" #include #include #include #include #include #include #include #include #include #include #include #include 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& 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 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> values_; }; class ConfigXdgImpl : public Config { public: ConfigXdgImpl() = default; bool load(std::string_view name, std::vector& 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(); if (!cfg->load(file, errors)) all_ok = false; configs_.push_back(std::move(cfg)); } } return all_ok; } [[nodiscard]] std::optional 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> configs_; }; } // namespace bool Config::has(std::string_view name) const { return get(name).has_value(); } std::optional 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 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 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::load(std::string_view name, std::vector& errors) { if (name.empty() || name.front() == '/') { auto ret = std::make_unique(); ret->load(name, errors); return ret; } auto ret = std::make_unique(); ret->load(name, errors); return ret; } } // namespace cfg