21namespace fs = std::filesystem;
22using json = nlohmann::json;
25 : mBatteryPath(batteryPath)
26 , mWorkspacePath(workspacePath)
29 mBatteryScalesPath = batteryPath +
"/../media/apps/scales";
30 mBatteryDefinitionsPath = mBatteryScalesPath +
"/definitions";
33 if (!workspacePath.empty()) {
34 mWorkspaceScalesPath = workspacePath +
"/scales";
35 mWorkspaceDefinitionsPath = mWorkspaceScalesPath +
"/definitions";
38 EnsureDirectoriesExist();
45void ScaleManager::EnsureDirectoriesExist()
50 if (!mWorkspacePath.empty()) {
52 fs::create_directories(mWorkspaceScalesPath);
53 }
catch (
const std::exception& e) {
61 std::set<std::string> scalesSet;
64 auto scanForScales = [&](
const std::string& basePath) {
66 if (!fs::exists(basePath))
return;
67 for (
const auto& entry : fs::directory_iterator(basePath)) {
68 if (entry.is_directory()) {
69 std::string dirName = entry.path().filename().string();
70 std::string defFile = entry.path().string() +
"/" + dirName +
".json";
71 std::string osdFile = entry.path().string() +
"/" + dirName +
".osd";
72 if (fs::exists(defFile) || fs::exists(osdFile)) {
73 scalesSet.insert(dirName);
77 }
catch (
const std::exception& e) {
83 scanForScales(mBatteryDefinitionsPath);
86 if (!mWorkspacePath.empty()) {
87 scanForScales(mWorkspaceScalesPath);
91 std::vector<std::string> scales(scalesSet.begin(), scalesSet.end());
92 std::sort(scales.begin(), scales.end());
104 auto scale = std::make_shared<ScaleDefinition>();
107 if (!mWorkspaceScalesPath.empty()) {
108 if (scale->LoadFromScalesDir(mWorkspaceScalesPath, code)) {
114 if (scale->LoadFromScalesDir(mBatteryDefinitionsPath, code)) {
129 std::string scaleDir =
GetScalesPath() +
"/" + scale->GetScaleInfo().code;
131 fs::create_directories(scaleDir);
132 }
catch (
const std::exception& e) {
133 printf(
"Error creating scale directory: %s\n", e.what());
137 return scale->ExportToOSD(scaleDir);
145 if (fs::exists(defPath)) {
150 auto transFiles = GetTranslationFiles(code);
151 for (
const auto& transFile : transFiles) {
152 if (fs::exists(transFile)) {
153 fs::remove(transFile);
158 }
catch (
const std::exception& e) {
175 std::vector<LooseOSDEntry> entries;
176 if (mWorkspaceScalesPath.empty())
return entries;
179 if (!fs::exists(mWorkspaceScalesPath))
return entries;
180 for (
const auto& entry : fs::directory_iterator(mWorkspaceScalesPath)) {
181 if (!entry.is_regular_file())
continue;
182 std::string filename = entry.path().filename().string();
183 if (filename.size() < 5 || filename.substr(filename.size() - 4) !=
".osd")
continue;
186 std::string stemCode = filename.substr(0, filename.size() - 4);
187 std::string subdirPath = mWorkspaceScalesPath +
"/" + stemCode;
188 if (fs::exists(subdirPath) && fs::is_directory(subdirPath))
continue;
192 std::ifstream f(entry.path().string());
193 if (!f.is_open())
continue;
194 json bundle = json::parse(f);
195 if (!bundle.contains(
"definition"))
continue;
198 e.
path = entry.path().string();
199 if (bundle[
"definition"].contains(
"scale_info")) {
200 auto& si = bundle[
"definition"][
"scale_info"];
201 if (si.contains(
"code")) e.
code = si[
"code"].get<std::string>();
202 if (si.contains(
"name")) e.
name = si[
"name"].get<std::string>();
204 if (e.
code.empty()) e.
code = stemCode;
206 entries.push_back(e);
219 printf(
"InstallLooseOSD: failed to load OSD from: %s\n", osdPath.c_str());
222 std::string code = scale->GetScaleInfo().code;
224 printf(
"InstallLooseOSD: scale has no code in: %s\n", osdPath.c_str());
229 std::string targetDir = mWorkspaceScalesPath +
"/" + code;
230 std::string targetFile = targetDir +
"/" + code +
".osd";
232 fs::create_directories(targetDir);
233 fs::copy_file(osdPath, targetFile, fs::copy_options::overwrite_existing);
235 printf(
"Installed OSD '%s' to: %s\n", code.c_str(), targetFile.c_str());
236 }
catch (
const std::exception& e) {
237 printf(
"InstallLooseOSD error: %s\n", e.what());
247 if (!mWorkspaceScalesPath.empty()) {
248 std::string path = mWorkspaceScalesPath +
"/" + code +
"/" + code +
".json";
249 if (fs::exists(path))
return path;
253 std::string path = mBatteryDefinitionsPath +
"/" + code +
"/" + code +
".json";
254 if (fs::exists(path))
return path;
257 return mBatteryDefinitionsPath +
"/" + code +
"/" + code +
".json";
263 if (!mWorkspaceScalesPath.empty()) {
264 std::string path = mWorkspaceScalesPath +
"/" + code +
"/" + code +
".osd";
265 if (fs::exists(path))
return path;
269 std::string path = mBatteryDefinitionsPath +
"/" + code +
"/" + code +
".osd";
270 if (fs::exists(path))
return path;
278 if (!mWorkspaceScalesPath.empty()) {
279 std::string newPath = mWorkspaceScalesPath +
"/" + code +
"/" + code +
"." + lang +
".json";
280 if (fs::exists(newPath))
return newPath;
281 std::string oldPath = mWorkspaceScalesPath +
"/" + code +
"/" + code +
".pbl-" + lang +
".json";
282 if (fs::exists(oldPath))
return oldPath;
286 std::string newPath = mBatteryDefinitionsPath +
"/" + code +
"/" + code +
"." + lang +
".json";
287 if (fs::exists(newPath))
return newPath;
288 std::string oldPath = mBatteryDefinitionsPath +
"/" + code +
"/" + code +
".pbl-" + lang +
".json";
289 if (fs::exists(oldPath))
return oldPath;
292 return mBatteryDefinitionsPath +
"/" + code +
"/" + code +
"." + lang +
".json";
300std::vector<std::string> ScaleManager::GetTranslationFiles(
const std::string& code)
const
302 std::set<std::string> langsSeen;
303 std::vector<std::string> files;
305 std::string newPrefix = code +
".";
306 std::string oldPrefix = code +
".pbl-";
308 auto scanDir = [&](
const std::string& dirPath) {
310 if (!fs::exists(dirPath))
return;
311 for (
const auto& entry : fs::directory_iterator(dirPath)) {
312 if (!entry.is_regular_file())
continue;
313 std::string filename = entry.path().filename().string();
314 if (filename.size() < 5 || filename.substr(filename.size() - 5) !=
".json")
continue;
319 if (filename.find(newPrefix) == 0) {
320 lang = filename.substr(newPrefix.length(), filename.size() - newPrefix.length() - 5);
322 if (lang.empty() || lang.find(
"pbl-") == 0) {
324 if (filename.find(oldPrefix) == 0) {
325 lang = filename.substr(oldPrefix.length(), filename.size() - oldPrefix.length() - 5);
332 else if (filename.find(oldPrefix) == 0) {
333 lang = filename.substr(oldPrefix.length(), filename.size() - oldPrefix.length() - 5);
338 if (!lang.empty() && langsSeen.find(lang) == langsSeen.end()) {
339 langsSeen.insert(lang);
340 files.push_back(entry.path().string());
343 }
catch (
const std::exception& e) {
349 if (!mWorkspaceScalesPath.empty()) {
350 scanDir(mWorkspaceScalesPath +
"/" + code);
352 scanDir(mBatteryDefinitionsPath +
"/" + code);
365 if (fs::exists(defPath)) {
367 std::ifstream file(defPath);
368 if (!file.is_open())
return meta;
373 if (j.contains(
"scale_info")) {
374 auto& info = j[
"scale_info"];
375 if (info.contains(
"name")) meta.
name = info[
"name"];
376 if (info.contains(
"description")) meta.
description = info[
"description"];
377 if (info.contains(
"author")) meta.
author = info[
"author"];
379 if (j.contains(
"questions") && j[
"questions"].is_array()) {
381 }
else if (j.contains(
"items") && j[
"items"].is_array()) {
386 auto transFiles = GetTranslationFiles(code);
387 for (
const auto& transFile : transFiles) {
388 fs::path p(transFile);
389 std::string filename = p.filename().string();
390 std::string prefix = code +
".pbl-";
391 if (filename.find(prefix) == 0 && filename.size() > prefix.length() + 5) {
392 std::string lang = filename.substr(prefix.length());
393 lang = lang.substr(0, lang.length() - 5);
400 if (osdPath.empty())
return meta;
402 std::ifstream file(osdPath);
403 if (!file.is_open())
return meta;
408 if (!bundle.contains(
"definition"))
return meta;
409 auto& j = bundle[
"definition"];
411 if (j.contains(
"scale_info")) {
412 auto& info = j[
"scale_info"];
413 if (info.contains(
"name")) meta.
name = info[
"name"];
414 if (info.contains(
"description")) meta.
description = info[
"description"];
415 if (info.contains(
"author")) meta.
author = info[
"author"];
417 if (j.contains(
"items") && j[
"items"].is_array()) {
419 }
else if (j.contains(
"questions") && j[
"questions"].is_array()) {
424 if (bundle.contains(
"translations") && bundle[
"translations"].is_object()) {
425 for (
auto& [lang, _] : bundle[
"translations"].items()) {
433 }
catch (
const std::exception& e) {
441 const std::string& workspaceStudiesPath,
442 const std::string& studyName)
445 printf(
"Error: Scale is null\n");
449 std::string scaleCode = scale->GetScaleInfo().code;
452 std::string actualStudyName = studyName.empty() ? scaleCode : studyName;
453 std::string studyPath = workspaceStudiesPath +
"/" + actualStudyName;
455 printf(
"Creating scale study at: %s\n", studyPath.c_str());
459 if (fs::exists(studyPath)) {
460 printf(
"Removing existing study directory: %s\n", studyPath.c_str());
461 fs::remove_all(studyPath);
467 printf(
"Error: Failed to create study structure\n");
472 study->SetDescription(
"Scale: " + scale->GetScaleInfo().name);
473 if (!scale->GetScaleInfo().citation.empty()) {
474 study->SetAuthor(scale->GetScaleInfo().citation);
483 std::string testPath = studyPath +
"/tests/" + scaleCode;
484 std::string paramsPath = testPath +
"/params";
485 fs::create_directories(testPath +
"/definitions");
486 fs::create_directories(testPath +
"/translations");
487 fs::create_directories(paramsPath);
490 std::string scaleRunnerSource = mBatteryScalesPath +
"/ScaleRunner.pbl";
491 std::string scaleRunnerDest = testPath +
"/" + scaleCode +
".pbl";
493 if (!fs::exists(scaleRunnerSource)) {
494 printf(
"Error: ScaleRunner.pbl not found at: %s\n", scaleRunnerSource.c_str());
498 fs::copy_file(scaleRunnerSource, scaleRunnerDest);
499 printf(
"Copied ScaleRunner.pbl to %s\n", scaleRunnerDest.c_str());
502 if (!scale->ExportToJSON(testPath +
"/definitions", testPath +
"/translations")) {
503 printf(
"Error: Failed to export scale files\n");
507 printf(
"Exported scale definition and translations\n");
511 std::string aboutPath = testPath +
"/" + scaleCode +
".pbl.about.txt";
512 std::string readmePath = mBatteryDefinitionsPath +
"/" + scaleCode +
"/README.md";
515 if (fs::exists(readmePath)) {
517 fs::copy_file(readmePath, aboutPath, fs::copy_options::overwrite_existing);
518 printf(
"Created about file from README.md: %s\n", aboutPath.c_str());
520 }
catch (
const std::exception& e) {
521 printf(
"Warning: Failed to copy README.md: %s\n", e.what());
526 if (!copied && !mWorkspaceScalesPath.empty()) {
527 std::string wsReadme = mWorkspaceScalesPath +
"/" + scaleCode +
"/README.md";
528 if (fs::exists(wsReadme)) {
530 fs::copy_file(wsReadme, aboutPath, fs::copy_options::overwrite_existing);
531 printf(
"Created about file from workspace README.md: %s\n", aboutPath.c_str());
533 }
catch (
const std::exception& e) {
534 printf(
"Warning: Failed to copy workspace README.md: %s\n", e.what());
541 std::ofstream aboutFile(aboutPath);
542 if (aboutFile.is_open()) {
543 const auto& info = scale->GetScaleInfo();
544 aboutFile << info.name << std::endl;
545 aboutFile << std::endl;
546 if (!info.description.empty()) {
547 aboutFile << info.description << std::endl;
548 aboutFile << std::endl;
550 if (!info.citation.empty()) {
551 aboutFile <<
"Citation:" << std::endl;
552 aboutFile << info.citation << std::endl;
553 aboutFile << std::endl;
555 if (!info.license.empty()) {
556 aboutFile <<
"License: " << info.license << std::endl;
559 printf(
"Created about file from metadata: %s\n", aboutPath.c_str());
565 GenerateSchemaFiles(*scale, testPath);
568 GenerateScreenshot(scaleCode, testPath);
577 study->AddTest(test);
580 if (!study->Save()) {
581 printf(
"Error: Failed to save study-info.json\n");
586 std::string chainPath = studyPath +
"/chains/Main.json";
588 "Default chain for " + scale->GetScaleInfo().name);
597 defaultChain->AddItem(testItem);
599 if (defaultChain->Save()) {
600 printf(
"Created default chain with test: Main.json\n");
602 printf(
"Warning: Failed to save default chain\n");
605 printf(
"Warning: Failed to create default chain\n");
608 printf(
"Successfully created scale study: %s\n", scaleCode.c_str());
611 }
catch (
const std::exception& e) {
612 printf(
"Exception while creating scale study: %s\n", e.what());
618 const std::string& studyPath)
621 printf(
"Error: Scale is null\n");
625 std::string scaleCode = scale->GetScaleInfo().code;
626 printf(
"Adding scale '%s' to study at: %s\n", scaleCode.c_str(), studyPath.c_str());
632 printf(
"Error: Failed to load study from: %s\n", studyPath.c_str());
637 std::string testPath = studyPath +
"/tests/" + scaleCode;
638 if (fs::exists(testPath)) {
639 printf(
"Removing existing test directory: %s\n", testPath.c_str());
640 fs::remove_all(testPath);
649 std::string paramsPath = testPath +
"/params";
650 fs::create_directories(testPath +
"/definitions");
651 fs::create_directories(testPath +
"/translations");
652 fs::create_directories(paramsPath);
655 std::string scaleRunnerSource = mBatteryScalesPath +
"/ScaleRunner.pbl";
656 std::string scaleRunnerDest = testPath +
"/" + scaleCode +
".pbl";
658 if (!fs::exists(scaleRunnerSource)) {
659 printf(
"Error: ScaleRunner.pbl not found at: %s\n", scaleRunnerSource.c_str());
663 fs::copy_file(scaleRunnerSource, scaleRunnerDest);
666 if (!scale->ExportToJSON(testPath +
"/definitions", testPath +
"/translations")) {
667 printf(
"Error: Failed to export scale files\n");
673 std::string aboutPath = testPath +
"/" + scaleCode +
".pbl.about.txt";
674 std::string readmePath = mBatteryDefinitionsPath +
"/" + scaleCode +
"/README.md";
677 if (fs::exists(readmePath)) {
679 fs::copy_file(readmePath, aboutPath, fs::copy_options::overwrite_existing);
681 }
catch (
const std::exception&) {}
684 if (!copied && !mWorkspaceScalesPath.empty()) {
685 std::string wsReadme = mWorkspaceScalesPath +
"/" + scaleCode +
"/README.md";
686 if (fs::exists(wsReadme)) {
688 fs::copy_file(wsReadme, aboutPath, fs::copy_options::overwrite_existing);
690 }
catch (
const std::exception&) {}
695 std::ofstream aboutFile(aboutPath);
696 if (aboutFile.is_open()) {
697 const auto& info = scale->GetScaleInfo();
698 aboutFile << info.name << std::endl;
699 aboutFile << std::endl;
700 if (!info.description.empty()) {
701 aboutFile << info.description << std::endl;
702 aboutFile << std::endl;
704 if (!info.citation.empty()) {
705 aboutFile <<
"Citation:" << std::endl;
706 aboutFile << info.citation << std::endl;
714 GenerateSchemaFiles(*scale, testPath);
717 GenerateScreenshot(scaleCode, testPath);
720 if (!study->GetTest(scaleCode)) {
726 study->AddTest(test);
730 if (!study->Save()) {
731 printf(
"Error: Failed to save study-info.json\n");
735 printf(
"Successfully added scale '%s' to study\n", scaleCode.c_str());
738 }
catch (
const std::exception& e) {
739 printf(
"Exception while adding scale to study: %s\n", e.what());
744bool ScaleManager::GenerateScreenshot(
const std::string& scaleCode,
const std::string& testPath)
746 std::string screenshotDest = testPath +
"/" + scaleCode +
".pbl.png";
749 if (fs::exists(screenshotDest)) {
750 printf(
"Screenshot already exists: %s\n", screenshotDest.c_str());
755 std::string libraryScreenshot = mBatteryDefinitionsPath +
"/" + scaleCode +
"/" + scaleCode +
".pbl.png";
756 if (fs::exists(libraryScreenshot)) {
758 fs::copy_file(libraryScreenshot, screenshotDest, fs::copy_options::overwrite_existing);
759 printf(
"Copied existing screenshot from library: %s\n", screenshotDest.c_str());
761 }
catch (
const std::exception& e) {
762 printf(
"Warning: Failed to copy library screenshot: %s\n", e.what());
767 if (!mWorkspaceDefinitionsPath.empty()) {
768 std::string wsScreenshot = mWorkspaceDefinitionsPath +
"/" + scaleCode +
"/" + scaleCode +
".pbl.png";
769 if (fs::exists(wsScreenshot)) {
771 fs::copy_file(wsScreenshot, screenshotDest, fs::copy_options::overwrite_existing);
772 printf(
"Copied existing screenshot from workspace: %s\n", screenshotDest.c_str());
774 }
catch (
const std::exception& e) {
775 printf(
"Warning: Failed to copy workspace screenshot: %s\n", e.what());
782 std::string peblPath;
784 char exeBuf[MAX_PATH];
785 GetModuleFileNameA(
NULL, exeBuf, MAX_PATH);
786 std::string exeStr(exeBuf);
787 size_t lastSlash = exeStr.find_last_of(
"\\/");
788 if (lastSlash != std::string::npos) {
789 peblPath = exeStr.substr(0, lastSlash + 1) +
"pebl2.exe";
791 peblPath =
"pebl2.exe";
795 ssize_t len = readlink(
"/proc/self/exe", exeBuf,
sizeof(exeBuf) - 1);
798 std::string exeStr(exeBuf);
799 size_t lastSlash = exeStr.find_last_of(
'/');
800 if (lastSlash != std::string::npos) {
801 peblPath = exeStr.substr(0, lastSlash + 1) +
"pebl2";
810 if (!fs::exists(peblPath)) {
811 printf(
"Warning: PEBL executable not found at: %s\n", peblPath.c_str());
816 std::string screenshotScript = mBatteryScalesPath +
"/../scale-screenshot.pbl";
817 if (!fs::exists(screenshotScript)) {
818 printf(
"Warning: scale-screenshot.pbl not found at: %s\n", screenshotScript.c_str());
823 std::string absScript = fs::canonical(screenshotScript).string();
824 std::string absTestPath = fs::canonical(testPath).string();
829 std::string cwd = absTestPath;
830 bool workspaceLayout =
false;
832 std::string directJson = absTestPath +
"/" + scaleCode +
".json";
833 std::string defsJson = absTestPath +
"/definitions/" + scaleCode +
".json";
835 if (fs::exists(directJson) && !fs::exists(defsJson)) {
837 cwd = fs::path(absTestPath).parent_path().string();
838 workspaceLayout =
true;
843 std::string cmd =
"cd /d \"" + cwd +
"\" && \"" + peblPath +
"\" \"" + absScript +
"\" -v " + scaleCode +
" >nul 2>&1";
845 std::string cmd =
"cd \"" + cwd +
"\" && \"" + peblPath +
"\" \"" + absScript +
"\" -v " + scaleCode +
" >/dev/null 2>&1";
848 printf(
"Generating screenshot: %s\n", cmd.c_str());
849 int ret = system(cmd.c_str());
852 if (workspaceLayout) {
853 std::string outputFile = cwd +
"/" + scaleCode +
".pbl.png";
854 if (fs::exists(outputFile)) {
856 fs::rename(outputFile, screenshotDest);
857 }
catch (
const std::exception&) {
860 fs::copy_file(outputFile, screenshotDest, fs::copy_options::overwrite_existing);
861 fs::remove(outputFile);
862 }
catch (
const std::exception& e) {
863 printf(
"Warning: Failed to move screenshot: %s\n", e.what());
869 if (fs::exists(screenshotDest)) {
870 printf(
"Screenshot generated: %s\n", screenshotDest.c_str());
873 printf(
"Warning: Screenshot generation failed (exit code %d)\n", ret);
882bool ScaleManager::GenerateSchemaFiles(
const ScaleDefinition& scale,
const std::string& testPath)
887 std::string paramsPath = testPath +
"/params";
889 fs::create_directories(paramsPath);
892 static const std::set<std::string> baseNames = {
"scale",
"shuffle_questions",
"show_header"};
895 auto jsonDefault = [](
const std::string& type,
const std::string& value) -> nlohmann::json {
896 if (type ==
"boolean" || type ==
"integer") {
897 try {
return std::stoi(value); }
catch (...) {}
898 }
else if (type ==
"float") {
899 try {
return std::stod(value); }
catch (...) {}
905 auto osdDefault = [&](
const std::string& pname, nlohmann::json fallback) -> nlohmann::json {
906 auto it = osdParams.find(pname);
907 if (it != osdParams.end() && !it->second.defaultValue.empty()) {
908 return jsonDefault(it->second.type, it->second.defaultValue);
913 nlohmann::json schemaParams = nlohmann::json::array();
914 nlohmann::json parDefaults = nlohmann::json::object();
917 schemaParams.push_back({
921 {
"description",
"OSD scale code (reads definitions/{code}.json)"},
924 parDefaults[
"scale"] = code;
928 auto def = osdDefault(
"shuffle_questions", 0);
929 schemaParams.push_back({
930 {
"name",
"shuffle_questions"},
933 {
"description",
"Randomize item order within randomization groups"},
934 {
"options", nlohmann::json::array({0, 1})}
936 parDefaults[
"shuffle_questions"] = def;
941 auto def = osdDefault(
"show_header", 1);
942 schemaParams.push_back({
943 {
"name",
"show_header"},
946 {
"description",
"Display the scale title header above the questionnaire"},
947 {
"options", nlohmann::json::array({0, 1})}
949 parDefaults[
"show_header"] = def;
953 for (
const auto& [pname, pdef] : osdParams) {
954 if (baseNames.count(pname))
continue;
958 sp[
"type"] = pdef.type.empty() ?
"string" : pdef.type;
960 if (!pdef.defaultValue.empty()) {
961 auto def = jsonDefault(pdef.type, pdef.defaultValue);
963 parDefaults[pname] = def;
965 if (!pdef.description.empty()) {
966 sp[
"description"] = pdef.description;
968 if (!pdef.options.empty()) {
969 nlohmann::json opts = nlohmann::json::array();
970 for (
const auto& o : pdef.options) opts.push_back(o);
971 sp[
"options"] = opts;
972 }
else if (pdef.type ==
"boolean") {
973 sp[
"options"] = nlohmann::json::array({0, 1});
976 schemaParams.push_back(sp);
981 std::set<std::string> coveredParams = baseNames;
982 for (
const auto& [pname, _] : osdParams) coveredParams.insert(pname);
984 for (
const auto& dim : scale.GetDimensions()) {
985 if (!dim.selectable)
continue;
988 std::string pname = dim.enabled_param.empty() ? (
"do_" + dim.id) : dim.enabled_param;
989 if (coveredParams.count(pname))
continue;
990 coveredParams.insert(pname);
992 int defVal = dim.default_enabled ? 1 : 0;
993 schemaParams.push_back({
997 {
"description",
"Include " + dim.name +
" dimension"},
998 {
"options", nlohmann::json::array({0, 1})}
1000 parDefaults[pname] = defVal;
1004 nlohmann::json schema = {
1007 {
"description", name +
" — auto-generated from scale definition"},
1008 {
"parameters", schemaParams}
1010 std::string schemaPath = paramsPath +
"/" + code +
".pbl.schema.json";
1011 std::ofstream sf(schemaPath);
1012 if (!sf.is_open()) {
1013 printf(
"Warning: Failed to write schema file: %s\n", schemaPath.c_str());
1016 sf << schema.dump(2);
1018 printf(
"Generated schema: %s\n", schemaPath.c_str());
1021 std::string parPath = paramsPath +
"/" + code +
".pbl.par.json";
1022 if (!fs::exists(parPath)) {
1023 std::ofstream pf(parPath);
1025 pf << parDefaults.dump(2);
1027 printf(
"Generated params: %s\n", parPath.c_str());
static std::shared_ptr< Chain > CreateNew(const std::string &path, const std::string &name, const std::string &description="")
std::map< std::string, ScaleParameter > & GetParameters()
static std::shared_ptr< ScaleDefinition > LoadFromFile(const std::string &jsonPath)
ScaleInfo & GetScaleInfo()
static std::shared_ptr< ScaleDefinition > CreateNew(const std::string &code)
static std::shared_ptr< ScaleDefinition > LoadFromOSDFile(const std::string &osdPath)
bool ExportToBattery(std::shared_ptr< ScaleDefinition > scale)
bool ScaleExists(const std::string &code) const
std::vector< std::string > GetAvailableScales()
bool AddScaleToStudy(std::shared_ptr< ScaleDefinition > scale, const std::string &studyPath)
bool CreateStudyFromScale(std::shared_ptr< ScaleDefinition > scale, const std::string &workspaceStudiesPath, const std::string &studyName="")
std::string GetDefinitionPath(const std::string &code) const
bool SaveScale(std::shared_ptr< ScaleDefinition > scale)
std::string GetScalesPath() const
std::vector< LooseOSDEntry > GetLooseOSDEntries() const
ScaleManager(const std::string &batteryPath, const std::string &workspacePath="")
bool DeleteScale(const std::string &code)
std::shared_ptr< ScaleDefinition > ImportFromFile(const std::string &filePath)
std::string GetOSDPath(const std::string &code) const
ScaleMetadata GetScaleMetadata(const std::string &code) const
std::string GetTranslationPath(const std::string &code, const std::string &lang) const
std::shared_ptr< ScaleDefinition > CreateScale(const std::string &code)
std::shared_ptr< ScaleDefinition > InstallLooseOSD(const std::string &osdPath)
std::shared_ptr< ScaleDefinition > LoadScale(const std::string &code)
static std::shared_ptr< Study > CreateNew(const std::string &path, const std::string &name, const std::string &author="")
static std::shared_ptr< Study > LoadFromDirectory(const std::string &path)