Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Delegate implementation for the `directoryvalue` plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include <numeric>
11 :
12 : #include <kdbassert.h>
13 : #include <kdbease.h>
14 : #include <kdblogger.h>
15 :
16 : #include "directoryvalue_delegate.hpp"
17 :
18 : using std::accumulate;
19 : using std::ignore;
20 : using std::make_pair;
21 : using std::pair;
22 : using std::range_error;
23 : using std::string;
24 : using std::tie;
25 :
26 : // -- Functions ----------------------------------------------------------------------------------------------------------------------------
27 :
28 : namespace elektra
29 : {
30 : using CppKey = kdb::Key;
31 :
32 : /**
33 : * @brief This function splits the given keyset into directory leaves (marked with `DIRECTORY_POSTFIX`) and other keys.
34 : *
35 : * @param input The function searches for directory leaves in this key set.
36 : *
37 : * @return A pair of key sets, where the first key set contains all directory leaves and the second key set contains all other keys
38 : */
39 276 : KeySetPair splitDirectoryLeavesOther (CppKeySet const & input)
40 : {
41 552 : CppKeySet directoryLeaves;
42 552 : CppKeySet other;
43 :
44 4011 : for (auto key : input)
45 : {
46 3183 : if (key.getBaseName () == DIRECTORY_POSTFIX)
47 : {
48 : directoryLeaves.append (key);
49 : }
50 : else
51 : {
52 : other.append (key);
53 : }
54 : }
55 552 : return make_pair (directoryLeaves, other);
56 : }
57 :
58 : /**
59 : * @brief This function splits the given keyset into array leaves (first element of an array parent) and other keys.
60 : *
61 : * @param input The function searches for array leaves in this key set.
62 : *
63 : * @return A pair of key sets, where the first key set contains all array leaves and the second key set contains all other keys
64 : */
65 276 : KeySetPair splitArrayLeavesOther (CppKeySet const & arrayParents, CppKeySet const & keys)
66 : {
67 276 : bool isFirstElement = false;
68 552 : CppKeySet firstElements;
69 552 : CppKeySet others;
70 :
71 2715 : for (auto key : keys)
72 : {
73 1080 : bool const isArrayLeaf = isFirstElement && key.isString () && key.getStringSize () > arrayValuePrefixSize &&
74 860 : strncmp (key.getString ().c_str (), ARRAY_VALUE_PREFIX, arrayValuePrefixSize) == 0;
75 1258 : (isArrayLeaf ? firstElements : others).append (key);
76 1887 : isFirstElement = arrayParents.lookup (key);
77 : }
78 :
79 552 : return make_pair (firstElements, others);
80 : }
81 :
82 : /**
83 : * @brief This function removes the basename (last level) from the given keys.
84 : *
85 : * @param keys This parameter contains the keys for which this function removes the basename.
86 : *
87 : * @return A copy of the input, where the last level of each key was removed
88 : */
89 552 : CppKeySet removeBaseName (CppKeySet const & keys)
90 : {
91 552 : CppKeySet directories;
92 :
93 2148 : for (auto key : keys)
94 : {
95 : ELEKTRA_LOG_DEBUG ("Remove basename from “%s”: “%s”", key.getName ().c_str (),
96 : key.getBinarySize () == 0 ? "NULL" : key.isBinary () ? "binary value!" : key.getString ().c_str ());
97 492 : CppKey directory = key.dup ();
98 164 : directory.delBaseName ();
99 164 : directories.append (directory);
100 : }
101 552 : return directories;
102 : }
103 :
104 : /**
105 : * @brief This function converts the given array leaves to directory keys.
106 : *
107 : * @param arrayLeaves This parameter contains array leaves (that start with `ARRAY_VALUE_PREFIX` and have the baseName = `#0`).
108 : *
109 : * @return A copy of `arrayLeaves`, where key values does not start with `ARRAY_VALUE_PREFIX` any more and the baseName of each key (`#0`)
110 : * was removed
111 : */
112 276 : CppKeySet convertArrayLeaves (CppKeySet const & arrayLeaves)
113 : {
114 276 : CppKeySet directories = removeBaseName (arrayLeaves);
115 975 : for (auto key : directories)
116 : {
117 : ELEKTRA_LOG_DEBUG ("Convert array leaf “%s”", key.getName ().c_str ());
118 49 : if (key.getStringSize () == arrayValuePrefixSize + 1)
119 : {
120 : key.setBinary (0, 0);
121 : ELEKTRA_LOG_DEBUG ("Set value of “%s” to NULL", key.getName ().c_str ());
122 : }
123 : else
124 : {
125 196 : key.setString (key.getString ().substr (arrayValuePrefixSize + 1));
126 : ELEKTRA_LOG_DEBUG ("Set value of “%s” to “%s”", key.getName ().c_str (), key.getString ().c_str ());
127 : }
128 : }
129 276 : return directories;
130 : }
131 :
132 : /**
133 : * @brief This function returns a modified copy of `child`, where child is directly below `parent`.
134 : *
135 : * For example, if `child` has the name `user/parent/level1/level2/level3` and parent has the name `user/parent`, then the function will
136 : * return a key with the name `user/parent/level1`.
137 : *
138 : * @pre The key `child` has to be below `parent`.
139 : *
140 : * @param parent This parameter specifies a parent key of `child`.
141 : * @param keys This variable stores a child key of `parent`.
142 : *
143 : * @return A copy of `child` that is directly below `parent`
144 : */
145 202 : CppKey convertToDirectChild (CppKey const & parent, CppKey const & child)
146 : {
147 202 : ELEKTRA_ASSERT (child.isBelow (parent), "The key `child` is not located below `parent`");
148 :
149 202 : CppKey directChild = child.dup ();
150 420 : while (!directChild.isDirectBelow (parent))
151 : {
152 218 : keySetBaseName (*directChild, 0);
153 : }
154 202 : return directChild;
155 : }
156 :
157 : /**
158 : * @brief This function checks if `element` is an array element of `parent`.
159 : *
160 : * @pre The key `child` must be below `parent`.
161 : *
162 : * @param parent This parameter specifies a parent key.
163 : * @param keys This variable stores a direct or indirect child of `parent`.
164 : *
165 : * @retval true If `element` is an array element
166 : * @retval false Otherwise
167 : */
168 233 : bool inline isArrayElementOf (CppKey const & parent, CppKey const & child)
169 : {
170 466 : char const * relative = elektraKeyGetRelativeName (*child, *parent);
171 233 : auto offsetIndex = elektraArrayValidateBaseNameString (relative);
172 233 : if (offsetIndex <= 0) return false;
173 : // Skip `#`, underscores and digits
174 217 : relative += 2 * offsetIndex;
175 : // The next character has to be the separation char (`/`) or end of string
176 217 : if (relative[0] != '\0' && relative[0] != '/') return false;
177 :
178 217 : return true;
179 : }
180 :
181 : /**
182 : * @brief This function determines if the given key is an array parent.
183 : *
184 : * @param parent This parameter specifies a possible array parent.
185 : * @param keys This variable stores the key set of `parent`.
186 : *
187 : * @retval true If `parent` is the parent key of an array
188 : * @retval false Otherwise
189 : */
190 70 : bool isArrayParent (CppKey const & parent, CppKeySet const & keys)
191 : {
192 1594 : for (auto const & key : keys)
193 : {
194 700 : if (!key.isBelow (parent)) continue;
195 249 : if (!isArrayElementOf (parent, key)) return false;
196 : }
197 :
198 54 : return true;
199 : }
200 :
201 : /**
202 : * @brief Split `keys` into two key sets, one for array parents and one for all other keys.
203 : *
204 : * @param keys This parameter contains the key set this function splits.
205 : *
206 : * @return A pair of key sets, where the first key set contains all array parents and the second key set contains all other keys
207 : */
208 381 : KeySetPair splitArrayParentsOther (CppKeySet const & keys)
209 : {
210 762 : CppKeySet arrayParents;
211 762 : CppKeySet others;
212 :
213 381 : keys.rewind ();
214 762 : CppKey previous;
215 9150 : for (previous = keys.next (); keys.next (); previous = keys.current ())
216 : {
217 : bool const previousIsArray =
218 9017 : previous.hasMeta ("array") || (keys.current ().isDirectBelow (previous) &&
219 865 : keys.current ().getBaseName ()[0] == '#' && isArrayParent (previous, keys));
220 :
221 2288 : (previousIsArray ? arrayParents : others).append (previous);
222 : }
223 1905 : (previous.hasMeta ("array") ? arrayParents : others).append (previous);
224 :
225 1143 : ELEKTRA_ASSERT (arrayParents.size () + others.size () == keys.size (),
226 : "Number of keys in split key sets: %zu ≠ number in given key set %zu", arrayParents.size () + others.size (),
227 381 : keys.size ());
228 :
229 762 : return make_pair (arrayParents, others);
230 : }
231 :
232 : /**
233 : * @brief This function splits `keys` into two key sets, one for array parents and elements, and the other one for all other keys.
234 : *
235 : * @param arrayParents This key set contains a (copy) of all array parents of `keys`.
236 : * @param keys This parameter contains the key set this function splits.
237 : *
238 : * @return A pair of key sets, where the first key set contains all array parents and elements,
239 : * and the second key set contains all other keys
240 : */
241 377 : KeySetPair splitArrayOther (CppKeySet const & arrayParents, CppKeySet const & keys)
242 : {
243 1131 : CppKeySet others = keys.dup ();
244 754 : CppKeySet arrays;
245 :
246 1728 : for (auto parent : arrayParents)
247 : {
248 995 : arrays.append (others.cut (parent));
249 : }
250 :
251 754 : return make_pair (arrays, others);
252 : }
253 :
254 : /**
255 : * @brief This function splits `keys` into two key sets, one for empty array parents that do not contain a value and one for all other keys.
256 : *
257 : * @param arrayParents This key set contains array parents.
258 : *
259 : * @return A pair of key sets, where the first key set contains all array parents without values, and the second key set contains all other
260 : * keys
261 : */
262 101 : KeySetPair splitEmptyArrayParents (CppKeySet const & arrayParents)
263 : {
264 202 : CppKeySet emptyParents;
265 202 : CppKeySet nonEmptyParents;
266 :
267 438 : for (auto arrayParent : arrayParents)
268 : {
269 135 : CppKey parent = arrayParent.dup ();
270 :
271 45 : parent.rewindMeta ();
272 45 : size_t metaSize = 0;
273 45 : bool isEmpty = parent.getBinarySize () == 0;
274 108 : while (isEmpty && parent.nextMeta ())
275 : {
276 0 : if (metaSize > 2 || parent.currentMeta ().getName () != "binary" || parent.currentMeta ().getName () != "array")
277 : {
278 0 : isEmpty = false;
279 : }
280 0 : metaSize++;
281 : }
282 90 : (isEmpty ? emptyParents : nonEmptyParents).append (arrayParent);
283 : }
284 202 : return make_pair (emptyParents, nonEmptyParents);
285 : }
286 :
287 : /**
288 : * @brief This function changes an array index of the given array element by one.
289 : *
290 : * @param parent This key set stores an array parent of `element`. The function will change the index of `element` that is directly below
291 : * this key.
292 : * @param element This parameter stores an array element.
293 : * @param increment This boolean parameter specifies if the function should increase or decrease the index by one.
294 : *
295 : * @return A key containing a copy of `element`, where the index below `parent` was increased or decreased by one and the value of the
296 : * updated index (e.g. `#_10`)
297 : */
298 202 : pair<CppKey, string> changeArrayIndexByOne (CppKey const & parent, CppKey const & element, bool increment = true)
299 : {
300 404 : CppKey elementNewIndex = convertToDirectChild (parent, element);
301 1212 : string postfix = elektraKeyGetRelativeName (*element, *elementNewIndex);
302 :
303 404 : if (increment ? elektraArrayIncName (*elementNewIndex) : elektraArrayDecName (*elementNewIndex))
304 : {
305 0 : throw range_error (string ("Unable to ") + (increment ? "increment" : "decrement") + " index of key “" +
306 0 : elementNewIndex.getName () + "”");
307 : }
308 404 : string newIndex = elementNewIndex.getBaseName ();
309 202 : elementNewIndex.addName (postfix);
310 :
311 : ELEKTRA_LOG_DEBUG ("New name of “%s” is “%s”", element.getName ().c_str (), elementNewIndex.getName ().c_str ());
312 :
313 404 : return make_pair (elementNewIndex, newIndex);
314 : }
315 :
316 : /**
317 : * @brief Decrease the array index of array elements by one.
318 : *
319 : * @param parents This parameter contains the array parents for which this function decrease the index by one.
320 : * @param arrays This variable stores the arrays elements (and parents) this function updates.
321 : *
322 : * @return A copy of `arrays`, where all indices specified by `parents` are decreased by one
323 : */
324 276 : CppKeySet decreaseArrayIndices (CppKeySet const & parents, CppKeySet const & arrays)
325 : {
326 552 : CppKeySet arraysIndexDecreased = arrays.dup ();
327 276 : CppKeySet arrayParents = parents.dup ();
328 :
329 699 : while (CppKey parent = arrayParents.pop ())
330 : {
331 : ELEKTRA_LOG_DEBUG ("Decrease indices for array parent “%s”", parent.getName ().c_str ());
332 :
333 196 : parent.setMeta ("array", ""); // Set meta key for empty arrays
334 :
335 : arraysIndexDecreased =
336 294 : accumulate (arraysIndexDecreased.begin (), arraysIndexDecreased.end (), CppKeySet{},
337 605 : [&parent](CppKeySet collected, CppKey key) {
338 369 : if (key.isBelow (parent))
339 : {
340 236 : string newIndex;
341 236 : tie (key, newIndex) = changeArrayIndexByOne (parent, key, false);
342 708 : parent.setMeta ("array", newIndex);
343 : ELEKTRA_LOG_DEBUG ("New last index of “%s” is “%s”", parent.getName ().c_str (),
344 : parent.getMeta<string> ("array").c_str ());
345 : }
346 369 : collected.append (key);
347 369 : return collected;
348 49 : });
349 :
350 49 : arraysIndexDecreased.append (parent); // Update meta data of parent key in `arrays`
351 : }
352 :
353 276 : return arraysIndexDecreased;
354 : }
355 :
356 : /**
357 : * @brief Increase the array index of array elements by one.
358 : *
359 : * Since it is also possible that one of the array parents is part of another array, this function also updates the indices of the given
360 : * array parents.
361 : *
362 : * @param parents This parameter contains the array parents for which this function increases the index by one.
363 : * @param arrays This variable stores the arrays elements this function updates.
364 : *
365 : * @return A pair containing a copy of `parents` and `arrays`, where all indices specified by `parents` are increased by one
366 : */
367 103 : KeySetPair increaseArrayIndices (CppKeySet const & parents, CppKeySet const & arrays)
368 : {
369 309 : CppKeySet arraysIncreasedIndex = arrays.dup ();
370 309 : CppKeySet arrayParents = parents.dup ();
371 206 : CppKeySet updatedParents = parents.dup ();
372 :
373 290 : while (CppKey parent = arrayParents.pop ())
374 : {
375 : ELEKTRA_LOG_DEBUG ("Increase indices for array parent “%s”", parent.getName ().c_str ());
376 :
377 56 : CppKeySet newArrays;
378 852 : for (auto key : arraysIncreasedIndex)
379 : {
380 256 : if (key.isBelow (parent))
381 : {
382 168 : CppKey updated;
383 168 : tie (updated, ignore) = changeArrayIndexByOne (parent, key);
384 252 : if (updatedParents.lookup (key, KDB_O_POP)) updatedParents.append (updated);
385 84 : newArrays.append (updated);
386 : }
387 : else
388 : {
389 : newArrays.append (key);
390 : }
391 : }
392 28 : arraysIncreasedIndex = newArrays;
393 : }
394 :
395 206 : return make_pair (updatedParents, arraysIncreasedIndex);
396 : }
397 :
398 : /**
399 : * @brief Split `keys` into two key sets, one for directories (keys without children) and one for all other keys.
400 : *
401 : * @param keys This parameter contains the key set this function splits.
402 : *
403 : * @return A pair of key sets, where the first key set contains all directories and the second key set contains all leaves
404 : */
405 101 : KeySetPair splitDirectoriesLeaves (CppKeySet const & keys)
406 : {
407 202 : CppKeySet leaves;
408 202 : CppKeySet directories;
409 :
410 101 : keys.rewind ();
411 202 : CppKey previous;
412 2064 : for (previous = keys.next (); keys.next (); previous = keys.current ())
413 : {
414 972 : (keys.current ().isBelow (previous) ? directories : leaves).append (previous);
415 : }
416 101 : leaves.append (previous);
417 :
418 202 : return make_pair (directories, leaves);
419 : }
420 :
421 : /**
422 : * @brief Convert all keys in `parents` to an empty array parent and an array element with index `#0` storing the data of the old key.
423 : *
424 : * @param parents This parameter contains the set of array parent this function converts.
425 : *
426 : * @return A key set containing only empty array parents and corresponding array elements storing the values of the old array parent
427 : */
428 101 : CppKeySet convertArrayParentsToLeaves (CppKeySet const & parents)
429 : {
430 101 : CppKeySet converted;
431 :
432 375 : for (auto parent : parents)
433 : {
434 72 : CppKey directory{ parent.getName (), KS_END };
435 72 : CppKey leaf = parent.dup ();
436 120 : leaf.delMeta ("array");
437 96 : leaf.addBaseName ("#0");
438 120 : leaf.setString (ARRAY_VALUE_PREFIX + (parent.isBinary () ? "" : (" " + parent.getString ())));
439 24 : converted.append (directory);
440 24 : converted.append (leaf);
441 : }
442 :
443 101 : return converted;
444 : }
445 :
446 : /**
447 : * @brief Convert all keys in `directories` to an empty key and a leaf key containing the data of the old key.
448 : *
449 : * @param directories This parameter contains a set of directory keys this function converts.
450 : *
451 : * @return A key set containing only empty directory keys and corresponding leaf keys storing the values of the old directory keys
452 : */
453 101 : CppKeySet convertDirectoriesToLeaves (CppKeySet const & directories)
454 : {
455 101 : CppKeySet directoryLeaves;
456 :
457 504 : for (auto directory : directories)
458 : {
459 201 : CppKey emptyDirectory{ directory.getName (), KS_END };
460 201 : CppKey leaf = directory.dup ();
461 268 : leaf.addBaseName (DIRECTORY_POSTFIX);
462 67 : directoryLeaves.append (leaf);
463 67 : directoryLeaves.append (emptyDirectory);
464 : }
465 :
466 101 : return directoryLeaves;
467 : }
468 :
469 : // -- Class --------------------------------------------------------------------------------------------------------------------------------
470 :
471 : /**
472 : * @brief This constructor creates a new delegate object used by the `directoryvalue` plugin
473 : *
474 : * @param config This key set contains configuration values provided by the `directoryvalue` plugin
475 : */
476 1138 : DirectoryValueDelegate::DirectoryValueDelegate (CppKeySet config ELEKTRA_UNUSED)
477 : {
478 1138 : }
479 :
480 : /**
481 : * @brief This method converts all leaf keys in the given key set to directory keys.
482 : *
483 : * @param keys This parameter specifies the key set this function converts.
484 : *
485 : * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS If the plugin converted any value in the given key set
486 : * @retval ELEKTRA_PLUGIN_STATUS_NO_UPDATE If the plugin did not update `keys`
487 : */
488 276 : int DirectoryValueDelegate::convertToDirectories (CppKeySet & keys)
489 : {
490 552 : CppKeySet directoryLeaves;
491 552 : CppKeySet nonDirectoryLeaves;
492 552 : CppKeySet arrayParents;
493 552 : CppKeySet notArrayParents;
494 552 : CppKeySet arrays;
495 552 : CppKeySet arrayLeaves;
496 552 : CppKeySet maps;
497 :
498 : /**
499 : * - Split array parents
500 : * - Split first array child containing directory value prefix, others
501 : * - Convert first array child back to array parent
502 : * - Decrease index of arrays
503 : * - Merge everything back together and convert directories to leaves
504 : */
505 :
506 1104 : tie (arrayParents, ignore) = splitArrayParentsOther (keys);
507 1104 : tie (arrays, maps) = splitArrayOther (arrayParents, keys);
508 1104 : tie (arrayLeaves, arrays) = splitArrayLeavesOther (arrayParents, arrays);
509 :
510 828 : arrayParents = convertArrayLeaves (arrayLeaves);
511 828 : notArrayParents = decreaseArrayIndices (arrayParents, arrays);
512 276 : notArrayParents.append (maps);
513 :
514 1104 : tie (directoryLeaves, nonDirectoryLeaves) = splitDirectoryLeavesOther (notArrayParents);
515 :
516 : bool const status =
517 472 : directoryLeaves.size () > 0 || arrayLeaves.size () > 0 ? ELEKTRA_PLUGIN_STATUS_SUCCESS : ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
518 :
519 552 : auto directories = removeBaseName (directoryLeaves);
520 :
521 276 : keys.clear ();
522 276 : keys.append (nonDirectoryLeaves);
523 276 : keys.append (directories);
524 :
525 552 : return status;
526 : }
527 :
528 : /**
529 : * @brief This method converts all directory keys in the given key set to leaf keys.
530 : *
531 : * @param keys This parameter specifies the key set this function converts.
532 : *
533 : * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS If the plugin converted any value in the given key set
534 : * @retval ELEKTRA_PLUGIN_STATUS_NO_UPDATE If the plugin did not update `keys`
535 : */
536 101 : int DirectoryValueDelegate::convertToLeaves (CppKeySet & keys)
537 : {
538 202 : CppKeySet notArrayParents;
539 202 : CppKeySet arrayParents;
540 202 : CppKeySet emptyArrayParents;
541 202 : CppKeySet arrays;
542 202 : CppKeySet nonArrays;
543 202 : CppKeySet directories;
544 202 : CppKeySet leaves;
545 :
546 404 : tie (arrayParents, ignore) = splitArrayParentsOther (keys);
547 404 : tie (arrays, nonArrays) = splitArrayOther (arrayParents, keys);
548 :
549 404 : tie (emptyArrayParents, arrayParents) = splitEmptyArrayParents (arrayParents);
550 404 : tie (arrayParents, arrays) = increaseArrayIndices (arrayParents, arrays);
551 :
552 101 : notArrayParents.append (arrays);
553 101 : notArrayParents.append (nonArrays);
554 606 : notArrayParents = accumulate (notArrayParents.begin (), notArrayParents.end (), CppKeySet{},
555 750 : [&arrayParents, &emptyArrayParents](CppKeySet collected, CppKey key) {
556 2250 : if (!arrayParents.lookup (key) && !emptyArrayParents.lookup (key)) collected.append (key);
557 387 : return collected;
558 101 : });
559 303 : arrayParents = convertArrayParentsToLeaves (arrayParents);
560 :
561 404 : tie (directories, leaves) = splitDirectoriesLeaves (notArrayParents);
562 : bool const status =
563 158 : directories.size () > 0 || arrayParents.size () > 0 ? ELEKTRA_PLUGIN_STATUS_SUCCESS : ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
564 :
565 202 : auto directoryLeaves = convertDirectoriesToLeaves (directories);
566 :
567 101 : keys.clear ();
568 101 : keys.append (arrays);
569 101 : keys.append (arrayParents);
570 101 : keys.append (emptyArrayParents);
571 101 : keys.append (directoryLeaves);
572 101 : keys.append (leaves);
573 :
574 202 : return status;
575 : }
576 :
577 : } // end namespace elektra
|