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

PredatorCZ / PreCore / 536

22 Sep 2024 12:23PM UTC coverage: 52.238% (-3.0%) from 55.265%
536

push

github

PredatorCZ
remove old xml serializers

4084 of 7818 relevant lines covered (52.24%)

10609.73 hits per line

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

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

3
    Copyright 2021-2024 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
#undef GetClassName
34
auto dlsym(void *handle, const char *name) {
35
  return GetProcAddress((HMODULE)handle, name);
36
}
37

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

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

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

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

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

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

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

79
REFLECT(CLASS(CLISettings),
4✔
80
        MEMBER(out, ReflDesc{"Output folder for processed files", "FOLDER"}))
81

82
REFLECT(
4✔
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(
4✔
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),
4✔
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
};
327

328
class ReflectorMemberFriend : public ReflectorMember {
329
public:
330
  using ReflectorMember::operator=;
331
  ReflectedInstanceFriend Ref() const { return ReflectedInstanceFriend{data}; }
×
332
  operator const ReflType &() const { return Ref().Refl()->types[id]; }
×
333
};
334

335
static auto &MainSettings() {
336
  static ReflectorWrap<MainAppConfFriend> wrap(mainSettings);
337
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
338
}
339

340
static auto &ExtractSettings() {
341
  static ReflectorWrap<ExtractConf> wrap(mainSettings.extractSettings);
342
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
343
}
344

345
static auto &CompressSettings() {
346
  static ReflectorWrap<CompressConf> wrap(mainSettings.compressSettings);
347
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
348
}
349

350
static auto &TexelSettings() {
351
  static ReflectorWrap<TexelConf> wrap(mainSettings.texelSettings);
352
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
353
}
354

355
static auto &CliSettings() {
356
  static ReflectorWrap<CLISettings> wrap(cliSettings);
357
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
358
}
359

360
static const reflectorStatic *RTTI(const ReflectorFriend &ref) {
361
  auto rawRTTI = ref.GetReflectedInstance();
×
362
  return static_cast<const ReflectedInstanceFriend &>(rawRTTI).Refl();
363
}
364

365
void APPContext::ResetSwitchSettings() {
×
366
  if (info->settings) {
×
367
    const size_t numValues = Settings().NumReflectedValues();
×
368
    for (size_t i = 0; i < numValues; i++) {
×
369
      ReflType rType = ReflectorMemberFriend{Settings()[i]};
×
370

371
      if (rType.type == REFType::Bool) {
×
372
        Settings()[i] = "false";
×
373
      }
374
    }
375
  }
376

377
  mainSettings.generateLog = mainSettings.extractSettings.folderPerArc =
×
378
      mainSettings.extractSettings.makeZIP = false;
×
379
}
380

381
int APPContext::ApplySetting(std::string_view key, std::string_view value) {
×
382
  JenHash keyHash(key);
383
  ReflectorFriend *refl = nullptr;
384
  static ReflectorFriend *settings[]{
385
      /**/ //
386
      &Settings(),
387
      &MainSettings(),
×
388
      &CliSettings(),
×
389
      &TexelSettings(),
×
390
      &ExtractSettings(),
×
391
      &CompressSettings(),
×
392
  };
393

394
  for (auto s : settings) {
×
395
    if (!s) {
×
396
      continue;
×
397
    }
398

399
    ReflectorMemberFriend member{(*s)[keyHash]};
×
400

401
    if (member) {
×
402
      refl = s;
403
      break;
×
404
    }
405
  }
406

407
  if (refl) {
×
408
    ReflectorMemberFriend member{(*refl)[keyHash]};
×
409
    ReflType rType = member;
×
410

411
    if (rType.type == REFType::Bool) {
×
412
      member = "true";
×
413
      return 0;
×
414
    } else {
415
      member = value;
×
416
      return 1;
×
417
    }
418
  } else {
419
    printerror("Invalid option: " << (key.size() > 1 ? "--" : "-") << key);
×
420
    return -1;
×
421
  }
422
}
423

