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

PredatorCZ / PreCore / 460

pending completion
460

push

github-actions-ci

PredatorCZ
try fix coverage

3204 of 6095 relevant lines covered (52.57%)

354.19 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
    Part of PreCore project
3

4
    Copyright 2021-2022 Lukas Cone
5

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

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

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

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

31
#if defined(_MSC_VER) || defined(__MINGW64__)
32
#include "spike/type/tchar.hpp"
33
#include <windows.h>
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),
×
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

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

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

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

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

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

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

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

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

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

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

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

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

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

173
        return true;
×
174
      };
175

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

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

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

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

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

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

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

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

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

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

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

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

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

238
  info_->internalSettings = &mainSettings;
×
239

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

593
    return str;
594
  };
595

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

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

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

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

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

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

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

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

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

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

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

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

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

680
      break;
681
    }
682

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

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

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

699
  {
700
    {
701
      AppHelpContextImpl helpCtx;
702

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

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

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

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

728
            lastTag = {};
×
729
          }
730

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

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

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

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

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

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

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

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

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

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

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

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

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

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