• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PredatorCZ / PreCore / 476

25 Aug 2023 03:59PM UTC coverage: 55.317% (+2.7%) from 52.584%
476

push

github-actions-ci

PredatorCZ
add texel context

1382 of 1382 new or added lines in 8 files covered. (100.0%)

4146 of 7495 relevant lines covered (55.32%)

8934.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.01
/src/app/context.cpp
1
/*  Spike is universal dedicated module handler
2

3
    Copyright 2021-2023 Lukas Cone
4

5
    Licensed under the Apache License, Version 2.0 (the "License");
6
    you may not use this file except in compliance with the License.
7
    You may obtain a copy of the License at
8

9
        http://www.apache.org/licenses/LICENSE-2.0
10

11
    Unless required by applicable law or agreed to in writing, software
12
    distributed under the License is distributed on an "AS IS" BASIS,
13
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
    See the License for the specific language governing permissions and
15
    limitations under the License.
16
*/
17

18
#include "spike/app/context.hpp"
19
#include "spike/io/binwritter.hpp"
20
#include "spike/io/directory_scanner.hpp"
21
#include "spike/master_printer.hpp"
22
#include "spike/reflect/reflector_xml.hpp"
23
#include "spike/type/tchar.hpp"
24
#include "spike/util/pugiex.hpp"
25
#include <algorithm>
26
#include <chrono>
27
#include <sstream>
28
#include <thread>
29

30
#if defined(_MSC_VER) || defined(__MINGW64__)
31
#include "spike/type/tchar.hpp"
32
#include <windows.h>
33
auto dlsym(void *handle, const char *name) {
34
  return GetProcAddress((HMODULE)handle, name);
35
}
36

37
void dlclose(void *handle) { FreeLibrary((HMODULE)handle); }
38

39
auto dlerror() {
40
  LPVOID lpMsgBuf;
41
  DWORD dw = GetLastError();
42

43
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
44
                    FORMAT_MESSAGE_IGNORE_INSERTS,
45
                NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
46
                (LPTSTR)&lpMsgBuf, 0, NULL);
47

48
  std::string retVal = std::to_string((LPTSTR)lpMsgBuf);
49
  LocalFree(lpMsgBuf);
50
  return retVal;
51
}
52
#else
53
#include <dlfcn.h>
54
#endif
55

56
MainAppConfFriend mainSettings{};
57
CLISettings cliSettings{};
58

59
// NOTE: ReflDesc flags, used for guis and logs
60
// MAX:n - Maximum number limit (numbers only)
61
// MIN:n - Minimum number limit (numbers only)
62
// HIDDEN - Hide text value (strings only)
63
// FILEPATH - Mark string as filepath for all files
64
// FILEPATH:<ext0>;<ext1>;<extn> - Mark string as filepath only for specified
65
//                                 extensions/patterns
66
// FOLDER - Mark string as folder
67

68
REFLECT(CLASS(MainAppConfFriend),
2✔
69
        MEMBERNAME(generateLog, "generate-log", "L",
70
                   ReflDesc{"Will generate text log of console output inside "
71
                            "application location."}),
72
        MEMBER(verbosity, "v",
73
               ReflDesc{"Prints more information per level.", "MAX:3"}),
74
        MEMBERNAME(extractSettings, "extract-settings"),
75
        MEMBERNAME(compressSettings, "compress-settings"),
76
        MEMBERNAME(texelSettings, "texel-settings"))
77

78
REFLECT(CLASS(CLISettings),
2✔
79
        MEMBERNAME(out, "output-directory",
80
                   ReflDesc{"Output folder for processed files", "FOLDER"}))
81

82
REFLECT(
2✔
83
    CLASS(ExtractConf),
84
    MEMBERNAME(
85
        folderPerArc, "folder-per-archive", "F",
86
        ReflDesc{
87
            "When extracting, create folder that uses input archive's name as "
88
            "output dir."}),
89
    MEMBERNAME(makeZIP, "create-zip", "Z",
90
               ReflDesc{"Pack extracted files inside ZIP file named after "
91
                        "input archive. Your HDD will thank you."}), )
92

