From 247e636a4dbff129a45fc548e057a8460280ac15 Mon Sep 17 00:00:00 2001 From: pv Date: Tue, 1 Aug 2023 01:31:02 +0300 Subject: [PATCH] Add yaml templates --- src/App/Project.hpp | 2 +- .../Declarative/Yaml/YamlReader.Template.cpp | 204 ++++++++++++++++++ .../Tweaks/Declarative/Yaml/YamlReader.cpp | 1 + .../Tweaks/Declarative/Yaml/YamlReader.hpp | 1 + src/App/Version.rc | 8 +- xmake.lua | 2 +- 6 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 src/App/Tweaks/Declarative/Yaml/YamlReader.Template.cpp diff --git a/src/App/Project.hpp b/src/App/Project.hpp index 07a4324..ef32237 100644 --- a/src/App/Project.hpp +++ b/src/App/Project.hpp @@ -12,5 +12,5 @@ constexpr auto Author = "psiberx"; constexpr auto NameW = L"TweakXL"; constexpr auto AuthorW = L"psiberx"; -constexpr auto Version = semver::from_string_noexcept("1.1.7").value(); +constexpr auto Version = semver::from_string_noexcept("1.2.0").value(); } diff --git a/src/App/Tweaks/Declarative/Yaml/YamlReader.Template.cpp b/src/App/Tweaks/Declarative/Yaml/YamlReader.Template.cpp new file mode 100644 index 0000000..1275427 --- /dev/null +++ b/src/App/Tweaks/Declarative/Yaml/YamlReader.Template.cpp @@ -0,0 +1,204 @@ +#include "YamlReader.hpp" + +namespace +{ +constexpr auto TemplateAttrKey = "$data"; + +constexpr auto AttrMark = '$'; +constexpr auto AttrOpen = '{'; +constexpr auto AttrClose = '}'; + +uint64_t MakeKey(const std::string& aName) +{ + return Red::FNV1a64(aName.c_str()); +} + +uint64_t MakeKey(const char* aName, uint32_t aSize) +{ + return Red::FNV1a64(reinterpret_cast(aName), aSize); +} + +std::string FormatString(const std::string& aInput, const Core::Map& aData) +{ + constexpr auto MaxLength = 512; + + char buffer[MaxLength + 1]; + char* out = buffer; + const char* max = buffer + MaxLength; + const char* str = aInput.c_str(); + + while (str && *str) + { + auto* attrOpen = strchr(str, AttrMark); + + if (!attrOpen) + { + if (out == buffer) + { + return aInput; + } + + while (*str && out < max) + { + *out = *str; + ++out; + ++str; + } + break; + } + + if (*(attrOpen + 1) != AttrOpen) + { + return aInput; + } + + auto* attrClose = strchr(str, AttrClose); + + if (!attrClose) + { + return aInput; + } + + while (str != attrOpen && out < max) + { + *out = *str; + ++out; + ++str; + } + + if (out == max) + { + break; + } + + str = attrClose + 1; + + const auto attr = MakeKey(attrOpen + 2, attrClose - attrOpen - 2); + const char* value = nullptr; + + const auto it = aData.find(attr); + if (it != aData.end()) + { + value = it.value().c_str(); + } + + if (value) + { + while (value && *value && out < max) + { + *out = *value; + ++out; + ++value; + } + + if (out == max) + { + break; + } + } + } + + *out = '\0'; + + return buffer; +} + +YAML::Node FormatNode(const YAML::Node& aNode, const Core::Map& aData) +{ + switch (aNode.Type()) + { + case YAML::NodeType::Scalar: + { + const auto& value = aNode.Scalar(); + if (value.find(AttrMark) != std::string::npos) + { + return YAML::Node(FormatString(value, aData)); + } + else + { + return aNode; + } + } + case YAML::NodeType::Map: + { + YAML::Node node; + for (auto& nodeIt : aNode) + { + node[nodeIt.first] = FormatNode(nodeIt.second, aData); + } + return node; + } + case YAML::NodeType::Sequence: + { + YAML::Node node; + for (std::size_t i = 0; i < aNode.size(); ++i) + { + node[i] = FormatNode(aNode[i], aData); + } + return node; + } + default: + return aNode; + } +} +} + +void App::YamlReader::ExpandTemplates(YAML::Node& aRootNode) +{ + Core::Vector> templates; + + for (const auto& nodeIt : aRootNode) + { + const auto& subKey = nodeIt.first.Scalar(); + const auto& subNode = nodeIt.second; + + if (!subNode.IsMap() || !subNode[TemplateAttrKey].IsDefined()) + continue; + + templates.emplace_back(subKey, subNode); + } + + for (auto& [templateName, templateNode] : templates) + { + aRootNode.remove(templateName); + + const auto& dataNode = templateNode[TemplateAttrKey]; + + if (!dataNode.IsSequence()) + { + LogError("{}: Template data must be an array of structs.", templateName); + continue; + } + + templateNode.remove(TemplateAttrKey); + + for (std::size_t i = 0; i < dataNode.size(); ++i) + { + Core::Map instanceData; + + for (const auto& propIt : dataNode[i]) + { + const auto propKey = MakeKey(propIt.first.Scalar()); + const auto& propValue = propIt.second; + + if (!propValue.IsScalar()) + continue; + + instanceData[propKey] = propValue.Scalar(); + } + + auto instanceName = FormatString(templateName, instanceData); + + if (aRootNode[instanceName].IsDefined()) + { + LogError("{}: Cannot instantiate {}, because it already exists.", + templateName, instanceName); + continue; + } + + auto instanceNode = FormatNode(templateNode, instanceData); + + aRootNode[instanceName] = instanceNode; + } + } +} diff --git a/src/App/Tweaks/Declarative/Yaml/YamlReader.cpp b/src/App/Tweaks/Declarative/Yaml/YamlReader.cpp index 5f27a40..8686eb2 100644 --- a/src/App/Tweaks/Declarative/Yaml/YamlReader.cpp +++ b/src/App/Tweaks/Declarative/Yaml/YamlReader.cpp @@ -68,6 +68,7 @@ void App::YamlReader::Read(App::TweakChangeset& aChangeset) } ConvertLegacyNodes(); + ExpandTemplates(m_data); auto propMode = ResolvePropertyMode(m_data); diff --git a/src/App/Tweaks/Declarative/Yaml/YamlReader.hpp b/src/App/Tweaks/Declarative/Yaml/YamlReader.hpp index f5aebc5..a229e4b 100644 --- a/src/App/Tweaks/Declarative/Yaml/YamlReader.hpp +++ b/src/App/Tweaks/Declarative/Yaml/YamlReader.hpp @@ -60,6 +60,7 @@ class YamlReader Red::InstancePtr<> MakeValue(const Red::CBaseRTTIType* aType, const YAML::Node& aNode); std::pair> TryMakeValue(const YAML::Node& aNode); + void ExpandTemplates(YAML::Node& aRootNode); void ConvertLegacyNodes(); std::filesystem::path m_path; diff --git a/src/App/Version.rc b/src/App/Version.rc index b79de85..636cbe9 100644 --- a/src/App/Version.rc +++ b/src/App/Version.rc @@ -1,9 +1,9 @@ -#define VER_PRODUCTVERSION 1,1,7,0 -#define VER_FILEVERSION 1,1,7,2306261621 +#define VER_PRODUCTVERSION 1,2,0,0 +#define VER_FILEVERSION 1,2,0,2308010056 #define VER_PRODUCTNAME_STR "TweakXL\0" -#define VER_PRODUCTVERSION_STR "1.1.7\0" -#define VER_FILEVERSION_STR "1.1.7.2306261621\0" +#define VER_PRODUCTVERSION_STR "1.2.0\0" +#define VER_FILEVERSION_STR "1.2.0.2308010056\0" 1 VERSIONINFO FILEVERSION VER_FILEVERSION diff --git a/xmake.lua b/xmake.lua index d2897d4..f8c9570 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,7 +1,7 @@ set_xmakever("2.5.9") set_project("TweakXL") -set_version("1.1.7", {build = "%y%m%d%H%M"}) +set_version("1.2.0", {build = "%y%m%d%H%M"}) set_arch("x64") set_languages("cxx20", "cxx2a")