|
4 | 4 | #include <filesystem> // C++17 |
5 | 5 | #include <sstream> |
6 | 6 | #include <algorithm> |
| 7 | +#include <fstream> |
7 | 8 | //////////////////////////////////////////////////////////////////////////////// |
8 | 9 |
|
9 | 10 | namespace jse |
@@ -46,19 +47,114 @@ namespace jse |
46 | 47 |
|
47 | 48 | namespace |
48 | 49 | { |
| 50 | + // adds key after the provided pointer, correctly handling the json special case that root has an ending / |
49 | 51 | std::string append_pointer(const std::string &pointer, const std::string &key) |
50 | 52 | { |
51 | 53 | if (pointer == "/") |
52 | 54 | return "/" + key; |
53 | 55 | else |
54 | 56 | return pointer + "/" + key; |
55 | 57 | } |
| 58 | + |
| 59 | + // adds key before the provided pointer, correctly handling the json special case that root has an ending / |
| 60 | + std::string prepend_pointer(const std::string &pointer, const std::string &key) |
| 61 | + { |
| 62 | + if (key == "/") |
| 63 | + return pointer; |
| 64 | + else if (pointer == "/") |
| 65 | + return key; |
| 66 | + else |
| 67 | + return key + pointer; |
| 68 | + } |
| 69 | + |
56 | 70 | } // namespace |
57 | 71 |
|
| 72 | + |
| 73 | + // enriches a given json spec with included json specs |
| 74 | + json JSE::inject_include(const json &rules) |
| 75 | + { |
| 76 | + std::vector<string> dirs = include_directories; |
| 77 | + dirs.push_back(""); // adding default path |
| 78 | + |
| 79 | + // max 10 levels of nesting to avoid infinite loops |
| 80 | + json current = rules; |
| 81 | + |
| 82 | + for (size_t x = 0; x < 10; x++) |
| 83 | + { |
| 84 | + // check if the rules have any include |
| 85 | + bool include_present = false; |
| 86 | + for (const auto& rule : current) |
| 87 | + if (rule.at("type") == "include") |
| 88 | + include_present = true; |
| 89 | + |
| 90 | + // if there are no includes, return the current ones |
| 91 | + if (!include_present) |
| 92 | + return current; |
| 93 | + |
| 94 | + json enriched; |
| 95 | + // otherwise, do a round of replacement |
| 96 | + for (const auto& rule : current) |
| 97 | + { |
| 98 | + // copy all rules that are not include |
| 99 | + if (rule.at("type") != "include") |
| 100 | + { |
| 101 | + enriched.push_back(rule); |
| 102 | + } |
| 103 | + // if the rule is an include, expand the node with a copy of the included file |
| 104 | + else |
| 105 | + { |
| 106 | + bool replaced = false; |
| 107 | + // the include file could be in any of the include directories |
| 108 | + for (const auto& dir : dirs) |
| 109 | + { |
| 110 | + string spec_file = rule.at("spec_file"); |
| 111 | + string f = dir + "/" + spec_file; |
| 112 | + // check if the file exists |
| 113 | + if (std::filesystem::is_regular_file(f)) |
| 114 | + { |
| 115 | + std::ifstream ifs(f); |
| 116 | + json include_rules = json::parse(ifs); |
| 117 | + |
| 118 | + // loop over all rules to add the prefix |
| 119 | + for (auto& i_rule : include_rules) |
| 120 | + { |
| 121 | + string prefix = rule.at("pointer"); |
| 122 | + string pointer = i_rule.at("pointer"); |
| 123 | + string new_pointer = prepend_pointer(pointer,prefix); |
| 124 | + i_rule.at("pointer") = new_pointer; |
| 125 | + } |
| 126 | + |
| 127 | + // save modified rules |
| 128 | + for (const auto& i_rule : include_rules) |
| 129 | + enriched.push_back(i_rule); |
| 130 | + |
| 131 | + // one substitution is enough, give up the search over include dirs |
| 132 | + replaced = true; |
| 133 | + break; |
| 134 | + } |
| 135 | + |
| 136 | + } |
| 137 | + |
| 138 | + if (!replaced) |
| 139 | + { |
| 140 | + string pointer = rule.at("pointer"); |
| 141 | + throw std::runtime_error("Failed to replace the include rule: " + pointer); |
| 142 | + assert(replaced == true); |
| 143 | + } |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + // now that we replaced the include, copy it back to current |
| 148 | + current = enriched; |
| 149 | + } |
| 150 | + |
| 151 | + throw std::runtime_error("Reached maximal 10 levels of include recursion."); |
| 152 | + |
| 153 | + } |
| 154 | + |
| 155 | + |
58 | 156 | bool JSE::verify_json(const string &pointer, json &input, const json &rules) |
59 | 157 | { |
60 | | - // if (pointer == "/common") |
61 | | - // std::cout << "gotcha" << std::endl; |
62 | 158 | // Find all rules that apply for the input node |
63 | 159 | // TODO: accelerate this |
64 | 160 | std::vector<json> matching_rules = collect_pointer(pointer, rules); |
@@ -216,6 +312,8 @@ namespace jse |
216 | 312 | return verify_rule_object(input, rule); |
217 | 313 | else if (type == "bool") |
218 | 314 | return verify_rule_bool(input, rule); |
| 315 | + else if (type == "include") |
| 316 | + return verify_rule_include(input, rule); |
219 | 317 | else |
220 | 318 | { |
221 | 319 | log.push_back(log_item("error", "Unknown rule type " + type)); |
@@ -362,6 +460,14 @@ namespace jse |
362 | 460 | return true; |
363 | 461 | } |
364 | 462 |
|
| 463 | + bool JSE::verify_rule_include(const json &input, const json &rule) |
| 464 | + { |
| 465 | + assert(rule.at("type") == "include"); |
| 466 | + |
| 467 | + // An include rule always fails, they should be processed first by the inject_include function |
| 468 | + return false; |
| 469 | + } |
| 470 | + |
365 | 471 | json JSE::collect_default_rules(const json &rules) |
366 | 472 | { |
367 | 473 | // Find all rules that apply for the input node |
|
0 commit comments