SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
FGD.cpp
Go to the documentation of this file.
1// ReSharper disable CppRedundantQualifier
2// ReSharper disable CppUseStructuredBinding
3
4#include <toolpp/FGD.h>
5
6#include <algorithm>
7#include <filesystem>
8#include <initializer_list>
9
10#include <BufferStream.h>
11
13#include <sourcepp/FS.h>
14#include <sourcepp/Math.h>
15
16using namespace sourcepp;
17using namespace std::string_view_literals;
18using namespace toolpp;
19
20namespace {
21
22constexpr auto INVALID_SYNTAX_MSG = "Invalid syntax found in FGD!";
23constexpr auto INVALID_CLASS_MSG = "Invalid class found in FGD!";
24
25[[nodiscard]] bool tryToEatSeparator(BufferStream& stream, char sep) {
27 return parser::text::tryToEatChar(stream, sep);
28}
29
30// FGD strings are weird - they can be split across several lines, only accept \n escapes,
31// and don't need to be terminated by a double quote. Really gross...
32[[nodiscard]] std::string_view readFGDString(BufferStreamReadOnly& stream, BufferStream& backing) {
34
35 static constexpr std::string_view END = "\"\n";
36
37 const auto startSpan = backing.tell();
38 while (true) {
39 char c = stream.read<char>();
40 if (c != '\"') {
41 stream.seek(-1, std::ios::cur);
42 const auto out = parser::text::readUnquotedStringToBuffer(stream, backing, ":", parser::text::EscapeSequenceMap{});
43 if (stream.seek(-1, std::ios::cur).peek<char>() != ':') {
44 stream.skip();
46 }
47 // ReSharper disable once CppDFALocalValueEscapesFunction
48 return out;
49 }
50
51 for (c = stream.read<char>(); END.find(c) == std::string_view::npos; c = stream.read<char>()) {
52 if (c == '\\') {
53 auto n = stream.read<char>();
54 if (n == 'n') {
55 backing << '\n';
56 } else if (END.find(n) != std::string_view::npos) {
57 break;
58 } else {
59 backing << c << n;
60 }
61 } else {
62 backing << c;
63 }
64 }
65
66 if (!::tryToEatSeparator(stream, '+')) {
67 break;
68 }
69 // We need to make sure the next line is actually a string, because sometimes + will lead to nothing. Lovely
71 if (stream.peek<char>() != '\"') {
72 break;
73 }
74 }
75
76 if (stream.seek(-1, std::ios::cur).peek<char>() != ':') {
77 stream.skip();
79 }
80
81 backing << '\0';
82 // ReSharper disable once CppRedundantCastExpression
83 return {reinterpret_cast<const char*>(backing.data()) + startSpan, static_cast<std::string_view::size_type>(backing.tell() - 1 - startSpan)};
84}
85
86void readVersion(BufferStreamReadOnly& stream, BufferStream& backing, int& version) {
87 if (stream.seek(-1, std::ios::cur).peek<char>() != '(') {
89 if (stream.peek<char>() != '(') {
90 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
91 }
92 }
93 std::string versionString{parser::text::readStringToBuffer(stream, backing, "(", ")", parser::text::EscapeSequenceMap{})};
94 string::trim(versionString);
95 string::toInt(versionString, version);
96}
97
98void readMapSize(BufferStreamReadOnly& stream, BufferStream& backing, math::Vec2i& mapSize) {
99 if (stream.seek(-1, std::ios::cur).peek<char>() != '(') {
101 if (stream.peek<char>() != '(') {
102 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
103 }
104 }
105 const auto mapSizeString = parser::text::readStringToBuffer(stream, backing, "(", ")", parser::text::EscapeSequenceMap{});
106 auto mapSizes = string::split(mapSizeString, ',');
107 if (mapSizes.size() != 2) {
108 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
109 }
110
111 string::trim(mapSizes[0]);
112 string::trim(mapSizes[1]);
113 string::toInt(mapSizes[0], mapSize[0]);
114 string::toInt(mapSizes[1], mapSize[1]);
115}
116
117void readMaterialExclusionDirs(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<std::string_view>& materialExclusionDirs) {
118 if (!::tryToEatSeparator(stream, '[')) {
119 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
120 }
121 while (!::tryToEatSeparator(stream, ']')) {
122 materialExclusionDirs.push_back(::readFGDString(stream, backing));
123 }
124 stream.skip();
125}
126
127void readAutoVisGroups(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<FGD::AutoVisGroup>& autoVisGroups) {
128 if (!::tryToEatSeparator(stream, '=')) {
129 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
130 }
131 const auto parentName = ::readFGDString(stream, backing);
132 if (!::tryToEatSeparator(stream, '[')) {
133 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
134 }
135 while (!::tryToEatSeparator(stream, ']')) {
136 auto& autoVisGroup = autoVisGroups.emplace_back();
137 autoVisGroup.parentName = parentName;
138 autoVisGroup.name = ::readFGDString(stream, backing);
139 if (!::tryToEatSeparator(stream, '[')) {
140 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
141 }
142 while (!::tryToEatSeparator(stream, ']')) {
143 autoVisGroup.entities.push_back(::readFGDString(stream, backing));
144 }
145 stream.skip();
146 }
147 stream.skip();
148}
149
150void readClassProperties(BufferStreamReadOnly& stream, BufferStream& backing, FGD::Entity& entity) {
152
153 while (stream.peek<char>() != '=') {
154 FGD::Entity::ClassProperty classProperty;
156 classProperty.arguments = "";
157
158 if (stream.seek(-1, std::ios::cur).peek<char>() != '(') {
160 if (stream.peek<char>() != '(') {
161 entity.classProperties.push_back(classProperty);
162 continue;
163 }
164 }
165 classProperty.arguments = parser::text::readStringToBuffer(stream, backing, "(", ")", {{'n', '\n'}});
166 entity.classProperties.push_back(classProperty);
167
169 }
170
171 stream.skip();
173}
174
175void readEntityIO(BufferStreamReadOnly& stream, BufferStream& backing, FGD::Entity& entity, bool input) {
176 auto& io = input ? entity.inputs.emplace_back() : entity.outputs.emplace_back();
178 if (stream.seek(-1, std::ios::cur).peek<char>() != '(') {
180 if (stream.peek<char>() != '(') {
181 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
182 }
183 }
184 io.valueType = parser::text::readStringToBuffer(stream, backing, "(", ")", parser::text::EscapeSequenceMap{});
185
186 if (!::tryToEatSeparator(stream, ':')) {
187 io.description = "";
188 return;
189 }
190 io.description = ::readFGDString(stream, backing);
191
193}
194
195void readEntityFieldModifiers(BufferStreamReadOnly& stream, BufferStream& backing, bool& readonly, bool& reportable) {
196 readonly = false;
197 reportable = false;
198
200 if (stream.peek<char>() == 'r') {
201 if (const auto modifier = parser::text::readUnquotedStringToBuffer(stream, backing); modifier == "readonly") {
202 readonly = true;
203 } else if (modifier == "report") {
204 reportable = true;
205 return;
206 } else {
207 stream.seek(-static_cast<int64_t>(modifier.length()), std::ios::cur);
208 return;
209 }
210 }
212 if (stream.peek<char>() == 'r') {
213 if (const auto modifier = parser::text::readUnquotedStringToBuffer(stream, backing); modifier == "report") {
214 reportable = true;
215 } else {
216 stream.seek(-static_cast<int64_t>(modifier.length()), std::ios::cur);
217 //return;
218 }
219 }
220}
221
222void readEntityKeyValue(BufferStreamReadOnly& stream, BufferStream& backing, FGD::Entity& entity) {
223 // Key and value type (looks like "key(valueType)", or "input key(valueType)" for i/o)
224 const auto name = parser::text::readUnquotedStringToBuffer(stream, backing, "(", parser::text::EscapeSequenceMap{});
226 if (string::iequals(name, "input")) {
227 ::readEntityIO(stream, backing, entity, true);
228 return;
229 }
230 if (string::iequals(name, "output")) {
231 ::readEntityIO(stream, backing, entity, false);
232 return;
233 }
234 const auto valueType = parser::text::readUnquotedStringToBuffer(stream, backing, ")", parser::text::EscapeSequenceMap{});
235 // If there is a space after the value type, we need to get rid of the parenthesis here
237 if (stream.peek<char>() == ')') {
238 stream.skip();
239 }
240
241 if (string::iequals(valueType, "choices")) {
242 auto& field = entity.fieldsWithChoices.emplace_back();
243 field.name = name;
244 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
245
246 if (::tryToEatSeparator(stream, ':')) {
247 field.displayName = ::readFGDString(stream, backing);
249 }
250
251 if (::tryToEatSeparator(stream, ':')) {
252 field.valueDefault = ::readFGDString(stream, backing);
254 }
255
256 if (::tryToEatSeparator(stream, ':')) {
257 field.description = ::readFGDString(stream, backing);
259 }
260
261 if (!::tryToEatSeparator(stream, '=') || !::tryToEatSeparator(stream, '[')) {
262 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
263 }
264 while (stream.peek<char>() != ']') {
265 auto& choice = field.choices.emplace_back();
266 choice.value = ::readFGDString(stream, backing);
267
268 if (::tryToEatSeparator(stream, ':')) {
269 choice.displayName = ::readFGDString(stream, backing);
270 } else {
271 choice.displayName = "";
272 }
273 }
274 stream.skip();
275 } else if (string::iequals(valueType, "flags")) {
276 auto& field = entity.fieldsWithFlags.emplace_back();
277 field.name = name;
278 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
279
280 if (::tryToEatSeparator(stream, ':')) {
281 field.displayName = ::readFGDString(stream, backing);
283 }
284
285 if (::tryToEatSeparator(stream, ':')) {
286 field.description = ::readFGDString(stream, backing);
288 }
289
290 if (!::tryToEatSeparator(stream, '=') || !::tryToEatSeparator(stream, '[')) {
291 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
292 }
294
295 while (stream.peek<char>() != ']') {
296 auto& flag = field.flags.emplace_back();
297 const auto valueString = ::readFGDString(stream, backing);
298 if (string::toInt(valueString, flag.value).ec != std::errc{}) {
299 flag.value = 0;
300 }
301
302 if (!::tryToEatSeparator(stream, ':')) {
303 continue;
304 }
305 flag.displayName = ::readFGDString(stream, backing);
306
307 if (!::tryToEatSeparator(stream, ':')) {
308 continue;
309 }
310 const auto enabledByDefaultString = ::readFGDString(stream, backing);
311 int enabledByDefault = 0;
312 if (string::toInt(enabledByDefaultString, enabledByDefault).ec != std::errc{}) {
313 flag.enabledByDefault = false;
314 } else {
315 flag.enabledByDefault = static_cast<bool>(enabledByDefault);
316 }
317
318 if (!::tryToEatSeparator(stream, ':')) {
319 continue;
320 }
321 flag.description = ::readFGDString(stream, backing);
322 }
323 stream.skip();
324 } else {
325 auto& field = entity.fields.emplace_back();
326 field.name = name;
327 field.valueType = valueType;
328 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
329 field.displayName = "";
330 field.valueDefault = "";
331 field.description = "";
332
333 if (!::tryToEatSeparator(stream, ':')) {
334 return;
335 }
336 field.displayName = ::readFGDString(stream, backing);
337
338 if (!::tryToEatSeparator(stream, ':')) {
339 return;
340 }
341 field.valueDefault = ::readFGDString(stream, backing);
342
343 if (!::tryToEatSeparator(stream, ':')) {
344 return;
345 }
346 field.description = ::readFGDString(stream, backing);
347
349 }
350}
351
352void overwriteEntity(FGD::Entity& oldEntity, const FGD::Entity& newEntity) {
353 oldEntity.classProperties = newEntity.classProperties;
354 if (!newEntity.description.empty()) {
355 oldEntity.description = newEntity.description;
356 }
357 if (!newEntity.docsURL.empty()) {
358 oldEntity.docsURL = newEntity.docsURL;
359 }
360 for (const auto& field : newEntity.fields) {
361 if (auto it = std::ranges::find_if(oldEntity.fields, [&field](const auto& oldField) {
362 return oldField.name == field.name;
363 }); it != oldEntity.fields.end()) {
364 it->valueType = field.valueType;
365 it->readonly = field.readonly;
366 it->reportable = field.reportable;
367 if (!field.displayName.empty()) {
368 it->displayName = field.displayName;
369 }
370 if (!field.valueDefault.empty()) {
371 it->valueDefault = field.valueDefault;
372 }
373 if (!field.description.empty()) {
374 it->description = field.description;
375 }
376 } else {
377 oldEntity.fields.push_back(field);
378 }
379 }
380 for (const auto& field : newEntity.fieldsWithChoices) {
381 if (auto it = std::ranges::find_if(oldEntity.fieldsWithChoices, [&field](const auto& oldField) {
382 return oldField.name == field.name;
383 }); it != oldEntity.fieldsWithChoices.end()) {
384 it->readonly = field.readonly;
385 it->reportable = field.reportable;
386 if (!field.displayName.empty()) {
387 it->displayName = field.displayName;
388 }
389 if (!field.valueDefault.empty()) {
390 it->valueDefault = field.valueDefault;
391 }
392 if (!field.description.empty()) {
393 it->description = field.description;
394 }
395 it->choices = field.choices;
396 } else {
397 oldEntity.fieldsWithChoices.push_back(field);
398 }
399 }
400 for (const auto& field : newEntity.fieldsWithFlags) {
401 if (auto it = std::ranges::find_if(oldEntity.fieldsWithFlags, [&field](const auto& oldField) {
402 return oldField.name == field.name;
403 }); it != oldEntity.fieldsWithFlags.end()) {
404 it->readonly = field.readonly;
405 it->reportable = field.reportable;
406 it->flags = field.flags;
407 } else {
408 oldEntity.fieldsWithFlags.push_back(field);
409 }
410 }
411 for (const auto& input : newEntity.inputs) {
412 if (auto it = std::ranges::find_if(oldEntity.inputs, [&input](const auto& oldInput) {
413 return oldInput.name == input.name;
414 }); it != oldEntity.inputs.end()) {
415 it->valueType = input.valueType;
416 if (!input.description.empty()) {
417 it->description = input.description;
418 }
419 } else {
420 oldEntity.inputs.push_back(input);
421 }
422 }
423 for (const auto& output : newEntity.outputs) {
424 if (auto it = std::ranges::find_if(oldEntity.outputs, [&output](const auto& oldOutput) {
425 return oldOutput.name == output.name;
426 }); it != oldEntity.outputs.end()) {
427 it->valueType = output.valueType;
428 if (!output.description.empty()) {
429 it->description = output.description;
430 }
431 } else {
432 oldEntity.outputs.push_back(output);
433 }
434 }
435}
436
437void readEntity(BufferStreamReadOnly& stream, BufferStream& backing, std::string_view classType, std::unordered_map<std::string_view, FGD::Entity>& entities, bool extension) {
438 FGD::Entity entity{};
439 entity.classType = classType;
440
441 // There are optionally a list of class properties after the class
442 if (!::tryToEatSeparator(stream, '=')) {
443 ::readClassProperties(stream, backing, entity);
444 }
445
446 // Entity name
448 const auto name = ::readFGDString(stream, backing);
449
450 // If a colon is here, the entity has a description
451 if (::tryToEatSeparator(stream, ':')) {
452 entity.description = ::readFGDString(stream, backing);
453 } else {
454 entity.description = "";
455 }
456
457 // If a colon is here, the entity has a docs URL
458 if (::tryToEatSeparator(stream, ':')) {
459 entity.docsURL = ::readFGDString(stream, backing);
460 } else {
461 entity.docsURL = "";
462 }
463
464 // Parse entity keyvalues
465 if (!::tryToEatSeparator(stream, '[')) {
466 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
467 }
468 while (!::tryToEatSeparator(stream, ']')) {
469 ::readEntityKeyValue(stream, backing, entity);
470 }
471 stream.skip();
472
473 if (extension && entities.contains(name)) {
474 // Overwrite/add parts of the entity description
475 ::overwriteEntity(entities[name], entity);
476 } else {
477 // Replace entity description entirely
478 entities[name] = entity;
479 }
480}
481
482void writeOptionalKeyValueStrings(BufferStream& writer, std::initializer_list<std::string_view> strings) {
483 static constexpr auto writeOptionalString = [](BufferStream& stream, std::string_view str) {
484 if (!str.empty()) {
485 stream
486 .write(" : \""sv, false)
487 .write(str, false)
488 .write('\"');
489 } else {
490 stream.write(" :"sv, false);
491 }
492 };
493 for (auto revString = std::rbegin(strings); revString != std::rend(strings); ++revString) {
494 if (revString->empty()) {
495 continue;
496 }
497 for (auto string = std::begin(strings); string != revString.base(); ++string) {
498 writeOptionalString(writer, std::string{*string});
499 }
500 return;
501 }
502}
503
504} // namespace
505
506FGD::FGD(const std::filesystem::path& fgdPath) {
507 this->load(fgdPath);
508}
509
510void FGD::load(const std::filesystem::path& fgdPath) {
511 auto fgdData = fs::readFileText(fgdPath);
512 if (fgdData.empty()) {
513 return;
514 }
515 BufferStreamReadOnly stream{fgdData};
516
517 try {
518 std::vector seenPaths{fgdPath};
519 this->readEntities(stream, fgdPath, seenPaths);
520 } catch (const std::overflow_error&) {}
521}
522
523int FGD::getVersion() const {
524 return this->version;
525}
526
527math::Vec2i FGD::getMapSize() const {
528 return this->mapSize;
529}
530
531const std::unordered_map<std::string_view, FGD::Entity>& FGD::getEntities() const {
532 return this->entities;
533}
534
535const std::vector<std::string_view>& FGD::getMaterialExclusionDirs() const {
536 return this->materialExclusionDirs;
537}
538
539const std::vector<FGD::AutoVisGroup>& FGD::getAutoVisGroups() const {
540 return this->autoVisGroups;
541}
542
543// NOLINTNEXTLINE(*-no-recursion)
544void FGD::readEntities(BufferStreamReadOnly& stream, const std::filesystem::path& path, std::vector<std::filesystem::path>& seenPaths) {
545 auto& backingString = this->backingData.emplace_back();
546 // Multiply by 2 to ensure buffer will have enough space (very generous)
547 backingString.resize(stream.size() * 2);
548 BufferStream backing{backingString, false};
549
550 while (true) {
552
553 // All entity definitions start with an '@' followed by the class type
554 if (stream.read<char>() != '@') {
555 throw parser::text::syntax_error{INVALID_SYNTAX_MSG};
556 }
557
558 const auto classType = parser::text::readUnquotedStringToBuffer(stream, backing, "(", parser::text::EscapeSequenceMap{});
559 if (string::iequals(classType, "include")) {
561 // Assume the include path is relative to the current file being processed
562 auto fgdPath = std::filesystem::path{path}.parent_path() / parser::text::readStringToBuffer(stream, backing);
563 if (std::ranges::find(seenPaths, fgdPath) != seenPaths.end()) {
564 continue;
565 }
566 seenPaths.push_back(fgdPath);
567
568 auto fgdData = fs::readFileText(fgdPath);
569 if (fgdData.empty()) {
570 continue;
571 }
572
573 BufferStreamReadOnly newStream{fgdData};
574 try {
575 // ReSharper disable once CppDFAInfiniteRecursion
576 this->readEntities(newStream, fgdPath, seenPaths);
577 } catch (const std::overflow_error&) {}
578 } else if (string::iequals(classType, "version")) {
579 ::readVersion(stream, backing, this->version);
580 } else if (string::iequals(classType, "mapsize")) {
581 ::readMapSize(stream, backing, this->mapSize);
582 } else if (string::iequals(classType, "MaterialExclusion")) {
583 ::readMaterialExclusionDirs(stream, backing, this->materialExclusionDirs);
584 } else if (string::iequals(classType, "AutoVisGroup")) {
585 ::readAutoVisGroups(stream, backing, this->autoVisGroups);
586 } else if (string::iequals(classType, "BaseClass") ||
587 string::iequals(classType, "PointClass") ||
588 string::iequals(classType, "NPCClass") ||
589 string::iequals(classType, "SolidClass") ||
590 string::iequals(classType, "KeyFrameClass") ||
591 string::iequals(classType, "MoveClass") ||
592 string::iequals(classType, "FilterClass")) {
593 ::readEntity(stream, backing, classType, this->entities, false);
594 } else if (string::iequals(classType, "ExtendClass")) {
595 ::readEntity(stream, backing, classType, this->entities, true);
596 } else {
597 throw parser::text::syntax_error{INVALID_CLASS_MSG};
598 }
599 }
600}
601
604
606 : parent(parent_) {}
607
610
613
616
618 return {};
619}
620
621FGDWriter& FGDWriter::include(const std::filesystem::path& fgdPath) {
622 this->writer
623 .write("@include \""sv, false)
624 .write(fgdPath.string(), false)
625 .write("\"\n\n"sv, false);
626 return *this;
627}
628
630 this->writer
631 .write("@version("sv, false)
632 .write(std::to_string(version), false)
633 .write(")\n\n"sv, false);
634 return *this;
635}
636
638 this->writer
639 .write("@mapsize("sv, false)
640 .write(std::to_string(mapSize[0]), false)
641 .write(", "sv, false)
642 .write(std::to_string(mapSize[1]), false)
643 .write(")\n\n"sv, false);
644 return *this;
645}
646
647FGDWriter& FGDWriter::materialExclusionDirs(std::initializer_list<std::string_view> dirs) {
648 return this->materialExclusionDirs(std::span{dirs});
649}
650
651FGDWriter& FGDWriter::materialExclusionDirs(std::span<const std::string_view> dirs) {
652 this->writer.write("@MaterialExclusion\n[\n"sv, false);
653 for (const auto& dir : dirs) {
654 this->writer << '\t' << '\"';
655 this->writer.write(dir, false);
656 this->writer << '\"' << '\n';
657 }
658 this->writer.write("]\n\n"sv, false);
659 return *this;
660}
661
663 this->writer
664 .write("@AutoVisGroup = \""sv, false)
665 .write(parentName, false)
666 .write("\"\n[\n"sv, false);
667 return AutoVisGroupWriter{*this};
668}
669
670FGDWriter::AutoVisGroupWriter &FGDWriter::AutoVisGroupWriter::visGroup(std::string_view name, std::initializer_list<std::string_view> entities) {
671 return this->visGroup(name, std::span{entities});
672}
673
674FGDWriter::AutoVisGroupWriter& FGDWriter::AutoVisGroupWriter::visGroup(std::string_view name, std::span<const std::string_view> entities) {
675 this->parent.writer
676 .write("\t\""sv, false)
677 .write(name, false)
678 .write("\"\n\t[\n"sv, false);
679 for (const auto& entity : entities) {
680 this->parent.writer
681 .write("\t\t\""sv, false)
682 .write(entity, false)
683 .write("\"\n"sv, false);
684 }
685 this->parent.writer.write("\t]\n"sv, false);
686 return *this;
687}
688
690 this->parent.writer.write("]\n\n"sv, false);
691 return this->parent;
692}
693
694FGDWriter::EntityWriter FGDWriter::beginEntity(std::string_view classType, std::initializer_list<std::string_view> classProperties, std::string_view name, std::string_view description, std::string_view docsURL) {
695 return this->beginEntity(classType, std::span{classProperties}, name, description, docsURL);
696}
697
698FGDWriter::EntityWriter FGDWriter::beginEntity(std::string_view classType, std::span<const std::string_view> classProperties, std::string_view name, std::string_view description, std::string_view docsURL) {
699 this->writer
700 .write('@')
701 .write(classType, false);
702 if (classProperties.empty()) {
703 this->writer << ' ';
704 } else {
705 this->writer << '\n';
706 for (const auto& classProperty : classProperties) {
707 this->writer
708 .write('\t')
709 .write(classProperty, false)
710 .write('\n');
711 }
712 }
713 this->writer
714 .write("= "sv, false)
715 .write(name, false)
716 .write(" :"sv, false);
717 // Put the description on the same line if it's short
718 if (description.size() < 32) {
719 this->writer
720 .write(" \""sv, false) // NOLINT(*-raw-string-literal)
721 .write(description, false);
722 } else {
723 this->writer
724 .write("\n\t\""sv, false)
725 .write(description, false);
726 }
727 this->writer.write('\"');
728 if (!docsURL.empty()) {
729 this->writer
730 .write(" : \""sv, false) // NOLINT(*-raw-string-literal)
731 .write(docsURL, false)
732 .write('\"');
733 }
734 this->writer.write("\n[\n"sv, false);
735 return EntityWriter{*this};
736}
737
738FGDWriter::EntityWriter& FGDWriter::EntityWriter::keyValue(std::string_view name, std::string_view valueType, std::string_view displayName, std::string_view valueDefault, std::string_view description, bool readOnly, bool report) {
739 this->parent.writer
740 .write('\t')
741 .write(name, false)
742 .write('(')
743 .write(valueType, false)
744 .write(')');
745 if (readOnly) {
746 this->parent.writer.write(" readonly"sv, false);
747 }
748 if (report) {
749 this->parent.writer.write(" report"sv, false);
750 }
751 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
752 this->parent.writer << '\n';
753 return *this;
754}
755
756FGDWriter::EntityWriter::KeyValueChoicesWriter FGDWriter::EntityWriter::beginKeyValueChoices(std::string_view name, std::string_view displayName, std::string_view valueDefault, std::string_view description, bool readOnly, bool report) {
757 this->parent.writer
758 .write('\t')
759 .write(name, false)
760 .write("(choices)"sv, false);
761 if (readOnly) {
762 this->parent.writer.write(" readonly"sv, false);
763 }
764 if (report) {
765 this->parent.writer.write(" report"sv, false);
766 }
767 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
768 this->parent.writer.write(" =\n\t[\n"sv, false);
769 return KeyValueChoicesWriter{*this};
770}
771
773 this->parent.parent.writer
774 .write("\t\t\""sv, false)
775 .write(value, false)
776 .write("\" : \""sv, false)
777 .write(displayName, false)
778 .write("\"\n"sv, false);
779 return *this;
780}
781
783 this->parent.parent.writer.write("\t]\n"sv, false);
784 return this->parent;
785}
786
787FGDWriter::EntityWriter::KeyValueFlagsWriter FGDWriter::EntityWriter::beginKeyValueFlags(std::string_view name, std::string_view displayName, std::string_view description, bool readOnly, bool report) {
788 this->parent.writer
789 .write('\t')
790 .write(name, false)
791 .write("(flags)"sv, false);
792 if (readOnly) {
793 this->parent.writer.write(" readonly"sv, false);
794 }
795 if (report) {
796 this->parent.writer.write(" report"sv, false);
797 }
798 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, description});
799 this->parent.writer.write(" =\n\t[\n"sv, false);
800 return KeyValueFlagsWriter{*this};
801}
802
803FGDWriter::EntityWriter::KeyValueFlagsWriter& FGDWriter::EntityWriter::KeyValueFlagsWriter::flag(uint64_t value, std::string_view displayName, bool enabledByDefault, std::string_view description) {
804 this->parent.parent.writer
805 .write("\t\t"sv, false)
806 .write(std::to_string(value), false)
807 .write(" : \""sv, false)
808 .write(displayName, false)
809 .write("\" : "sv, false)
810 .write(std::to_string(enabledByDefault), false);
811 if (!description.empty()) {
812 this->parent.parent.writer
813 .write(" : \""sv, false)
814 .write(description, false)
815 .write('\"');
816 }
817 this->parent.parent.writer.write('\n');
818 return *this;
819}
820
822 this->parent.parent.writer.write("\t]\n"sv, false);
823 return this->parent;
824}
825
826FGDWriter::EntityWriter& FGDWriter::EntityWriter::input(std::string_view name, std::string_view valueType, std::string_view description) {
827 this->parent.writer
828 .write('\t')
829 .write("input "sv, false)
830 .write(name, false)
831 .write('(')
832 .write(valueType, false)
833 .write(')');
834 if (!description.empty()) {
835 this->parent.writer
836 .write(" : \""sv, false)
837 .write(description, false)
838 .write('\"');
839 }
840 this->parent.writer << '\n';
841 return *this;
842}
843
844FGDWriter::EntityWriter& FGDWriter::EntityWriter::output(std::string_view name, std::string_view valueType, std::string_view description) {
845 this->parent.writer
846 .write('\t')
847 .write("output "sv, false)
848 .write(name, false)
849 .write('(')
850 .write(valueType, false)
851 .write(')');
852 if (!description.empty()) {
853 this->parent.writer
854 .write(" : \""sv, false)
855 .write(description, false)
856 .write('\"');
857 }
858 this->parent.writer << '\n';
859 return *this;
860}
861
863 this->parent.writer.write("]\n\n"sv, false);
864 return this->parent;
865}
866
867std::string FGDWriter::bake() const {
868 // ReSharper disable once CppRedundantCastExpression
869 std::string_view out{this->backingData.data(), static_cast<std::string_view::size_type>(this->writer.tell())};
870 while (out.ends_with("\n\n")) {
871 out = out.substr(0, out.size() - 1);
872 }
873 return std::string{out};
874}
875
876bool FGDWriter::bake(const std::filesystem::path& fgdPath) const {
877 return fs::writeFileText(fgdPath, this->bake());
878}
AutoVisGroupWriter(FGDWriter &parent_)
Definition FGD.cpp:602
FGDWriter & endAutoVisGroup() const
Definition FGD.cpp:689
AutoVisGroupWriter & visGroup(std::string_view name, std::initializer_list< std::string_view > entities)
Definition FGD.cpp:670
KeyValueChoicesWriter & choice(std::string_view value, std::string_view displayName)
Definition FGD.cpp:772
KeyValueFlagsWriter & flag(uint64_t value, std::string_view displayName, bool enabledByDefault, std::string_view description="")
Definition FGD.cpp:803
FGDWriter & endEntity() const
Definition FGD.cpp:862
KeyValueChoicesWriter beginKeyValueChoices(std::string_view name, std::string_view displayName="", std::string_view valueDefault="", std::string_view description="", bool readOnly=false, bool report=false)
Definition FGD.cpp:756
EntityWriter & output(std::string_view name, std::string_view valueType, std::string_view description="")
Definition FGD.cpp:844
KeyValueFlagsWriter beginKeyValueFlags(std::string_view name, std::string_view displayName="", std::string_view description="", bool readOnly=false, bool report=false)
Definition FGD.cpp:787
EntityWriter & keyValue(std::string_view name, std::string_view valueType, std::string_view displayName="", std::string_view valueDefault="", std::string_view description="", bool readOnly=false, bool report=false)
Definition FGD.cpp:738
EntityWriter(FGDWriter &parent_)
Definition FGD.cpp:605
EntityWriter & input(std::string_view name, std::string_view valueType, std::string_view description="")
Definition FGD.cpp:826
std::string backingData
Definition FGD.h:208
BufferStream writer
Definition FGD.h:209
static FGDWriter begin()
Definition FGD.cpp:617
EntityWriter beginEntity(std::string_view classType, std::initializer_list< std::string_view > classProperties, std::string_view name, std::string_view description="", std::string_view docsURL="")
Definition FGD.cpp:694
AutoVisGroupWriter beginAutoVisGroup(std::string_view parentName)
Definition FGD.cpp:662
FGDWriter & version(int version)
Definition FGD.cpp:629
FGDWriter & mapSize(sourcepp::math::Vec2i mapSize)
Definition FGD.cpp:637
FGDWriter & include(const std::filesystem::path &fgdPath)
Definition FGD.cpp:621
std::string bake() const
Definition FGD.cpp:867
FGDWriter & materialExclusionDirs(std::initializer_list< std::string_view > dirs)
Definition FGD.cpp:647
void readEntities(BufferStreamReadOnly &stream, const std::filesystem::path &path, std::vector< std::filesystem::path > &seenPaths)
Definition FGD.cpp:544
void load(const std::filesystem::path &fgdPath)
Can be called multiple times in succession to load multiple FGD files.
Definition FGD.cpp:510
sourcepp::math::Vec2i getMapSize() const
Definition FGD.cpp:527
FGD()=default
const std::vector< AutoVisGroup > & getAutoVisGroups() const
Definition FGD.cpp:539
const std::unordered_map< std::string_view, Entity > & getEntities() const
Definition FGD.cpp:531
int getVersion() const
Definition FGD.cpp:523
const std::vector< std::string_view > & getMaterialExclusionDirs() const
Definition FGD.cpp:535
std::list< std::string > backingData
Definition FGD.h:114
std::string readFileText(const std::filesystem::path &filepath, std::size_t startOffset=0)
Definition FS.cpp:16
bool writeFileText(const std::filesystem::path &filepath, std::string_view text)
Definition FS.cpp:34
std::unordered_map< char, char > EscapeSequenceMap
Definition Text.h:17
void eatWhitespaceAndSingleLineComments(BufferStream &stream, std::string_view singleLineCommentStart=DEFAULT_SINGLE_LINE_COMMENT_START)
Eat all whitespace and single line comments after the current stream position.
Definition Text.cpp:108
void eatWhitespace(BufferStream &stream)
Eat all whitespace after the current stream position.
Definition Text.cpp:93
bool tryToEatChar(BufferStream &stream, char c)
If the given char exists at the current position, skip over it.
Definition Text.cpp:139
std::string_view readUnquotedStringToBuffer(BufferStream &stream, BufferStream &backing, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
Definition Text.cpp:217
std::string_view readStringToBuffer(BufferStream &stream, BufferStream &backing, std::string_view start=DEFAULT_STRING_START, std::string_view end=DEFAULT_STRING_END, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
Definition Text.cpp:180
std::vector< std::string > split(std::string_view s, char delim)
Definition String.cpp:155
std::from_chars_result toInt(std::string_view number, std::integral auto &out, int base=10)
Definition String.h:81
void trim(std::string &s)
Definition String.cpp:89
bool iequals(std::string_view s1, std::string_view s2)
Definition String.cpp:61
Definition CmdSeq.h:9
std::string_view name
Definition FGD.h:20
std::string_view arguments
Definition FGD.h:21
std::vector< ClassProperty > classProperties
Definition FGD.h:72
std::vector< IO > inputs
Definition FGD.h:80
std::vector< FieldFlags > fieldsWithFlags
Definition FGD.h:79
std::vector< FieldChoices > fieldsWithChoices
Definition FGD.h:78
std::vector< IO > outputs
Definition FGD.h:81
std::string_view description
Definition FGD.h:74
std::vector< Field > fields
Definition FGD.h:77
std::string_view classType
Definition FGD.h:71
std::string_view docsURL
Definition FGD.h:75