Skip to content

Commit

Permalink
feat(config): support append and merge syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
kionz authored and lotem committed Sep 13, 2017
1 parent 14ec858 commit 04dcf42
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 23 deletions.
124 changes: 116 additions & 8 deletions src/rime/config/config_compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,21 @@ bool PendingChild::Resolve(ConfigCompiler* compiler) {
static an<ConfigItem> ResolveReference(ConfigCompiler* compiler,
const Reference& reference);

static bool MergeTree(an<ConfigItemRef> target, an<ConfigMap> 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<ConfigMap>(**target);
*target = included;
if (overrides && !overrides->empty() && !MergeTree(target, overrides)) {
LOG(ERROR) << "failed to merge tree: " << reference;
return false;
}
return true;
}

Expand All @@ -152,19 +160,119 @@ bool PatchReference::Resolve(ConfigCompiler* compiler) {
return patch.Resolve(compiler);
}

static bool AppendToString(an<ConfigItemRef> target, an<ConfigValue> value) {
if (!value)
return false;
auto existing_value = As<ConfigValue>(**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<ConfigItemRef> target, an<ConfigList> list) {
if (!list)
return false;
auto existing_list = As<ConfigList>(**target);
if (!existing_list) {
LOG(ERROR) << "trying to append list to other value";
return false;
}
if (list->empty())
return true;
auto copy = New<ConfigList>(*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<ConfigItemRef> target,
const string& key,
const an<ConfigItem>& value,
bool merge_tree);

static bool MergeTree(an<ConfigItemRef> target, an<ConfigMap> 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<ConfigItem>& value,
bool merge_tree) {
return key == ConfigCompiler::MERGE_DIRECTIVE ||
boost::ends_with(key, ADD_SUFFIX_OPERATOR) ||
(merge_tree && Is<ConfigMap>(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<ConfigItemRef> root, const string& path,
an<ConfigItem> item);
function<bool (an<ConfigItemRef> target)> writer);

static bool EditNode(an<ConfigItemRef> target,
const string& key,
const an<ConfigItem>& value,
bool merge_tree) {
DLOG(INFO) << "EditNode(" << key << "," << merge_tree << ")";
bool appending = IsAppending(key);
bool merging = IsMerging(key, value, merge_tree);
auto writer = [=](an<ConfigItemRef> target) {
if ((appending || merging) && **target) {
DLOG(INFO) << "writer: editing node";
return !value ||
(appending && (AppendToString(target, As<ConfigValue>(value)) ||
AppendToList(target, As<ConfigList>(value)))) ||
(merging && MergeTree(target, As<ConfigMap>(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;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/rime/config/config_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
35 changes: 20 additions & 15 deletions src/rime/config/config_data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,17 @@ class ConfigMapEntryCowRef : public ConfigItemRef {
return map ? map->Get(key_) : nullptr;
}
void SetItem(an<ConfigItem> item) override {
auto copy = Cow(As<ConfigMap>(**parent_), key_);
copy->Set(key_, item);
*parent_ = copy;
auto map = As<ConfigMap>(**parent_);
if (!copied_) {
*parent_ = map = Cow(map, key_);
copied_ = true;
}
map->Set(key_, item);
}
protected:
an<ConfigItemRef> parent_;
string key_;
bool copied_ = false;
};

class ConfigListEntryCowRef : public ConfigMapEntryCowRef {
Expand All @@ -195,9 +199,12 @@ class ConfigListEntryCowRef : public ConfigMapEntryCowRef {
return list ? list->GetAt(index(list, true)) : nullptr;
}
void SetItem(an<ConfigItem> item) override {
auto copy = Cow(As<ConfigList>(**parent_), key_);
copy->SetAt(index(copy, false), item);
*parent_ = copy;
auto list = As<ConfigList>(**parent_);
if (!copied_) {
*parent_ = list = Cow(list, key_);
copied_ = true;
}
list->SetAt(index(list, false), item);
}
private:
size_t index(an<ConfigList> list, bool read_only) const {
Expand All @@ -206,11 +213,10 @@ class ConfigListEntryCowRef : public ConfigMapEntryCowRef {
};

bool TraverseCopyOnWrite(an<ConfigItemRef> root, const string& path,
an<ConfigItem> item) {
function<bool (an<ConfigItemRef> target)> writer) {
DLOG(INFO) << "TraverseCopyOnWrite(" << path << ")";
if (path.empty() || path == "/") {
*root = item;
return true;
return writer(root);
}
an<ConfigItemRef> head = root;
vector<string> keys = ConfigData::SplitPath(path);
Expand All @@ -230,18 +236,17 @@ bool TraverseCopyOnWrite(an<ConfigItemRef> root, const string& path,
head = New<ConfigMapEntryCowRef>(head, key);
}
}
*head = item;
return true;
return writer(head);
}

bool ConfigData::TraverseWrite(const string& path, an<ConfigItem> item) {
LOG(INFO) << "write: " << path;
auto root = New<ConfigDataRootRef>(this);
bool result = TraverseCopyOnWrite(root, path, item);
if (result) {
return TraverseCopyOnWrite(root, path, [=](an<ConfigItemRef> target) {
*target = item;
set_modified();
}
return result;
return true;
});
}

vector<string> ConfigData::SplitPath(const string& path) {
Expand Down
16 changes: 16 additions & 0 deletions src/rime/config/config_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class ConfigItem {

ValueType type() const { return type_; }

virtual bool empty() const {
return type_ == kNull;
}

protected:
ConfigItem(ValueType type) : type_(type) {}

Expand Down Expand Up @@ -50,6 +54,10 @@ class ConfigValue : public ConfigItem {

const string& str() const { return value_; }

bool empty() const override {
return value_.empty();
}

protected:
string value_;
};
Expand All @@ -72,6 +80,10 @@ class ConfigList : public ConfigItem {
Iterator begin();
Iterator end();

bool empty() const override {
return seq_.empty();
}

protected:
Sequence seq_;
};
Expand All @@ -92,6 +104,10 @@ class ConfigMap : public ConfigItem {
Iterator begin();
Iterator end();

bool empty() const override {
return map_.empty();
}

protected:
Map map_;
};
Expand Down

0 comments on commit 04dcf42

Please sign in to comment.