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")