From 04dcf42711e60cf9e4efd9a09e3bc71ea157f9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Tue, 12 Sep 2017 19:25:45 +0800 Subject: [PATCH] feat(config): support append and merge syntax --- src/rime/config/config_compiler.cc | 124 +++++++++++++++++++++++++++-- src/rime/config/config_compiler.h | 2 + src/rime/config/config_data.cc | 35 ++++---- src/rime/config/config_types.h | 16 ++++ 4 files changed, 154 insertions(+), 23 deletions(-) diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 7dce33783..d3ea0c2d0 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -127,13 +127,21 @@ bool PendingChild::Resolve(ConfigCompiler* compiler) { static an ResolveReference(ConfigCompiler* compiler, const Reference& reference); +static bool MergeTree(an target, an map); + bool IncludeReference::Resolve(ConfigCompiler* compiler) { DLOG(INFO) << "IncludeReference::Resolve(reference = " << reference << ")"; - auto item = ResolveReference(compiler, reference); - if (!item) { + auto included = ResolveReference(compiler, reference); + if (!included) { return reference.optional; } - *target = item; + // merge literal key-values into the included map + auto overrides = As(**target); + *target = included; + if (overrides && !overrides->empty() && !MergeTree(target, overrides)) { + LOG(ERROR) << "failed to merge tree: " << reference; + return false; + } return true; } @@ -152,19 +160,119 @@ bool PatchReference::Resolve(ConfigCompiler* compiler) { return patch.Resolve(compiler); } +static bool AppendToString(an target, an value) { + if (!value) + return false; + auto existing_value = As(**target); + if (!existing_value) { + LOG(ERROR) << "trying to append string to non scalar"; + return false; + } + *target = existing_value->str() + value->str(); + return true; +} + +static bool AppendToList(an target, an list) { + if (!list) + return false; + auto existing_list = As(**target); + if (!existing_list) { + LOG(ERROR) << "trying to append list to other value"; + return false; + } + if (list->empty()) + return true; + auto copy = New(*existing_list); + for (ConfigList::Iterator iter = list->begin(); iter != list->end(); ++iter) { + if (!copy->Append(*iter)) + return false; + } + *target = copy; + return true; +} + +static bool EditNode(an target, + const string& key, + const an& value, + bool merge_tree); + +static bool MergeTree(an target, an map) { + if (!map) + return false; + // NOTE: the referenced content of target can be any type + for (ConfigMap::Iterator iter = map->begin(); iter != map->end(); ++iter) { + const auto& key = iter->first; + const auto& value = iter->second; + if (!EditNode(target, key, value, true)) { + LOG(ERROR) << "error merging branch " << key; + return false; + } + } + return true; +} + +static constexpr const char* ADD_SUFFIX_OPERATOR = "/+"; +static constexpr const char* EQU_SUFFIX_OPERATOR = "/="; + +inline static bool IsAppending(const string& key) { + return key == ConfigCompiler::APPEND_DIRECTIVE || + boost::ends_with(key, ADD_SUFFIX_OPERATOR); +} +inline static bool IsMerging(const string& key, + const an& value, + bool merge_tree) { + return key == ConfigCompiler::MERGE_DIRECTIVE || + boost::ends_with(key, ADD_SUFFIX_OPERATOR) || + (merge_tree && Is(value) && + !boost::ends_with(key, EQU_SUFFIX_OPERATOR)); +} + +inline static string StripOperator(const string& key, bool adding) { + return (key == ConfigCompiler::APPEND_DIRECTIVE || + key == ConfigCompiler::MERGE_DIRECTIVE) ? "" : + boost::erase_last_copy( + key, adding ? ADD_SUFFIX_OPERATOR : EQU_SUFFIX_OPERATOR); +} + // defined in config_data.cc bool TraverseCopyOnWrite(an root, const string& path, - an item); + function target)> writer); + +static bool EditNode(an target, + const string& key, + const an& value, + bool merge_tree) { + DLOG(INFO) << "EditNode(" << key << "," << merge_tree << ")"; + bool appending = IsAppending(key); + bool merging = IsMerging(key, value, merge_tree); + auto writer = [=](an target) { + if ((appending || merging) && **target) { + DLOG(INFO) << "writer: editing node"; + return !value || + (appending && (AppendToString(target, As(value)) || + AppendToList(target, As(value)))) || + (merging && MergeTree(target, As(value))); + } else { + DLOG(INFO) << "writer: overwriting node"; + *target = value; + return true; + } + }; + string path = StripOperator(key, appending || merging); + DLOG(INFO) << "appending: " << appending << ", merging: " << merging + << ", path: " << path; + return TraverseCopyOnWrite(target, path, writer); +} bool PatchLiteral::Resolve(ConfigCompiler* compiler) { DLOG(INFO) << "PatchLiteral::Resolve()"; bool success = true; for (const auto& entry : *patch) { - const auto& path = entry.first; + const auto& key = entry.first; const auto& value = entry.second; - LOG(INFO) << "patching " << path; - if (!TraverseCopyOnWrite(target, path, value)) { - LOG(ERROR) << "error applying patch to " << path; + LOG(INFO) << "patching " << key; + if (!EditNode(target, key, value, false)) { + LOG(ERROR) << "error applying patch to " << key; success = false; } } diff --git a/src/rime/config/config_compiler.h b/src/rime/config/config_compiler.h index efeb0fe9e..16704d49f 100644 --- a/src/rime/config/config_compiler.h +++ b/src/rime/config/config_compiler.h @@ -43,6 +43,8 @@ class ConfigCompiler { public: static constexpr const char* INCLUDE_DIRECTIVE = "__include"; static constexpr const char* PATCH_DIRECTIVE = "__patch"; + static constexpr const char* APPEND_DIRECTIVE = "__append"; + static constexpr const char* MERGE_DIRECTIVE = "__merge"; explicit ConfigCompiler(ResourceResolver* resource_resolver); virtual ~ConfigCompiler(); diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index 24073bcb2..ae61d2829 100644 --- a/src/rime/config/config_data.cc +++ b/src/rime/config/config_data.cc @@ -176,13 +176,17 @@ class ConfigMapEntryCowRef : public ConfigItemRef { return map ? map->Get(key_) : nullptr; } void SetItem(an item) override { - auto copy = Cow(As(**parent_), key_); - copy->Set(key_, item); - *parent_ = copy; + auto map = As(**parent_); + if (!copied_) { + *parent_ = map = Cow(map, key_); + copied_ = true; + } + map->Set(key_, item); } protected: an parent_; string key_; + bool copied_ = false; }; class ConfigListEntryCowRef : public ConfigMapEntryCowRef { @@ -195,9 +199,12 @@ class ConfigListEntryCowRef : public ConfigMapEntryCowRef { return list ? list->GetAt(index(list, true)) : nullptr; } void SetItem(an item) override { - auto copy = Cow(As(**parent_), key_); - copy->SetAt(index(copy, false), item); - *parent_ = copy; + auto list = As(**parent_); + if (!copied_) { + *parent_ = list = Cow(list, key_); + copied_ = true; + } + list->SetAt(index(list, false), item); } private: size_t index(an list, bool read_only) const { @@ -206,11 +213,10 @@ class ConfigListEntryCowRef : public ConfigMapEntryCowRef { }; bool TraverseCopyOnWrite(an root, const string& path, - an item) { + function target)> writer) { DLOG(INFO) << "TraverseCopyOnWrite(" << path << ")"; if (path.empty() || path == "/") { - *root = item; - return true; + return writer(root); } an head = root; vector keys = ConfigData::SplitPath(path); @@ -230,18 +236,17 @@ bool TraverseCopyOnWrite(an root, const string& path, head = New(head, key); } } - *head = item; - return true; + return writer(head); } bool ConfigData::TraverseWrite(const string& path, an item) { LOG(INFO) << "write: " << path; auto root = New(this); - bool result = TraverseCopyOnWrite(root, path, item); - if (result) { + return TraverseCopyOnWrite(root, path, [=](an target) { + *target = item; set_modified(); - } - return result; + return true; + }); } vector ConfigData::SplitPath(const string& path) { diff --git a/src/rime/config/config_types.h b/src/rime/config/config_types.h index 27b0ec4ea..d88f8244a 100644 --- a/src/rime/config/config_types.h +++ b/src/rime/config/config_types.h @@ -22,6 +22,10 @@ class ConfigItem { ValueType type() const { return type_; } + virtual bool empty() const { + return type_ == kNull; + } + protected: ConfigItem(ValueType type) : type_(type) {} @@ -50,6 +54,10 @@ class ConfigValue : public ConfigItem { const string& str() const { return value_; } + bool empty() const override { + return value_.empty(); + } + protected: string value_; }; @@ -72,6 +80,10 @@ class ConfigList : public ConfigItem { Iterator begin(); Iterator end(); + bool empty() const override { + return seq_.empty(); + } + protected: Sequence seq_; }; @@ -92,6 +104,10 @@ class ConfigMap : public ConfigItem { Iterator begin(); Iterator end(); + bool empty() const override { + return map_.empty(); + } + protected: Map map_; };