From b3dd09e801ebb7cdd26b72e4544b5cff95485ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81=20Schwaiger?= Date: Fri, 22 Mar 2019 11:43:33 +0100 Subject: [PATCH] YAML CPP: Support maps containing array keys See also: [Issue #1862](https://issues.libelektra.org/1862) --- src/plugins/yamlcpp/README.md | 1 + src/plugins/yamlcpp/testmod_yamlcpp.c | 8 + src/plugins/yamlcpp/write.cpp | 188 +++++++++++++++++- .../yamlcpp/yamlcpp/mapping_with_array_key.h | 6 + 4 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 src/plugins/yamlcpp/yamlcpp/mapping_with_array_key.h diff --git a/src/plugins/yamlcpp/README.md b/src/plugins/yamlcpp/README.md index c88d492e4b0..30801f98054 100644 --- a/src/plugins/yamlcpp/README.md +++ b/src/plugins/yamlcpp/README.md @@ -138,6 +138,7 @@ sudo kdb mount config.yaml user/tests/yamlcpp yamlcpp # Add some key value pairs kdb set user/tests/yamlcpp/key value +kdb set user/tests/yamlcpp/array kdb set user/tests/yamlcpp/array/#0 scalar kdb set user/tests/yamlcpp/array/#1/key value kdb set user/tests/yamlcpp/array/#1/šŸ”‘ šŸ™ˆ diff --git a/src/plugins/yamlcpp/testmod_yamlcpp.c b/src/plugins/yamlcpp/testmod_yamlcpp.c index c1ca544983d..e34537ba811 100644 --- a/src/plugins/yamlcpp/testmod_yamlcpp.c +++ b/src/plugins/yamlcpp/testmod_yamlcpp.c @@ -138,6 +138,13 @@ static void test_array (void) ); } +static void test_map_array_key (void) +{ + test_write_read ( +#include "yamlcpp/mapping_with_array_key.h" + ); +} + // -- Main --------------------------------------------------------------------------------------------------------------------------------- int main (int argc, char ** argv) @@ -151,6 +158,7 @@ int main (int argc, char ** argv) test_flat (); test_nested (); test_array (); + test_map_array_key (); print_result ("testmod_yamlcpp"); diff --git a/src/plugins/yamlcpp/write.cpp b/src/plugins/yamlcpp/write.cpp index 74458e5020a..cbff3fcc386 100644 --- a/src/plugins/yamlcpp/write.cpp +++ b/src/plugins/yamlcpp/write.cpp @@ -9,6 +9,7 @@ #include "write.hpp" #include "yaml-cpp/yaml.h" +#include #include #include #include @@ -21,6 +22,105 @@ using namespace kdb; namespace { +using KeySetPair = pair; + +/** + * @brief This function checks if `element` is an array element of `parent`. + * + * @pre The key `child` must be below `parent`. + * + * @param parent This parameter specifies a parent key. + * @param keys This variable stores a direct or indirect child of `parent`. + * + * @retval true If `element` is an array element + * @retval false Otherwise + */ +bool isArrayElementOf (Key const & parent, Key const & child) +{ + char const * relative = elektraKeyGetRelativeName (*child, *parent); + auto offsetIndex = ckdb::elektraArrayValidateBaseNameString (relative); + if (offsetIndex <= 0) return false; + // Skip `#`, underscores and digits + relative += 2 * offsetIndex; + // The next character has to be the separation char (`/`) or end of string + if (relative[0] != '\0' && relative[0] != '/') return false; + + return true; +} + +/** + * @brief This function determines if the given key is an array parent. + * + * @param parent This parameter specifies a possible array parent. + * @param keys This variable stores the key set of `parent`. + * + * @retval true If `parent` is the parent key of an array + * @retval false Otherwise + */ +bool isArrayParent (Key const & parent, KeySet const & keys) +{ + for (auto const & key : keys) + { + if (!key.isBelow (parent)) continue; + if (!isArrayElementOf (parent, key)) return false; + } + + return true; +} + +/** + * @brief Split `keys` into two key sets, one for array parents and one for all other keys. + * + * @param keys This parameter contains the key set this function splits. + * + * @return A pair of key sets, where the first key set contains all array parents and the second key set contains all other keys + */ +KeySetPair splitArrayParentsOther (KeySet const & keys) +{ + KeySet arrayParents; + KeySet others; + + keys.rewind (); + Key previous; + for (previous = keys.next (); keys.next (); previous = keys.current ()) + { + bool const previousIsArray = + previous.hasMeta ("array") || + (keys.current ().isBelow (previous) && keys.current ().getBaseName ()[0] == '#' && isArrayParent (previous, keys)); + + (previousIsArray ? arrayParents : others).append (previous); + } + (previous.hasMeta ("array") ? arrayParents : others).append (previous); + + ELEKTRA_ASSERT (arrayParents.size () + others.size () == keys.size (), + "Number of keys in split key sets: %zu ā‰  number in given key set %zu", arrayParents.size () + others.size (), + keys.size ()); + + return make_pair (arrayParents, others); +} + +/** + * @brief This function splits `keys` into two key sets, one for array parents and elements, and the other one for all other keys. + * + * @param arrayParents This key set contains a (copy) of all array parents of `keys`. + * @param keys This parameter contains the key set this function splits. + * + * @return A pair of key sets, where the first key set contains all array parents and elements, + * and the second key set contains all other keys + */ +KeySetPair splitArrayOther (KeySet const & arrayParents, KeySet const & keys) +{ + KeySet others = keys.dup (); + KeySet arrays; + + for (auto parent : arrayParents) + { + arrays.append (others.cut (parent)); + } + + return make_pair (arrays, others); +} + /** * @brief This function returns a `NameIterator` starting at the first level that is not part of `parent`. * @@ -151,7 +251,7 @@ void addEmptyArrayElements (YAML::Node & sequence, unsigned long long const numb * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`. * @param key This parameter specifies the key that should be added to `data`. */ -void addKey (YAML::Node & data, NameIterator & keyIterator, Key & key) +void addKeyArray (YAML::Node & data, NameIterator & keyIterator, Key & key) { auto const isArrayAndIndex = isArrayIndex (keyIterator); auto const isArray = isArrayAndIndex.first; @@ -198,7 +298,77 @@ void addKey (YAML::Node & data, NameIterator & keyIterator, Key & key) node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node (); data[*keyIterator] = node; } - addKey (node, ++keyIterator, key); + addKeyArray (node, ++keyIterator, key); +} + +/** + * @brief This function adds a key to a YAML node. + * + * @param data This node stores the data specified via `keyIterator`. + * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`. + * @param key This parameter specifies the key that should be added to `data`. + */ +void addKeyNoArray (YAML::Node & data, NameIterator & keyIterator, Key & key) +{ + if (data.IsScalar ()) data = YAML::Node (YAML::NodeType::Undefined); + +#ifdef HAVE_LOGGER + ostringstream output; + output << data; + ELEKTRA_LOG_DEBUG ("Add key part ā€œ%sā€ to node ā€œ%sā€", (*keyIterator).c_str (), output.str ().c_str ()); +#endif + + if (keyIterator == key.end ()) + { + ELEKTRA_LOG_DEBUG ("Create leaf node for key ā€œ%sā€", key.getName ().c_str ()); + data = createLeafNode (key); + return; + } + if (keyIterator == --key.end ()) + { + data[*keyIterator] = createLeafNode (key); + return; + } + + YAML::Node node; + + node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node (); + data[*keyIterator] = node; + addKeyNoArray (node, ++keyIterator, key); +} + +/** + * @brief This function adds a key set to a YAML node. + * + * @param data This node stores the data specified via `mappings`. + * @param mappings This keyset specifies all keys and values this function adds to `data`. + * @param parent This key is the root of all keys stored in `mappings`. + */ +void addKeysArray (YAML::Node & data, KeySet const & mappings, Key const & parent) +{ + for (auto key : mappings) + { + ELEKTRA_LOG_DEBUG ("Convert key ā€œ%sā€: ā€œ%sā€", key.getName ().c_str (), + key.getBinarySize () == 0 ? "NULL" : key.isString () ? key.getString ().c_str () : "binary value!"); + NameIterator keyIterator = relativeKeyIterator (key, parent); + addKeyArray (data, keyIterator, key); + +#ifdef HAVE_LOGGER + ostringstream output; + output << data; + + ELEKTRA_LOG_DEBUG ("Converted Data:"); + ELEKTRA_LOG_DEBUG ("__________"); + + istringstream stream (output.str ()); + for (string line; std::getline (stream, line);) + { + ELEKTRA_LOG_DEBUG ("%s", line.c_str ()); + } + + ELEKTRA_LOG_DEBUG ("__________"); +#endif + } } /** @@ -208,14 +378,14 @@ void addKey (YAML::Node & data, NameIterator & keyIterator, Key & key) * @param mappings This keyset specifies all keys and values this function adds to `data`. * @param parent This key is the root of all keys stored in `mappings`. */ -void addKeys (YAML::Node & data, KeySet const & mappings, Key const & parent) +void addKeysNoArray (YAML::Node & data, KeySet const & mappings, Key const & parent) { for (auto key : mappings) { ELEKTRA_LOG_DEBUG ("Convert key ā€œ%sā€: ā€œ%sā€", key.getName ().c_str (), key.getBinarySize () == 0 ? "NULL" : key.isString () ? key.getString ().c_str () : "binary value!"); NameIterator keyIterator = relativeKeyIterator (key, parent); - addKey (data, keyIterator, key); + addKeyNoArray (data, keyIterator, key); #ifdef HAVE_LOGGER ostringstream output; @@ -247,7 +417,15 @@ void yamlcpp::yamlWrite (KeySet const & mappings, Key const & parent) { ofstream output (parent.getString ()); auto data = YAML::Node (); - addKeys (data, mappings, parent); + + KeySet arrayParents; + KeySet arrays; + KeySet nonArrays; + tie (arrayParents, std::ignore) = splitArrayParentsOther (mappings); + tie (arrays, nonArrays) = splitArrayOther (arrayParents, mappings); + + addKeysArray (data, arrays, parent); + addKeysNoArray (data, nonArrays, parent); #ifdef HAVE_LOGGER ELEKTRA_LOG_DEBUG ("Write Data:"); diff --git a/src/plugins/yamlcpp/yamlcpp/mapping_with_array_key.h b/src/plugins/yamlcpp/yamlcpp/mapping_with_array_key.h new file mode 100644 index 00000000000..71032741916 --- /dev/null +++ b/src/plugins/yamlcpp/yamlcpp/mapping_with_array_key.h @@ -0,0 +1,6 @@ +// clang-format off + +ksNew (20, + keyNew (PREFIX "not/an/array/#0", KEY_VALUE, "element", KEY_END), + keyNew (PREFIX "not/an/array/key", KEY_VALUE, "value", KEY_END), + KS_END)