93
REFLECT(
2✔
94
    CLASS(CompressConf),
95
    MEMBERNAME(ratioThreshold, "ratio-threshold", "c",
96
               ReflDesc{
97
                   "Writes compressed data only when compression ratio is less "
98
                   "than specified threshold [0 - 100]%",
99
                   "MAX:100"}),
100
    MEMBERNAME(minFileSize, "min-file-size", "m",
101
               ReflDesc{"Files that are smaller than specified size won't be "
102
                        "compressed."}), );
103

104
REFLECT(ENUMERATION(CubemapFace), ENUM_MEMBER(NONE), ENUM_MEMBER(Right),
3✔
105
        ENUM_MEMBER(Left), ENUM_MEMBER(Up), ENUM_MEMBER(Down),
106
        ENUM_MEMBER(Front), ENUM_MEMBER(Back));
107

108
REFLECT(
2✔
109
    ENUMERATION(TexelContextFormat),
110
    ENUM_MEMBERDESC(
111
        DDS_Legacy,
112
        R"(Use legacy dds container but decode to uncompressed formats if necessary
113
Supported legacy formats:
114
  BC1, BC2, BC3, Grayscale, RGB8, RGBA8, RGB565, RGBA4
115
Other formats will be converted to 8bit channeled format like:
116
  BC4 -> Grayscale
117
  BC5 -> RGX8
118
  BC7 -> RGBA8 or RGB8
119
Support for mipmaps, cubemaps, volumetrics
120
snorm will be converted to unorm)"),
121
    ENUM_MEMBERDESC(DDS,
122
                    R"(dds, data format is unchanged
123
PVRTC and ETC will be converted to RGBA8
124
Support for mipmaps, cubemaps, arrays, volumetrics)"),
125
    ENUM_MEMBERDESC(QOI_BMP,
126
                    R"(Decode to Quite OK Image format.
127
Only for RGX, RGB and RGBA
128
2 channels will be converted to RGX8
129
Decode to BMP for P8, P4, Grayscale,
130
No mipmap support)"),
131
    ENUM_MEMBERDESC(QOI,
132
                    R"(Decode to Quite OK Image format.
133
Normal formats RGB and RGBA are kept instact
134
2 channels will be converted to RGX8
135
8bit (P8) and 4bit (P4) palletes will be expanded to RGB or RGBA
136
Grayscale will be expanded to RGB
137
No mipmap support)"));
138

139
REFLECT(CLASS(TexelConf),
2✔
140
        MEMBERNAME(outputFormat, "output-format",
141
                   ReflDesc{"Specify output format for images"}),
142
        MEMBERNAME(cubemapToEquirectangular, "single-cube",
143
                   ReflDesc{"Convert cubemaps into equirectangular layout"}),
144
        MEMBERNAME(processMipMaps, "process-mipmaps",
145
                   ReflDesc{"Save only largest mipmap for each mipmap chain"}))
146

147
struct ReflectedInstanceFriend : ReflectedInstance {
148
  const reflectorStatic *Refl() const { return rfStatic; }
×
149
};
150

151
struct VersionHandler {
×
152
  uint32 versions[4]{};
153
  const std::string *path;
154

155
  bool operator<(const VersionHandler &other) const {
156
    if (versions[0] == other.versions[0]) {
×
157
      if (versions[1] == other.versions[1]) {
×
158
        if (versions[2] == other.versions[2]) {
×
159
          return versions[3] < other.versions[3];
×
160
        } else {
161
          return versions[2] < other.versions[2];
×
162
        }
163
      } else {
164
        return versions[1] < other.versions[1];
×
165
      }
166
    } else {
167
      return versions[0] < other.versions[0];
×
168
    }
169
  }
170
};
171

172
APPContext::APPContext(const char *moduleName_, const std::string &appFolder_,
×
173
                       const std::string &appName_)
×
174
    : appFolder(appFolder_), appName(appName_) {
×
175
  moduleName = moduleName_;
×
176

177
  auto modulePath = [&] {
×
178
    DirectoryScanner esmScan;
×
179
    esmScan.AddFilter((std::string(1, '^') + moduleName) + "*.spk$");
×
180
    esmScan.Scan(appFolder);
×
181
    std::vector<VersionHandler> versionedFiles;
×
182

183
    for (auto &f : esmScan) {
×
184
      const size_t lastDotPos = f.find_last_of('.');
185
      const size_t slashPos = f.find_last_of('/');
186
      std::string_view extension(f.data() + lastDotPos);
187
      std::string_view fileName(f.data() + slashPos, lastDotPos - slashPos);
×
188
      char *nextDot = nullptr;
×
189
      const size_t versionDotPos = fileName.find_first_of('.');
190

191
      if (versionDotPos == fileName.npos) {
×
192
        continue;
×
193
      }
194

195
      const char *versionBegin = fileName.data() + versionDotPos;
×
196
      size_t curIndex = 0;
×
197
      VersionHandler currentHandler;
198
      currentHandler.path = &f;
×
199
      auto &versions = currentHandler.versions;
200

201
      auto ChooseOne = [&] {
×
202
        if (*versionBegin != '.') {
×
203
          return false;
204
        }
205

206
        versionBegin++;
×
207
        const uint32 newVersion = std::strtoul(versionBegin, &nextDot, 10);
×
208

209
        if (versionBegin == nextDot) {
×
210
          return false;
211
        }
212

213
        versionBegin = nextDot;
×
214
        versions[curIndex++] = newVersion;
×
215

216
        return true;
×
217
      };
218

219
      if (ChooseOne()) {
×
220
        if (ChooseOne()) {
×
221
          if (ChooseOne()) {
×
222
            ChooseOne();
×
223
          }
224
        }
225
      }
226

227
      versionedFiles.push_back(currentHandler);
×
228
    }
229

230
    std::sort(versionedFiles.begin(), versionedFiles.end());
×
231

232
    if (versionedFiles.empty()) {
×
233
      throw std::runtime_error(std::string("Couldn't find module: ") +
×
234
                               moduleName);
×
235
    }
236

237
    return *versionedFiles.back().path;
×
238
  }();
×
239

240
  auto postError = [] {
×
241
    throw std::runtime_error(std::string("APPContext Error: ") + dlerror());
×
242
  };
243

244
  auto assign = [&](auto &value, auto name) {
×
245
    using type_ = std::decay_t<decltype(value)>;
246
    value = reinterpret_cast<type_>(dlsym(dlHandle, name));
×
247

248
    if (!value) {
×
249
      postError();
×
250
    }
251
  };
252

253
  auto tryAssign = [&](auto &value, auto name) {
254
    using type_ = typename std::decay_t<decltype(value)>::value_type;
255
    return value = reinterpret_cast<type_>(dlsym(dlHandle, name));
×
256
  };
257

258
  if (mainSettings.verbosity > 1) {
×
259
    printinfo("Module open: " << modulePath);
×
260
  }
261

262
#if defined(_MSC_VER) || defined(__MINGW64__)
263
  auto modPath = ToTSTRING(modulePath);
264
  dlHandle = LoadLibrary(modPath.data());
265
#else
266
  dlHandle = dlopen(modulePath.data(), RTLD_NOW);
×
267
#endif
268
  if (!dlHandle) {
×
269
    postError();
×
270
  }
271

272
  func<decltype(AppInitModule)> InitModule;
273
  assign(InitModule, "AppInitModule");
×
274
  AppInfo_s *info_ = InitModule();
×
275
  info = info_;
×
276

277
  if (info->contextVersion != AppInfo_s::CONTEXT_VERSION) {
×
278
    throw std::runtime_error("Module context version mismatch!");
×
279
  }
280

281
  info_->internalSettings = &mainSettings;
×
282

283
  tryAssign(AdditionalHelp, "AppAdditionalHelp");
284
  tryAssign(InitContext, "AppInitContext");
285
  tryAssign(FinishContext, "AppFinishContext");
286
  tryAssign(NewArchive, "AppNewArchive");
287
  tryAssign(ProcessFile, "AppProcessFile");
288
  tryAssign(ExtractStat, "AppExtractStat");
289

290
  if (NewArchive && ProcessFile) {
×
291
    throw std::logic_error("Module uses 2 or more contexts!");
×
292
  }
293
}
294

295
APPContext::APPContext(APPContext &&other)
×
296
    : APPContextCopyData(other), appFolder(std::move(other.appFolder)),
×
297
      appName(std::move(other.appName)) {
×
298
  if (dlHandle && dlHandle != other.dlHandle) {
×
299
    dlclose(dlHandle);
×
300
  }
301
  dlHandle = other.dlHandle;
×
302
  std::construct_at(&other);
303
}
304

305
APPContext &APPContext::operator=(APPContext &&other) {
×
306
  static_cast<APPContextCopyData &>(*this) = other;
307
  appFolder = std::move(other.appFolder);
×
308
  appName = std::move(other.appName);
×
309
  if (dlHandle && dlHandle != other.dlHandle) {
×
310
    dlclose(dlHandle);
×
311
  }
312
  dlHandle = other.dlHandle;
×
313
  std::construct_at(&other);
314
  return *this;
×
315
}
316

317
APPContext::~APPContext() {
×
318
  if (dlHandle) {
×
319
    dlclose(dlHandle);
×
320
  }
321
}
322

323
class ReflectorFriend : public Reflector {
324
public:
325
  using Reflector::GetReflectedInstance;
326
  using Reflector::GetReflectedType;
327
  using Reflector::SetReflectedValue;
328
};
329

330
static auto &MainSettings() {
331
  static ReflectorWrap<MainAppConfFriend> wrap(mainSettings);
332
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
333
}
334

335
static auto &ExtractSettings() {
336
  static ReflectorWrap<ExtractConf> wrap(mainSettings.extractSettings);
337
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
338
}
339

340
static auto &CompressSettings() {
341
  static ReflectorWrap<CompressConf> wrap(mainSettings.compressSettings);
342
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
343
}
344

345
static auto &TexelSettings() {
346
  static ReflectorWrap<TexelConf> wrap(mainSettings.texelSettings);
347
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
348
}
349

350
static auto &CliSettings() {
351
  static ReflectorWrap<CLISettings> wrap(cliSettings);
352
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
353
}
354

355
static const reflectorStatic *RTTI(const ReflectorFriend &ref) {
356
  auto rawRTTI = ref.GetReflectedInstance();
×
357
  return static_cast<const ReflectedInstanceFriend &>(rawRTTI).Refl();
358
}
359

360
void APPContext::ResetSwitchSettings() {
×
361
  if (info->settings) {
×
362
    const size_t numValues = Settings().GetNumReflectedValues();
×
363
    for (size_t i = 0; i < numValues; i++) {
×
364
      auto rType = Settings().GetReflectedType(i);
×
365

366
      if (rType->type == REFType::Bool) {
×
367
        Settings().SetReflectedValue(i, "false");
×
368
      }
369
    }
370
  }
371

372
  mainSettings.generateLog = mainSettings.extractSettings.folderPerArc =
×
373
      mainSettings.extractSettings.makeZIP = false;
×
374
}
375

376
int APPContext::ApplySetting(std::string_view key, std::string_view value) {
×
377
  JenHash keyHash(key);
378
  ReflectorFriend *refl = nullptr;
379
  const ReflType *rType = nullptr;
380
  static ReflectorFriend *settings[]{
381
      /**/ //
382
      &Settings(),
383
      &MainSettings(),
×
384
      &CliSettings(),
×
385
      &TexelSettings(),
×
386
      &ExtractSettings(),
×
387
      &CompressSettings(),
×
388
  };
389

390
  for (auto s : settings) {
×
391
    rType = Settings().GetReflectedType(keyHash);
×
392
    if (rType) {
×
393
      refl = s;
394
      break;
395
    }
396
  }
397

398
  if (rType) {
×
399
    if (rType->type == REFType::Bool) {
×
400
      refl->SetReflectedValue(*rType, "true");
×
401
      return 0;
×
402
    } else {
403
      refl->SetReflectedValue(*rType, value);
×
404
      return 1;
×
405
    }
406
  } else {
407
    printerror("Invalid option: " << (key.size() > 1 ? "--" : "-") << key);
×
408
    return -1;
×
409
  }
410
}
411

412
void APPContext::PrintCLIHelp() const {
×
413
  printline("Options:" << std::endl);
×
414

415
  auto printStuff = [](auto rtti) {
×
416
    for (size_t i = 0; i < rtti->nTypes; i++) {
×
417
      if (rtti->typeAliases && rtti->typeAliases[i]) {
×
418
        es::print::Get() << "-" << rtti->typeAliases[i] << ", ";
×
419
      }
420

421
      es::print::Get() << "--" << rtti->typeNames[i];
×
422
      es::print::Get() << "  = " << rtti->typeDescs[i].part1 << std::endl;
×
423
    }
424
  };
425

426
  printStuff(::RTTI(MainSettings()));
×
427
  printStuff(::RTTI(TexelSettings()));
×
428

429
  if (ProcessFile) {
×
430
    printStuff(::RTTI(ExtractSettings()));
×
431
  } else if (NewArchive) {
×
432
    printStuff(::RTTI(CompressSettings()));
×
433
  }
434

435
  if (info->settings) {
×
436
    printStuff(RTTI());
×
437
  }
438
  printline("");
×
439
}
440

441
void DumpTypeMD(std::ostream &out, const ReflectorFriend &info,
×
442
                size_t indent = 0) {
443
  auto rtti = RTTI(info);
444

445
  auto gi = [&]() -> std::ostream & {
446
    static const char indents[]{"                "};
447
    return out << indents + (8 - indent) * 2;
×
448
  };
449

450
  for (size_t i = 0; i < rtti->nTypes; i++) {
×
451
    gi() << "- **" << rtti->typeNames[i] << "**\n\n";
×
452

453
    if (info.IsReflectedSubClass(i)) {
×
454
      auto sub = info.GetReflectedSubClass(i);
×
455
      ReflectorPureWrap subRef(sub);
456
      DumpTypeMD(
×
457
          out, static_cast<ReflectorFriend &>(static_cast<Reflector &>(subRef)),
458
          indent + 1);
459
      continue;
460
    }
461

462
    gi() << "  **CLI Long:** ***--" << rtti->typeNames[i] << "***\\\n";
×
463

464
    if (rtti->typeAliases && rtti->typeAliases[i]) {
×
465
      gi() << "  **CLI Short:** ***-" << rtti->typeAliases[i] << "***\n\n";
×
466
    }
467

468
    if (auto val = info.GetReflectedValue(i); !val.empty()) {
×
469
      gi() << "  **Default value:** " << val << "\n\n";
×
470
    }
471

472
    if (auto &rType = rtti->types[i]; rType.type == REFType::Enum) {
×
473
      auto refEnum =
474
          ReflectedEnum::Registry().at(JenHash(rType.asClass.typeHash));
×
475
      gi() << "  **Valid values:** ";
×
476

477
      if ([&] {
×
478
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
479
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
480
                return true;
481
              }
482
            }
483
            return false;
484
          }()) {
485
        out << "\n\n";
×
486
        indent++;
×
487

488
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
489
          gi() << "- " << refEnum->names[e];
×
490
          if (std::string_view desc(refEnum->descriptions[e]);
×
491
              desc.size() > 0) {
492
            size_t curLine = 0;
493
            size_t nextLine = desc.find('\n');
×
494
            out << ':';
×
495

496
            if (nextLine != desc.npos) {
×
497
              out << '\n';
×
498
              while (nextLine != desc.npos) {
×
499
                gi() << desc.substr(curLine, nextLine + 1 - curLine);
×
500
                curLine = nextLine + 1;
×
501
                nextLine = desc.find('\n', curLine);
×
502
              }
503
            } else {
504
              out << ' ' << desc << "\n\n";
×
505
            }
506

507
            out << "\n\n";
×
508
          } else {
509
            out << ", "
510
                << "\n\n";
×
511
          }
512
        }
513
        indent--;
×
514
      } else {
515
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
516
          out << refEnum->names[e] << ", ";
×
517
        }
518
      }
