LCOV - code coverage report
Current view: top level - src/plugins/yamlcpp - write.cpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 150 151 99.3 %
Date: 2019-09-12 12:28:41 Functions: 15 15 100.0 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief Write key sets using yaml-cpp
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  */
       8             : 
       9             : #include "write.hpp"
      10             : #include "log.hpp"
      11             : #include "yaml-cpp/yaml.h"
      12             : 
      13             : #include <kdbassert.h>
      14             : #include <kdbease.h>
      15             : #include <kdblogger.h>
      16             : #include <kdbplugin.h>
      17             : 
      18             : #include <fstream>
      19             : 
      20             : using namespace std;
      21             : using namespace kdb;
      22             : 
      23             : namespace
      24             : {
      25             : 
      26             : using KeySetPair = pair<KeySet, KeySet>;
      27             : 
      28             : /**
      29             :  * @brief This function checks if `element` is an array element of `parent`.
      30             :  *
      31             :  * @pre The key `child` must be below `parent`.
      32             :  *
      33             :  * @param parent This parameter specifies a parent key.
      34             :  * @param keys This variable stores a direct or indirect child of `parent`.
      35             :  *
      36             :  * @retval true If `element` is an array element
      37             :  * @retval false Otherwise
      38             :  */
      39         593 : bool isArrayElementOf (Key const & parent, Key const & child)
      40             : {
      41        1186 :         char const * relative = elektraKeyGetRelativeName (*child, *parent);
      42         593 :         auto offsetIndex = ckdb::elektraArrayValidateBaseNameString (relative);
      43         593 :         if (offsetIndex <= 0) return false;
      44             :         // Skip `#`, underscores and digits
      45         588 :         relative += 2 * offsetIndex;
      46             :         // The next character has to be the separation char (`/`) or end of string
      47         588 :         if (relative[0] != '\0' && relative[0] != '/') return false;
      48             : 
      49         588 :         return true;
      50             : }
      51             : 
      52             : /**
      53             :  * @brief This function determines if the given key is an array parent.
      54             :  *
      55             :  * @param parent This parameter specifies a possible array parent.
      56             :  * @param keys This variable stores the key set of `parent`.
      57             :  *
      58             :  * @retval true If `parent` is the parent key of an array
      59             :  * @retval false Otherwise
      60             :  */
      61         111 : bool isArrayParent (Key const & parent, KeySet const & keys)
      62             : {
      63        2522 :         for (auto const & key : keys)
      64             :         {
      65        1097 :                 if (!key.isBelow (parent)) continue;
      66         598 :                 if (!isArrayElementOf (parent, key)) return false;
      67             :         }
      68             : 
      69         106 :         return true;
      70             : }
      71             : 
      72             : /**
      73             :  * @brief This function returns all array parents for a given key set.
      74             :  *
      75             :  * @note This function also adds empty parent keys for arrays, if they did not exist beforehand. For example for the key set that **only**
      76             :  *       contains the keys:
      77             :  *
      78             :  *       - `user/array/#0`, and
      79             :  *       - `user/array/#1`
      80             :  *
      81             :  *       the function will add the array parent `user/array` to the returned key set.
      82             :  *
      83             :  * @param keys This parameter contains the key set this function searches for array parents.
      84             :  *
      85             :  * @return A key sets that contains all array parents stored in `keys`
      86             :  */
      87          68 : KeySet splitArrayParents (KeySet const & keys)
      88             : {
      89          68 :         KeySet arrayParents;
      90             : 
      91          68 :         keys.rewind ();
      92          68 :         Key previous;
      93        1902 :         for (; keys.next (); previous = keys.current ())
      94             :         {
      95        1698 :                 if (keys.current ().hasMeta ("array"))
      96             :                 {
      97           3 :                         arrayParents.append (keys.current ());
      98           1 :                         continue;
      99             :                 }
     100             : 
     101        1410 :                 if (keys.current ().getBaseName ()[0] == '#')
     102             :                 {
     103         333 :                         if (!keys.current ().isDirectBelow (previous))
     104             :                         {
     105         276 :                                 Key directParent{ keys.current ().getName (), KEY_END };
     106          69 :                                 ckdb::keySetBaseName (*directParent, NULL);
     107         207 :                                 if (isArrayParent (*directParent, keys)) arrayParents.append (directParent);
     108             :                         }
     109         126 :                         else if (isArrayParent (*previous, keys))
     110             :                         {
     111             :                                 arrayParents.append (previous);
     112             :                         }
     113             :                 }
     114             :         }
     115             : 
     116             : #ifdef HAVE_LOGGER
     117             :         ELEKTRA_LOG_DEBUG ("Array parents:");
     118             :         logKeySet (arrayParents);
     119             : #endif
     120             : 
     121          68 :         return arrayParents;
     122             : }
     123             : 
     124             : /**
     125             :  * @brief This function splits `keys` into two key sets, one for array parents and elements, and the other one for all other keys.
     126             :  *
     127             :  * @param arrayParents This key set contains a (copy) of all array parents of `keys`.
     128             :  * @param keys This parameter contains the key set this function splits.
     129             :  *
     130             :  * @return A pair of key sets, where the first key set contains all array parents and elements,
     131             :  *         and the second key set contains all other keys
     132             :  */
     133          68 : KeySetPair splitArrayOther (KeySet const & arrayParents, KeySet const & keys)
     134             : {
     135         204 :         KeySet others = keys.dup ();
     136         136 :         KeySet arrays;
     137             : 
     138         333 :         for (auto const & parent : arrayParents)
     139             :         {
     140         215 :                 arrays.append (others.cut (parent));
     141             :         }
     142             : 
     143         136 :         return make_pair (arrays, others);
     144             : }
     145             : 
     146             : /**
     147             :  * @brief This function removes all **non-essential** array metadata from a given key set.
     148             :  *
     149             :  * @param keys This parameter contains the key set this function modifies.
     150             :  *
     151             :  * @return A copy of `keys` that only contains array metadata for empty arrays
     152             :  */
     153          68 : KeySet removeArrayMetaData (KeySet const & keys)
     154             : {
     155          68 :         KeySet result;
     156         966 :         for (auto const & key : keys)
     157             :         {
     158        1016 :                 result.append (key.dup ());
     159             :         }
     160             : 
     161         136 :         Key previous;
     162             :         result.rewind ();
     163        1220 :         while (result.next ())
     164             :         {
     165        1222 :                 if (result.current ().isBelow (previous)) previous.delMeta ("array");
     166         762 :                 previous = result.current ();
     167             :         }
     168             : 
     169         136 :         ELEKTRA_ASSERT (keys.size () == result.size (), "Size of input and output keys set is different (%zu ≠ %zu)", keys.size (),
     170          68 :                         result.size ());
     171             : 
     172          68 :         return result;
     173             : }
     174             : /**
     175             :  * @brief This function determines all keys “missing” from the given keyset.
     176             :  *
     177             :  * The term “missing” refers to keys that are not part of the hierarchy. For example in a key set with the parent key
     178             :  *
     179             :  *  - `user/parent`
     180             :  *
     181             :  * that contains the keys
     182             :  *
     183             :  * - `user/parent/level1/level2`, and
     184             :  * - `user/parent/level1/level2/level3/level4`
     185             :  *
     186             :  * , the keys
     187             :  *
     188             :  * - `user/parent/level1`, and
     189             :  * - user/parent/level1/level2/level3
     190             :  *
     191             :  * are missing.
     192             :  *
     193             :  * @param keys This parameter contains the key set for which this function determines missing keys.
     194             :  * @param parent This value stores the parent key of `keys`.
     195             :  *
     196             :  * @return A key set that contains all keys missing from `keys`
     197             :  */
     198          68 : KeySet missingKeys (KeySet const & keys, Key const & parent)
     199             : {
     200          68 :         KeySet missing;
     201             : 
     202          68 :         keys.rewind ();
     203         204 :         Key previous{ parent.getName (), KEY_BINARY, KEY_END };
     204        1728 :         for (; keys.next (); previous = keys.current ())
     205             :         {
     206        1746 :                 if (keys.current ().isDirectBelow (previous) || !keys.current ().isBelow (previous)) continue;
     207             : 
     208          76 :                 Key current{ keys.current ().getName (), KEY_BINARY, KEY_END };
     209          48 :                 while (!current.isDirectBelow (previous))
     210             :                 {
     211          29 :                         ckdb::keySetBaseName (*current, NULL);
     212          29 :                         missing.append (current);
     213          29 :                         current = current.dup ();
     214             :                 }
     215             :         }
     216             : 
     217          68 :         return missing;
     218             : }
     219             : 
     220             : /**
     221             :  * @brief This function returns a `NameIterator` starting at the first level that is not part of `parent`.
     222             :  *
     223             :  * @pre The parameter `key` must be a child of `parent`.
     224             :  *
     225             :  * @param key This is the key for which this function returns a relative iterator.
     226             :  * @param parent This key specifies the part of the name iterator that will not be part of the return value of this function.
     227             :  *
     228             :  * @returns A relative iterator that starts with the first part of the name of `key` not contained in `parent`.
     229             :  */
     230         283 : NameIterator relativeKeyIterator (Key const & key, Key const & parent)
     231             : {
     232         283 :         auto parentIterator = parent.begin ();
     233             :         auto keyIterator = key.begin ();
     234        5961 :         while (parentIterator != parent.end () && keyIterator != key.end ())
     235             :         {
     236        1704 :                 parentIterator++;
     237        1704 :                 keyIterator++;
     238             :         }
     239         283 :         return keyIterator;
     240             : }
     241             : 
     242             : /**
     243             :  * @brief This function checks if a key name specifies an array key.
     244             :  *
     245             :  * If the key name contains a valid array index that is smaller than `unsigned long long`, then the function will also return this index.
     246             :  *
     247             :  * @param nameIterator This iterator specifies the name of the key.
     248             :  *
     249             :  * @retval (true, arrayIndex) if `name` specifies an array key, where `arrayIndex` specifies the index stored in the array key.
     250             :  * @retval (false, 0) otherwise
     251             :  */
     252         294 : std::pair<bool, unsigned long long> isArrayIndex (NameIterator const & nameIterator)
     253             : {
     254         588 :         string const name = *nameIterator;
     255         294 :         auto const offsetIndex = ckdb::elektraArrayValidateBaseNameString (name.c_str ());
     256         294 :         auto const isArrayElement = offsetIndex >= 1;
     257        1303 :         return { isArrayElement, isArrayElement ? stoull (name.substr (offsetIndex)) : 0 };
     258             : }
     259             : 
     260             : /**
     261             :  * @brief This function creates a YAML node representing a key value.
     262             :  *
     263             :  * @param key This key specifies the data that should be saved in the YAML node returned by this function.
     264             :  *
     265             :  * @note Since YAML does not support non-empty binary data directly this function replaces data stored in binary keys with the string
     266             :  *       `Unsupported binary value!`. If you need support for binary data, please load the Base64 plugin before you use YAML CPP.
     267             :  *
     268             :  * @returns A new YAML node containing the data specified in `key`
     269             :  */
     270         283 : YAML::Node createMetaDataNode (Key const & key)
     271             : {
     272        1132 :         if (key.hasMeta ("array"))
     273             :         {
     274           1 :                 return YAML::Node (YAML::NodeType::Sequence);
     275             :         }
     276         282 :         if (key.getBinarySize () == 0)
     277             :         {
     278          92 :                 return YAML::Node (YAML::NodeType::Null);
     279             :         }
     280         190 :         if (key.isBinary ())
     281             :         {
     282           0 :                 return YAML::Node ("Unsupported binary value!");
     283             :         }
     284             : 
     285         190 :         auto value = key.get<string> ();
     286         379 :         if (value == "0" || value == "1")
     287             :         {
     288           4 :                 return YAML::Node (key.get<bool> ());
     289             :         }
     290         186 :         return YAML::Node (value);
     291             : }
     292             : 
     293             : /**
     294             :  * @brief This function creates a YAML Node containing a key value and optionally metadata.
     295             :  *
     296             :  * @param key This key specifies the data that should be saved in the YAML node returned by this function.
     297             :  *
     298             :  * @note Since YAML does not support non-empty binary data directly this function replaces data stored in binary keys with the string
     299             :  *       `Unsupported binary value!`. If you need support for binary data, please load the Base64 before you use YAML CPP.
     300             :  *
     301             :  * @returns A new YAML node containing the data and metadata specified in `key`
     302             :  */
     303         283 : YAML::Node createLeafNode (Key & key)
     304             : {
     305             : 
     306         566 :         YAML::Node metaNode{ YAML::Node (YAML::NodeType::Map) };
     307         566 :         YAML::Node dataNode = createMetaDataNode (key);
     308             : 
     309             :         key.rewindMeta ();
     310         805 :         while (Key meta = key.nextMeta ())
     311             :         {
     312         647 :                 if (meta.getName () == "array" || meta.getName () == "binary") continue;
     313          64 :                 if (meta.getName () == "type" && meta.getString () == "binary")
     314             :                 {
     315           4 :                         dataNode.SetTag ("tag:yaml.org,2002:binary");
     316           1 :                         continue;
     317             :                 }
     318          68 :                 metaNode[meta.getName ()] = meta.getString ();
     319             :                 ELEKTRA_LOG_DEBUG ("Add metakey “%s: %s”", meta.getName ().c_str (), meta.getString ().c_str ());
     320             :         }
     321             : 
     322         566 :         if (metaNode.size () <= 0)
     323             :         {
     324             :                 ELEKTRA_LOG_DEBUG ("Return leaf node with value “%s”",
     325             :                                    dataNode.IsNull () ? "~" : dataNode.IsSequence () ? "[]" : dataNode.as<string> ().c_str ());
     326             :                 return dataNode;
     327             :         }
     328             : 
     329          16 :         YAML::Node node{ YAML::Node (YAML::NodeType::Sequence) };
     330          64 :         node.SetTag ("!elektra/meta");
     331          16 :         node.push_back (dataNode);
     332          16 :         node.push_back (metaNode);
     333             : 
     334             : #ifdef HAVE_LOGGER
     335             :         ostringstream data;
     336             :         data << node;
     337             :         ELEKTRA_LOG_DEBUG ("Return meta leaf node with value “%s”", data.str ().c_str ());
     338             : #endif
     339             : 
     340          16 :         return node;
     341             : }
     342             : 
     343             : /**
     344             :  * @brief This function adds `null` elements to the given YAML collection.
     345             :  *
     346             :  * @param sequence This node stores the collection to which this function adds `numberOfElements` empty elements.
     347             :  * @param numberOfElements This parameter specifies the number of empty element this function adds to `sequence`.
     348             :  */
     349         106 : void addEmptyArrayElements (YAML::Node & sequence, unsigned long long const numberOfElements)
     350             : {
     351             :         ELEKTRA_LOG_DEBUG ("Add %lld empty array elements", numberOfElements);
     352         112 :         for (auto missingFields = numberOfElements; missingFields > 0; missingFields--)
     353             :         {
     354          12 :                 sequence.push_back ({});
     355             :         }
     356         106 : }
     357             : 
     358             : /**
     359             :  * @brief This function adds a key that is not part of any array to a YAML node.
     360             :  *
     361             :  * @param data This node stores the data specified via `keyIterator`.
     362             :  * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`.
     363             :  * @param key This parameter specifies the key that should be added to `data`.
     364             :  */
     365         215 : void addKeyNoArray (YAML::Node & data, NameIterator & keyIterator, Key & key)
     366             : {
     367         218 :         if (data.IsScalar ()) data = YAML::Node (YAML::NodeType::Undefined);
     368             : 
     369             : #ifdef HAVE_LOGGER
     370             :         ostringstream output;
     371             :         output << data;
     372             :         ELEKTRA_LOG_DEBUG ("Add key part “%s”", (*keyIterator).c_str ());
     373             : #endif
     374             : 
     375         430 :         if (keyIterator == key.end ())
     376             :         {
     377             :                 ELEKTRA_LOG_DEBUG ("Create leaf node for key “%s”", key.getName ().c_str ());
     378           6 :                 data = createLeafNode (key);
     379         134 :                 return;
     380             :         }
     381         424 :         if (keyIterator == --key.end ())
     382             :         {
     383         640 :                 data[*keyIterator] = createLeafNode (key);
     384         128 :                 return;
     385             :         }
     386             : 
     387         168 :         YAML::Node node;
     388             : 
     389         992 :         node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node ();
     390         252 :         data[*keyIterator] = node;
     391          84 :         addKeyNoArray (node, ++keyIterator, key);
     392             : }
     393             : 
     394             : /**
     395             :  * @brief This function adds a key that is either, element of an array, or an array parent to a YAML node.
     396             :  *
     397             :  * @param data This node stores the data specified via `keyIterator`.
     398             :  * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`.
     399             :  * @param key This parameter specifies the key that should be added to `data`.
     400             :  */
     401         294 : void addKeyArray (YAML::Node & data, NameIterator & keyIterator, Key & key)
     402             : {
     403         294 :         auto const isArrayAndIndex = isArrayIndex (keyIterator);
     404         294 :         auto const isArrayElement = isArrayAndIndex.first;
     405         294 :         auto const arrayIndex = isArrayAndIndex.second;
     406             : 
     407         296 :         if (data.IsScalar ()) data = YAML::Node (YAML::NodeType::Undefined);
     408             : 
     409             : #ifdef HAVE_LOGGER
     410             :         ostringstream output;
     411             :         output << data;
     412             :         ELEKTRA_LOG_DEBUG ("Add key part “%s”", (*keyIterator).c_str ());
     413             : #endif
     414             : 
     415         588 :         if (keyIterator == key.end ())
     416             :         {
     417             :                 ELEKTRA_LOG_DEBUG ("Create leaf node for key “%s”", key.getName ().c_str ());
     418           8 :                 data = createLeafNode (key);
     419         156 :                 return;
     420             :         }
     421         580 :         if (keyIterator == --key.end ())
     422             :         {
     423         148 :                 if (isArrayElement)
     424             :                 {
     425         106 :                         addEmptyArrayElements (data, arrayIndex - data.size ());
     426         212 :                         data.push_back (createLeafNode (key));
     427             :                 }
     428             :                 else
     429             :                 {
     430         210 :                         data[*keyIterator] = createLeafNode (key);
     431             :                 }
     432             : 
     433             :                 return;
     434             :         }
     435             : 
     436         284 :         YAML::Node node;
     437             : 
     438         142 :         if (isArrayElement)
     439             :         {
     440         131 :                 node = (data[arrayIndex] && !data[arrayIndex].IsScalar ()) ? data[arrayIndex] : YAML::Node ();
     441          42 :                 data[arrayIndex] = node;
     442             :         }
     443             :         else
     444             :         {
     445        1543 :                 node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node ();
     446         363 :                 data[*keyIterator] = node;
     447             :         }
     448         142 :         addKeyArray (node, ++keyIterator, key);
     449             : }
     450             : 
     451             : /**
     452             :  * @brief This function adds a key set to a YAML node.
     453             :  *
     454             :  * @param data This node stores the data specified via `mappings`.
     455             :  * @param mappings This keyset specifies all keys and values this function adds to `data`.
     456             :  * @param parent This key is the root of all keys stored in `mappings`.
     457             :  * @param isArray This value specifies if the keys inside `keys` are all part of an array (either element or parent), or if none of them is
     458             :  *                part of an array.
     459             :  */
     460         136 : void addKeys (YAML::Node & data, KeySet const & mappings, Key const & parent, bool const isArray = false)
     461             : {
     462        1257 :         for (auto key : mappings)
     463             :         {
     464             :                 ELEKTRA_LOG_DEBUG ("Convert key “%s”: “%s”", key.getName ().c_str (),
     465             :                                    key.getBinarySize () == 0 ? "NULL" : key.isString () ? key.getString ().c_str () : "binary value!");
     466         283 :                 NameIterator keyIterator = relativeKeyIterator (key, parent);
     467             : 
     468         283 :                 if (isArray)
     469             :                 {
     470         152 :                         addKeyArray (data, keyIterator, key);
     471             :                 }
     472             :                 else
     473             :                 {
     474         131 :                         addKeyNoArray (data, keyIterator, key);
     475             :                 }
     476             : 
     477             : #ifdef HAVE_LOGGER
     478             :                 ostringstream output;
     479             :                 output << data;
     480             : 
     481             :                 ELEKTRA_LOG_DEBUG ("Converted Data:");
     482             :                 ELEKTRA_LOG_DEBUG ("——————————");
     483             : 
     484             :                 istringstream stream (output.str ());
     485             :                 for (string line; std::getline (stream, line);)
     486             :                 {
     487             :                         ELEKTRA_LOG_DEBUG ("%s", line.c_str ());
     488             :                 }
     489             : 
     490             :                 ELEKTRA_LOG_DEBUG ("——————————");
     491             : #endif
     492             :         }
     493         136 : }
     494             : 
     495             : } // end namespace
     496             : 
     497             : /**
     498             :  * @brief This function saves the key-value pairs stored in `mappings` as YAML data in the location specified via `parent`.
     499             :  *
     500             :  * @param mappings This key set stores the mappings that should be saved as YAML data.
     501             :  * @param parent This key specifies the path to the YAML data file that should be written.
     502             :  */
     503          68 : void yamlcpp::yamlWrite (KeySet const & mappings, Key const & parent)
     504             : {
     505         136 :         auto keys = removeArrayMetaData (mappings);
     506         136 :         auto missing = missingKeys (keys, parent);
     507          68 :         keys.append (missing);
     508             : 
     509         136 :         KeySet arrayParents;
     510         136 :         KeySet arrays;
     511         136 :         KeySet nonArrays;
     512             : 
     513         204 :         arrayParents = splitArrayParents (keys);
     514         272 :         tie (arrays, nonArrays) = splitArrayOther (arrayParents, keys);
     515             : 
     516         136 :         auto data = YAML::Node ();
     517          68 :         addKeys (data, nonArrays, parent);
     518          68 :         addKeys (data, arrays, parent, true);
     519             : 
     520             : #ifdef HAVE_LOGGER
     521             :         ELEKTRA_LOG_DEBUG ("Write Data:");
     522             :         ELEKTRA_LOG_DEBUG ("——————————");
     523             : 
     524             :         ostringstream outputString;
     525             :         outputString << data;
     526             :         istringstream stream (outputString.str ());
     527             :         for (string line; std::getline (stream, line);)
     528             :         {
     529             :                 ELEKTRA_LOG_DEBUG ("%s", line.c_str ());
     530             :         }
     531             : 
     532             :         ELEKTRA_LOG_DEBUG ("——————————");
     533             : #endif
     534             : 
     535         204 :         ofstream output (parent.getString ());
     536          68 :         output << data;
     537          68 : }

Generated by: LCOV version 1.13