Skip to content

Commit d71168e

Browse files
jimmy54jimmy8854
andauthored
feat(config): support indexed list insertion (#1081)
Add support for inserting elements at specific positions in config lists using the syntax "/N+" where N is the insertion index. Changes: - Add ParseIndexedAppend() function to parse "/N+" syntax from config keys - Extend AppendToList() to accept optional insert_pos parameter - Update IsAppending(), IsMerging(), and StripOperator() to handle indexed insertion - Add bounds checking for insertion position with error logging - Support both append and insert operations in EditNode() Examples: - "key/0+" inserts at beginning of list - "key/2+" inserts at index 2 - "key+" continues to append at end (backward compatible) This enhancement allows more precise control over list element positioning in RIME configuration files while maintaining backward compatibility. --------- Co-authored-by: jimmy54 <jimmy54@126.com>
1 parent 1d42b00 commit d71168e

1 file changed

Lines changed: 76 additions & 19 deletions

File tree

src/rime/config/config_compiler.cc

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
#include <algorithm>
2+
#include <cctype>
3+
#include <cstdlib>
4+
#include <optional>
15
#include <boost/algorithm/string.hpp>
26
#include <rime/common.h>
37
#include <rime/resource.h>
@@ -101,7 +105,9 @@ static bool AppendToString(an<ConfigItemRef> target, an<ConfigValue> value) {
101105
return true;
102106
}
103107

104-
static bool AppendToList(an<ConfigItemRef> target, an<ConfigList> list) {
108+
static bool AppendToList(an<ConfigItemRef> target,
109+
an<ConfigList> list,
110+
std::optional<size_t> insert_pos = std::nullopt) {
105111
if (!list)
106112
return false;
107113
auto existing_list = As<ConfigList>(**target);
@@ -117,9 +123,20 @@ static bool AppendToList(an<ConfigItemRef> target, an<ConfigList> list) {
117123
if (list->empty())
118124
return true;
119125
auto copy = New<ConfigList>(*existing_list);
126+
if (insert_pos && *insert_pos > copy->size()) {
127+
LOG(ERROR) << "list insert position out of range: " << *insert_pos;
128+
return false;
129+
}
130+
size_t current_index = insert_pos.value_or(copy->size());
120131
for (ConfigList::Iterator iter = list->begin(); iter != list->end(); ++iter) {
121-
if (!copy->Append(*iter))
122-
return false;
132+
if (insert_pos) {
133+
if (!copy->Insert(current_index, *iter))
134+
return false;
135+
++current_index;
136+
} else {
137+
if (!copy->Append(*iter))
138+
return false;
139+
}
123140
}
124141
*target = copy;
125142
return true;
@@ -148,25 +165,63 @@ static bool MergeTree(an<ConfigItemRef> target, an<ConfigMap> map) {
148165
static constexpr const char* ADD_SUFFIX_OPERATOR = "/+";
149166
static constexpr const char* EQU_SUFFIX_OPERATOR = "/=";
150167

151-
inline static bool IsAppending(const string& key) {
168+
static std::optional<size_t> ParseIndexedAppend(const string& key) {
169+
if (key.empty() || key.back() != '+')
170+
return std::nullopt;
171+
size_t plus_pos = key.size() - 1;
172+
size_t digit_begin = plus_pos;
173+
while (digit_begin > 0 &&
174+
std::isdigit(static_cast<unsigned char>(key[digit_begin - 1]))) {
175+
--digit_begin;
176+
}
177+
if (digit_begin == plus_pos)
178+
return std::nullopt;
179+
if (digit_begin > 0 && key[digit_begin - 1] != '/')
180+
return std::nullopt;
181+
string index_part = key.substr(digit_begin, plus_pos - digit_begin);
182+
if (index_part.empty())
183+
return std::nullopt;
184+
return static_cast<size_t>(std::strtoull(index_part.c_str(), nullptr, 10));
185+
}
186+
187+
inline static bool IsAppending(const string& key,
188+
const std::optional<size_t>& indexed_append) {
152189
return key == ConfigCompiler::APPEND_DIRECTIVE ||
153-
boost::ends_with(key, ADD_SUFFIX_OPERATOR);
190+
boost::ends_with(key, ADD_SUFFIX_OPERATOR) ||
191+
indexed_append.has_value();
154192
}
155193
inline static bool IsMerging(const string& key,
156194
const an<ConfigItem>& value,
157-
bool merge_tree) {
158-
return key == ConfigCompiler::MERGE_DIRECTIVE ||
159-
boost::ends_with(key, ADD_SUFFIX_OPERATOR) ||
195+
bool merge_tree,
196+
const std::optional<size_t>& indexed_append) {
197+
bool has_plain_add_suffix =
198+
boost::ends_with(key, ADD_SUFFIX_OPERATOR) && !indexed_append.has_value();
199+
return key == ConfigCompiler::MERGE_DIRECTIVE || has_plain_add_suffix ||
160200
(merge_tree && (!value || Is<ConfigMap>(value)) &&
161201
!boost::ends_with(key, EQU_SUFFIX_OPERATOR));
162202
}
163203

164-
inline static string StripOperator(const string& key, bool adding) {
165-
return (key == ConfigCompiler::APPEND_DIRECTIVE ||
166-
key == ConfigCompiler::MERGE_DIRECTIVE)
167-
? ""
168-
: boost::erase_last_copy(
169-
key, adding ? ADD_SUFFIX_OPERATOR : EQU_SUFFIX_OPERATOR);
204+
inline static string StripOperator(
205+
const string& key,
206+
bool adding,
207+
const std::optional<size_t>& indexed_append) {
208+
if (key == ConfigCompiler::APPEND_DIRECTIVE ||
209+
key == ConfigCompiler::MERGE_DIRECTIVE) {
210+
return "";
211+
}
212+
if (indexed_append) {
213+
auto suffix_with_slash =
214+
string("/") + std::to_string(*indexed_append) + "+";
215+
if (boost::ends_with(key, suffix_with_slash)) {
216+
return key.substr(0, key.size() - suffix_with_slash.size());
217+
}
218+
auto suffix_plain = std::to_string(*indexed_append) + "+";
219+
if (boost::ends_with(key, suffix_plain)) {
220+
return key.substr(0, key.size() - suffix_plain.size());
221+
}
222+
}
223+
return boost::erase_last_copy(
224+
key, adding ? ADD_SUFFIX_OPERATOR : EQU_SUFFIX_OPERATOR);
170225
}
171226

172227
// defined in config_data.cc
@@ -180,9 +235,10 @@ static bool EditNode(an<ConfigItemRef> head,
180235
const an<ConfigItem>& value,
181236
bool merge_tree) {
182237
DLOG(INFO) << "edit node: " << key << ", merge_tree: " << merge_tree;
183-
bool appending = IsAppending(key);
184-
bool merging = IsMerging(key, value, merge_tree);
185-
string path = StripOperator(key, appending || merging);
238+
auto indexed_append = ParseIndexedAppend(key);
239+
bool appending = IsAppending(key, indexed_append);
240+
bool merging = IsMerging(key, value, merge_tree, indexed_append);
241+
string path = StripOperator(key, appending || merging, indexed_append);
186242
DLOG(INFO) << "appending: " << appending << ", merging: " << merging
187243
<< ", path: " << path;
188244
auto find_target_node =
@@ -195,8 +251,9 @@ static bool EditNode(an<ConfigItemRef> head,
195251
if ((appending || merging) && **target) {
196252
DLOG(INFO) << "writer: editing node";
197253
return !value || // no-op
198-
(appending && (AppendToString(target, As<ConfigValue>(value)) ||
199-
AppendToList(target, As<ConfigList>(value)))) ||
254+
(appending &&
255+
(AppendToString(target, As<ConfigValue>(value)) ||
256+
AppendToList(target, As<ConfigList>(value), indexed_append))) ||
200257
(merging && MergeTree(target, As<ConfigMap>(value)));
201258
} else {
202259
DLOG(INFO) << "writer: overwriting node";

0 commit comments

Comments
 (0)