diff options
author | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
---|---|---|
committer | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
commit | 06e5c21a835c0e923506c4ff27929f34e00761c2 (patch) | |
tree | 75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /library/cpp/yconf/patcher/config_patcher.cpp | |
parent | 03f024c4412e3aa613bb543cf1660176320ba8f4 (diff) | |
download | ydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz |
fix ya.make
Diffstat (limited to 'library/cpp/yconf/patcher/config_patcher.cpp')
-rw-r--r-- | library/cpp/yconf/patcher/config_patcher.cpp | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/library/cpp/yconf/patcher/config_patcher.cpp b/library/cpp/yconf/patcher/config_patcher.cpp new file mode 100644 index 0000000000..93b8649705 --- /dev/null +++ b/library/cpp/yconf/patcher/config_patcher.cpp @@ -0,0 +1,203 @@ +#include "config_patcher.h" + +#include <library/cpp/yconf/patcher/unstrict_config.h> + +#include <library/cpp/json/json_reader.h> +#include <library/cpp/json/json_prettifier.h> + +#include <util/generic/maybe.h> +#include <util/generic/ptr.h> +#include <util/generic/xrange.h> +#include <util/stream/file.h> +#include <util/string/subst.h> + +#include <array> + +namespace { + class TWrapper { + public: + TWrapper(const TString& base, const NJson::TJsonValue& patch, const TString& prefix); + + void Patch(); + TString GetPatchedConfig() { + Patch(); + return PatchedConfixText; + } + + private: + void Preprocess(); + void Postprocess(); + + private: + static const TString IncludeDirective; + static const TString IncludeGuard; + + private: + TString BaseConfigText; + THolder<TUnstrictConfig> Config; + NJson::TJsonValue JsonPatch; + TString Prefix; + TString PatchedConfixText; + bool Patched; + bool NeedsPostprocessing; + }; + + const TString TWrapper::IncludeDirective = "#include"; + const TString TWrapper::IncludeGuard = "__INCLUDE__ :"; + + TWrapper::TWrapper(const TString& base, const NJson::TJsonValue& patch, const TString& prefix) + : BaseConfigText(base) + , JsonPatch(patch) + , Prefix(prefix) + , Patched(false) + , NeedsPostprocessing(false) + { + } + + void TWrapper::Patch() { + if (Patched) + return; + Preprocess(); + + const NJson::TJsonValue::TMapType* values; + if (!JsonPatch.GetMapPointer(&values)) + ythrow yexception() << "unable to get map pointer in the json patch."; + for (const auto& value : *values) { + Config->PatchEntry(value.first, value.second.GetStringRobust(), Prefix); + } + + Postprocess(); + Patched = true; + } + + void TWrapper::Preprocess() { + TString parsed; + size_t pos = BaseConfigText.find(IncludeDirective); + if (pos != TString::npos) { + NeedsPostprocessing = true; + parsed = BaseConfigText.replace(pos, IncludeDirective.size(), IncludeGuard); + } else { + parsed = BaseConfigText; + } + Config.Reset(new TUnstrictConfig); + if (!Config->ParseMemory(parsed.data())) { + TString errors; + Config->PrintErrors(errors); + ythrow yexception() << "Can't parse config:" << errors; + } + } + + void TWrapper::Postprocess() { + PatchedConfixText = Config->ToString(); + if (NeedsPostprocessing) { + SubstGlobal(PatchedConfixText, IncludeGuard, IncludeDirective); + } + } + + void MakeDiff( + NJson::TJsonValue& container, + const TYandexConfig::Section* source, + const TYandexConfig::Section* target, + const TString& parentPrefix = TString()) { + Y_VERIFY(target); + const TString& prefix = parentPrefix ? (parentPrefix + ".") : parentPrefix; + + for (const auto& [name, value]: target->GetDirectives()) { + const auto p = source ? source->GetDirectives().FindPtr(name) : nullptr; + if (!p || TString(*p) != value) { + container[prefix + name] = value; + } + } + + if (source) { + for (const auto& [name, value] : source->GetDirectives()) { + if (!target->GetDirectives().contains(name)) { + container[prefix + name] = "__remove__"; + } + } + } + + TMap<TCiString, std::array<TVector<const TYandexConfig::Section *>, 2>> alignedSections; + + auto fillSections = [&](const TYandexConfig::TSectionsMap& sections, size_t targetIndex) { + for (const auto& [sectionName, section] : sections) { + alignedSections[sectionName][targetIndex].push_back(section); + } + }; + + if (source) { + fillSections(source->GetAllChildren(), 0); + } + fillSections(target->GetAllChildren(), 1); + + for (const auto& [sectionName, pair]: alignedSections) { + // Cannot use structured binding on std::array here because of a bug in MSVC. + const auto& sourceSections = pair[0]; + const auto& targetSections = pair[1]; + const bool needsIndex = targetSections.size() > 1; + + if (targetSections.empty()) { + Y_VERIFY(source); + container[prefix + sectionName] = "__remove_all__"; + } else { + for (const size_t i: xrange(targetSections.size())) { + MakeDiff( + container, + i < sourceSections.size() ? sourceSections[i] : nullptr, + targetSections[i], + !needsIndex ? prefix + sectionName : prefix + sectionName + '[' + ToString(i) + ']'); + } + if (sourceSections.size() > targetSections.size()) { + container[ + prefix + + sectionName + + '[' + + ToString(targetSections.size()) + + ':' + + ToString(sourceSections.size() - 1) + + ']'] = "__remove__"; + } + } + } + + if (target->GetAllChildren().empty() && target->GetDirectives().empty()) { + container[prefix] = "__add_section__"; + } + } +} + +namespace NConfigPatcher { + TString Patch(const TString& config, const TString& patch, const TOptions& options) { + if (!patch) { + return config; + } + + NJson::TJsonValue parsedPatch; + TStringInput ss(patch); + if (!NJson::ReadJsonTree(&ss, true, &parsedPatch, true)) { + ythrow yexception() << "Cannot parse patch as json"; + } + return Patch(config, parsedPatch, options); + } + + TString Patch(const TString& config, const NJson::TJsonValue& parsedPatch, const TOptions& options) { + TWrapper patcher(config, parsedPatch, options.Prefix); + return patcher.GetPatchedConfig(); + } + + TString Diff(const TString& sourceText, const TString& targetText) { + TUnstrictConfig source; + if (!source.ParseMemory(sourceText.data())) { + throw yexception() << "Cannot parse source config"; + } + + TUnstrictConfig target; + if (!target.ParseMemory(targetText.data())) { + throw yexception() << "Cannot parse target config"; + } + + NJson::TJsonValue diff(NJson::JSON_MAP); + MakeDiff(diff, source.GetRootSection(), target.GetRootSection()); + return NJson::PrettifyJson(diff.GetStringRobust()); + } +} |