From dabfcfc180f84fc9c60672e0513a6a97843783c3 Mon Sep 17 00:00:00 2001 From: Sude Date: Sat, 15 Apr 2023 22:19:07 +0300 Subject: [PATCH] Add option to transform gamenames Configuration file $XDG_CONFIG_HOME/lgogdownloader/transformations.json can be used to set rules for transforming gamenames. New subdir templates: %gamename_transformed% and %gamename_transformed_firstletter% "--list transformations" can be used to show transformed gamenames --- include/config.h | 3 +++ include/globalconstants.h | 12 +++++---- main.cpp | 22 +++++++++++++++- man/lgogdownloader.supplemental.groff | 36 +++++++++++++++++++++++++++ src/downloader.cpp | 26 +++++++++++++------ src/util.cpp | 28 +++++++++++++++++++++ 6 files changed, 113 insertions(+), 14 deletions(-) diff --git a/include/config.h b/include/config.h index 8b5ba03..252dc1e 100644 --- a/include/config.h +++ b/include/config.h @@ -282,6 +282,7 @@ class Config std::string sIgnorelistFilePath; std::string sGameHasDLCListFilePath; std::string sReportFilePath; + std::string sTransformConfigFilePath; std::string sXMLFile; @@ -321,6 +322,8 @@ class Config int iProgressInterval; int iMsgLevel; unsigned int iListFormat; + + Json::Value transformationsJSON; }; #endif // CONFIG_H__ diff --git a/include/globalconstants.h b/include/globalconstants.h index e34d006..6b0f4e0 100644 --- a/include/globalconstants.h +++ b/include/globalconstants.h @@ -118,17 +118,19 @@ namespace GlobalConstants { CDN_AKAMAI, "akamai_edgecast_proxy", "Akamai", "akamai|akamai_cdn|akamai_ec|akamai_edgecast_proxy" } }; - const unsigned int LIST_FORMAT_GAMES = 1 << 0; - const unsigned int LIST_FORMAT_DETAILS_TEXT = 1 << 1; - const unsigned int LIST_FORMAT_DETAILS_JSON = 1 << 2; - const unsigned int LIST_FORMAT_TAGS = 1 << 3; + const unsigned int LIST_FORMAT_GAMES = 1 << 0; + const unsigned int LIST_FORMAT_DETAILS_TEXT = 1 << 1; + const unsigned int LIST_FORMAT_DETAILS_JSON = 1 << 2; + const unsigned int LIST_FORMAT_TAGS = 1 << 3; + const unsigned int LIST_FORMAT_TRANSFORMATIONS = 1 << 4; const std::vector LIST_FORMAT = { { LIST_FORMAT_GAMES, "games", "Games", "g|games" }, { LIST_FORMAT_DETAILS_TEXT, "details", "Details", "d|details" }, { LIST_FORMAT_DETAILS_JSON, "json", "JSON", "j|json" }, - { LIST_FORMAT_TAGS, "tags", "Tags", "t|tags" } + { LIST_FORMAT_TAGS, "tags", "Tags", "t|tags" }, + { LIST_FORMAT_TRANSFORMATIONS, "transform", "Transformations", "tr|transform|transformations" } }; } diff --git a/main.cpp b/main.cpp index f1b70b5..a7a7651 100644 --- a/main.cpp +++ b/main.cpp @@ -79,6 +79,7 @@ int main(int argc, char *argv[]) Globals::globalConfig.sBlacklistFilePath = Globals::globalConfig.sConfigDirectory + "/blacklist.txt"; Globals::globalConfig.sIgnorelistFilePath = Globals::globalConfig.sConfigDirectory + "/ignorelist.txt"; Globals::globalConfig.sGameHasDLCListFilePath = Globals::globalConfig.sConfigDirectory + "/game_has_dlc.txt"; + Globals::globalConfig.sTransformConfigFilePath = Globals::globalConfig.sConfigDirectory + "/transformations.json"; Globals::galaxyConf.setFilepath(Globals::globalConfig.sConfigDirectory + "/galaxy_tokens.json"); @@ -151,7 +152,7 @@ int main(int argc, char *argv[]) std::string check_orphans_text = "Check for orphaned files (files found on local filesystem that are not found on GOG servers). Sets regular expression filter (Perl syntax) for files to check. If no argument is given then the regex defaults to '" + orphans_regex_default + "'"; // Help text for subdir options - std::string subdir_help_text = "\nTemplates:\n- %platform%\n- %gamename%\n- %gamename_firstletter%\n- %dlcname%"; + std::string subdir_help_text = "\nTemplates:\n- %platform%\n- %gamename%\n- %gamename_firstletter%\n- %dlcname%\n- %gamename_transformed%\n- %gamename_transformed_firstletter%"; // Help text for include and exclude options std::string include_options_text; @@ -432,6 +433,25 @@ int main(int argc, char *argv[]) } } + if (boost::filesystem::exists(Globals::globalConfig.sTransformConfigFilePath)) + { + std::ifstream ifs(Globals::globalConfig.sTransformConfigFilePath, std::ifstream::binary); + Json::Value json; + try { + ifs >> json; + if (!json.empty()) + { + Globals::globalConfig.transformationsJSON = json; + } + } catch (const Json::Exception& exc) { + std::cerr << "Failed to parse " << Globals::globalConfig.sTransformConfigFilePath<< std::endl; + std::cerr << exc.what() << std::endl; + } + + if (ifs) + ifs.close(); + } + if (!bUseDLCList) Globals::globalConfig.sGameHasDLCList = ""; diff --git a/man/lgogdownloader.supplemental.groff b/man/lgogdownloader.supplemental.groff index 7468bf2..a81a506 100644 --- a/man/lgogdownloader.supplemental.groff +++ b/man/lgogdownloader.supplemental.groff @@ -109,6 +109,42 @@ Must be in the following format: .br } +.TP +\fI$XDG_CONFIG_HOME/lgogdownloader/transformations.json\fP +JSON formatted file. Used to transform gamenames. +.br +Must be in the following format: +.br +{ + : + { + "regex" : , + "replacement" : , + }, + : + { + "regex" : , + "replacement" : , + }, +.br +} +.br +Member names are used to match the gamename. +If it matches then \fBregex\fP is used for the actual replacement using the value in \fBreplacement\fP. +.br +\fBExample:\fP +.br +match all games beginning with "\fBb\fP" and if they end with "\fB_the\fP" then remove "\fB_the\fP" at the end and prefix it with "\fBthe_\fP" +.br +{ + "^b" : + { + "regex" : "(.*)_the$", + "replacement" : "the_\\\\1", + }, +.br +} + [priorities] Separating values with "," when using \fBlanguage\fP and \fBplatform\fP switches enables a priority-based mode: only the first matching one will be downloaded. .PP diff --git a/src/downloader.cpp b/src/downloader.cpp index 0ae3d35..168ca8b 100644 --- a/src/downloader.cpp +++ b/src/downloader.cpp @@ -526,7 +526,8 @@ int Downloader::getGameDetails() int Downloader::listGames() { - if (Globals::globalConfig.iListFormat == GlobalConstants::LIST_FORMAT_GAMES) + if (Globals::globalConfig.iListFormat == GlobalConstants::LIST_FORMAT_GAMES || + Globals::globalConfig.iListFormat == GlobalConstants::LIST_FORMAT_TRANSFORMATIONS) { if (gameItems.empty()) this->getGameList(); @@ -534,15 +535,24 @@ int Downloader::listGames() for (unsigned int i = 0; i < gameItems.size(); ++i) { std::string gamename = gameItems[i].name; - if (gameItems[i].updates > 0) + if (Globals::globalConfig.iListFormat == GlobalConstants::LIST_FORMAT_TRANSFORMATIONS) { - gamename += " [" + std::to_string(gameItems[i].updates) + "]"; - if (Globals::globalConfig.bColor) - gamename = "\033[32m" + gamename + "\033[0m"; + std::string str = "%gamename% -> %gamename_transformed%"; + Util::filepathReplaceReservedStrings(str, gamename); + std::cout << str << std::endl; + } + else + { + if (gameItems[i].updates > 0) + { + gamename += " [" + std::to_string(gameItems[i].updates) + "]"; + if (Globals::globalConfig.bColor) + gamename = "\033[32m" + gamename + "\033[0m"; + } + std::cout << gamename << std::endl; + for (unsigned int j = 0; j < gameItems[i].dlcnames.size(); ++j) + std::cout << "+> " << gameItems[i].dlcnames[j] << std::endl; } - std::cout << gamename << std::endl; - for (unsigned int j = 0; j < gameItems[i].dlcnames.size(); ++j) - std::cout << "+> " << gameItems[i].dlcnames[j] << std::endl; } } else if (Globals::globalConfig.iListFormat == GlobalConstants::LIST_FORMAT_TAGS) diff --git a/src/util.cpp b/src/util.cpp index 3228f92..c4bfc69 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -438,6 +438,34 @@ void Util::filepathReplaceReservedStrings(std::string& str, const std::string& g gamename_firstletter = gamename.front(); } + std::string gamename_transformed = gamename; + if (str.find("%gamename_transformed%") != std::string::npos || str.find("%gamename_transformed_firstletter%") != std::string::npos) + { + for (auto transformMatch : Globals::globalConfig.transformationsJSON.getMemberNames()) + { + boost::regex expression(transformMatch); + boost::match_results what; + if (boost::regex_search(gamename, what, expression)) + { + boost::regex transformRegex(Globals::globalConfig.transformationsJSON[transformMatch]["regex"].asString()); + std::string transformReplacement = Globals::globalConfig.transformationsJSON[transformMatch]["replacement"].asString(); + gamename_transformed = boost::regex_replace(gamename, transformRegex, transformReplacement); + } + } + + std::string gamename_transformed_firstletter; + if (!gamename_transformed.empty()) + { + if (std::isdigit(gamename_transformed.front())) + gamename_transformed_firstletter = "0"; + else + gamename_transformed_firstletter = gamename_transformed.front(); + } + + while (Util::replaceString(str, "%gamename_transformed%", gamename_transformed)); + while (Util::replaceString(str, "%gamename_transformed_firstletter%", gamename_transformed_firstletter)); + } + while (Util::replaceString(str, "%gamename_firstletter%", gamename_firstletter)); while (Util::replaceString(str, "%gamename%", gamename)); while (Util::replaceString(str, "%dlcname%", dlcname));