Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #include "highlevel.hpp"
10 :
11 : #include "common.hpp"
12 : #include "enums.hpp"
13 : #include "structs.hpp"
14 :
15 : #include <command.hpp>
16 : #include <modules.hpp>
17 :
18 : #include <kdb.h>
19 : #include <kdbease.h>
20 : #include <kdbhelper.h>
21 : #include <kdbplugin.h>
22 : #include <kdbtypes.h>
23 :
24 : #include <fstream>
25 : #include <memory>
26 : #include <regex>
27 : #include <set>
28 : #include <streambuf>
29 : #include <string>
30 :
31 : const char * HighlevelGenTemplate::Params::InitFunctionName = "initFn";
32 : const char * HighlevelGenTemplate::Params::HelpFunctionName = "helpFn";
33 : const char * HighlevelGenTemplate::Params::SpecloadFunctionName = "specloadFn";
34 : const char * HighlevelGenTemplate::Params::TagPrefix = "tagPrefix";
35 : const char * HighlevelGenTemplate::Params::EnumConversion = "enumConv";
36 : const char * HighlevelGenTemplate::Params::AdditionalHeaders = "headers";
37 : const char * HighlevelGenTemplate::Params::GenerateSetters = "genSetters";
38 : const char * HighlevelGenTemplate::Params::EmbeddedSpec = "embeddedSpec";
39 : const char * HighlevelGenTemplate::Params::SpecValidation = "specValidation";
40 : const char * HighlevelGenTemplate::Params::InstallPrefix = "installPrefix";
41 :
42 : enum class EmbeddedSpec
43 : {
44 : Full,
45 : Defaults,
46 : None
47 : };
48 :
49 : enum class SpecValidation
50 : {
51 : None,
52 : Minimal,
53 : Full // TODO: implement?
54 : };
55 :
56 27 : static std::string createIncludeGuard (const std::string & fileName)
57 : {
58 27 : std::string result;
59 54 : result.resize (fileName.length ());
60 108 : std::transform (fileName.begin (), fileName.end (), result.begin (), ::toupper);
61 27 : escapeNonAlphaNum (result);
62 27 : return result;
63 : }
64 :
65 24 : static inline std::string getArgName (const kdb::Key & key, kdb_long_long_t index, const std::string & defaultPrefix)
66 : {
67 48 : auto indexStr = std::to_string (index);
68 168 : auto metaName = "gen/arg/name/#" + std::string (indexStr.length () - 1, '_') + indexStr;
69 48 : return key.hasMeta (metaName) ? key.getMeta<std::string> (metaName) : defaultPrefix + indexStr;
70 : }
71 :
72 24 : static inline std::string getArgDescription (const kdb::Key & key, kdb_long_long_t index, const std::string & kind)
73 : {
74 48 : auto indexStr = std::to_string (index);
75 168 : auto metaName = "gen/arg/description/#" + std::string (indexStr.length () - 1, '_') + indexStr;
76 24 : return key.hasMeta (metaName) ? key.getMeta<std::string> (metaName) :
77 120 : "Replaces occurence no. " + indexStr + " of " + kind + " in the keyname.";
78 : }
79 :
80 85 : static void getKeyArgs (const kdb::Key & key, const size_t parentKeyParts, kainjow::mustache::list & args, std::string & fmtString)
81 : {
82 : using namespace kainjow::mustache;
83 170 : auto parts = getKeyParts (key);
84 595 : parts.erase (parts.begin (), parts.begin () + parentKeyParts);
85 :
86 170 : std::stringstream fmt;
87 :
88 85 : size_t pos = 1;
89 85 : size_t names = 1;
90 85 : size_t indices = 1;
91 467 : for (const auto & part : parts)
92 : {
93 127 : if (part == "_")
94 : {
95 50 : const std::string & argName = getArgName (key, names, "name");
96 : auto arg = object{ { "native_type", "const char *" },
97 : { "name", argName },
98 : { "code", argName },
99 : { "index?", false },
100 90 : { "description", getArgDescription (key, names, "_") } };
101 20 : args.push_back (arg);
102 10 : fmt << "%s/";
103 10 : ++pos;
104 10 : ++names;
105 : }
106 117 : else if (part == "#")
107 : {
108 70 : const std::string & argName = getArgName (key, indices, "index");
109 :
110 42 : std::string argCode = "elektra_len (" + argName + "), elektra_len (";
111 42 : argCode += argName + "), \"#___________________\", (long long) ";
112 14 : argCode += argName;
113 :
114 : auto arg = object{ { "native_type", "kdb_long_long_t" },
115 : { "name", argName },
116 : { "code", argCode },
117 : { "index?", true },
118 126 : { "description", getArgDescription (key, indices, "#") } };
119 28 : args.push_back (arg);
120 14 : fmt << "%*.*s%lld/";
121 14 : pos += 4;
122 14 : ++indices;
123 : }
124 : else
125 : {
126 : // escape backslashes first too avoid collision
127 309 : fmt << std::regex_replace (part, std::regex ("[\\\\/]"), "\\\\$0") << "/";
128 : }
129 : }
130 :
131 85 : if (!args.empty ())
132 : {
133 132 : args.back ()["last?"] = true;
134 : }
135 :
136 170 : if (args.size () > 1)
137 : {
138 14 : args[args.size () - 2]["last_but_one?"] = true;
139 : }
140 :
141 170 : fmtString = fmt.str ();
142 85 : fmtString.pop_back ();
143 85 : }
144 :
145 54 : static std::string keySetToCCode (kdb::KeySet & set)
146 : {
147 : using namespace kdb;
148 : using namespace kdb::tools;
149 :
150 108 : Modules modules;
151 324 : PluginPtr plugin = modules.load ("c", KeySet ());
152 :
153 216 : auto file = "/tmp/elektra.highlevelgen." + std::to_string (std::time (nullptr));
154 108 : Key errorKey ("", KEY_VALUE, file.c_str (), KEY_END);
155 54 : if (plugin->set (set, errorKey) == ELEKTRA_PLUGIN_STATUS_ERROR)
156 : {
157 0 : throw CommandAbortException ("c (plugin) failed");
158 : }
159 :
160 108 : std::ifstream is (file);
161 108 : std::string line;
162 :
163 108 : std::stringstream ss;
164 744 : while (std::getline (is, line))
165 : {
166 318 : ss << line << std::endl;
167 : }
168 :
169 108 : return ss.str ();
170 : }
171 :
172 2 : static void keySetToQuickdump (kdb::KeySet & set, const std::string & path, const std::string & parent)
173 : {
174 : using namespace kdb;
175 : using namespace kdb::tools;
176 :
177 4 : Modules modules;
178 4 : KeySet config;
179 6 : config.append (Key ("system/noparent", KEY_END));
180 10 : PluginPtr plugin = modules.load ("quickdump", config);
181 :
182 6 : Key parentKey (parent.c_str (), KEY_VALUE, path.c_str (), KEY_END);
183 2 : if (plugin->set (set, parentKey) == ELEKTRA_PLUGIN_STATUS_ERROR)
184 : {
185 0 : throw CommandAbortException ("quickdump failed");
186 : }
187 2 : }
188 :
189 27 : static kdb::KeySet cascadingToSpec (const kdb::KeySet & ks)
190 : {
191 27 : auto result = kdb::KeySet (ks.size (), KS_END);
192 617 : for (auto it = ks.begin (); it != ks.end (); ++it)
193 : {
194 804 : if (it->isCascading ())
195 : {
196 550 : auto specKey = kdb::Key (it->dup ());
197 330 : specKey.setName ("spec" + specKey.getName ());
198 110 : result.append (specKey);
199 : }
200 804 : if (it->isSpec ())
201 : {
202 474 : result.append (*it);
203 : }
204 : }
205 27 : return result;
206 : }
207 :
208 27 : kainjow::mustache::data HighlevelGenTemplate::getTemplateData (const std::string & outputName, const std::string & part,
209 : const kdb::KeySet & keySet, const std::string & parentKey) const
210 : {
211 : using namespace kainjow::mustache;
212 :
213 54 : auto headerFile = outputName + ".h";
214 54 : auto includeGuard = createIncludeGuard (headerFile);
215 216 : auto initFunctionName = getParameter (Params::InitFunctionName, "loadConfiguration");
216 216 : auto helpFunctionName = getParameter (Params::HelpFunctionName, "printHelpMessage");
217 216 : auto specloadFunctionName = getParameter (Params::SpecloadFunctionName, "exitForSpecload");
218 216 : auto tagPrefix = getParameter (Params::TagPrefix, "ELEKTRA_TAG_");
219 216 : auto installPrefix = getParameter (Params::InstallPrefix, "/usr/local");
220 243 : auto additionalHeaders = split (getParameter (Params::AdditionalHeaders), ',');
221 216 : auto enumConversionString = getParameter (Params::EnumConversion, "auto");
222 108 : auto generateSetters = getBoolParameter (Params::GenerateSetters, true);
223 351 : auto specHandling = getParameter<EmbeddedSpec> (Params::EmbeddedSpec, { { "", EmbeddedSpec::Full },
224 : { "full", EmbeddedSpec::Full },
225 : { "defaults", EmbeddedSpec::Defaults },
226 27 : { "none", EmbeddedSpec::None } });
227 324 : auto specValidation = getParameter<SpecValidation> (
228 : Params::SpecValidation,
229 27 : { { "", SpecValidation::None }, { "none", SpecValidation::None }, { "minimal", SpecValidation::Minimal } });
230 :
231 351 : auto enumConversion = getParameter<EnumConversion> (Params::EnumConversion, { { "", EnumConversion::Auto },
232 : { "auto", EnumConversion::Auto },
233 : { "switch", EnumConversion::Trie },
234 27 : { "strcmp", EnumConversion::Strcmp } });
235 :
236 :
237 54 : std::string cascadingParent;
238 54 : std::string specParentName;
239 54 : kdb::KeySet ks;
240 :
241 27 : if (parentKey[0] == '/')
242 : {
243 27 : cascadingParent = parentKey;
244 54 : specParentName = "spec" + parentKey;
245 81 : ks = cascadingToSpec (keySet);
246 : }
247 0 : else if (parentKey.substr (0, 5) == "spec/")
248 : {
249 0 : cascadingParent = parentKey.substr (4);
250 0 : specParentName = parentKey;
251 0 : ks = keySet;
252 : }
253 : else
254 : {
255 0 : throw CommandAbortException ("parentKey has to start with spec/ or /");
256 : }
257 :
258 : auto data = object{ { "header_file", headerFile },
259 : { "include_guard", includeGuard },
260 : { "spec_parent_key", specParentName },
261 : { "parent_key", cascadingParent },
262 : { "init_function_name", initFunctionName },
263 : { "help_function_name", helpFunctionName },
264 : { "specload_function_name", specloadFunctionName },
265 : { "generate_setters?", generateSetters },
266 54 : { "embed_spec?", specHandling == EmbeddedSpec::Full },
267 27 : { "embed_defaults?", specHandling == EmbeddedSpec::Defaults },
268 27 : { "spec_as_defaults?", specHandling == EmbeddedSpec::Full },
269 351 : { "more_headers", list (additionalHeaders.begin (), additionalHeaders.end ()) } };
270 :
271 54 : list enums;
272 54 : list structs;
273 54 : list keys;
274 54 : list unions;
275 :
276 81 : auto specParent = kdb::Key (specParentName, KEY_END);
277 :
278 54 : EnumProcessor enumProcessor (enumConversion);
279 54 : StructProcessor structProcessor (specParent, ks);
280 :
281 27 : auto parentLength = specParentName.length ();
282 :
283 54 : kdb::KeySet spec;
284 54 : kdb::KeySet defaults;
285 :
286 135 : kdb::Key parent = ks.lookup (specParent).dup ();
287 108 : parent.setName ("");
288 27 : spec.append (parent);
289 :
290 54 : auto parentKeyParts = getKeyParts (specParent);
291 :
292 135 : auto mountpoint = parent.getMeta<std::string> ("mountpoint");
293 :
294 54 : std::regex appNameRegex ("/sw/[^/]+/[^/]+/#0/current");
295 54 : std::string appName;
296 54 : std::string appNameWithOrg;
297 27 : if (std::regex_match (cascadingParent, appNameRegex))
298 : {
299 0 : appName = parentKeyParts[3];
300 0 : escapeNonAlphaNum (appName);
301 0 : std::string orgName = parentKeyParts[2];
302 0 : escapeNonAlphaNum (orgName);
303 0 : appNameWithOrg = orgName + "/" + appName;
304 : }
305 : else
306 : {
307 54 : appName = cascadingParent.substr (1);
308 27 : escapeNonAlphaNum (appName);
309 : appNameWithOrg = appName;
310 : }
311 :
312 27 : if (part == ".mount.sh")
313 : {
314 112 : return object{ { "parent_key", cascadingParent },
315 : { "spec_parent_key", specParentName },
316 16 : { "mount_file", appName + ".overlay.spec.eqd" },
317 : { "spec_mount_file", mountpoint },
318 16 : { "direct_file?", specHandling != EmbeddedSpec::Full },
319 : { "org_and_app", appNameWithOrg },
320 64 : { "app", appName } };
321 : }
322 :
323 275 : for (auto it = ks.begin (); it != ks.end (); ++it)
324 : {
325 195 : kdb::Key key = *it;
326 :
327 220 : if (!key.isSpec () || !key.isBelow (specParent))
328 : {
329 25 : continue;
330 : }
331 :
332 267 : kdb::Key specKey = key.dup ();
333 273 : specKey.setName (specKey.getName ().substr (parentLength));
334 91 : spec.append (specKey);
335 :
336 91 : if (!hasType (key))
337 : {
338 : continue;
339 : }
340 :
341 170 : auto type = getType (key);
342 170 : auto name = key.getName ();
343 85 : name.erase (0, sizeof ("spec") - 1);
344 :
345 170 : std::string fmtString;
346 170 : list args;
347 170 : getKeyArgs (key, parentKeyParts.size (), args, fmtString);
348 :
349 342 : if (!key.hasMeta ("default") && !key.hasMeta ("require"))
350 : {
351 3 : throw CommandAbortException ("The key '" + name +
352 3 : "' doesn't have a default value and is not marked with 'require'!");
353 : }
354 :
355 336 : kdb::Key defaultsKey (key.getName ().substr (parentLength), KEY_END);
356 672 : defaultsKey.setMeta ("default", key.getMeta<std::string> ("default"));
357 672 : defaultsKey.setMeta ("type", key.getMeta<std::string> ("type"));
358 84 : defaults.append (defaultsKey);
359 :
360 : std::unordered_set<std::string> allowedTypes = { "enum",
361 : "string",
362 : "boolean",
363 : "char",
364 : "octet",
365 : "short",
366 : "unsigned_short",
367 : "long",
368 : "unsigned_long",
369 : "long_long",
370 : "unsigned_long_long",
371 : "float",
372 : "double",
373 : "long_double",
374 : "struct",
375 : "struct_ref",
376 3360 : "discriminator" };
377 :
378 252 : if (allowedTypes.find (type) == allowedTypes.end ())
379 : {
380 0 : auto msg = "The key '" + name;
381 0 : msg += "' has an unsupported type ('" + type + "')!";
382 0 : throw CommandAbortException (msg);
383 : }
384 :
385 84 : if (type == "discriminator")
386 : {
387 0 : type = "enum";
388 0 : specKey.setMeta ("type", "enum");
389 : }
390 :
391 84 : bool isString = type == "string";
392 252 : auto nativeType = isString ? "const char *" : "kdb_" + type + "_t";
393 168 : auto typeName = snakeCaseToPascalCase (type);
394 :
395 168 : auto tagName = getTagName (key, specParentName);
396 :
397 252 : auto isArray = key.getBaseName () == "#";
398 :
399 168 : object keyObject = { { "name", name.substr (cascadingParent.size () + 1) }, // + 2 to remove slash
400 : { "native_type", nativeType },
401 252 : { "macro_name", tagPrefix + snakeCaseToMacroCase (tagName) },
402 168 : { "tag_name", snakeCaseToPascalCase (tagName) },
403 : { "type_name", typeName },
404 : { "is_string?", isString },
405 840 : { "is_array?", isArray } };
406 :
407 84 : if (!args.empty ())
408 : {
409 198 : keyObject["args?"] = object{ { "args", args } };
410 110 : keyObject["args"] = args;
411 110 : keyObject["fmt_string"] = fmtString;
412 : }
413 :
414 84 : if (isArray)
415 : {
416 28 : if (args.size () > 1)
417 : {
418 : // remove last argument and last part of format string
419 12 : auto arrayArgs = list{ args.begin (), args.end () - 1 };
420 12 : arrayArgs.back ()["last?"] = true;
421 :
422 10 : keyObject["array_args?"] =
423 14 : object ({ { "args", arrayArgs }, { "fmt_string", fmtString.substr (0, fmtString.rfind ('/')) } });
424 : }
425 : // remove last part ('/#') from name
426 84 : keyObject["array_name"] = name.substr (cascadingParent.size () + 1, name.size () - cascadingParent.size () - 3);
427 : }
428 :
429 84 : if (type == "enum")
430 : {
431 20 : auto enumData = enumProcessor.process (key, tagName);
432 :
433 100 : keyObject["type_name"] = enumData["type_name"].string_value ();
434 100 : keyObject["native_type"] = enumData["native_type"].string_value ();
435 :
436 50 : if (enumData["new"].is_true ())
437 : {
438 8 : enums.emplace_back (enumData);
439 : }
440 : }
441 74 : else if (type == "struct_ref")
442 : {
443 : bool allocate;
444 8 : std::string dummyString;
445 :
446 : bool processed;
447 4 : if (isArray)
448 : {
449 : processed = StructFieldsProcessor::processArrayStructRef (key, specParent, ks, typeName, nativeType,
450 4 : allocate, dummyString);
451 : }
452 : else
453 : {
454 : processed = StructFieldsProcessor::processStructRef (key, specParent, ks, typeName, nativeType, allocate,
455 0 : dummyString);
456 : }
457 :
458 4 : if (processed)
459 : {
460 20 : keyObject["type_name"] = typeName;
461 20 : keyObject["native_type"] = nativeType;
462 20 : keyObject["is_struct_ref?"] = true;
463 24 : keyObject["alloc?"] = allocate;
464 20 : keyObject["generate_setters?"] = false;
465 : }
466 : else
467 : {
468 0 : continue;
469 : }
470 : }
471 70 : else if (type == "struct")
472 : {
473 28 : auto maxDepth = key.hasMeta ("gen/struct/depth") ? key.getMeta<kdb::short_t> ("gen/struct/depth") : 1;
474 12 : auto baseDepth = getKeyParts (key).size ();
475 :
476 12 : kdb::KeySet subkeys;
477 60 : for (auto cur = it + 1; cur != ks.end (); ++cur)
478 : {
479 66 : if (cur->isBelow (key))
480 : {
481 36 : if (StructProcessor::isFieldIgnored (*cur))
482 : {
483 0 : continue;
484 : }
485 :
486 54 : auto parts = getKeyParts (*cur);
487 36 : if (parts.size () <= baseDepth + maxDepth)
488 : {
489 112 : if (std::any_of (parts.begin () + baseDepth, parts.end () - 1,
490 70 : [](const std::string & s) { return s == "_" || s == "#"; }) ||
491 32 : parts.back () == "_")
492 : {
493 0 : throw CommandAbortException ("struct cannot contain globbed keys (_, #).");
494 : }
495 :
496 48 : subkeys.append (*cur);
497 : }
498 6 : else if (parts.size () <= baseDepth + maxDepth + 1 && parts.back () == "#")
499 : {
500 6 : subkeys.append (*cur);
501 : }
502 : }
503 : else
504 : {
505 : break;
506 : }
507 : }
508 :
509 12 : kainjow::mustache::list structUnions;
510 12 : auto structData = structProcessor.process (key, subkeys, tagName, specParentName, structUnions);
511 :
512 24 : for (const auto & u : structUnions)
513 : {
514 0 : unions.push_back (u);
515 : }
516 :
517 60 : keyObject["type_name"] = structData["type_name"].string_value ();
518 60 : keyObject["native_type"] = structData["native_type"].string_value ();
519 30 : keyObject["is_struct?"] = true;
520 60 : keyObject["alloc?"] = structData["alloc?"].is_true ();
521 60 : keyObject["generate_setters?"] = structData["generate_setters?"].is_true ();
522 :
523 30 : if (structData["new"].is_true ())
524 : {
525 6 : structs.emplace_back (structData);
526 : }
527 : }
528 :
529 84 : keys.emplace_back (keyObject);
530 : }
531 :
532 18 : kdb::KeySet contract;
533 54 : contract.append (kdb::Key ("system/elektra/ensure/plugins/global/gopts", KEY_VALUE, "mounted", KEY_END));
534 :
535 18 : if (specValidation == SpecValidation::Minimal)
536 : {
537 0 : contract.append (kdb::Key ("system/elektra/highlevel/validation", KEY_VALUE, "minimal", KEY_END));
538 : }
539 :
540 144 : data["keys_count"] = std::to_string (keys.size ());
541 90 : data["keys"] = keys;
542 90 : data["enums"] = enums;
543 90 : data["unions"] = unions;
544 90 : data["structs"] = structs;
545 108 : data["spec"] = keySetToCCode (spec);
546 108 : data["defaults"] = keySetToCCode (defaults);
547 108 : data["contract"] = keySetToCCode (contract);
548 90 : data["specload_arg"] = "--elektra-spec";
549 :
550 18 : if (part == ".spec.eqd")
551 : {
552 4 : keySetToQuickdump (spec, outputName + part, specParentName);
553 : return kainjow::mustache::data (false);
554 : }
555 :
556 16 : return data;
557 : }
558 :
559 162 : std::string HighlevelGenTemplate::escapeFunction (const std::string & str) const
560 : {
561 324 : std::stringstream ss;
562 2426 : for (const auto & c : str)
563 : {
564 1778 : switch (c)
565 : {
566 : case '\a':
567 0 : ss << "\\a";
568 : break;
569 : case '\b':
570 0 : ss << "\\b";
571 : break;
572 : case '\f':
573 0 : ss << "\\f";
574 : break;
575 : case '\n':
576 0 : ss << "\\n";
577 : break;
578 : case '\r':
579 0 : ss << "\\r";
580 : break;
581 : case '\t':
582 0 : ss << "\\t";
583 : break;
584 : case '\v':
585 0 : ss << "\\v";
586 : break;
587 : case '\\':
588 0 : ss << "\\\\";
589 : break;
590 : case '\'':
591 0 : ss << "\\'";
592 : break;
593 : case '"':
594 0 : ss << "\\\"";
595 : break;
596 : default:
597 1778 : if (isprint (c))
598 : {
599 1778 : ss << c;
600 : }
601 : else
602 : {
603 0 : ss << "\\x" << std::hex << std::setw (2) << static_cast<unsigned char> (c);
604 : }
605 : }
606 : }
607 :
608 324 : return ss.str ();
609 : }
610 :
611 9 : std::vector<std::string> HighlevelGenTemplate::getActualParts () const
612 : {
613 9 : std::vector<std::string> parts (GenTemplate::getActualParts ());
614 81 : if (getParameter (Params::EmbeddedSpec, "full") == "full")
615 : {
616 49 : parts.erase (std::remove (parts.begin (), parts.end (), ".spec.eqd"), parts.end ());
617 : }
618 9 : return parts;
619 7164 : }
|