config_patcher.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. #include "config_patcher.h"
  2. #include <library/cpp/yconf/patcher/unstrict_config.h>
  3. #include <library/cpp/json/json_reader.h>
  4. #include <library/cpp/json/json_prettifier.h>
  5. #include <util/generic/maybe.h>
  6. #include <util/generic/ptr.h>
  7. #include <util/generic/xrange.h>
  8. #include <util/stream/file.h>
  9. #include <util/string/subst.h>
  10. #include <array>
  11. namespace {
  12. class TWrapper {
  13. public:
  14. TWrapper(const TString& base, const NJson::TJsonValue& patch, const TString& prefix);
  15. void Patch();
  16. TString GetPatchedConfig() {
  17. Patch();
  18. return PatchedConfixText;
  19. }
  20. private:
  21. void Preprocess();
  22. void Postprocess();
  23. private:
  24. static const TString IncludeDirective;
  25. static const TString IncludeGuard;
  26. private:
  27. TString BaseConfigText;
  28. THolder<TUnstrictConfig> Config;
  29. NJson::TJsonValue JsonPatch;
  30. TString Prefix;
  31. TString PatchedConfixText;
  32. bool Patched;
  33. bool NeedsPostprocessing;
  34. };
  35. const TString TWrapper::IncludeDirective = "#include";
  36. const TString TWrapper::IncludeGuard = "__INCLUDE__ :";
  37. TWrapper::TWrapper(const TString& base, const NJson::TJsonValue& patch, const TString& prefix)
  38. : BaseConfigText(base)
  39. , JsonPatch(patch)
  40. , Prefix(prefix)
  41. , Patched(false)
  42. , NeedsPostprocessing(false)
  43. {
  44. }
  45. void TWrapper::Patch() {
  46. if (Patched)
  47. return;
  48. Preprocess();
  49. const NJson::TJsonValue::TMapType* values;
  50. if (!JsonPatch.GetMapPointer(&values))
  51. ythrow yexception() << "unable to get map pointer in the json patch.";
  52. for (const auto& value : *values) {
  53. Config->PatchEntry(value.first, value.second.GetStringRobust(), Prefix);
  54. }
  55. Postprocess();
  56. Patched = true;
  57. }
  58. void TWrapper::Preprocess() {
  59. TString parsed;
  60. size_t pos = BaseConfigText.find(IncludeDirective);
  61. if (pos != TString::npos) {
  62. NeedsPostprocessing = true;
  63. parsed = BaseConfigText.replace(pos, IncludeDirective.size(), IncludeGuard);
  64. } else {
  65. parsed = BaseConfigText;
  66. }
  67. Config.Reset(new TUnstrictConfig);
  68. if (!Config->ParseMemory(parsed.data())) {
  69. TString errors;
  70. Config->PrintErrors(errors);
  71. ythrow yexception() << "Can't parse config:" << errors;
  72. }
  73. }
  74. void TWrapper::Postprocess() {
  75. PatchedConfixText = Config->ToString();
  76. if (NeedsPostprocessing) {
  77. SubstGlobal(PatchedConfixText, IncludeGuard, IncludeDirective);
  78. }
  79. }
  80. void MakeDiff(
  81. NJson::TJsonValue& container,
  82. const TYandexConfig::Section* source,
  83. const TYandexConfig::Section* target,
  84. const TString& parentPrefix = TString()) {
  85. Y_ABORT_UNLESS(target);
  86. const TString& prefix = parentPrefix ? (parentPrefix + ".") : parentPrefix;
  87. for (const auto& [name, value]: target->GetDirectives()) {
  88. const auto p = source ? source->GetDirectives().FindPtr(name) : nullptr;
  89. if (!p || TString(*p) != value) {
  90. container[prefix + name] = value;
  91. }
  92. }
  93. if (source) {
  94. for (const auto& [name, value] : source->GetDirectives()) {
  95. if (!target->GetDirectives().contains(name)) {
  96. container[prefix + name] = "__remove__";
  97. }
  98. }
  99. }
  100. TMap<TCiString, std::array<TVector<const TYandexConfig::Section *>, 2>> alignedSections;
  101. auto fillSections = [&](const TYandexConfig::TSectionsMap& sections, size_t targetIndex) {
  102. for (const auto& [sectionName, section] : sections) {
  103. alignedSections[sectionName][targetIndex].push_back(section);
  104. }
  105. };
  106. if (source) {
  107. fillSections(source->GetAllChildren(), 0);
  108. }
  109. fillSections(target->GetAllChildren(), 1);
  110. for (const auto& [sectionName, pair]: alignedSections) {
  111. // Cannot use structured binding on std::array here because of a bug in MSVC.
  112. const auto& sourceSections = pair[0];
  113. const auto& targetSections = pair[1];
  114. const bool needsIndex = targetSections.size() > 1;
  115. if (targetSections.empty()) {
  116. Y_ABORT_UNLESS(source);
  117. container[prefix + sectionName] = "__remove_all__";
  118. } else {
  119. for (const size_t i: xrange(targetSections.size())) {
  120. MakeDiff(
  121. container,
  122. i < sourceSections.size() ? sourceSections[i] : nullptr,
  123. targetSections[i],
  124. !needsIndex ? prefix + sectionName : prefix + sectionName + '[' + ToString(i) + ']');
  125. }
  126. if (sourceSections.size() > targetSections.size()) {
  127. container[
  128. prefix +
  129. sectionName +
  130. '[' +
  131. ToString(targetSections.size()) +
  132. ':' +
  133. ToString(sourceSections.size() - 1) +
  134. ']'] = "__remove__";
  135. }
  136. }
  137. }
  138. if (target->GetAllChildren().empty() && target->GetDirectives().empty()) {
  139. container[prefix] = "__add_section__";
  140. }
  141. }
  142. }
  143. namespace NConfigPatcher {
  144. TString Patch(const TString& config, const TString& patch, const TOptions& options) {
  145. if (!patch) {
  146. return config;
  147. }
  148. NJson::TJsonValue parsedPatch;
  149. TStringInput ss(patch);
  150. if (!NJson::ReadJsonTree(&ss, true, &parsedPatch, true)) {
  151. ythrow yexception() << "Cannot parse patch as json";
  152. }
  153. return Patch(config, parsedPatch, options);
  154. }
  155. TString Patch(const TString& config, const NJson::TJsonValue& parsedPatch, const TOptions& options) {
  156. TWrapper patcher(config, parsedPatch, options.Prefix);
  157. return patcher.GetPatchedConfig();
  158. }
  159. TString Diff(const TString& sourceText, const TString& targetText) {
  160. TUnstrictConfig source;
  161. if (!source.ParseMemory(sourceText.data())) {
  162. throw yexception() << "Cannot parse source config";
  163. }
  164. TUnstrictConfig target;
  165. if (!target.ParseMemory(targetText.data())) {
  166. throw yexception() << "Cannot parse target config";
  167. }
  168. NJson::TJsonValue diff(NJson::JSON_MAP);
  169. MakeDiff(diff, source.GetRootSection(), target.GetRootSection());
  170. return NJson::PrettifyJson(diff.GetStringRobust());
  171. }
  172. }