123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- #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_ABORT_UNLESS(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_ABORT_UNLESS(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());
- }
- }
|