519

520
      out.seekp(out.tellp() - std::streamoff(2));
×
521
      out << "\n\n";
×
522
    }
523

524
    if (auto desc = rtti->typeDescs[i].part1; desc) {
×
525
      gi() << "  " << rtti->typeDescs[i].part1 << "\n\n";
×
526
    }
527
  }
528
}
529

530
void APPContext::GetMarkdownDoc(std::ostream &out, pugi::xml_node node) const {
×
531
  const char *className = "[[MODULE CLASS NAME]]";
532
  const char *description = "[[MODULE DESCRIPTION]]";
533

534
  if (info->settings) {
×
535
    className = RTTI()->className;
×
536
  }
537

538
  if (node) {
×
539
    if (auto child = node.attribute("name"); child) {
×
540
      className = child.as_string();
×
541
    }
542
    description = node.text().as_string();
×
543
  }
544

545
  out << "## " << className << "\n\n### Module command: " << moduleName
×
546
      << "\n\n"
547
      << description << "\n\n";
×
548

549
  if (!info->settings) {
×
550
    return;
551
  }
552

553
  out << "### Settings\n\n";
×
554

555
  DumpTypeMD(out, Settings());
×
556
}
557

558
void APPContext::SetupModule() {
×
559
  if (mainSettings.generateLog) {
×
560
    CreateLog();
×
561
  }
562

563
  if (InitContext && !InitContext(appFolder + "data/")) {
×
564
    throw std::runtime_error("Error while initializing context.");
×
565
  }
566
}
567