424
void APPContext::PrintCLIHelp() const {
×
425
  printline("Options:" << std::endl);
×
426

427
  auto printStuff = [](auto rtti) {
×
428
    for (size_t i = 0; i < rtti->nTypes; i++) {
×
429
      if (rtti->typeAliases && rtti->typeAliases[i]) {
×
430
        es::print::Get() << "-" << rtti->typeAliases[i] << ", ";
×
431
      }
432

433
      es::print::Get() << "--" << rtti->typeNames[i];
×
434
      es::print::Get() << "  = " << rtti->typeDescs[i].part1 << std::endl;
×
435
    }
436
  };
437

438
  printStuff(::RTTI(MainSettings()));
×
439
  printStuff(::RTTI(TexelSettings()));
×
440

441
  if (ProcessFile) {
×
442
    printStuff(::RTTI(ExtractSettings()));
×
443
  } else if (NewArchive) {
×
444
    printStuff(::RTTI(CompressSettings()));
×
445
  }
446

447
  if (info->settings) {
×
448
    printStuff(RTTI());
×
449
  }
450
  printline("");
×
451
}
452

453
void DumpTypeMD(std::ostream &out, const ReflectorFriend &info,
×
454
                size_t indent = 0) {
455
  auto rtti = RTTI(info);
456

457
  auto gi = [&]() -> std::ostream & {
458
    static const char indents[]{"                "};
459
    return out << indents + (8 - indent) * 2;
×
460
  };
461

462
  for (size_t i = 0; i < rtti->nTypes; i++) {
×
463
    gi() << "- **" << rtti->typeNames[i] << "**\n\n";
×
464
    ReflectorMember member = info[i];
×
465

466
    if (member.IsReflectedSubClass()) {
×
467
      auto sub = member.ReflectedSubClass();
×
468
      ReflectorPureWrap subRef(sub);
469
      DumpTypeMD(
×
470
          out, static_cast<ReflectorFriend &>(static_cast<Reflector &>(subRef)),
471
          indent + 1);
472
      continue;
473
    }
474

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

477
    if (rtti->typeAliases && rtti->typeAliases[i]) {
×
478
      gi() << "  **CLI Short:** ***-" << rtti->typeAliases[i] << "***\n\n";
×
479
    }
480

481
    if (auto val = member.ReflectedValue(); !val.empty()) {
×
482
      gi() << "  **Default value:** " << val << "\n\n";
×
483
    }
484

485
    if (auto &rType = rtti->types[i]; rType.type == REFType::Enum) {
×
486
      auto refEnum =
487
          ReflectedEnum::Registry().at(JenHash(rType.asClass.typeHash));
×
488
      gi() << "  **Valid values:** ";
×
489

490
      if ([&] {
×
491
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
492
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
493
                return true;
494
              }
495
            }
496
            return false;
497
          }()) {
498
        out << "\n\n";
×
499
        indent++;
×
500

501
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
502
          gi() << "- " << refEnum->names[e];
×
503
          if (std::string_view desc(refEnum->descriptions[e]);
×
504
              desc.size() > 0) {
505
            size_t curLine = 0;
506
            size_t nextLine = desc.find('\n');
×
507
            out << ':';
×
508

509
            if (nextLine != desc.npos) {
×
510
              out << '\n';
×
511
              while (nextLine != desc.npos) {
×
512
                gi() << desc.substr(curLine, nextLine + 1 - curLine);
×
513
                curLine = nextLine + 1;
×
514
                nextLine = desc.find('\n', curLine);
×
515
              }
516
            } else {
517
              out << ' ' << desc << "\n\n";
×
518
            }
519

520
            out << "\n\n";
×
521
          } else {
522
            out << ", "
523
                << "\n\n";
×
524
          }
525
        }
526
        indent--;
×
527
      } else {
528
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
529
          out << refEnum->names[e] << ", ";
×
530
        }
531
      }
532

533
      out.seekp(out.tellp() - std::streamoff(2));
×
534
      out << "\n\n";
×
535
    }
536

537
    if (auto desc = rtti->typeDescs[i].part1; desc) {
×
538
      gi() << "  " << rtti->typeDescs[i].part1 << "\n\n";
×
539
    }
540
  }
541
}
542

543
std::string APPContext::GetClassName(pugi::xml_node node) const {
×
544
  const char *className = "[[MODULE CLASS NAME]]";
545

546
  if (info->settings) {
×
547
    className = RTTI()->className;
×
548
  }
549

550
  if (node) {
×
551
    if (auto child = node.attribute("name"); child) {
×
552
      className = child.as_string();
×
553
    }
554
  }
555

556
  return className;
×
557
}
558

