38 auto base_dir = std::filesystem::path(path).parent_path();
40 std::unordered_set<std::string> visited;
41 auto canonical_path = std::filesystem::canonical(path).string();
42 visited.insert(canonical_path);
44 YAML::Node root = YAML::LoadFile(path);
45 ResolveIncludes(root, base_dir, visited);
47 }
catch (
const std::filesystem::filesystem_error &e) {
48 throw YamlError(path,
"filesystem error: " + std::string(e.what()));
49 }
catch (
const YAML::Exception &e) {
62 throw YamlError(
"merge",
"no files provided for merge");
67 result = YAML::LoadFile(paths[0]);
68 }
catch (
const YAML::Exception &e) {
72 for (std::size_t i = 1; i < paths.size(); ++i) {
74 YAML::Node overlay = YAML::LoadFile(paths[i]);
75 MergeNodes(result, overlay);
76 }
catch (
const YAML::Exception &e) {
88 if (!std::filesystem::exists(path)) {
94 }
catch (
const YAML::Exception &) {
102 static std::vector<std::filesystem::path>
104 std::vector<std::filesystem::path> results;
106 if (!std::filesystem::exists(directory)) {
110 auto is_yaml = [](
const std::filesystem::path &p) {
111 auto ext = p.extension().string();
112 return ext ==
".yaml" || ext ==
".yml";
116 for (
const auto &entry :
117 std::filesystem::recursive_directory_iterator(directory)) {
118 if (entry.is_regular_file() && is_yaml(entry.path())) {
119 results.push_back(entry.path());
123 for (
const auto &entry :
124 std::filesystem::directory_iterator(directory)) {
125 if (entry.is_regular_file() && is_yaml(entry.path())) {
126 results.push_back(entry.path());
136 static void ResolveIncludes(YAML::Node &node,
137 const std::filesystem::path &base_dir,
138 std::unordered_set<std::string> &visited) {
140 for (
auto it = node.begin(); it != node.end(); ++it) {
141 auto value = it->second;
144 if (value.Tag() ==
"!include" && value.IsScalar()) {
145 std::string include_path = value.as<std::string>();
146 auto full_path = base_dir / include_path;
149 auto canonical_path =
150 std::filesystem::canonical(full_path).string();
151 if (visited.count(canonical_path)) {
153 "circular include detected: " +
157 visited.insert(canonical_path);
158 YAML::Node included =
159 YAML::LoadFile(full_path.string());
160 auto include_dir = full_path.parent_path();
161 ResolveIncludes(included, include_dir, visited);
162 node[it->first] = included;
163 visited.erase(canonical_path);
164 }
catch (
const std::filesystem::filesystem_error &e) {
165 throw YamlError(full_path.string(),
166 "filesystem error: " +
167 std::string(e.what()));
168 }
catch (
const YAML::Exception &e) {
169 throw YamlError(full_path.string(), e.what());
172 ResolveIncludes(value, base_dir, visited);
175 }
else if (node.IsSequence()) {
176 for (std::size_t i = 0; i < node.size(); ++i) {
177 auto element = node[i];
180 if (element.Tag() ==
"!include" && element.IsScalar()) {
181 std::string include_path = element.as<std::string>();
182 auto full_path = base_dir / include_path;
185 auto canonical_path =
186 std::filesystem::canonical(full_path).string();
187 if (visited.count(canonical_path)) {
188 throw YamlError(full_path.string(),
189 "circular include detected: " +
193 visited.insert(canonical_path);
194 YAML::Node included =
195 YAML::LoadFile(full_path.string());
196 auto include_dir = full_path.parent_path();
197 ResolveIncludes(included, include_dir, visited);
199 visited.erase(canonical_path);
200 }
catch (
const std::filesystem::filesystem_error &e) {
201 throw YamlError(full_path.string(),
202 "filesystem error: " +
203 std::string(e.what()));
204 }
catch (
const YAML::Exception &e) {
205 throw YamlError(full_path.string(), e.what());
208 ResolveIncludes(element, base_dir, visited);
215 static void MergeNodes(YAML::Node &base,
const YAML::Node &overlay) {
216 if (overlay.IsMap()) {
217 for (
const auto &pair : overlay) {
218 std::string key = pair.first.as<std::string>();
219 if (base[key] && base[key].IsMap() && pair.second.IsMap()) {
221 YAML::Node child = base[key];
222 MergeNodes(child, pair.second);
226 base[key] = pair.second;