#include "config.hh" #include <string> #include <utility> #include <vector> #include <absl/strings/numbers.h> #include <absl/strings/str_split.h> #include <absl/strings/string_view.h> #include <glog/logging.h> #include "args.hh" #include "json.hh" namespace nix { bool Config::set(const std::string& name, const std::string& value) { auto i = _settings.find(name); if (i == _settings.end()) { return false; } i->second.setting->set(value); i->second.setting->overriden = true; return true; } void Config::addSetting(AbstractSetting* setting) { _settings.emplace(setting->name, Config::SettingData(false, setting)); for (auto& alias : setting->aliases) { _settings.emplace(alias, Config::SettingData(true, setting)); } bool set = false; auto i = unknownSettings.find(setting->name); if (i != unknownSettings.end()) { setting->set(i->second); setting->overriden = true; unknownSettings.erase(i); set = true; } for (auto& alias : setting->aliases) { auto i = unknownSettings.find(alias); if (i != unknownSettings.end()) { if (set) { LOG(WARNING) << "setting '" << alias << "' is set, but it's an alias of '" << setting->name << "', which is also set"; } else { setting->set(i->second); setting->overriden = true; unknownSettings.erase(i); set = true; } } } } void AbstractConfig::warnUnknownSettings() { for (auto& s : unknownSettings) { LOG(WARNING) << "unknown setting: " << s.first; } } void AbstractConfig::reapplyUnknownSettings() { auto unknownSettings2 = std::move(unknownSettings); for (auto& s : unknownSettings2) { set(s.first, s.second); } } void Config::getSettings(std::map<std::string, SettingInfo>& res, bool overridenOnly) { for (auto& opt : _settings) { if (!opt.second.isAlias && (!overridenOnly || opt.second.setting->overriden)) { res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description}); } } } void AbstractConfig::applyConfigFile(const Path& path) { try { std::string contents = readFile(path); unsigned int pos = 0; while (pos < contents.size()) { std::string line; while (pos < contents.size() && contents[pos] != '\n') { line += contents[pos++]; } pos++; std::string::size_type hash = line.find('#'); if (hash != std::string::npos) { line = std::string(line, 0, hash); } // TODO(tazjin): absl::string_view after path functions are fixed. std::vector<std::string> tokens = absl::StrSplit(line, absl::ByAnyChar(" \t\n\r")); if (tokens.empty()) { continue; } if (tokens.size() < 2) { throw UsageError("illegal configuration line '%1%' in '%2%'", line, path); } auto include = false; auto ignoreMissing = false; if (tokens[0] == "include") { include = true; } else if (tokens[0] == "!include") { include = true; ignoreMissing = true; } if (include) { if (tokens.size() != 2) { throw UsageError("illegal configuration line '%1%' in '%2%'", line, path); } auto p = absPath(tokens[1], dirOf(path)); if (pathExists(p)) { applyConfigFile(p); } else if (!ignoreMissing) { throw Error("file '%1%' included from '%2%' not found", p, path); } continue; } if (tokens[1] != "=") { throw UsageError("illegal configuration line '%1%' in '%2%'", line, path); } std::string name = tokens[0]; auto i = tokens.begin(); advance(i, 2); set(name, concatStringsSep(" ", Strings(i, tokens.end()))); // FIXME: slow }; } catch (SysError&) { } } void Config::resetOverriden() { for (auto& s : _settings) { s.second.setting->overriden = false; } } void Config::toJSON(JSONObject& out) { for (auto& s : _settings) { if (!s.second.isAlias) { JSONObject out2(out.object(s.first)); out2.attr("description", s.second.setting->description); JSONPlaceholder out3(out2.placeholder("value")); s.second.setting->toJSON(out3); } } } void Config::convertToArgs(Args& args, const std::string& category) { for (auto& s : _settings) { if (!s.second.isAlias) { s.second.setting->convertToArg(args, category); } } } AbstractSetting::AbstractSetting(std::string name, std::string description, std::set<std::string> aliases) : name(std::move(name)), description(std::move(description)), aliases(std::move(aliases)) {} void AbstractSetting::toJSON(JSONPlaceholder& out) { out.write(to_string()); } void AbstractSetting::convertToArg(Args& args, const std::string& category) {} template <typename T> void BaseSetting<T>::toJSON(JSONPlaceholder& out) { out.write(value); } template <typename T> void BaseSetting<T>::convertToArg(Args& args, const std::string& category) { args.mkFlag() .longName(name) .description(description) .arity(1) .handler([=](std::vector<std::string> ss) { overriden = true; set(ss[0]); }) .category(category); } template <> void BaseSetting<std::string>::set(const std::string& str) { value = str; } template <> std::string BaseSetting<std::string>::to_string() { return value; } template <typename T> void BaseSetting<T>::set(const std::string& str) { static_assert(std::is_integral<T>::value, "Integer required."); if (!absl::SimpleAtoi(str, &value)) { throw UsageError("setting '%s' has invalid value '%s'", name, str); } } template <typename T> std::string BaseSetting<T>::to_string() { static_assert(std::is_integral<T>::value, "Integer required."); return std::to_string(value); } template <> void BaseSetting<bool>::set(const std::string& str) { if (str == "true" || str == "yes" || str == "1") { value = true; } else if (str == "false" || str == "no" || str == "0") { value = false; } else { throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str); } } template <> std::string BaseSetting<bool>::to_string() { return value ? "true" : "false"; } template <> void BaseSetting<bool>::convertToArg(Args& args, const std::string& category) { args.mkFlag() .longName(name) .description(description) .handler([=](const std::vector<std::string>& ss) { override(true); }) .category(category); args.mkFlag() .longName("no-" + name) .description(description) .handler([=](const std::vector<std::string>& ss) { override(false); }) .category(category); } template <> void BaseSetting<Strings>::set(const std::string& str) { value = absl::StrSplit(str, absl::ByAnyChar(" \t\n\r")); } template <> std::string BaseSetting<Strings>::to_string() { return concatStringsSep(" ", value); } template <> void BaseSetting<Strings>::toJSON(JSONPlaceholder& out) { JSONList list(out.list()); for (auto& s : value) { list.elem(s); } } template <> void BaseSetting<StringSet>::set(const std::string& str) { value = absl::StrSplit(str, absl::ByAnyChar(" \t\n\r")); } template <> std::string BaseSetting<StringSet>::to_string() { return concatStringsSep(" ", value); } template <> void BaseSetting<StringSet>::toJSON(JSONPlaceholder& out) { JSONList list(out.list()); for (auto& s : value) { list.elem(s); } } template class BaseSetting<int>; template class BaseSetting<unsigned int>; template class BaseSetting<long>; template class BaseSetting<unsigned long>; template class BaseSetting<long long>; template class BaseSetting<unsigned long long>; template class BaseSetting<bool>; template class BaseSetting<std::string>; template class BaseSetting<Strings>; template class BaseSetting<StringSet>; void PathSetting::set(const std::string& str) { if (str.empty()) { if (allowEmpty) { value = ""; } else { throw UsageError("setting '%s' cannot be empty", name); } } else { value = canonPath(str); } } bool GlobalConfig::set(const std::string& name, const std::string& value) { for (auto& config : *configRegistrations) { if (config->set(name, value)) { return true; } } unknownSettings.emplace(name, value); return false; } void GlobalConfig::getSettings(std::map<std::string, SettingInfo>& res, bool overridenOnly) { for (auto& config : *configRegistrations) { config->getSettings(res, overridenOnly); } } void GlobalConfig::resetOverriden() { for (auto& config : *configRegistrations) { config->resetOverriden(); } } void GlobalConfig::toJSON(JSONObject& out) { for (auto& config : *configRegistrations) { config->toJSON(out); } } void GlobalConfig::convertToArgs(Args& args, const std::string& category) { for (auto& config : *configRegistrations) { config->convertToArgs(args, category); } } GlobalConfig globalConfig; GlobalConfig::ConfigRegistrations* GlobalConfig::configRegistrations; GlobalConfig::Register::Register(Config* config) { if (configRegistrations == nullptr) { configRegistrations = new ConfigRegistrations; } configRegistrations->emplace_back(config); } } // namespace nix