559
void APPContext::GetMarkdownDoc(std::ostream &out, pugi::xml_node node) const {
×
560
  const char *description = "[[MODULE DESCRIPTION]]";
561

562
  if (node) {
×
563
    description = node.text().as_string();
×
564
  }
565

566
  out << "## " << GetClassName(node) << "\n\n### Module command: " << moduleName
×
567
      << "\n\n"
568
      << description << "\n\n";
×
569

570
  if (info->filters.size() > 0) {
×
571
    if (info->batchControlFilters.size() > 0) {
×
572
      out << "> [!NOTE]\n> The following file patterns apply to `batch.json` "
573
             "which is "
574
             "described "
575
             "[HERE](https://github.com/PredatorCZ/Spike/wiki/"
576
             "Spike---Batching)\n\n";
×
577

578
      out << "### Main file patterns: ";
×
579

580
      for (auto &filter : info->batchControlFilters) {
×
581
        out << '`' << filter << "`, ";
×
582
      }
583

584
      out.seekp(size_t(out.tellp()) - 2);
×
585
      out << "\n\n### Secondary file patterns: ";
×
586

587
      for (auto &filter : info->filters) {
×
588
        out << '`' << filter << "`, ";
×
589
      }
590

591
      out.seekp(size_t(out.tellp()) - 2);
×
592
    } else {
593
      out << "### Input file patterns: ";
×
594

595
      for (auto &filter : info->filters) {
×
596
        out << '`' << filter << "`, ";
×
597
      }
598

599
      out.seekp(size_t(out.tellp()) - 2);
×
600
    }
601

602
    out << "\n\n";
×
603
  }
604

605
  if (!info->settings) {
×
606
    return;
607
  }
608

609
  out << "### Settings\n\n";
×
610

611
  DumpTypeMD(out, Settings());
×
612
}
613

614
void APPContext::SetupModule() {
×
615
  if (mainSettings.generateLog) {
×
616
    CreateLog();
×
617
  }
618

619
  if (InitContext && !InitContext(appFolder + "data/")) {
×
620
    throw std::runtime_error("Error while initializing context.");
×
621
  }
622
}
623

624
using stream_type = BinWritter_t<BinCoreOpenMode::Text>;
625
static stream_type &GetStream() {
626
  static stream_type outStream;
627
  return outStream;
×
628
}
629

630
static std::ostream &GetLogger() { return GetStream().BaseStream(); }
631

632
static void printf(const char *str) { GetLogger() << str; }
633

634
const reflectorStatic *APPContext::RTTI() const { return ::RTTI(Settings()); }
×
635

636
void APPContext::CreateLog() {
×
637
  time_t curTime =
638
      std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
×
639
  std::tm timeStruct = *localtime(&curTime);
×
640
  TCHAR dateBuffer[128]{};
×
641
  const size_t dateBufferSize = sizeof(dateBuffer) / sizeof(TCHAR);
642

643
  _tcsftime(dateBuffer, dateBufferSize, _T("_%y_%m_%d-%H.%M.%S"), &timeStruct);
×
644
  auto logName = appFolder + moduleName + std::to_string(dateBuffer) + ".txt";
×
645
  GetStream().Open(logName);
×
646
  es::print::AddPrinterFunction(printf, false);
×
647

648
  _tcsftime(dateBuffer, dateBufferSize, _T("%c %Z"), &timeStruct);
×
649

650
  GetLogger() << "Current time: " << std::to_string(dateBuffer) << std::endl;
×
651
  GetLogger() << "Number of concurrent threads: "
652
              << std::thread::hardware_concurrency() << std::endl;
×
653
  GetLogger() << "Configuration:" << std::endl;
654

655
  auto PrintStuff = [](auto &what) {
×
656
    const size_t numSettings = what.NumReflectedValues();
×
657
    auto rtti = ::RTTI(what);
658

659
    for (size_t t = 0; t < numSettings; t++) {
×
660
      ReflectorMember member = what[t];
×
661
      std::string_view desc2;
662

663
      if (rtti->typeDescs && rtti->typeDescs[t].part2) {
×
664
        desc2 = rtti->typeDescs[t].part2;
665
      }
666

667
      ReflectorMember::KVPair pair = member.ReflectedPair();
×
668

669
      GetLogger() << '\t' << pair.name << ": ";
×
670

671
      if (desc2 == "HIDDEN") {
×
672
        GetLogger() << "--hidden--";
×
673
      } else {
674
        GetLogger() << pair.value;
675
      }
676

677
      GetLogger() << std::endl;
678
    }
679
  };
680

681
  PrintStuff(MainSettings());
×
682
  PrintStuff(TexelSettings());
×
683
  if (ProcessFile) {
×
684
    PrintStuff(ExtractSettings());
×
685
  } else if (NewArchive) {
×
686
    PrintStuff(CompressSettings());
×
687
  }
688

689
  if (info->settings) {
×
690
    PrintStuff(Settings());
×
691
  }
692

693
  GetLogger() << std::endl;
694
}
695

