// -*- mode: c++; c-basic-offset: 2; -*- #include "common.hh" #include "config.hh" #include "paths.hh" #include "strings.hh" #include "xdg.hh" #include #include #include #include namespace { class ConfigImpl : public Config { public: ConfigImpl() : good_(true) { } bool load_name(std::string const& name) override { name_ = name; auto dirs = XDG::config_dirs(); bool good = true; std::string error; std::unordered_map data; for (auto& dir : dirs) { std::string tmp_error; std::unordered_map tmp_data; if (load_file(Paths::join(dir, name_), &tmp_data, &tmp_error, false)) { data.insert(tmp_data.begin(), tmp_data.end()); } else { if (good) { // We want the most import error error = tmp_error; good = false; } } } return update(good, error, data); } bool load_file(std::string const& filename) override { std::string error; std::unordered_map data; bool good = load_file(filename, &data, &error, true); return update(good, error, data); } bool good() const override { return good_; } std::string const& last_error() const override { return last_error_; } std::string const& get(std::string const& key, std::string const& fallback) override { auto i = override_.find(key); if (i != override_.end()) return i->second; i = data_.find(key); if (i != data_.end()) return i->second; return fallback; } char const* get(std::string const& key, char const* fallback) override { auto i = override_.find(key); if (i != override_.end()) return i->second.c_str(); i = data_.find(key); if (i != data_.end()) return i->second.c_str(); return fallback; } bool is_set(std::string const& key) override { return override_.count(key) > 0 || data_.count(key) > 0; } bool get(std::string const& key, bool fallback) override { auto ret = get(key, nullptr); if (!ret) return fallback; return strcmp(ret, "true") == 0; } void set(std::string const& key, std::string const& value) override { auto ret = override_.insert(std::make_pair(key, value)); if (!ret.second) { ret.first->second = value; } } void remove(std::string const& key) override { override_.erase(key); } private: bool update(bool good, std::string const& last_error, std::unordered_map& data) { good_ = good; if (!good_) { last_error_ = last_error; } else { data_.clear(); data_.swap(data); } return good_; } static bool load_file(std::string const& filename, std::unordered_map* data, std::string* error, bool should_exist) { bool good = true; data->clear(); error->clear(); std::ifstream in(filename); if (!in) { if (should_exist) { std::stringstream ss; ss << "Unable to read: " << filename; *error = ss.str(); good = false; } return good; } std::string line; uint32_t count = 0; std::string key, value; while (std::getline(in, line)) { count++; if (line.empty() || line[0] == '#') continue; auto idx = line.find('='); if (idx == 0 || idx == std::string::npos) { std::stringstream ss; if (idx == 0) { ss << filename << ':' << count << ": Invalid line, starts with '='"; } else { ss << filename << ':' << count << ": Invalid line, no '=' found"; } *error = ss.str(); good = false; break; } size_t start = 0, end = idx; key.assign(Strings::trim(line, start, end)); if (data->count(key) > 0) { std::stringstream ss; ss << filename << ':' << count << ": '" << key << "' is already set"; *error = ss.str(); good = false; break; } start = idx + 1; end = line.size(); Strings::trim(line, &start, &end); if (line[start] == '"' && line[end - 1] == '"') { value.assign(Strings::unquote(line, start, end)); } else { value.assign(line.substr(start, end - start)); } (*data)[key] = value; } if (good && in.bad()) { std::stringstream ss; ss << filename << ": I/O error: " << strerror(errno); *error = ss.str(); good = false; } if (!good) data->clear(); return good; } bool good_; std::string name_; std::string last_error_; std::unordered_map data_; std::unordered_map override_; }; } // namespace // static Config* Config::create() { return new ConfigImpl(); }