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

PredatorCZ / PreCore / 514

17 Apr 2024 06:12PM UTC coverage: 54.165% (-0.04%) from 54.2%
514

push

github

PredatorCZ
update doc generator

0 of 11 new or added lines in 2 files covered. (0.0%)

2 existing lines in 1 file now uncovered.

4142 of 7647 relevant lines covered (54.17%)

8767.24 hits per line

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

1.9
/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
        MEMBER(out, ReflDesc{"Output folder for processed files", "FOLDER"}))
80

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

215
        return true;
×
216
      };
217

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

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

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

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

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

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

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

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

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

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

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

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

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

280
  info_->internalSettings = &mainSettings;
×
281

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

389
  for (auto s : settings) {
×
390
    if (!s) {
×
391
      continue;
×
392
    }
393

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

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

415
void APPContext::PrintCLIHelp() const {
×
416
  printline("Options:" << std::endl);
×
417

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

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

429
  printStuff(::RTTI(MainSettings()));
×
430
  printStuff(::RTTI(TexelSettings()));
×
431

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

438
  if (info->settings) {
×
439
    printStuff(RTTI());
×
440
  }
441
  printline("");
×
442
}
443

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NEW
546
  return className;
×
547
}
548

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

691
    return str;
692
  };
693

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

796
      break;
797
    }
798

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

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

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

815
  {
816
    {
817
      AppHelpContextImpl helpCtx;
818

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

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

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

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

844
            lastTag = {};
×
845
          }
846

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

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

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

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

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

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

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

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

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

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

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

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

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

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