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

PredatorCZ / PreCore / 549

24 Sep 2024 04:33PM UTC coverage: 53.543% (+0.2%) from 53.36%
549

push

github

PredatorCZ
new print api

3 of 21 new or added lines in 3 files covered. (14.29%)

6 existing lines in 3 files now uncovered.

4269 of 7973 relevant lines covered (53.54%)

10410.26 hits per line

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

1.89
/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
#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),
2✔
80
        MEMBER(out, 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
    if (!s) {
×
392
      continue;
×
393
    }
394

395
    rType = s->GetReflectedType(keyHash);
×
396
    if (rType) {
×
397
      refl = s;
398
      break;
399
    }
400
  }
401

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

416
void APPContext::PrintCLIHelp() const {
×
417
  printline("Options:" << std::endl);
×
NEW
418
  es::print::PrintFn(es::print::MPType::PREV, [&](std::ostream &str) {
×
NEW
419
    auto printStuff = [&](auto rtti) {
×
NEW
420
      for (size_t i = 0; i < rtti->nTypes; i++) {
×
NEW
421
        if (rtti->typeAliases && rtti->typeAliases[i]) {
×
NEW
422
          str << "-" << rtti->typeAliases[i] << ", ";
×
423
        }
424

NEW
425
        str << "--" << rtti->typeNames[i];
×
NEW
426
        str << "  = " << rtti->typeDescs[i].part1 << std::endl;
×
427
      }
428
    };
429

NEW
430
    printStuff(::RTTI(MainSettings()));
×
NEW
431
    printStuff(::RTTI(TexelSettings()));
×
432

NEW
433
    if (ProcessFile) {
×
NEW
434
      printStuff(::RTTI(ExtractSettings()));
×
NEW
435
    } else if (NewArchive) {
×
NEW
436
      printStuff(::RTTI(CompressSettings()));
×
437
    }
438

NEW
439
    if (info->settings) {
×
NEW
440
      printStuff(RTTI());
×
441
    }
NEW
442
  });
×
443
}
444

445
void DumpTypeMD(std::ostream &out, const ReflectorFriend &info,
×
446
                size_t indent = 0) {
447
  auto rtti = RTTI(info);
448

449
  auto gi = [&]() -> std::ostream & {
450
    static const char indents[]{"                "};
451
    return out << indents + (8 - indent) * 2;
×
452
  };
453

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

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

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

468
    if (rtti->typeAliases && rtti->typeAliases[i]) {
×
469
      gi() << "  **CLI Short:** ***-" << rtti->typeAliases[i] << "***\n\n";
×
470
    }
471

472
    if (auto val = info.GetReflectedValue(i); !val.empty()) {
×
473
      gi() << "  **Default value:** " << val << "\n\n";
×
474
    }
475

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

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

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

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

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

524
      out.seekp(out.tellp() - std::streamoff(2));
×
525
      out << "\n\n";
×
526
    }
527

528
    if (auto desc = rtti->typeDescs[i].part1; desc) {
×
529
      gi() << "  " << rtti->typeDescs[i].part1 << "\n\n";
×
530
    }
531
  }
532
}
533

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

537
  if (info->settings) {
×
538
    className = RTTI()->className;
×
539
  }
540

541
  if (node) {
×
542
    if (auto child = node.attribute("name"); child) {
×
543
      className = child.as_string();
×
544
    }
545
  }
546

547
  return className;
×
548
}
549

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

553
  if (node) {
×
554
    description = node.text().as_string();
×
555
  }
556

557
  out << "## " << GetClassName(node) << "\n\n### Module command: " << moduleName
×
558
      << "\n\n"
559
      << description << "\n\n";
×
560