568
using stream_type = BinWritter_t<BinCoreOpenMode::Text>;
569
static stream_type &GetStream() {
570
  static stream_type outStream;
571
  return outStream;
×
572
}
573

574
static std::ostream &GetLogger() { return GetStream().BaseStream(); }
575

576
static void printf(const char *str) { GetLogger() << str; }
577

578
const reflectorStatic *APPContext::RTTI() const { return ::RTTI(Settings()); }
×
579

580
void APPContext::CreateLog() {
×
581
  time_t curTime =
582
      std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
×
583
  std::tm timeStruct = *localtime(&curTime);
×
584
  TCHAR dateBuffer[128]{};
×
585
  const size_t dateBufferSize = sizeof(dateBuffer) / sizeof(TCHAR);
586

587
  _tcsftime(dateBuffer, dateBufferSize, _T("_%y_%m_%d-%H.%M.%S"), &timeStruct);
×
588
  auto logName = appFolder + moduleName + std::to_string(dateBuffer) + ".txt";
×
589
  GetStream().Open(logName);
×
590
  es::print::AddPrinterFunction(printf, false);
×
591

592
  _tcsftime(dateBuffer, dateBufferSize, _T("%c %Z"), &timeStruct);
×
593

594
  GetLogger() << "Current time: " << std::to_string(dateBuffer) << std::endl;
×
595
  GetLogger() << "Number of concurrent threads: "
596
              << std::thread::hardware_concurrency() << std::endl;
×
597
  GetLogger() << "Configuration:" << std::endl;
598

599
  auto PrintStuff = [](auto &what) {
×
600
    const size_t numSettings = what.GetNumReflectedValues();
×
601
    auto rtti = ::RTTI(what);
602

603
    for (size_t t = 0; t < numSettings; t++) {
×
604
      std::string_view desc2;
605

606
      if (rtti->typeDescs && rtti->typeDescs[t].part2) {
×
607
        desc2 = rtti->typeDescs[t].part2;
608
      }
609

610
      Reflector::KVPair pair = what.GetReflectedPair(t);
×
611

612
      GetLogger() << '\t' << pair.name << ": ";
×
613

614
      if (desc2 == "HIDDEN") {
×
615
        GetLogger() << "--hidden--";
×
616
      } else {
617
        GetLogger() << pair.value;
618
      }
619

620
      GetLogger() << std::endl;
621
    }
622
  };
623

624
  PrintStuff(MainSettings());
×
625
  PrintStuff(TexelSettings());
×
626
  if (ProcessFile) {
×
627
    PrintStuff(ExtractSettings());
×
628
  } else if (NewArchive) {
×
629
    PrintStuff(CompressSettings());
×
630
  }
631

632
  if (info->settings) {
×
633
    PrintStuff(Settings());
×
634
  }
635

636
  GetLogger() << std::endl;
637
}
638

