/* * Copyright (c) Robin E.R. Davies * All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "Tone3000Tone.hpp" #include "ss.hpp" using namespace pipedal; using namespace pipedal::tone3000; namespace fs = std::filesystem; Tone::Tone(const std::string &json) { std::istringstream ss(json); json_reader reader(ss); reader.read(this); } namespace { static std::string mdSanitize(const std::string &str, const std::string &illegalCharacters = "") { std::ostringstream os; for (char c : str) { if ((unsigned char)c < 0x20) continue; if (c == '<') { os << "<"; continue; } auto npos = illegalCharacters.find_first_of(c); if (npos != std::string::npos) { continue; } os << c; } return os.str(); } static std::string mdDate(const tone3000_time_point &date_) { std::ostringstream os; // C++ doesn't handle timezones. Just zap the timezone, and replace it with "Z" std::string date = date_; // Remove timezone offset if present and replace with Z size_t plusPos = date.find('+'); if (plusPos != std::string::npos) { date = date.substr(0, plusPos) + "Z"; } // Parse ISO 8601 date string into tm std::tm tm = {}; std::istringstream ss(date); ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S"); if (ss.fail()) { os << date; return os.str(); } os << std::put_time(&tm, "%x"); return os.str(); } static std::string mdEnum(const std::string &value, const std::map &enumValues) { auto ff = enumValues.find(value); if (ff == enumValues.end()) { return value; } return ff->second; } static std::string mdEnumList( const std::vector &values, const std::map &enumValues) { if (values.size() == 1) { return mdEnum(values[0], enumValues); } std::ostringstream os; os << '['; bool first = true; for (const auto &value : values) { if (first) { first = false; } else { os << ", "; } os << mdEnum(value, enumValues); } os << ']'; return os.str(); } static std::map sizeEnumValues = { {"standard", "Standard"}, {"lite", "Lite"}, {"feather", "Feather"}, {"nano", "Nano"}, {"custom", "Custom"}}; static std::string mdSizes(const std::vector &sizes) { return mdEnumList(sizes, sizeEnumValues); } static std::map gearEnumValues = { {"amp", "Amp only"}, {"full-rig", "Full rig"}, {"pedal", "Pedal"}, {"outboard", "Outboard"}, {"ir", "IR"}}; static std::string mdGear(const std::string &gear) { return mdEnum(gear, gearEnumValues); } namespace { enum class LicenseFlags { None = 0, Cc = 1, By = 2, CcBy = 3, // = Cc | By Sa = 4, Nc = 8, Nd = 16, Cc0 = 32, }; static bool operator&(LicenseFlags v1, LicenseFlags v2) { return (((int)v1) & ((int)v2)) != 0; } static LicenseFlags operator|(LicenseFlags v1, LicenseFlags v2) { return (LicenseFlags)(((int)(v1)) | ((int)(v2))); } struct LicenseInfo { std::string key; std::string displayName; std::string url; LicenseFlags licenseFlags; }; } static std::vector licenses{ {.key = "t3k", .displayName = "T3K", .url = "licenses/t3k_license.html", .licenseFlags = LicenseFlags::None}, {.key = "cc-by", .displayName = "CC BY 4.0", .url = "https://creativecommons.org/licenses/by/4.0/", .licenseFlags = LicenseFlags::CcBy}, {.key = "cc-by-sa", .displayName = "CC BY-SA 4.0", .url = "https://creativecommons.org/licenses/by-sa/4.0/", .licenseFlags = LicenseFlags::CcBy | LicenseFlags::Sa}, {.key = "cc-by-nc", .displayName = "CC BY-NC 4.0", .url = "https://creativecommons.org/licenses/by-nc/4.0/", .licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nc}, {.key = "cc-by-nc-sa", .displayName = "CC BY-NC-SA 4.0", .url = "https://creativecommons.org/licenses/by-nc-sa/4.0/", .licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nc | LicenseFlags::Sa}, {.key = "cc-by-nd", .displayName = "CC BY-ND 4.0", .url = "https://creativecommons.org/licenses/by-nd/4.0/", .licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nd}, {.key = "cc-by-nc-nd", .displayName = "CC BY-NC-ND 4.0", .url = "https://creativecommons.org/licenses/by-nc-nd/4.0/", .licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nc | LicenseFlags::Nd}, { .key = "cco", .displayName = "CC0", .url = "https://creativecommons.org/publicdomain/zero/1.0/", .licenseFlags = LicenseFlags::Cc | LicenseFlags::Cc0, }, }; static std::map makeLicenseEnum() { std::map result; for (const auto &license : licenses) { result[license.key] = license; } return result; } static std::map licenseEnum = makeLicenseEnum(); static std::string mdLicenseIcon(LicenseFlags licenseFlag) { std::ostringstream os; std::string url; switch (licenseFlag) { case LicenseFlags::Cc: url = "img/cc.svg"; break; case LicenseFlags::By: url = "img/by.svg"; break; case LicenseFlags::Sa: url = "img/sa.svg"; break; case LicenseFlags::Nc: url = "img/nc.svg"; break; case LicenseFlags::Nd: url = "img/nd.svg"; break; case LicenseFlags::Cc0: url = "img/cc0.svg"; break; case LicenseFlags::None: throw std::runtime_error("Invalid argument."); } os << ""; return os.str(); } static void mdEscapeString(std::ostream &f, const std::string &str) { size_t ix = 0; while (ix < str.size()) { char c = str[ix++]; if (c == '\n') { if (ix < str.size() && str[ix] == '\n') { f << "\n\n"; } else { f << " \n"; // a
in md. } } else { f << c; } } } static std::string mdHref(const std::string &url) { std::ostringstream os; os << '\''; for (char c : url) { if (c == '\'') { continue; } os << c; } os << '\''; return os.str(); } static std::string mdLicense(const std::string &license) { auto ff = licenseEnum.find(license); LicenseInfo licenseInfo; if (ff != licenseEnum.end()) { licenseInfo = ff->second; } else { licenseInfo.displayName = license; } std::ostringstream os; if (licenseInfo.licenseFlags != LicenseFlags::None) { for (LicenseFlags licenseFlag : std::vector{LicenseFlags::Cc, LicenseFlags::By, LicenseFlags::Cc0, LicenseFlags::Nc, LicenseFlags::Sa, LicenseFlags::Nd}) { if (licenseInfo.licenseFlags & licenseFlag) { os << mdLicenseIcon(licenseFlag); } } os << " "; } if (licenseInfo.url != "") { os << "" << mdSanitize(licenseInfo.displayName) << ""; } else { os << mdSanitize(licenseInfo.displayName); } return os.str(); } // static std::string mdTestLicenses() // { // std::ostringstream os; // for (auto license: licenseEnum) // { // os << "
" << mdLicense(license.first) << "\n"; // } // return os.str(); // } static std::map platformEnumValues = { {"nam", "NAM"}, {"ir", "IR"}, {"aida-x", "Aida X"}, {"aa-snapshot", "aa-snapshot"}, {"proteus", "Proteus"}}; static std::string mdPlatform(const std::string &platform) { return mdEnum(platform, platformEnumValues); } static std::string mdUser(const tone3000::User &user) { return mdSanitize(user.username()); } static void mdLink(std::ostream &f, const std::string &label, const std::string &url) { f << "[" << mdSanitize(label, "]") << "](" << mdSanitize(url, ")") << ")"; } }; void pipedal::tone3000::WriteTone3000Readme(const std::filesystem::path &filePath, const tone3000::Tone &tone, const std::string &thumbnailUrl_) { std::string thumbnailUrl = SS("/var/tone3000_thumbnail?id=" << tone.id()); std::ofstream of{filePath}; if (!of.is_open()) { throw std::runtime_error(SS("Unable to create file " << filePath)); } // clang-format off of << "
\n"; if (!thumbnailUrl.empty()) { of << " \n"; } of << "
\n" << "

" << mdSanitize(tone.title()) << "

\n" << "
\n" << "

\n" << mdDate(tone.updated_at()) << " " << mdUser(tone.user()) << "
\n" << " "; if (tone.sizes().has_value() && tone.sizes().value().size() != 0) { of << mdSizes(tone.sizes().value()) << ", " ; } if (tone.platform() == "ir") { of << (mdPlatform(tone.platform())); } else { of << mdGear(tone.gear()) << ", " << mdPlatform(tone.platform()); } of << "
\n"; if (tone.license() != "") { of << " License: " << mdLicense(tone.license()) << "\n"; } of // << " " << mdTestLicenses() << "

\n" << "
\n" << "
\n" << "
\n"; // clang-format on of << "\n\n"; mdEscapeString(of, tone.description()); if (tone.links().size() > 0) { of << std::endl; of << "## Links" << std::endl << std::endl; for (auto &link : tone.links()) { of << "- " << "[" << link << "](" << link << ")" << std::endl; } } } JSON_MAP_BEGIN(Model) JSON_MAP_REFERENCE(Model, id) JSON_MAP_REFERENCE(Model, name) JSON_MAP_REFERENCE(Model, model_url) JSON_MAP_REFERENCE(Model, created_at) JSON_MAP_REFERENCE(Model, size) JSON_MAP_REFERENCE(Model, user_id) JSON_MAP_END() JSON_MAP_BEGIN(User) JSON_MAP_REFERENCE(User, id) JSON_MAP_REFERENCE(User, username) JSON_MAP_REFERENCE(User, avatar_url) JSON_MAP_REFERENCE(User, url) JSON_MAP_END() JSON_MAP_BEGIN(Tone) JSON_MAP_REFERENCE(Tone, id) JSON_MAP_REFERENCE(Tone, user_id) JSON_MAP_REFERENCE(Tone, title) JSON_MAP_REFERENCE(Tone, description) JSON_MAP_REFERENCE(Tone, created_at) JSON_MAP_REFERENCE(Tone, updated_at) JSON_MAP_REFERENCE(Tone, gear) JSON_MAP_REFERENCE(Tone, images) JSON_MAP_REFERENCE(Tone, is_public) JSON_MAP_REFERENCE(Tone, links) JSON_MAP_REFERENCE(Tone, platform) JSON_MAP_REFERENCE(Tone, models_count) JSON_MAP_REFERENCE(Tone, favorites_count) JSON_MAP_REFERENCE(Tone, downloads_count) JSON_MAP_REFERENCE(Tone, license) JSON_MAP_REFERENCE(Tone, sizes) JSON_MAP_REFERENCE(Tone, a1_models_count) JSON_MAP_REFERENCE(Tone, a2_models_count) JSON_MAP_REFERENCE(Tone, irs_count) JSON_MAP_REFERENCE(Tone, custom_models_count) JSON_MAP_REFERENCE(Tone, user) JSON_MAP_REFERENCE(Tone, url) JSON_MAP_REFERENCE(Tone, user) JSON_MAP_END();