Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for yamlsmith plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : // -- Imports ------------------------------------------------------------------------------------------------------------------------------
11 :
12 : #include <fstream>
13 : #include <iostream>
14 :
15 : #include "yamlsmith.hpp"
16 :
17 : #include <kdb.hpp>
18 : #include <kdbease.h>
19 : #include <kdberrors.h>
20 :
21 : using std::endl;
22 : using std::ofstream;
23 : using std::string;
24 :
25 : using ckdb::Key;
26 : using ckdb::KeySet;
27 :
28 : using ckdb::keyNew;
29 :
30 : using CppKey = kdb::Key;
31 : using CppKeySet = kdb::KeySet;
32 : using NameIterator = kdb::NameIterator;
33 :
34 : // -- Functions ----------------------------------------------------------------------------------------------------------------------------
35 :
36 : namespace
37 : {
38 :
39 : /**
40 : * @brief This function returns a key set containing the contract of the plugin.
41 : *
42 : * @return A contract describing the functionality of this plugin
43 : */
44 108 : CppKeySet contractYamlsmith ()
45 : {
46 : return CppKeySet{ 30,
47 : keyNew ("system/elektra/modules/yamlsmith", KEY_VALUE, "yamlsmith plugin waits for your orders", KEY_END),
48 : keyNew ("system/elektra/modules/yamlsmith/exports", KEY_END),
49 : keyNew ("system/elektra/modules/yamlsmith/exports/get", KEY_FUNC, elektraYamlsmithGet, KEY_END),
50 : keyNew ("system/elektra/modules/yamlsmith/exports/set", KEY_FUNC, elektraYamlsmithSet, KEY_END),
51 : #include ELEKTRA_README
52 : keyNew ("system/elektra/modules/yamlsmith/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END),
53 : keyNew ("system/elektra/modules/yamlcpp/config/needs/boolean/restore", KEY_VALUE, "#1", KEY_END),
54 108 : KS_END };
55 : }
56 :
57 : /**
58 : * @brief This function collects leaf keys (keys without any key below) for a given key set.
59 : *
60 : * @param keys This parameter stores the key set for which this function retrieves all leaf keys.
61 : *
62 : * @return A key set containing only leaf keys
63 : */
64 20 : CppKeySet leaves (CppKeySet const & keys)
65 : {
66 20 : CppKeySet leaves;
67 :
68 20 : auto current = keys.begin ();
69 40 : if (current == keys.end ()) return leaves;
70 :
71 0 : CppKey previous = *current;
72 348 : while (++current != keys.end ())
73 : {
74 216 : bool isLeaf = !current->isBelow (previous);
75 72 : if (isLeaf)
76 : {
77 : leaves.append (previous);
78 : }
79 :
80 216 : previous = *current;
81 : }
82 : // The last key is always a leaf
83 20 : leaves.append (previous);
84 :
85 : return leaves;
86 : }
87 :
88 : /**
89 : * @brief This function counts the levels of a certain key name.
90 : *
91 : * @param key This parameter stores the key for which this function retrieves the number of parts.
92 : *
93 : * @return The number of parts of `key`
94 : */
95 20 : size_t countKeyLevels (CppKey const & key)
96 : {
97 : auto keyIterator = key.begin ();
98 : size_t levels = 0;
99 :
100 226 : while (keyIterator != key.end ())
101 : {
102 124 : keyIterator++;
103 62 : levels++;
104 : }
105 20 : return levels;
106 : }
107 :
108 : /**
109 : * @brief This function writes a YAML collection entry (either mapping key or array element) to the given stream.
110 : *
111 : * @pre The parameter `output` must be a valid and open output stream.
112 : *
113 : * @param output This parameter specifies where this function should put the serialized YAML data.
114 : * @param key The function uses the basename of this key to decide if the entry is an array element or a mapping key.
115 : * @param indent This string specifies the indentation for the collection entry.
116 : */
117 82 : inline void writeCollectionEntry (ofstream & output, CppKey const & key, string const & indent)
118 : {
119 164 : output << indent;
120 82 : if (elektraArrayValidateName (*key) == 1)
121 : {
122 30 : output << "-" << endl;
123 : }
124 : else
125 : {
126 335 : output << key.getBaseName () << ":" << endl;
127 : }
128 82 : }
129 :
130 : /**
131 : * @brief This function returns a name iterator for a key that starts after `levelsToSkip` levels.
132 : *
133 : * @param key This parameter stores the key for which this function returns a name iterator.
134 : * @param levelsToSkip This value stores the number of parts of `key` that the returned iterator should skip.
135 : *
136 : * @return A name iterator for `key`, that skips the first `levelsToSkip` parts of `key`
137 : */
138 136 : inline NameIterator getIteratorSkippedLevels (CppKey const & key, size_t levelsToSkip)
139 : {
140 136 : auto iterator = key.begin ();
141 548 : for (auto levels = levelsToSkip; levels > 0; levels--)
142 : {
143 824 : iterator++;
144 : }
145 :
146 136 : return iterator;
147 : }
148 :
149 : /**
150 : * @brief This function writes a representation of a key value to the given output stream.
151 : *
152 : * @param output This parameter specifies where this function should emit the serialized YAML data.
153 : * @param key This parameter stores the key which stores the value this function should emit to `output`.
154 : */
155 68 : void writeYAMLScalar (ofstream & output, CppKey const & key)
156 : {
157 70 : if (!key.isString ()) return;
158 :
159 133 : string value = key.getString ();
160 :
161 67 : if (value == "0")
162 : {
163 1 : output << "false";
164 : return;
165 : }
166 :
167 66 : if (value == "1")
168 : {
169 0 : output << "true";
170 : return;
171 : }
172 :
173 264 : output << '"' << value << '"';
174 : }
175 :
176 : /**
177 : * @brief This function converts a `KeySet` into the YAML serialization format.
178 : *
179 : * @pre The parameter `output` must be a valid and open output stream.
180 : *
181 : * @param output This parameter specifies where this function should emit the serialized YAML data.
182 : * @param keys This parameter stores the key set which this function converts to YAML data.
183 : * @param parent This value represents the root key of `keys`.
184 : */
185 20 : void writeYAML (ofstream & output, CppKeySet && keys, CppKey const & parent)
186 : {
187 20 : auto levelsParent = countKeyLevels (parent);
188 :
189 : ELEKTRA_LOG_DEBUG ("Convert %zu key%s", keys.size (), keys.size () == 1 ? "" : "s");
190 20 : keys.rewind ();
191 488 : for (CppKey last = parent; keys.next (); last = keys.current ())
192 : {
193 : ELEKTRA_LOG_DEBUG ("Convert key ā%s: %sā", keys.current ().getName ().c_str (), keys.current ().getString ().c_str ());
194 :
195 : // Skip common prefix (parent key name) for all keys in key set
196 68 : auto relativeLast = getIteratorSkippedLevels (last, levelsParent);
197 136 : auto relative = getIteratorSkippedLevels (keys.current (), levelsParent);
198 :
199 : // Add indentation for each part of the key that was already added to the file
200 68 : string indent;
201 863 : while (relativeLast != last.end () && relative != keys.current ().end () && *relative == *relativeLast)
202 : {
203 50 : relative++;
204 50 : relativeLast++;
205 : indent += " ";
206 : }
207 :
208 : // Add YAML mapping key for each part of the key we did not already write into the file
209 204 : auto endCurrent = keys.current ().end ();
210 136 : CppKey current{ "user", KEY_END };
211 :
212 150 : while (relative != endCurrent)
213 : {
214 164 : current.addBaseName (*relative);
215 : ELEKTRA_LOG_DEBUG ("Current name: %s", current.getName ().c_str ());
216 246 : writeCollectionEntry (output, *current, indent);
217 164 : relative++;
218 : indent += " ";
219 : }
220 :
221 136 : output << indent;
222 136 : writeYAMLScalar (output, keys.current ());
223 136 : output << endl;
224 : }
225 20 : }
226 :
227 : } // end namespace
228 :
229 : extern "C" {
230 : // ====================
231 : // = Plugin Interface =
232 : // ====================
233 :
234 : /** @see elektraDocGet */
235 108 : int elektraYamlsmithGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
236 : {
237 216 : CppKey parent{ parentKey };
238 216 : CppKeySet keys{ returned };
239 :
240 324 : if (parent.getName () == "system/elektra/modules/yamlsmith")
241 : {
242 324 : keys.append (contractYamlsmith ());
243 108 : parent.release ();
244 : keys.release ();
245 :
246 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
247 : }
248 :
249 : parent.release ();
250 :
251 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
252 : }
253 :
254 : /** @see elektraDocSet */
255 20 : int elektraYamlsmithSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
256 : {
257 40 : CppKey parent{ parentKey };
258 40 : CppKeySet keys{ returned };
259 :
260 60 : ofstream file{ parent.getString () };
261 20 : if (file.is_open ())
262 : {
263 40 : writeYAML (file, leaves (keys), parent);
264 : }
265 : else
266 : {
267 0 : ELEKTRA_SET_RESOURCE_ERRORF (parent.getKey (), "Unable to open file '%s'", parent.getString ().c_str ());
268 : }
269 :
270 20 : parent.release ();
271 20 : keys.release ();
272 :
273 20 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
274 : }
275 :
276 520 : Plugin * ELEKTRA_PLUGIN_EXPORT
277 : {
278 : return elektraPluginExport ("yamlsmith", ELEKTRA_PLUGIN_GET, &elektraYamlsmithGet, ELEKTRA_PLUGIN_SET, &elektraYamlsmithSet,
279 520 : ELEKTRA_PLUGIN_END);
280 : }
281 :
282 274 : } // end extern "C"
|