libutil: extract out AbstractConfig
libutil config.{hh,cc} and libstore/globals.{hh,cc} contain so many
interrelated things. Hopefully we can separate them out a little bit for
clarity.
Change-Id: Ib78fc46fe305d13aada8886e2e168d446a6a6964
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
#include "lix/libutil/abstract-config.hh"
|
||||
#include "lix/libutil/file-system.hh"
|
||||
#include "lix/libutil/logging.hh"
|
||||
#include "lix/libutil/strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static void applyConfigInner(
|
||||
const std::string & contents,
|
||||
const ApplyConfigOptions & options,
|
||||
std::vector<std::pair<std::string, std::string>> & parsedContents
|
||||
)
|
||||
{
|
||||
unsigned int pos = 0;
|
||||
|
||||
while (pos < contents.size()) {
|
||||
std::string line;
|
||||
while (pos < contents.size() && contents[pos] != '\n') {
|
||||
line += contents[pos++];
|
||||
}
|
||||
pos++;
|
||||
|
||||
if (auto hash = line.find('#'); hash != line.npos) {
|
||||
line = std::string(line, 0, hash);
|
||||
}
|
||||
|
||||
auto tokens = tokenizeString<std::vector<std::string>>(line);
|
||||
if (tokens.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokens.size() < 2) {
|
||||
throw UsageError(
|
||||
"illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()
|
||||
);
|
||||
}
|
||||
|
||||
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, options.relativeDisplay()
|
||||
);
|
||||
}
|
||||
if (!options.path) {
|
||||
throw UsageError("can only include configuration '%1%' from files", tokens[1]);
|
||||
}
|
||||
auto pathToInclude = absPath(tildePath(tokens[1], options.home), dirOf(*options.path));
|
||||
if (pathExists(pathToInclude)) {
|
||||
auto includeOptions = ApplyConfigOptions{
|
||||
.path = pathToInclude,
|
||||
.home = options.home,
|
||||
};
|
||||
try {
|
||||
std::string includedContents = readFile(pathToInclude);
|
||||
applyConfigInner(includedContents, includeOptions, parsedContents);
|
||||
} catch (SysError &) {
|
||||
// TODO: Do we actually want to ignore this? Or is it better to fail?
|
||||
}
|
||||
} else if (!ignoreMissing) {
|
||||
throw Error(
|
||||
"file '%1%' included from '%2%' not found", pathToInclude, *options.path
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokens[1] != "=") {
|
||||
throw UsageError(
|
||||
"illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()
|
||||
);
|
||||
}
|
||||
|
||||
std::string name = std::move(tokens[0]);
|
||||
|
||||
auto i = tokens.begin();
|
||||
advance(i, 2);
|
||||
|
||||
parsedContents.push_back({
|
||||
std::move(name),
|
||||
concatStringsSep(" ", Strings(i, tokens.end())),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
AbstractConfig::AbstractConfig(StringMap initials) : unknownSettings(std::move(initials)) {}
|
||||
|
||||
void AbstractConfig::applyConfig(const std::string & contents, const ApplyConfigOptions & options)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> parsedContents;
|
||||
|
||||
applyConfigInner(contents, options, parsedContents);
|
||||
|
||||
// First apply experimental-feature related settings
|
||||
for (const auto & [name, value] : parsedContents) {
|
||||
if (name == "experimental-features" || name == "extra-experimental-features") {
|
||||
set(name, value, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Then apply other settings
|
||||
for (const auto & [name, value] : parsedContents) {
|
||||
if (name != "experimental-features" && name != "extra-experimental-features") {
|
||||
set(name, value, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractConfig::warnUnknownSettings()
|
||||
{
|
||||
for (const auto & s : unknownSettings) {
|
||||
printTaggedWarning("unknown setting '%s'", s.first);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractConfig::reapplyUnknownSettings()
|
||||
{
|
||||
auto unknownSettings2 = std::move(unknownSettings);
|
||||
unknownSettings = {};
|
||||
for (auto const & [name, value] : unknownSettings2) {
|
||||
set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "lix/libutil/apply-config-options.hh"
|
||||
#include "lix/libutil/json-fwd.hh"
|
||||
#include "lix/libutil/types.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Args;
|
||||
class AbstractSetting;
|
||||
|
||||
class AbstractConfig
|
||||
{
|
||||
// Types.
|
||||
public:
|
||||
struct SettingInfo
|
||||
{
|
||||
std::string value;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
// Fields.
|
||||
protected:
|
||||
StringMap unknownSettings;
|
||||
|
||||
// Specials.
|
||||
protected:
|
||||
AbstractConfig(StringMap initials = {});
|
||||
|
||||
// Abstract methods.
|
||||
public:
|
||||
/**
|
||||
* Sets the value referenced by `name` to `value`. Returns true if the
|
||||
* setting is known, false otherwise.
|
||||
*/
|
||||
virtual bool set(
|
||||
const std::string & name,
|
||||
const std::string & value,
|
||||
const ApplyConfigOptions & options = {}
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Adds the currently known settings to the given result map `res`.
|
||||
* - res: map to store settings in
|
||||
* - overriddenOnly: when set to true only overridden settings will be added to `res`
|
||||
*/
|
||||
virtual void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) = 0;
|
||||
|
||||
/**
|
||||
* Resets the `overridden` flag of all Settings
|
||||
*/
|
||||
virtual void resetOverridden() = 0;
|
||||
|
||||
/**
|
||||
* Outputs all settings to JSON
|
||||
* - out: JSONObject to write the configuration to
|
||||
*/
|
||||
virtual JSON toJSON() = 0;
|
||||
|
||||
/**
|
||||
* Converts settings to `Args` to be used on the command line interface
|
||||
* - args: args to write to
|
||||
* - category: category of the settings
|
||||
*/
|
||||
virtual void convertToArgs(Args & args, const std::string & category) = 0;
|
||||
|
||||
// Provided methods.
|
||||
public:
|
||||
/**
|
||||
* Parses the configuration in `contents` and applies it
|
||||
* - contents: configuration contents to be parsed and applied
|
||||
* - path: location of the configuration file
|
||||
*/
|
||||
void applyConfig(const std::string & contents, const ApplyConfigOptions & options = {});
|
||||
|
||||
/**
|
||||
* Logs a warning for each unregistered setting
|
||||
*/
|
||||
void warnUnknownSettings();
|
||||
|
||||
/**
|
||||
* Re-applies all previously attempted changes to unknown settings
|
||||
*/
|
||||
void reapplyUnknownSettings();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -66,24 +66,6 @@ void Config::addSetting(AbstractSetting * setting)
|
||||
}
|
||||
}
|
||||
|
||||
AbstractConfig::AbstractConfig(StringMap initials)
|
||||
: unknownSettings(std::move(initials))
|
||||
{ }
|
||||
|
||||
void AbstractConfig::warnUnknownSettings()
|
||||
{
|
||||
for (const auto & s : unknownSettings)
|
||||
printTaggedWarning("unknown setting '%s'", s.first);
|
||||
}
|
||||
|
||||
void AbstractConfig::reapplyUnknownSettings()
|
||||
{
|
||||
auto unknownSettings2 = std::move(unknownSettings);
|
||||
unknownSettings = {};
|
||||
for (auto & s : unknownSettings2)
|
||||
set(s.first, s.second);
|
||||
}
|
||||
|
||||
void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
|
||||
{
|
||||
for (const auto & opt : _settings)
|
||||
@@ -91,90 +73,6 @@ void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overridd
|
||||
res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description});
|
||||
}
|
||||
|
||||
|
||||
static void applyConfigInner(const std::string & contents, const ApplyConfigOptions & options, std::vector<std::pair<std::string, std::string>> & parsedContents) {
|
||||
unsigned int pos = 0;
|
||||
|
||||
while (pos < contents.size()) {
|
||||
std::string line;
|
||||
while (pos < contents.size() && contents[pos] != '\n')
|
||||
line += contents[pos++];
|
||||
pos++;
|
||||
|
||||
if (auto hash = line.find('#'); hash != line.npos)
|
||||
line = std::string(line, 0, hash);
|
||||
|
||||
auto tokens = tokenizeString<std::vector<std::string>>(line);
|
||||
if (tokens.empty()) continue;
|
||||
|
||||
if (tokens.size() < 2)
|
||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
|
||||
|
||||
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, options.relativeDisplay());
|
||||
}
|
||||
if (!options.path) {
|
||||
throw UsageError("can only include configuration '%1%' from files", tokens[1]);
|
||||
}
|
||||
auto pathToInclude = absPath(tildePath(tokens[1], options.home), dirOf(*options.path));
|
||||
if (pathExists(pathToInclude)) {
|
||||
auto includeOptions = ApplyConfigOptions {
|
||||
.path = pathToInclude,
|
||||
.home = options.home,
|
||||
};
|
||||
try {
|
||||
std::string includedContents = readFile(pathToInclude);
|
||||
applyConfigInner(includedContents, includeOptions, parsedContents);
|
||||
} catch (SysError &) {
|
||||
// TODO: Do we actually want to ignore this? Or is it better to fail?
|
||||
}
|
||||
} else if (!ignoreMissing) {
|
||||
throw Error("file '%1%' included from '%2%' not found", pathToInclude, *options.path);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokens[1] != "=")
|
||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
|
||||
|
||||
std::string name = std::move(tokens[0]);
|
||||
|
||||
auto i = tokens.begin();
|
||||
advance(i, 2);
|
||||
|
||||
parsedContents.push_back({
|
||||
std::move(name),
|
||||
concatStringsSep(" ", Strings(i, tokens.end())),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
void AbstractConfig::applyConfig(const std::string & contents, const ApplyConfigOptions & options) {
|
||||
std::vector<std::pair<std::string, std::string>> parsedContents;
|
||||
|
||||
applyConfigInner(contents, options, parsedContents);
|
||||
|
||||
// First apply experimental-feature related settings
|
||||
for (const auto & [name, value] : parsedContents)
|
||||
if (name == "experimental-features" || name == "extra-experimental-features")
|
||||
set(name, value, options);
|
||||
|
||||
// Then apply other settings
|
||||
for (const auto & [name, value] : parsedContents)
|
||||
if (name != "experimental-features" && name != "extra-experimental-features")
|
||||
set(name, value, options);
|
||||
}
|
||||
|
||||
void Config::resetOverridden()
|
||||
{
|
||||
for (auto & s : _settings)
|
||||
|
||||
+1
-64
@@ -5,6 +5,7 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "lix/libutil/abstract-config.hh"
|
||||
#include "lix/libutil/json-fwd.hh"
|
||||
#include "lix/libutil/types.hh"
|
||||
#include "lix/libutil/experimental-features.hh"
|
||||
@@ -48,70 +49,6 @@ namespace nix {
|
||||
class Args;
|
||||
class AbstractSetting;
|
||||
|
||||
class AbstractConfig
|
||||
{
|
||||
protected:
|
||||
StringMap unknownSettings;
|
||||
|
||||
AbstractConfig(StringMap initials = {});
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Sets the value referenced by `name` to `value`. Returns true if the
|
||||
* setting is known, false otherwise.
|
||||
*/
|
||||
virtual bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) = 0;
|
||||
|
||||
struct SettingInfo
|
||||
{
|
||||
std::string value;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the currently known settings to the given result map `res`.
|
||||
* - res: map to store settings in
|
||||
* - overriddenOnly: when set to true only overridden settings will be added to `res`
|
||||
*/
|
||||
virtual void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) = 0;
|
||||
|
||||
/**
|
||||
* Parses the configuration in `contents` and applies it
|
||||
* - contents: configuration contents to be parsed and applied
|
||||
* - path: location of the configuration file
|
||||
*/
|
||||
void applyConfig(const std::string & contents, const ApplyConfigOptions & options = {});
|
||||
|
||||
/**
|
||||
* Resets the `overridden` flag of all Settings
|
||||
*/
|
||||
virtual void resetOverridden() = 0;
|
||||
|
||||
/**
|
||||
* Outputs all settings to JSON
|
||||
* - out: JSONObject to write the configuration to
|
||||
*/
|
||||
virtual JSON toJSON() = 0;
|
||||
|
||||
/**
|
||||
* Converts settings to `Args` to be used on the command line interface
|
||||
* - args: args to write to
|
||||
* - category: category of the settings
|
||||
*/
|
||||
virtual void convertToArgs(Args & args, const std::string & category) = 0;
|
||||
|
||||
/**
|
||||
* Logs a warning for each unregistered setting
|
||||
*/
|
||||
void warnUnknownSettings();
|
||||
|
||||
/**
|
||||
* Re-applies all previously attempted changes to unknown settings
|
||||
*/
|
||||
void reapplyUnknownSettings();
|
||||
};
|
||||
|
||||
/**
|
||||
* A class to simplify providing configuration settings. The typical
|
||||
* use is to inherit Config and add Setting<T> members:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
libutil_sources = files(
|
||||
# keep-sorted start
|
||||
'abstract-config.cc',
|
||||
'archive.cc',
|
||||
'args.cc',
|
||||
'async-io.cc',
|
||||
@@ -58,6 +59,7 @@ libutil_sources = files(
|
||||
|
||||
libutil_headers = files(
|
||||
# keep-sorted start
|
||||
'abstract-config.hh',
|
||||
'abstract-setting-to-json.hh',
|
||||
'ansicolor.hh',
|
||||
'apply-config-options.hh',
|
||||
|
||||
Reference in New Issue
Block a user