696
void GetHelp(std::ostream &str, const reflectorStatic *ref, size_t level = 1) {
×
697
  auto fillIndent = [&](size_t mod = 0) -> std::ostream & {
698
    for (size_t i = 0; i < level + mod; i++) {
×
699
      str << '\t';
×
700
    }
701

702
    return str;
703
  };
704

705
  for (size_t i = 0; i < ref->nTypes; i++) {
×
706
    std::string_view elName = ref->typeNames[i];
×
707
    auto elDesc = ref->typeDescs[i];
×
708
    fillIndent() << elName << std::endl;
709

710
    if (elDesc.part1) {
×
711
      fillIndent(1) << elDesc.part1 << std::endl;
×
712
    }
713

714
    auto fl = ref->types[i];
×
715

716
    if (fl.type == REFType::Class || fl.type == REFType::BitFieldClass) {
×
717
      GetHelp(str, reflectorStatic::Registry().at(JenHash(fl.asClass.typeHash)),
×
718
              level + 1);
719
    } else if (fl.container == REFContainer::InlineArray) {
×
720
      const auto &arr = fl.asArray;
721

722
      if (arr.type == REFType::Class || arr.type == REFType::BitFieldClass) {
×
723
        GetHelp(str,
×
724
                reflectorStatic::Registry().at(JenHash(arr.asClass.typeHash)),
×
725
                level + 1);
726
      }
727
    } else if (fl.type == REFType::Enum) {
×
728
      auto refEnum = ReflectedEnum::Registry().at(JenHash(fl.asClass.typeHash));
×
729
      fillIndent(1) << "Values: ";
×
730

731
      if ([&] {
×
732
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
733
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
734
                return true;
735
              }
736
            }
737
            return false;
738
          }()) {
739
        str << std::endl;
740

741
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
742
          fillIndent(2) << refEnum->names[e];
×
743
          if (refEnum->descriptions[e]) {
×
744
            if (std::string_view desc(refEnum->descriptions[e]);
×
745
                desc.size() > 0) {
746
              size_t curLine = 0;
747
              size_t nextLine = desc.find('\n');
×
748
              str << ':';
×
749

750
              if (nextLine != desc.npos) {
×
751
                str << '\n';
×
752
                while (nextLine != desc.npos) {
×
753
                  fillIndent(3) << desc.substr(curLine, nextLine + 1 - curLine);
×
754
                  curLine = nextLine + 1;
×
755
                  nextLine = desc.find('\n', curLine);
×
756
                }
757
              } else {
758
                str << ' ' << desc << std::endl;
×
759
              }
760

761
              str << std::endl;
762
            }
763
          } else {
764
            str << ", " << std::endl;
765
          }
766
        }
767
      } else {
768
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
769
          str << refEnum->names[e] << ", ";
×
770
        }
771
        str << std::endl;
772
      }
773
    }
774
  }
775
}
776

777
void APPContext::GetHelp(std::ostream &str) {
×
778
  str << moduleName << " settings." << std::endl;
×
779
  ::GetHelp(str, RTTI());
×
780
}
781

782
struct AppHelpContextImpl : AppHelpContext {
×
783
  std::map<std::string, std::stringstream> tagBuffers;
784

785
  std::ostream &GetStream(const std::string &tag) override {
×
786
    return tagBuffers[tag] = std::stringstream{};
×
787
  }
788
};
789

