Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
YamlFile.hpp
Go to the documentation of this file.
1
10
11#pragma once
12
14
15#include <filesystem>
16#include <fstream>
17#include <string>
18#include <unordered_set>
19#include <vector>
20
21namespace vulcan::io {
22
26class YamlFile {
27 public:
37 static YamlNode LoadWithIncludes(const std::string &path) {
38 auto base_dir = std::filesystem::path(path).parent_path();
39 try {
40 std::unordered_set<std::string> visited;
41 auto canonical_path = std::filesystem::canonical(path).string();
42 visited.insert(canonical_path);
43
44 YAML::Node root = YAML::LoadFile(path);
45 ResolveIncludes(root, base_dir, visited);
46 return YamlNode(root, path);
47 } catch (const std::filesystem::filesystem_error &e) {
48 throw YamlError(path, "filesystem error: " + std::string(e.what()));
49 } catch (const YAML::Exception &e) {
50 throw YamlError(path, e.what());
51 }
52 }
53
60 static YamlNode MergeFiles(const std::vector<std::string> &paths) {
61 if (paths.empty()) {
62 throw YamlError("merge", "no files provided for merge");
63 }
64
65 YAML::Node result;
66 try {
67 result = YAML::LoadFile(paths[0]);
68 } catch (const YAML::Exception &e) {
69 throw YamlError(paths[0], e.what());
70 }
71
72 for (std::size_t i = 1; i < paths.size(); ++i) {
73 try {
74 YAML::Node overlay = YAML::LoadFile(paths[i]);
75 MergeNodes(result, overlay);
76 } catch (const YAML::Exception &e) {
77 throw YamlError(paths[i], e.what());
78 }
79 }
80
81 return YamlNode(result, "merged");
82 }
83
87 static bool IsValidYaml(const std::string &path) {
88 if (!std::filesystem::exists(path)) {
89 return false;
90 }
91 try {
92 YAML::LoadFile(path);
93 return true;
94 } catch (const YAML::Exception &) {
95 return false;
96 }
97 }
98
102 static std::vector<std::filesystem::path>
103 FindYamlFiles(const std::string &directory, bool recursive = false) {
104 std::vector<std::filesystem::path> results;
105
106 if (!std::filesystem::exists(directory)) {
107 return results;
108 }
109
110 auto is_yaml = [](const std::filesystem::path &p) {
111 auto ext = p.extension().string();
112 return ext == ".yaml" || ext == ".yml";
113 };
114
115 if (recursive) {
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());
120 }
121 }
122 } else {
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());
127 }
128 }
129 }
130
131 return results;
132 }
133
134 private:
136 static void ResolveIncludes(YAML::Node &node,
137 const std::filesystem::path &base_dir,
138 std::unordered_set<std::string> &visited) {
139 if (node.IsMap()) {
140 for (auto it = node.begin(); it != node.end(); ++it) {
141 auto value = it->second;
142
143 // Check for !include tag
144 if (value.Tag() == "!include" && value.IsScalar()) {
145 std::string include_path = value.as<std::string>();
146 auto full_path = base_dir / include_path;
147
148 try {
149 auto canonical_path =
150 std::filesystem::canonical(full_path).string();
151 if (visited.count(canonical_path)) {
152 throw YamlError(full_path.string(),
153 "circular include detected: " +
154 canonical_path);
155 }
156
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());
170 }
171 } else {
172 ResolveIncludes(value, base_dir, visited);
173 }
174 }
175 } else if (node.IsSequence()) {
176 for (std::size_t i = 0; i < node.size(); ++i) {
177 auto element = node[i];
178
179 // Check for !include in sequence
180 if (element.Tag() == "!include" && element.IsScalar()) {
181 std::string include_path = element.as<std::string>();
182 auto full_path = base_dir / include_path;
183
184 try {
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: " +
190 canonical_path);
191 }
192
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);
198 node[i] = included;
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());
206 }
207 } else {
208 ResolveIncludes(element, base_dir, visited);
209 }
210 }
211 }
212 }
213
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()) {
220 // Recursive merge for nested maps
221 YAML::Node child = base[key];
222 MergeNodes(child, pair.second);
223 base[key] = child;
224 } else {
225 // Override
226 base[key] = pair.second;
227 }
228 }
229 } else {
230 // Non-map overlay replaces base entirely
231 base = overlay;
232 }
233 }
234};
235
236} // namespace vulcan::io
Type-safe YAML wrapper with path-aware error messages.
Exception for YAML parsing/access errors.
Definition YamlNode.hpp:27
YAML file utilities.
Definition YamlFile.hpp:26
static YamlNode LoadWithIncludes(const std::string &path)
Load YAML from file with !include directive resolution.
Definition YamlFile.hpp:37
static YamlNode MergeFiles(const std::vector< std::string > &paths)
Merge multiple YAML files (later files override earlier).
Definition YamlFile.hpp:60
static std::vector< std::filesystem::path > FindYamlFiles(const std::string &directory, bool recursive=false)
Get all YAML files in directory.
Definition YamlFile.hpp:103
static bool IsValidYaml(const std::string &path)
Check if file exists and is valid YAML.
Definition YamlFile.hpp:87
Wrapper around yaml-cpp node with ergonomic accessors.
Definition YamlNode.hpp:47
Definition CSVExport.hpp:20