639
void GetHelp(std::ostream &str, const reflectorStatic *ref, size_t level = 1) {
×
640
  auto fillIndent = [&](size_t mod = 0) -> std::ostream & {
641
    for (size_t i = 0; i < level + mod; i++) {
×
642
      str << '\t';
×
643
    }
644

645
    return str;
646
  };
647

648
  for (size_t i = 0; i < ref->nTypes; i++) {
×
649
    std::string_view elName = ref->typeNames[i];
×
650
    auto elDesc = ref->typeDescs[i];
×
651
    fillIndent() << elName << std::endl;
652

653
    if (elDesc.part1) {
×
654
      fillIndent(1) << elDesc.part1 << std::endl;
×
655
    }
656

657
    auto fl = ref->types[i];
×
658

659
    if (fl.type == REFType::Class || fl.type == REFType::BitFieldClass) {
×
660
      GetHelp(str, reflectorStatic::Registry().at(JenHash(fl.asClass.typeHash)),
×
661
              level + 1);
662
    } else if (fl.type == REFType::Array || fl.type == REFType::ArrayClass) {
×
663
      const auto &arr = fl.asArray;
664

665
      if (arr.type == REFType::Class || arr.type == REFType::BitFieldClass) {
×
666
        GetHelp(str,
×
667
                reflectorStatic::Registry().at(JenHash(arr.asClass.typeHash)),
×
668
                level + 1);
669
      }
670
    } else if (fl.type == REFType::Enum) {
×
671
      auto refEnum = ReflectedEnum::Registry().at(JenHash(fl.asClass.typeHash));
×
672
      fillIndent(1) << "Values: ";
×
673

674
      if ([&] {
×
675
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
676
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
677
                return true;
678
              }
679
            }
680
            return false;
681
          }()) {
682
        str << std::endl;
683

684
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
685
          fillIndent(2) << refEnum->names[e];
×
686
          if (refEnum->descriptions[e]) {
×
687
            if (std::string_view desc(refEnum->descriptions[e]);
×
688
                desc.size() > 0) {
689
              size_t curLine = 0;
690
              size_t nextLine = desc.find('\n');
×
691
              str << ':';
×
692

693
              if (nextLine != desc.npos) {
×
694
                str << '\n';
×
695
                while (nextLine != desc.npos) {
×
696
                  fillIndent(3) << desc.substr(curLine, nextLine + 1 - curLine);
×
697
                  curLine = nextLine + 1;
×
698
                  nextLine = desc.find('\n', curLine);
×
699
                }
700
              } else {
701
                str << ' ' << desc << std::endl;
×
702
              }
703

704
              str << std::endl;
705
            }
706
          } else {
707
            str << ", " << std::endl;
708
          }
709
        }
710
      } else {
711
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
712
          str << refEnum->names[e] << ", ";
×
713
        }