790
void APPContext::FromConfig() {
×
791
  auto configName = (appFolder + appName) + ".config";
×
792
  pugi::xml_document doc = {};
×
793

794
  auto TryFile = [](auto cb) {
×
795
    constexpr size_t numTries = 10;
796
    size_t curTry = 0;
797

798
    for (; curTry < numTries; curTry++) {
×
799
      try {
800
        cb();
×
801
      } catch (const es::FileNotFoundError &) {
×
802
      } catch (const es::FileInvalidAccessError &) {
×
803
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
×
804
        continue;
805
      }
806

807
      break;
808
    }
809

810
    if (curTry == numTries) {
×
811
      throw std::runtime_error("Cannot access config. File is locked.");
×
812
    }
813
  };
814

×
815
  TryFile([&] {
816
    auto flags = XMLDefaultParseFlags;
817
    flags += XMLParseFlag::Comments;
818
    doc = XMLFromFile(configName, flags);
×
819
    ReflectorXMLUtil::Load(MainSettings(), doc.child("common"));
820

821
    if (info->settings) {
×
822
      ReflectorXMLUtil::Load(Settings(), doc.child(moduleName));
×
823
    }
×
824
  });
825

826
  {
827
    {
828
      AppHelpContextImpl helpCtx;
829

830
      if (auto commentNode = doc.find_child([](pugi::xml_node &node) {
×
831
            return node.type() == pugi::xml_node_type::node_comment &&
×
832
                   std::string_view(node.value()).starts_with("common");
833
          });
834
          commentNode) {
×
835
        std::string_view comment(commentNode.value());
836
        std::string_view lastTag;
837
        size_t lastPos = 0;
838

×
839
        while (true) {
840
          lastPos = comment.find("<-tag:", lastPos);
×
841

×
842
          if (!lastTag.empty()) {
×
843
            std::string tagName(lastTag);
×
844
            lastTag.substr(0, comment.size());
845
            size_t dataBegin = lastTag.find_first_of('\n');
846

847
            if (dataBegin != lastTag.npos) {
848
              dataBegin++;
849
              const size_t dataEnd =
850
                  lastPos != lastTag.npos ? lastPos : lastTag.size();
×
851
              helpCtx.GetStream(tagName)
×
852
                  << lastTag.substr(dataBegin, dataEnd - dataBegin);
853
            }
854

855
            lastTag = {};
×
856
          }
857

858
          if (lastPos == comment.npos) {
×
859
            break;
×
860
          }
861

×
862
          const size_t tagBegin = lastPos += 6;
×
863

864
          lastPos = comment.find("->", lastPos);
×
865

866
          if (lastPos == comment.npos) {
867
            break;
868
          }
869

870
          auto tagName = comment.substr(tagBegin, lastPos - tagBegin);
×
871
          lastTag = es::TrimWhitespace(tagName);
×
872
        }
×
873

874
        doc.remove_child(commentNode);
×
875
      }
×
876

×
877
      std::stringstream str;
878
      str << "common settings." << std::endl;
879
      ::GetHelp(str, ::RTTI(MainSettings()));
880
      AdditionalHelp(&helpCtx, 1);
881

882
      for (auto &[tag, data] : helpCtx.tagBuffers) {
×
883
        str << "\t<-tag: " << tag << "->\n" << data.str();
×
884
      }
×
885

886
      auto buff = std::move(str).str();
887
      pugi::xml_node commonNode;
×
888
      auto commentNode = doc.append_child(pugi::node_comment);
×
889
      commentNode.set_value(buff.data());
890

×
891
      if (commonNode = doc.child("common"); commonNode) {
×
892
        doc.insert_move_after(commonNode, commentNode);
×
893
      } else {
894
        commonNode = doc.append_child("common");
895
      }
×
896
      ReflectorXMLUtil::Save(MainSettings(), commonNode,
897
                             ReflectorXMLUtil::Flags_StringAsAttribute);
898
    }
×
899

900
    if (info->settings) {
901
      if (auto commentNode = doc.find_child([&](pugi::xml_node &node) {
902
            return node.type() == pugi::xml_node_type::node_comment &&
×
903
                   std::string_view(node.value()).starts_with(moduleName);
904
          });
905
          commentNode) {
906
        doc.remove_child(commentNode);
×
907
      }
908

909
      std::stringstream str;
910
      GetHelp(str);
×
911
      auto buff = std::move(str).str();
×
912
      pugi::xml_node node;
913
      auto commentNode = doc.append_child(pugi::node_comment);
914
      commentNode.set_value(buff.data());
×
915

916
      if (node = doc.child(moduleName); node) {
917
        doc.insert_move_after(node, commentNode);
×
918
      } else {
919
        node = doc.append_child(moduleName);
×
920
      }
921
      ReflectorXMLUtil::Save(Settings(), node,
922
                             {ReflectorXMLUtil::Flags_StringAsAttribute});
×
923
    }
×
924

925
    TryFile([&] {
926
      XMLToFile(configName, doc,
927
                {XMLFormatFlag::WriteBOM, XMLFormatFlag::IndentAttributes});
×
928
    });
×
929
  }
×
930
}
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