561
  if (info->filters.size() > 0) {
×
562
    if (info->batchControlFilters.size() > 0) {
×
563
      out << "> [!NOTE]\n> The following file patterns apply to `batch.json` "
564
             "which is "
565
             "described "
566
             "[HERE](https://github.com/PredatorCZ/Spike/wiki/"
567
             "Spike---Batching)\n\n";
×
568

569
      out << "### Main file patterns: ";
×
570

571
      for (auto &filter : info->batchControlFilters) {
×
572
        out << '`' << filter << "`, ";
×
573
      }
574

575
      out.seekp(size_t(out.tellp()) - 2);
×
576
      out << "\n\n### Secondary file patterns: ";
×
577

578
      for (auto &filter : info->filters) {
×
579
        out << '`' << filter << "`, ";
×
580
      }
581

582
      out.seekp(size_t(out.tellp()) - 2);
×
583
    } else {
584
      out << "### Input file patterns: ";
×
585

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

590
      out.seekp(size_t(out.tellp()) - 2);
×
591
    }
592

593
    out << "\n\n";
×
594
  }
595

596
  if (!info->settings) {
×
597
    return;
598
  }
599

600
  out << "### Settings\n\n";
×
601

602
  DumpTypeMD(out, Settings());
×
603
}
604

605
void APPContext::SetupModule() {
×
606
  if (mainSettings.generateLog) {
×
607
    CreateLog();
×
608
  }
609

610
  if (InitContext && !InitContext(appFolder + "data/")) {
×
611
    throw std::runtime_error("Error while initializing context.");
×
612
  }
613
}
614

615
using stream_type = BinWritter_t<BinCoreOpenMode::Text>;
616
static stream_type &GetStream() {
617
  static stream_type outStream;
618
  return outStream;
×
619
}
620

621
static std::ostream &GetLogger() { return GetStream().BaseStream(); }
622

623
static void printf(const char *str) { GetLogger() << str; }
624

625
const reflectorStatic *APPContext::RTTI() const { return ::RTTI(Settings()); }
×
626

627
void APPContext::CreateLog() {
×
628
  time_t curTime =
629
      std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
×
630
  std::tm timeStruct = *localtime(&curTime);
×
631
  TCHAR dateBuffer[128]{};
×
632
  const size_t dateBufferSize = sizeof(dateBuffer) / sizeof(TCHAR);
633

634
  _tcsftime(dateBuffer, dateBufferSize, _T("_%y_%m_%d-%H.%M.%S"), &timeStruct);
×
635
  auto logName = appFolder + moduleName + std::to_string(dateBuffer) + ".txt";
×
636
  GetStream().Open(logName);
×
637
  es::print::AddPrinterFunction(printf, false);
×
638

639
  _tcsftime(dateBuffer, dateBufferSize, _T("%c %Z"), &timeStruct);
×
640

641
  GetLogger() << "Current time: " << std::to_string(dateBuffer) << std::endl;
×
642
  GetLogger() << "Number of concurrent threads: "
643
              << std::thread::hardware_concurrency() << std::endl;
×
644
  GetLogger() << "Configuration:" << std::endl;
645

646
  auto PrintStuff = [](auto &what) {
×
647
    const size_t numSettings = what.GetNumReflectedValues();
×
648
    auto rtti = ::RTTI(what);
649

650
    for (size_t t = 0; t < numSettings; t++) {
×
651
      std::string_view desc2;
652

653
      if (rtti->typeDescs && rtti->typeDescs[t].part2) {
×
654
        desc2 = rtti->typeDescs[t].part2;
655
      }
656

657
      Reflector::KVPair pair = what.GetReflectedPair(t);
×
658

659
      GetLogger() << '\t' << pair.name << ": ";
×
660

661
      if (desc2 == "HIDDEN") {
×
662
        GetLogger() << "--hidden--";
×
663
      } else {
664
        GetLogger() << pair.value;
665
      }
666

667
      GetLogger() << std::endl;
668
    }
669
  };
670

671
  PrintStuff(MainSettings());
×
672
  PrintStuff(TexelSettings());
×
673
  if (ProcessFile) {
×
674
    PrintStuff(ExtractSettings());
×
675
  } else if (NewArchive) {
×
676
    PrintStuff(CompressSettings());
×
677
  }
678

679
  if (info->settings) {
×
680
    PrintStuff(Settings());
×
681
  }
682

683
  GetLogger() << std::endl;
684
}
685