714
        str << std::endl;
715
      }
716
    }
717
  }
718
}
719

720
void APPContext::GetHelp(std::ostream &str) {
×
721
  str << moduleName << " settings." << std::endl;
×
722
  ::GetHelp(str, RTTI());
×
723
}
724

725
struct AppHelpContextImpl : AppHelpContext {
×
726
  std::map<std::string, std::stringstream> tagBuffers;
727

728
  std::ostream &GetStream(const std::string &tag) override {
×
729
    return tagBuffers[tag] = std::stringstream{};
×
730
  }
731
};
732

733
void APPContext::FromConfig() {
×
734
  auto configName = (appFolder + appName) + ".config";
×
735
  pugi::xml_document doc = {};
×
736

737
  auto TryFile = [](auto cb) {
×
738
    constexpr size_t numTries = 10;
739
    size_t curTry = 0;
740

741
    for (; curTry < numTries; curTry++) {
×
742
      try {
743
        cb();
×
744
      } catch (const es::FileNotFoundError &) {
×
745
      } catch (const es::FileInvalidAccessError &) {
×
746
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
×
747
        continue;
748
      }
749

750
      break;
751
    }
752

753
    if (curTry == numTries) {
×
754
      throw std::runtime_error("Cannot access config. File is locked.");
×
755
    }
756
  };
757

×
758
  TryFile([&] {
759
    auto flags = XMLDefaultParseFlags;
760
    flags += XMLParseFlag::Comments;
761
    doc = XMLFromFile(configName, flags);
×
762
    ReflectorXMLUtil::LoadV2(MainSettings(), doc.child("common"));
763

764
    if (info->settings) {
×
765
      ReflectorXMLUtil::LoadV2(Settings(), doc.child(moduleName));
×
766
    }
×
767
  });
768

769
  {
770
    {
771
      AppHelpContextImpl helpCtx;
772

773
      if (auto commentNode = doc.find_child([](pugi::xml_node &node) {
×
774
            return node.type() == pugi::xml_node_type::node_comment &&
×
775
                   std::string_view(node.value()).starts_with("common");
776
          });
777
          commentNode) {
×
778
        std::string_view comment(commentNode.value());
779
        std::string_view lastTag;
780
        size_t lastPos = 0;
781

×
782
        while (true) {
783
          lastPos = comment.find("<-tag:", lastPos);
×
784

×
785
          if (!lastTag.empty()) {
×
786
            std::string tagName(lastTag);
×
787
            lastTag.substr(0, comment.size());
788
            size_t dataBegin = lastTag.find_first_of('\n');
789

790
            if (dataBegin != lastTag.npos) {
791
              dataBegin++;
792
              const size_t dataEnd =
793
                  lastPos != lastTag.npos ? lastPos : lastTag.size();
×
794
              helpCtx.GetStream(tagName)
×
795
                  << lastTag.substr(dataBegin, dataEnd - dataBegin);
796
            }
797

798
            lastTag = {};
×
799
          }
800

801
          if (lastPos == comment.npos) {
×
802
            break;
×
803
          }
804

×
805
          const size_t tagBegin = lastPos += 6;
×
806

807
          lastPos = comment.find("->", lastPos);
×
808

809
          if (lastPos == comment.npos) {
810
            break;
811
          }
812

813
          auto tagName = comment.substr(tagBegin, lastPos - tagBegin);
×
814
          lastTag = es::TrimWhitespace(tagName);
×
815
        }
×
816

817
        doc.remove_child(commentNode);
×
818
      }
×
819

×
820
      std::stringstream str;
821
      str << "common settings." << std::endl;
822
      ::GetHelp(str, ::RTTI(MainSettings()));
823
      AdditionalHelp(&helpCtx, 1);
824

825
      for (auto &[tag, data] : helpCtx.tagBuffers) {
×
826
        str << "\t<-tag: " << tag << "->\n" << data.str();
×
827
      }
×
828

829
      auto buff = std::move(str).str();
830
      pugi::xml_node commonNode;
×
831
      auto commentNode = doc.append_child(pugi::node_comment);
×
832
      commentNode.set_value(buff.data());
833

×
834
      if (commonNode = doc.child("common"); commonNode) {
×
835
        doc.insert_move_after(commonNode, commentNode);
×
836
      } else {
837
        commonNode = doc.append_child("common");
838
      }
×
839
      ReflectorXMLUtil::SaveV2a(MainSettings(), commonNode,
840
                                ReflectorXMLUtil::Flags_StringAsAttribute);
841
    }
×
842

843
    if (info->settings) {
844
      if (auto commentNode = doc.find_child([&](pugi::xml_node &node) {
845
            return node.type() == pugi::xml_node_type::node_comment &&
×
846
                   std::string_view(node.value()).starts_with(moduleName);
847
          });
848
          commentNode) {
849
        doc.remove_child(commentNode);
×
850
      }
851

852
      std::stringstream str;
853
      GetHelp(str);
×
854
      auto buff = std::move(str).str();
×
855
      pugi::xml_node node;
856
      auto commentNode = doc.append_child(pugi::node_comment);
857
      commentNode.set_value(buff.data());
×
858

859
      if (node = doc.child(moduleName); node) {
860
        doc.insert_move_after(node, commentNode);
×
861
      } else {
862
        node = doc.append_child(moduleName);
×
863
      }
864
      ReflectorXMLUtil::SaveV2a(Settings(), node,
865
                                {ReflectorXMLUtil::Flags_StringAsAttribute});
×
866
    }
×
867

868
    TryFile([&] {
869
      XMLToFile(configName, doc,
870
                {XMLFormatFlag::WriteBOM, XMLFormatFlag::IndentAttributes});
×
871
    });
×
872
  }
×
873
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc