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

PredatorCZ / PreCore / 461

pending completion
461

push

github-actions-ci

PredatorCZ
update readme

3204 of 6096 relevant lines covered (52.56%)

354.05 hits per line

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

0.0
/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),
×
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

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

81
REFLECT(
×
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(
×
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
struct ReflectedInstanceFriend : ReflectedInstance {
104
  const reflectorStatic *Refl() const { return rfStatic; }
×
105
};
106

107
struct VersionHandler {
×
108
  uint32 versions[4]{};
109
  const std::string *path;
110

111
  bool operator<(const VersionHandler &other) const {
112
    if (versions[0] == other.versions[0]) {
×
113
      if (versions[1] == other.versions[1]) {
×
114
        if (versions[2] == other.versions[2]) {
×
115
          return versions[3] < other.versions[3];
×
116
        } else {
117
          return versions[2] < other.versions[2];
×
118
        }
119
      } else {
120
        return versions[1] < other.versions[1];
×
121
      }
122
    } else {
123
      return versions[0] < other.versions[0];
×
124
    }
125
  }
126
};
127

128
APPContext::APPContext(const char *moduleName_, const std::string &appFolder_,
×
129
                       const std::string &appName_)
×
130
    : appFolder(appFolder_), appName(appName_) {
×
131
  moduleName = moduleName_;
×
132

133
  auto modulePath = [&] {
×
134
    DirectoryScanner esmScan;
×
135
    esmScan.AddFilter((std::string(1, '^') + moduleName) + "*.spk$");
×
136
    esmScan.Scan(appFolder);
×
137
    std::vector<VersionHandler> versionedFiles;
×
138

139
    for (auto &f : esmScan) {
×
140
      const size_t lastDotPos = f.find_last_of('.');
141
      const size_t slashPos = f.find_last_of('/');
142
      std::string_view extension(f.data() + lastDotPos);
143
      std::string_view fileName(f.data() + slashPos, lastDotPos - slashPos);
×
144
      char *nextDot = nullptr;
×
145
      const size_t versionDotPos = fileName.find_first_of('.');
146

147
      if (versionDotPos == fileName.npos) {
×
148
        continue;
×
149
      }
150

151
      const char *versionBegin = fileName.data() + versionDotPos;
×
152
      size_t curIndex = 0;
×
153
      VersionHandler currentHandler;
154
      currentHandler.path = &f;
×
155
      auto &versions = currentHandler.versions;
156

157
      auto ChooseOne = [&] {
×
158
        if (*versionBegin != '.') {
×
159
          return false;
160
        }
161

162
        versionBegin++;
×
163
        const uint32 newVersion = std::strtoul(versionBegin, &nextDot, 10);
×
164

165
        if (versionBegin == nextDot) {
×
166
          return false;
167
        }
168

169
        versionBegin = nextDot;
×
170
        versions[curIndex++] = newVersion;
×
171

172
        return true;
×
173
      };
174

175
      if (ChooseOne()) {
×
176
        if (ChooseOne()) {
×
177
          if (ChooseOne()) {
×
178
            ChooseOne();
×
179
          }
180
        }
181
      }
182

183
      versionedFiles.push_back(currentHandler);
×
184
    }
185

186
    std::sort(versionedFiles.begin(), versionedFiles.end());
×
187

188
    if (versionedFiles.empty()) {
×
189
      throw std::runtime_error(std::string("Couldn't find module: ") +
×
190
                               moduleName);
×
191
    }
192

193
    return *versionedFiles.back().path;
×
194
  }();
×
195

196
  auto postError = [] {
×
197
    throw std::runtime_error(std::string("APPContext Error: ") + dlerror());
×
198
  };
199

200
  auto assign = [&](auto &value, auto name) {
×
201
    using type_ = std::decay_t<decltype(value)>;
202
    value = reinterpret_cast<type_>(dlsym(dlHandle, name));
×
203

204
    if (!value) {
×
205
      postError();
×
206
    }
207
  };
208

209
  auto tryAssign = [&](auto &value, auto name) {
210
    using type_ = typename std::decay_t<decltype(value)>::value_type;
211
    return value = reinterpret_cast<type_>(dlsym(dlHandle, name));
×
212
  };
213

214
  if (mainSettings.verbosity > 1) {
×
215
    printinfo("Module open: " << modulePath);
×
216
  }
217

218
#if defined(_MSC_VER) || defined(__MINGW64__)
219
  auto modPath = ToTSTRING(modulePath);
220
  dlHandle = LoadLibrary(modPath.data());
221
#else
222
  dlHandle = dlopen(modulePath.data(), RTLD_NOW);
×
223
#endif
224
  if (!dlHandle) {
×
225
    postError();
×
226
  }
227

228
  func<decltype(AppInitModule)> InitModule;
229
  assign(InitModule, "AppInitModule");
×
230
  AppInfo_s *info_ = InitModule();
×
231
  info = info_;
×
232

233
  if (info->contextVersion != AppInfo_s::CONTEXT_VERSION) {
×
234
    throw std::runtime_error("Module context version mismatch!");
×
235
  }
236

237
  info_->internalSettings = &mainSettings;
×
238

239
  tryAssign(AdditionalHelp, "AppAdditionalHelp");
240
  tryAssign(InitContext, "AppInitContext");
241
  tryAssign(FinishContext, "AppFinishContext");
242
  tryAssign(NewArchive, "AppNewArchive");
243
  tryAssign(ProcessFile, "AppProcessFile");
244
  tryAssign(ExtractStat, "AppExtractStat");
245

246
  if (NewArchive && ProcessFile) {
×
247
    throw std::logic_error("Module uses 2 or more contexts!");
×
248
  }
249
}
250

251
APPContext::APPContext(APPContext &&other)
×
252
    : APPContextCopyData(other), appFolder(std::move(other.appFolder)),
×
253
      appName(std::move(other.appName)) {
×
254
  if (dlHandle && dlHandle != other.dlHandle) {
×
255
    dlclose(dlHandle);
×
256
  }
257
  dlHandle = other.dlHandle;
×
258
  std::construct_at(&other);
259
}
260

261
APPContext &APPContext::operator=(APPContext &&other) {
×
262
  static_cast<APPContextCopyData &>(*this) = other;
263
  appFolder = std::move(other.appFolder);
×
264
  appName = std::move(other.appName);
×
265
  if (dlHandle && dlHandle != other.dlHandle) {
×
266
    dlclose(dlHandle);
×
267
  }
268
  dlHandle = other.dlHandle;
×
269
  std::construct_at(&other);
270
  return *this;
×
271
}
272

273
APPContext::~APPContext() {
×
274
  if (dlHandle) {
×
275
    dlclose(dlHandle);
×
276
  }
277
}
278

279
class ReflectorFriend : public Reflector {
280
public:
281
  using Reflector::GetReflectedInstance;
282
  using Reflector::GetReflectedType;
283
  using Reflector::SetReflectedValue;
284
};
285

286
static auto &MainSettings() {
287
  static ReflectorWrap<MainAppConfFriend> wrap(mainSettings);
288
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
289
}
290

291
static auto &ExtractSettings() {
292
  static ReflectorWrap<ExtractConf> wrap(mainSettings.extractSettings);
293
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
294
}
295

296
static auto &CompressSettings() {
297
  static ReflectorWrap<CompressConf> wrap(mainSettings.compressSettings);
298
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
299
}
300

301
static auto &CliSettings() {
302
  static ReflectorWrap<CLISettings> wrap(cliSettings);
303
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
304
}
305

306
static const reflectorStatic *RTTI(const ReflectorFriend &ref) {
307
  auto rawRTTI = ref.GetReflectedInstance();
×
308
  return static_cast<const ReflectedInstanceFriend &>(rawRTTI).Refl();
309
}
310

311
void APPContext::ResetSwitchSettings() {
×
312
  if (info->settings) {
×
313
    const size_t numValues = Settings().GetNumReflectedValues();
×
314
    for (size_t i = 0; i < numValues; i++) {
×
315
      auto rType = Settings().GetReflectedType(i);
×
316

317
      if (rType->type == REFType::Bool) {
×
318
        Settings().SetReflectedValue(i, "false");
×
319
      }
320
    }
321
  }
322

323
  mainSettings.generateLog = mainSettings.extractSettings.folderPerArc =
×
324
      mainSettings.extractSettings.makeZIP = false;
×
325
}
326

327
int APPContext::ApplySetting(std::string_view key, std::string_view value) {
×
328
  JenHash keyHash(key);
329
  ReflectorFriend *refl = nullptr;
330
  const ReflType *rType = nullptr;
331
  if (info->settings) {
×
332
    rType = Settings().GetReflectedType(keyHash);
×
333
  }
334

335
  if (rType) {
×
336
    refl = &Settings();
337
  } else {
338
    rType = MainSettings().GetReflectedType(keyHash);
×
339

340
    if (rType) {
×
341
      refl = &MainSettings();
×
342
    } else {
343
      rType = CliSettings().GetReflectedType(keyHash);
×
344

345
      if (rType) {
×
346
        refl = &CliSettings();
×
347
      } else if (ProcessFile) {
×
348
        rType = ExtractSettings().GetReflectedType(keyHash);
×
349

350
        if (rType) {
×
351
          refl = &ExtractSettings();
×
352
        }
353
      } else if (NewArchive) {
×
354
        rType = CompressSettings().GetReflectedType(keyHash);
×
355

356
        if (rType) {
×
357
          refl = &CompressSettings();
×
358
        }
359
      }
360
    }
361
  }
362

363
  if (rType) {
×
364
    if (rType->type == REFType::Bool) {
×
365
      refl->SetReflectedValue(*rType, "true");
×
366
      return 0;
×
367
    } else {
368
      refl->SetReflectedValue(*rType, value);
×
369
      return 1;
×
370
    }
371
  } else {
372
    printerror("Invalid option: " << (key.size() > 1 ? "--" : "-") << key);
×
373
    return -1;
×
374
  }
375
}
376

377
void APPContext::PrintCLIHelp() const {
×
378
  printline("Options:" << std::endl);
×
379

380
  auto printStuff = [](auto rtti) {
×
381
    for (size_t i = 0; i < rtti->nTypes; i++) {
×
382
      if (rtti->typeAliases && rtti->typeAliases[i]) {
×
383
        es::print::Get() << "-" << rtti->typeAliases[i] << ", ";
×
384
      }
385

386
      es::print::Get() << "--" << rtti->typeNames[i];
×
387
      es::print::Get() << "  = " << rtti->typeDescs[i].part1 << std::endl;
×
388
    }
389
  };
390

391
  printStuff(::RTTI(MainSettings()));
×
392

393
  if (ProcessFile) {
×
394
    printStuff(::RTTI(ExtractSettings()));
×
395
  } else if (NewArchive) {
×
396
    printStuff(::RTTI(CompressSettings()));
×
397
  }
398

399
  if (info->settings) {
×
400
    printStuff(RTTI());
×
401
  }
402
  printline("");
×
403
}
404

405
void DumpTypeMD(std::ostream &out, const ReflectorFriend &info,
×
406
                size_t indent = 0) {
407
  auto rtti = RTTI(info);
408

409
  auto gi = [&]() -> std::ostream & {
410
    static const char indents[]{"                "};
411
    return out << indents + (8 - indent) * 2;
×
412
  };
413

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

417
    if (info.IsReflectedSubClass(i)) {
×
418
      auto sub = info.GetReflectedSubClass(i);
×
419
      ReflectorPureWrap subRef(sub);
420
      DumpTypeMD(
×
421
          out, static_cast<ReflectorFriend &>(static_cast<Reflector &>(subRef)),
422
          indent + 1);
423
      continue;
424
    }
425

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

428
    if (rtti->typeAliases && rtti->typeAliases[i]) {
×
429
      gi() << "  **CLI Short:** ***-" << rtti->typeAliases[i] << "***\n\n";
×
430
    }
431

432
    if (auto val = info.GetReflectedValue(i); !val.empty()) {
×
433
      gi() << "  **Default value:** " << val << "\n\n";
×
434
    }
435

436
    if (auto &rType = rtti->types[i]; rType.type == REFType::Enum) {
×
437
      auto refEnum =
438
          ReflectedEnum::Registry().at(JenHash(rType.asClass.typeHash));
×
439
      gi() << "  **Valid values:** ";
×
440

441
      if ([&] {
×
442
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
443
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
444
                return true;
445
              }
446
            }
447
            return false;
448
          }()) {
449
        out << "\n\n";
×
450
        indent++;
×
451

452
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
453
          gi() << "- " << refEnum->names[e];
×
454
          if (refEnum->descriptions[e]) {
×
455
            out << ": " << refEnum->descriptions[e] << "\n\n";
×
456
          } else {
457
            out << ", "
458
                << "\n\n";
×
459
          }
460
        }
461
        indent--;
×
462
      } else {
463
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
464
          out << refEnum->names[e] << ", ";
×
465
        }
466
      }
467

468
      out.seekp(out.tellp() - std::streamoff(2));
×
469
      out << "\n\n";
×
470
    }
471

472
    if (auto desc = rtti->typeDescs[i].part1; desc) {
×
473
      gi() << "  " << rtti->typeDescs[i].part1 << "\n\n";
×
474
    }
475
  }
476
}
477

478
void APPContext::GetMarkdownDoc(std::ostream &out, pugi::xml_node node) const {
×
479
  const char *className = "[[MODULE CLASS NAME]]";
480
  const char *description = "[[MODULE DESCRIPTION]]";
481

482
  if (info->settings) {
×
483
    className = RTTI()->className;
×
484
  }
485

486
  if (node) {
×
487
    if (auto child = node.attribute("name"); child) {
×
488
      className = child.as_string();
×
489
    }
490
    description = node.text().as_string();
×
491
  }
492

493
  out << "## " << className << "\n\n### Module command: " << moduleName
×
494
      << "\n\n"
495
      << description << "\n\n";
×
496

497
  if (!info->settings) {
×
498
    return;
499
  }
500

501
  out << "### Settings\n\n";
×
502

503
  DumpTypeMD(out, Settings());
×
504
}
505

506
void APPContext::SetupModule() {
×
507
  if (mainSettings.generateLog) {
×
508
    CreateLog();
×
509
  }
510

511
  if (InitContext && !InitContext(appFolder + "data/")) {
×
512
    throw std::runtime_error("Error while initializing context.");
×
513
  }
514
}
515

516
using stream_type = BinWritter_t<BinCoreOpenMode::Text>;
517
static stream_type &GetStream() {
518
  static stream_type outStream;
519
  return outStream;
×
520
}
521

522
static std::ostream &GetLogger() { return GetStream().BaseStream(); }
523

524
static void printf(const char *str) { GetLogger() << str; }
525

526
const reflectorStatic *APPContext::RTTI() const { return ::RTTI(Settings()); }
×
527

528
void APPContext::CreateLog() {
×
529
  time_t curTime =
530
      std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
×
531
  std::tm timeStruct = *localtime(&curTime);
×
532
  TCHAR dateBuffer[128]{};
×
533
  const size_t dateBufferSize = sizeof(dateBuffer) / sizeof(TCHAR);
534

535
  _tcsftime(dateBuffer, dateBufferSize, _T("_%y_%m_%d-%H.%M.%S"), &timeStruct);
×
536
  auto logName = appFolder + moduleName + std::to_string(dateBuffer) + ".txt";
×
537
  GetStream().Open(logName);
×
538
  es::print::AddPrinterFunction(printf, false);
×
539

540
  _tcsftime(dateBuffer, dateBufferSize, _T("%c %Z"), &timeStruct);
×
541

542
  GetLogger() << "Current time: " << std::to_string(dateBuffer) << std::endl;
×
543
  GetLogger() << "Number of concurrent threads: "
544
              << std::thread::hardware_concurrency() << std::endl;
×
545
  GetLogger() << "Configuration:" << std::endl;
546

547
  auto PrintStuff = [](auto &what) {
×
548
    const size_t numSettings = what.GetNumReflectedValues();
×
549
    auto rtti = ::RTTI(what);
550

551
    for (size_t t = 0; t < numSettings; t++) {
×
552
      std::string_view desc2;
553

554
      if (rtti->typeDescs && rtti->typeDescs[t].part2) {
×
555
        desc2 = rtti->typeDescs[t].part2;
556
      }
557

558
      Reflector::KVPair pair = what.GetReflectedPair(t);
×
559

560
      GetLogger() << '\t' << pair.name << ": ";
×
561

562
      if (desc2 == "HIDDEN") {
×
563
        GetLogger() << "--hidden--";
×
564
      } else {
565
        GetLogger() << pair.value;
566
      }
567

568
      GetLogger() << std::endl;
569
    }
570
  };
571

572
  PrintStuff(MainSettings());
×
573
  if (ProcessFile) {
×
574
    PrintStuff(ExtractSettings());
×
575
  } else if (NewArchive) {
×
576
    PrintStuff(CompressSettings());
×
577
  }
578

579
  if (info->settings) {
×
580
    PrintStuff(Settings());
×
581
  }
582

583
  GetLogger() << std::endl;
584
}
585

586
void GetHelp(std::ostream &str, const reflectorStatic *ref, size_t level = 1) {
×
587
  auto fillIndent = [&](size_t mod = 0) -> std::ostream & {
588
    for (size_t i = 0; i < level + mod; i++) {
×
589
      str << '\t';
×
590
    }
591

592
    return str;
593
  };
594

595
  for (size_t i = 0; i < ref->nTypes; i++) {
×
596
    std::string_view elName = ref->typeNames[i];
×
597
    auto elDesc = ref->typeDescs[i];
×
598
    fillIndent() << elName << std::endl;
599

600
    if (elDesc.part1) {
×
601
      fillIndent(1) << elDesc.part1 << std::endl;
×
602
    }
603

604
    auto fl = ref->types[i];
×
605

606
    if (fl.type == REFType::Class || fl.type == REFType::BitFieldClass) {
×
607
      GetHelp(str, reflectorStatic::Registry().at(JenHash(fl.asClass.typeHash)),
×
608
              level + 1);
609
    } else if (fl.type == REFType::Array || fl.type == REFType::ArrayClass) {
×
610
      const auto &arr = fl.asArray;
611

612
      if (arr.type == REFType::Class || arr.type == REFType::BitFieldClass) {
×
613
        GetHelp(str,
×
614
                reflectorStatic::Registry().at(JenHash(arr.asClass.typeHash)),
×
615
                level + 1);
616
      }
617
    } else if (fl.type == REFType::Enum) {
×
618
      auto refEnum = ReflectedEnum::Registry().at(JenHash(fl.asClass.typeHash));
×
619
      fillIndent(1) << "Values: ";
×
620

621
      if ([&] {
×
622
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
623
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
624
                return true;
625
              }
626
            }
627
            return false;
628
          }()) {
629
        str << std::endl;
630

631
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
632
          fillIndent(2) << refEnum->names[e];
×
633
          if (refEnum->descriptions[e]) {
×
634
            str << ": " << refEnum->descriptions[e] << std::endl;
×
635
          } else {
636
            str << ", " << std::endl;
637
          }
638
        }
639
      } else {
640
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
641
          str << refEnum->names[e] << ", ";
×
642
        }
643
        str << std::endl;
644
      }
645
    }
646
  }
647
}
648

649
void APPContext::GetHelp(std::ostream &str) {
×
650
  str << moduleName << " settings." << std::endl;
×
651
  ::GetHelp(str, RTTI());
×
652
}
653

654
struct AppHelpContextImpl : AppHelpContext {
×
655
  std::map<std::string, std::stringstream> tagBuffers;
656

657
  std::ostream &GetStream(const std::string &tag) override {
×
658
    return tagBuffers[tag] = std::stringstream{};
×
659
  }
660
};
661

662
void APPContext::FromConfig() {
×
663
  auto configName = (appFolder + appName) + ".config";
×
664
  pugi::xml_document doc = {};
×
665

666
  auto TryFile = [](auto cb) {
×
667
    constexpr size_t numTries = 10;
668
    size_t curTry = 0;
669

670
    for (; curTry < numTries; curTry++) {
×
671
      try {
672
        cb();
×
673
      } catch (const es::FileNotFoundError &) {
×
674
      } catch (const es::FileInvalidAccessError &) {
×
675
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
×
676
        continue;
677
      }
678

679
      break;
680
    }
681

682
    if (curTry == numTries) {
×
683
      throw std::runtime_error("Cannot access config. File is locked.");
×
684
    }
685
  };
686

×
687
  TryFile([&] {
688
    auto flags = XMLDefaultParseFlags;
689
    flags += XMLParseFlag::Comments;
690
    doc = XMLFromFile(configName, flags);
×
691
    ReflectorXMLUtil::LoadV2(MainSettings(), doc.child("common"));
692

693
    if (info->settings) {
×
694
      ReflectorXMLUtil::LoadV2(Settings(), doc.child(moduleName));
×
695
    }
×
696
  });
697

698
  {
699
    {
700
      AppHelpContextImpl helpCtx;
701

702
      if (auto commentNode = doc.find_child([](pugi::xml_node &node) {
×
703
            return node.type() == pugi::xml_node_type::node_comment &&
×
704
                   std::string_view(node.value()).starts_with("common");
705
          });
706
          commentNode) {
×
707
        std::string_view comment(commentNode.value());
708
        std::string_view lastTag;
709
        size_t lastPos = 0;
710

×
711
        while (true) {
712
          lastPos = comment.find("<-tag:", lastPos);
×
713

×
714
          if (!lastTag.empty()) {
×
715
            std::string tagName(lastTag);
×
716
            lastTag.substr(0, comment.size());
717
            size_t dataBegin = lastTag.find_first_of('\n');
718

719
            if (dataBegin != lastTag.npos) {
720
              dataBegin++;
721
              const size_t dataEnd =
722
                  lastPos != lastTag.npos ? lastPos : lastTag.size();
×
723
              helpCtx.GetStream(tagName)
×
724
                  << lastTag.substr(dataBegin, dataEnd - dataBegin);
725
            }
726

727
            lastTag = {};
×
728
          }
729

730
          if (lastPos == comment.npos) {
×
731
            break;
×
732
          }
733

×
734
          const size_t tagBegin = lastPos += 6;
×
735

736
          lastPos = comment.find("->", lastPos);
×
737

738
          if (lastPos == comment.npos) {
739
            break;
740
          }
741

742
          auto tagName = comment.substr(tagBegin, lastPos - tagBegin);
×
743
          lastTag = es::TrimWhitespace(tagName);
×
744
        }
×
745

746
        doc.remove_child(commentNode);
×
747
      }
×
748

×
749
      std::stringstream str;
750
      str << "common settings." << std::endl;
751
      ::GetHelp(str, ::RTTI(MainSettings()));
752
      AdditionalHelp(&helpCtx, 1);
753

754
      for (auto &[tag, data] : helpCtx.tagBuffers) {
×
755
        str << "\t<-tag: " << tag << "->\n" << data.str();
×
756
      }
×
757

758
      auto buff = std::move(str).str();
759
      pugi::xml_node commonNode;
×
760
      auto commentNode = doc.append_child(pugi::node_comment);
×
761
      commentNode.set_value(buff.data());
762

×
763
      if (commonNode = doc.child("common"); commonNode) {
×
764
        doc.insert_move_after(commonNode, commentNode);
×
765
      } else {
766
        commonNode = doc.append_child("common");
767
      }
×
768
      ReflectorXMLUtil::SaveV2a(MainSettings(), commonNode,
769
                                ReflectorXMLUtil::Flags_StringAsAttribute);
770
    }
×
771

772
    if (info->settings) {
773
      if (auto commentNode = doc.find_child([&](pugi::xml_node &node) {
774
            return node.type() == pugi::xml_node_type::node_comment &&
×
775
                   std::string_view(node.value()).starts_with(moduleName);
776
          });
777
          commentNode) {
778
        doc.remove_child(commentNode);
×
779
      }
780

781
      std::stringstream str;
782
      GetHelp(str);
×
783
      auto buff = std::move(str).str();
×
784
      pugi::xml_node node;
785
      auto commentNode = doc.append_child(pugi::node_comment);
786
      commentNode.set_value(buff.data());
×
787

788
      if (node = doc.child(moduleName); node) {
789
        doc.insert_move_after(node, commentNode);
×
790
      } else {
791
        node = doc.append_child(moduleName);
×
792
      }
793
      ReflectorXMLUtil::SaveV2a(Settings(), node,
794
                                {ReflectorXMLUtil::Flags_StringAsAttribute});
×
795
    }
×
796

797
    TryFile([&] {
798
      XMLToFile(configName, doc,
799
                {XMLFormatFlag::WriteBOM, XMLFormatFlag::IndentAttributes});
×
800
    });
×
801
  }
×
802
}
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