686
void GetHelp(std::ostream &str, const reflectorStatic *ref, size_t level = 1) {
×
687
  auto fillIndent = [&](size_t mod = 0) -> std::ostream & {
688
    for (size_t i = 0; i < level + mod; i++) {
×
689
      str << '\t';
×
690
    }
691

692
    return str;
693
  };
694

695
  for (size_t i = 0; i < ref->nTypes; i++) {
×
696
    std::string_view elName = ref->typeNames[i];
×
697
    auto elDesc = ref->typeDescs[i];
×
698
    fillIndent() << elName << std::endl;
699

700
    if (elDesc.part1) {
×
701
      fillIndent(1) << elDesc.part1 << std::endl;
×
702
    }
703

704
    auto fl = ref->types[i];
×
705

706
    if (fl.type == REFType::Class || fl.type == REFType::BitFieldClass) {
×
707
      GetHelp(str, reflectorStatic::Registry().at(JenHash(fl.asClass.typeHash)),
×
708
              level + 1);
709
    } else if (fl.type == REFType::Array || fl.type == REFType::ArrayClass) {
×
710
      const auto &arr = fl.asArray;
711

712
      if (arr.type == REFType::Class || arr.type == REFType::BitFieldClass) {
×
713
        GetHelp(str,
×
714
                reflectorStatic::Registry().at(JenHash(arr.asClass.typeHash)),
×
715
                level + 1);
716
      }
717
    } else if (fl.type == REFType::Enum) {
×
718
      auto refEnum = ReflectedEnum::Registry().at(JenHash(fl.asClass.typeHash));
×
719
      fillIndent(1) << "Values: ";
×
720

721
      if ([&] {
×
722
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
723
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
724
                return true;
725
              }
726
            }
727
            return false;
728
          }()) {
729
        str << std::endl;
730

731
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
732
          fillIndent(2) << refEnum->names[e];
×
733
          if (refEnum->descriptions[e]) {
×
734
            if (std::string_view desc(refEnum->descriptions[e]);
×
735
                desc.size() > 0) {
736
              size_t curLine = 0;
737
              size_t nextLine = desc.find('\n');
×
738
              str << ':';
×
739

740
              if (nextLine != desc.npos) {
×
741
                str << '\n';
×
742
                while (nextLine != desc.npos) {
×
743
                  fillIndent(3) << desc.substr(curLine, nextLine + 1 - curLine);
×
744
                  curLine = nextLine + 1;
×
745
                  nextLine = desc.find('\n', curLine);
×
746
                }
747
              } else {
748
                str << ' ' << desc << std::endl;
×
749
              }
750

751
              str << std::endl;
752
            }
753
          } else {
754
            str << ", " << std::endl;
755
          }
756
        }
757
      } else {
758
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
759
          str << refEnum->names[e] << ", ";
×
760
        }
761
        str << std::endl;
762
      }
763
    }
764
  }
765
}
766

767
void APPContext::GetHelp(std::ostream &str) {
×
768
  str << moduleName << " settings." << std::endl;
×
769
  ::GetHelp(str, RTTI());
×
770
}
771

772
struct AppHelpContextImpl : AppHelpContext {
×
773
  std::map<std::string, std::stringstream> tagBuffers;
774

775
  std::ostream &GetStream(const std::string &tag) override {
×
776
    return tagBuffers[tag] = std::stringstream{};
×
777
  }
778
};
779

