Skip to content

Commit 04dcf42

Browse files
kionzlotem
authored andcommitted
feat(config): support append and merge syntax
1 parent 14ec858 commit 04dcf42

File tree

4 files changed

+154
-23
lines changed

4 files changed

+154
-23
lines changed

src/rime/config/config_compiler.cc

+116-8
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,21 @@ bool PendingChild::Resolve(ConfigCompiler* compiler) {
127127
static an<ConfigItem> ResolveReference(ConfigCompiler* compiler,
128128
const Reference& reference);
129129

130+
static bool MergeTree(an<ConfigItemRef> target, an<ConfigMap> map);
131+
130132
bool IncludeReference::Resolve(ConfigCompiler* compiler) {
131133
DLOG(INFO) << "IncludeReference::Resolve(reference = " << reference << ")";
132-
auto item = ResolveReference(compiler, reference);
133-
if (!item) {
134+
auto included = ResolveReference(compiler, reference);
135+
if (!included) {
134136
return reference.optional;
135137
}
136-
*target = item;
138+
// merge literal key-values into the included map
139+
auto overrides = As<ConfigMap>(**target);
140+
*target = included;
141+
if (overrides && !overrides->empty() && !MergeTree(target, overrides)) {
142+
LOG(ERROR) << "failed to merge tree: " << reference;
143+
return false;
144+
}
137145
return true;
138146
}
139147

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

163+
static bool AppendToString(an<ConfigItemRef> target, an<ConfigValue> value) {
164+
if (!value)
165+
return false;
166+
auto existing_value = As<ConfigValue>(**target);
167+
if (!existing_value) {
168+
LOG(ERROR) << "trying to append string to non scalar";
169+
return false;
170+
}
171+
*target = existing_value->str() + value->str();
172+
return true;
173+
}
174+
175+
static bool AppendToList(an<ConfigItemRef> target, an<ConfigList> list) {
176+
if (!list)
177+
return false;
178+
auto existing_list = As<ConfigList>(**target);
179+
if (!existing_list) {
180+
LOG(ERROR) << "trying to append list to other value";
181+
return false;
182+
}
183+
if (list->empty())
184+
return true;
185+
auto copy = New<ConfigList>(*existing_list);
186+
for (ConfigList::Iterator iter = list->begin(); iter != list->end(); ++iter) {
187+
if (!copy->Append(*iter))
188+
return false;
189+
}
190+
*target = copy;
191+
return true;
192+
}
193+
194+
static bool EditNode(an<ConfigItemRef> target,
195+
const string& key,
196+
const an<ConfigItem>& value,
197+
bool merge_tree);
198+
199+
static bool MergeTree(an<ConfigItemRef> target, an<ConfigMap> map) {
200+
if (!map)
201+
return false;
202+
// NOTE: the referenced content of target can be any type
203+
for (ConfigMap::Iterator iter = map->begin(); iter != map->end(); ++iter) {
204+
const auto& key = iter->first;
205+
const auto& value = iter->second;
206+
if (!EditNode(target, key, value, true)) {
207+
LOG(ERROR) << "error merging branch " << key;
208+
return false;
209+
}
210+
}
211+
return true;
212+
}
213+
214+
static constexpr const char* ADD_SUFFIX_OPERATOR = "/+";
215+
static constexpr const char* EQU_SUFFIX_OPERATOR = "/=";
216+
217+
inline static bool IsAppending(const string& key) {
218+
return key == ConfigCompiler::APPEND_DIRECTIVE ||
219+
boost::ends_with(key, ADD_SUFFIX_OPERATOR);
220+
}
221+
inline static bool IsMerging(const string& key,
222+
const an<ConfigItem>& value,
223+
bool merge_tree) {
224+
return key == ConfigCompiler::MERGE_DIRECTIVE ||
225+
boost::ends_with(key, ADD_SUFFIX_OPERATOR) ||
226+
(merge_tree && Is<ConfigMap>(value) &&
227+
!boost::ends_with(key, EQU_SUFFIX_OPERATOR));
228+
}
229+
230+
inline static string StripOperator(const string& key, bool adding) {
231+
return (key == ConfigCompiler::APPEND_DIRECTIVE ||
232+
key == ConfigCompiler::MERGE_DIRECTIVE) ? "" :
233+
boost::erase_last_copy(
234+
key, adding ? ADD_SUFFIX_OPERATOR : EQU_SUFFIX_OPERATOR);
235+
}
236+
155237
// defined in config_data.cc
156238
bool TraverseCopyOnWrite(an<ConfigItemRef> root, const string& path,
157-
an<ConfigItem> item);
239+
function<bool (an<ConfigItemRef> target)> writer);
240+
241+
static bool EditNode(an<ConfigItemRef> target,
242+
const string& key,
243+
const an<ConfigItem>& value,
244+
bool merge_tree) {
245+
DLOG(INFO) << "EditNode(" << key << "," << merge_tree << ")";
246+
bool appending = IsAppending(key);
247+
bool merging = IsMerging(key, value, merge_tree);
248+
auto writer = [=](an<ConfigItemRef> target) {
249+
if ((appending || merging) && **target) {
250+
DLOG(INFO) << "writer: editing node";
251+
return !value ||
252+
(appending && (AppendToString(target, As<ConfigValue>(value)) ||
253+
AppendToList(target, As<ConfigList>(value)))) ||
254+
(merging && MergeTree(target, As<ConfigMap>(value)));
255+
} else {
256+
DLOG(INFO) << "writer: overwriting node";
257+
*target = value;
258+
return true;
259+
}
260+
};
261+
string path = StripOperator(key, appending || merging);
262+
DLOG(INFO) << "appending: " << appending << ", merging: " << merging
263+
<< ", path: " << path;
264+
return TraverseCopyOnWrite(target, path, writer);
265+
}
158266

159267
bool PatchLiteral::Resolve(ConfigCompiler* compiler) {
160268
DLOG(INFO) << "PatchLiteral::Resolve()";
161269
bool success = true;
162270
for (const auto& entry : *patch) {
163-
const auto& path = entry.first;
271+
const auto& key = entry.first;
164272
const auto& value = entry.second;
165-
LOG(INFO) << "patching " << path;
166-
if (!TraverseCopyOnWrite(target, path, value)) {
167-
LOG(ERROR) << "error applying patch to " << path;
273+
LOG(INFO) << "patching " << key;
274+
if (!EditNode(target, key, value, false)) {
275+
LOG(ERROR) << "error applying patch to " << key;
168276
success = false;
169277
}
170278
}

src/rime/config/config_compiler.h

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class ConfigCompiler {
4343
public:
4444
static constexpr const char* INCLUDE_DIRECTIVE = "__include";
4545
static constexpr const char* PATCH_DIRECTIVE = "__patch";
46+
static constexpr const char* APPEND_DIRECTIVE = "__append";
47+
static constexpr const char* MERGE_DIRECTIVE = "__merge";
4648

4749
explicit ConfigCompiler(ResourceResolver* resource_resolver);
4850
virtual ~ConfigCompiler();

src/rime/config/config_data.cc

+20-15
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,17 @@ class ConfigMapEntryCowRef : public ConfigItemRef {
176176
return map ? map->Get(key_) : nullptr;
177177
}
178178
void SetItem(an<ConfigItem> item) override {
179-
auto copy = Cow(As<ConfigMap>(**parent_), key_);
180-
copy->Set(key_, item);
181-
*parent_ = copy;
179+
auto map = As<ConfigMap>(**parent_);
180+
if (!copied_) {
181+
*parent_ = map = Cow(map, key_);
182+
copied_ = true;
183+
}
184+
map->Set(key_, item);
182185
}
183186
protected:
184187
an<ConfigItemRef> parent_;
185188
string key_;
189+
bool copied_ = false;
186190
};
187191

188192
class ConfigListEntryCowRef : public ConfigMapEntryCowRef {
@@ -195,9 +199,12 @@ class ConfigListEntryCowRef : public ConfigMapEntryCowRef {
195199
return list ? list->GetAt(index(list, true)) : nullptr;
196200
}
197201
void SetItem(an<ConfigItem> item) override {
198-
auto copy = Cow(As<ConfigList>(**parent_), key_);
199-
copy->SetAt(index(copy, false), item);
200-
*parent_ = copy;
202+
auto list = As<ConfigList>(**parent_);
203+
if (!copied_) {
204+
*parent_ = list = Cow(list, key_);
205+
copied_ = true;
206+
}
207+
list->SetAt(index(list, false), item);
201208
}
202209
private:
203210
size_t index(an<ConfigList> list, bool read_only) const {
@@ -206,11 +213,10 @@ class ConfigListEntryCowRef : public ConfigMapEntryCowRef {
206213
};
207214

208215
bool TraverseCopyOnWrite(an<ConfigItemRef> root, const string& path,
209-
an<ConfigItem> item) {
216+
function<bool (an<ConfigItemRef> target)> writer) {
210217
DLOG(INFO) << "TraverseCopyOnWrite(" << path << ")";
211218
if (path.empty() || path == "/") {
212-
*root = item;
213-
return true;
219+
return writer(root);
214220
}
215221
an<ConfigItemRef> head = root;
216222
vector<string> keys = ConfigData::SplitPath(path);
@@ -230,18 +236,17 @@ bool TraverseCopyOnWrite(an<ConfigItemRef> root, const string& path,
230236
head = New<ConfigMapEntryCowRef>(head, key);
231237
}
232238
}
233-
*head = item;
234-
return true;
239+
return writer(head);
235240
}
236241

237242
bool ConfigData::TraverseWrite(const string& path, an<ConfigItem> item) {
238243
LOG(INFO) << "write: " << path;
239244
auto root = New<ConfigDataRootRef>(this);
240-
bool result = TraverseCopyOnWrite(root, path, item);
241-
if (result) {
245+
return TraverseCopyOnWrite(root, path, [=](an<ConfigItemRef> target) {
246+
*target = item;
242247
set_modified();
243-
}
244-
return result;
248+
return true;
249+
});
245250
}
246251

247252
vector<string> ConfigData::SplitPath(const string& path) {

src/rime/config/config_types.h

+16
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class ConfigItem {
2222

2323
ValueType type() const { return type_; }
2424

25+
virtual bool empty() const {
26+
return type_ == kNull;
27+
}
28+
2529
protected:
2630
ConfigItem(ValueType type) : type_(type) {}
2731

@@ -50,6 +54,10 @@ class ConfigValue : public ConfigItem {
5054

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

57+
bool empty() const override {
58+
return value_.empty();
59+
}
60+
5361
protected:
5462
string value_;
5563
};
@@ -72,6 +80,10 @@ class ConfigList : public ConfigItem {
7280
Iterator begin();
7381
Iterator end();
7482

83+
bool empty() const override {
84+
return seq_.empty();
85+
}
86+
7587
protected:
7688
Sequence seq_;
7789
};
@@ -92,6 +104,10 @@ class ConfigMap : public ConfigItem {
92104
Iterator begin();
93105
Iterator end();
94106

107+
bool empty() const override {
108+
return map_.empty();
109+
}
110+
95111
protected:
96112
Map map_;
97113
};

0 commit comments

Comments
 (0)