780
void APPContext::FromConfig() {
×
781
  auto configName = (appFolder + appName) + ".config";
×
782
  pugi::xml_document doc = {};
×
783

784
  auto TryFile = [](auto cb) {
×
785
    constexpr size_t numTries = 10;
786
    size_t curTry = 0;
787

788
    for (; curTry < numTries; curTry++) {
×
789
      try {
790
        cb();
×
791
      } catch (const es::FileNotFoundError &) {
×
792
      } catch (const es::FileInvalidAccessError &) {
×
793
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
×
794
        continue;
795
      }
796

797
      break;
798
    }
799

800
    if (curTry == numTries) {
×
801
      throw std::runtime_error("Cannot access config. File is locked.");
×
802
    }
803
  };
804

×
805
  TryFile([&] {
806
    auto flags = XMLDefaultParseFlags;
807
    flags += XMLParseFlag::Comments;
808
    doc = XMLFromFile(configName, flags);
×
809
    ReflectorXMLUtil::LoadV2(MainSettings(), doc.child("common"));
810

811
    if (info->settings) {
×
812
      ReflectorXMLUtil::LoadV2(Settings(), doc.child(moduleName));
×
813
    }
×
814
  });
815

816
  {
817
    {
818
      AppHelpContextImpl helpCtx;
819

820
      if (auto commentNode = doc.find_child([](pugi::xml_node &node) {
×
821
            return node.type() == pugi::xml_node_type::node_comment &&
×
822
                   std::string_view(node.value()).starts_with("common");
823
          });
824
          commentNode) {
×
825
        std::string_view comment(commentNode.value());
826
        std::string_view lastTag;
827
        size_t lastPos = 0;
828

×
829
        while (true) {
830
          lastPos = comment.find("<-tag:", lastPos);
×
831

×
832
          if (!lastTag.empty()) {
×
833
            std::string tagName(lastTag);
×
834
            lastTag.substr(0, comment.size());
835
            size_t dataBegin = lastTag.find_first_of('\n');
836

837
            if (dataBegin != lastTag.npos) {
838
              dataBegin++;
839
              const size_t dataEnd =
840
                  lastPos != lastTag.npos ? lastPos : lastTag.size();
×
841
              helpCtx.GetStream(tagName)
×
842
                  << lastTag.substr(dataBegin, dataEnd - dataBegin);
843
            }
844

845
            lastTag = {};
×
846
          }
847

848
          if (lastPos == comment.npos) {
×
849
            break;
×
850
          }
851

×
852
          const size_t tagBegin = lastPos += 6;
×
853

854
          lastPos = comment.find("->", lastPos);
×
855

856
          if (lastPos == comment.npos) {
857
            break;
858
          }
859

860
          auto tagName = comment.substr(tagBegin, lastPos - tagBegin);
×
861
          lastTag = es::TrimWhitespace(tagName);
×
862
        }
×
863

864
        doc.remove_child(commentNode);
×
865
      }
×
866

×
867
      std::stringstream str;
868
      str << "common settings." << std::endl;
869
      ::GetHelp(str, ::RTTI(MainSettings()));
870
      AdditionalHelp(&helpCtx, 1);
871

872
      for (auto &[tag, data] : helpCtx.tagBuffers) {
×
873
        str << "\t<-tag: " << tag << "->\n" << data.str();
×
874
      }
×
875

876
      auto buff = std::move(str).str();
877
      pugi::xml_node commonNode;
×
878
      auto commentNode = doc.append_child(pugi::node_comment);
×
879
      commentNode.set_value(buff.data());
880

×
881
      if (commonNode = doc.child("common"); commonNode) {
×
882
        doc.insert_move_after(commonNode, commentNode);
×
883
      } else {
884
        commonNode = doc.append_child("common");
885
      }
×
886
      ReflectorXMLUtil::SaveV2a(MainSettings(), commonNode,
887
                                ReflectorXMLUtil::Flags_StringAsAttribute);
888
    }
×
889

890
    if (info->settings) {
891
      if (auto commentNode = doc.find_child([&](pugi::xml_node &node) {
892
            return node.type() == pugi::xml_node_type::node_comment &&
×
893
                   std::string_view(node.value()).starts_with(moduleName);
894
          });
895
          commentNode) {
896
        doc.remove_child(commentNode);
×
897
      }
898

899
      std::stringstream str;
900
      GetHelp(str);
×
901
      auto buff = std::move(str).str();
×
902
      pugi::xml_node node;
903
      auto commentNode = doc.append_child(pugi::node_comment);
904
      commentNode.set_value(buff.data());
×
905

906
      if (node = doc.child(moduleName); node) {
907
        doc.insert_move_after(node, commentNode);
×
908
      } else {
909
        node = doc.append_child(moduleName);
×
910
      }
911
      ReflectorXMLUtil::SaveV2a(Settings(), node,
912
                                {ReflectorXMLUtil::Flags_StringAsAttribute});
×
913
    }
×
914

915
    TryFile([&] {
916
      XMLToFile(configName, doc,
917
                {XMLFormatFlag::WriteBOM, XMLFormatFlag::IndentAttributes});
×
918
    });
×
919
  }
×
920
}
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