version.cpp (4762B)
1 /* ReaPack: Package manager for REAPER 2 * Copyright (C) 2015-2025 Christian Fillion 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "version.hpp" 19 20 #include "errors.hpp" 21 #include "package.hpp" 22 #include "source.hpp" 23 24 #include <boost/lexical_cast.hpp> 25 #include <cctype> 26 #include <regex> 27 28 std::string Version::displayAuthor(const std::string &author) 29 { 30 if(author.empty()) 31 return "Unknown"; 32 else 33 return author; 34 } 35 36 Version::Version(const std::string &str, const Package *pkg) 37 : m_name(str), m_time(), m_package(pkg) 38 { 39 } 40 41 Version::~Version() 42 { 43 for(const Source *source : m_sources) 44 delete source; 45 } 46 47 std::string Version::fullName() const 48 { 49 std::string name = m_package->fullName(); 50 name += " v"; 51 name += m_name.toString(); 52 53 return name; 54 } 55 56 bool Version::addSource(const Source *source) 57 { 58 if(source->version() != this) 59 throw reapack_error("source belongs to another version"); 60 else if(!source->platform().test()) 61 return false; 62 63 const Path path = source->targetPath(); 64 65 if(m_files.count(path)) 66 return false; 67 68 m_files.insert(path); 69 m_sources.push_back(source); 70 71 return true; 72 } 73 74 std::ostream &operator<<(std::ostream &os, const Version &ver) 75 { 76 os << 'v' << ver.name().toString(); 77 78 if(!ver.author().empty()) 79 os << " by " << ver.author(); 80 81 if(ver.time()) 82 os << " – " << ver.time(); 83 84 os << "\r\n"; 85 86 const std::string &changelog = ver.changelog(); 87 os << String::indent(changelog.empty() ? "No changelog" : changelog); 88 89 return os; 90 } 91 92 VersionName::VersionName() : m_stable(true) 93 {} 94 95 VersionName::VersionName(const std::string &str) 96 { 97 parse(str); 98 } 99 100 VersionName::VersionName(const VersionName &o) 101 : m_string(o.m_string), m_segments(o.m_segments), m_stable(o.m_stable) 102 { 103 } 104 105 void VersionName::parse(const std::string &str) 106 { 107 static const std::regex pattern("\\d+|[a-zA-Z]+"); 108 109 const auto &begin = std::sregex_iterator(str.begin(), str.end(), pattern); 110 const std::sregex_iterator end; 111 112 size_t letters = 0; 113 std::vector<Segment> segments; 114 115 for(std::sregex_iterator it = begin; it != end; it++) { 116 const std::string &match = it->str(0); 117 118 if(isalpha(match[0])) { 119 if(segments.empty()) // got leading letters 120 throw reapack_error(String::format("invalid version name '%s'", str.c_str())); 121 122 segments.push_back(match); 123 letters++; 124 } 125 else { 126 try { 127 segments.push_back(boost::lexical_cast<Numeric>(match)); 128 } 129 catch(const boost::bad_lexical_cast &) { 130 throw reapack_error(String::format("version segment overflow in '%s'", str.c_str())); 131 } 132 } 133 } 134 135 if(segments.empty()) // version doesn't have any numbers 136 throw reapack_error(String::format("invalid version name '%s'", str.c_str())); 137 138 m_string = str; 139 swap(m_segments, segments); 140 m_stable = letters < 1; 141 } 142 143 bool VersionName::tryParse(const std::string &str, std::string *errorOut) 144 { 145 try { 146 parse(str); 147 return true; 148 } 149 catch(const reapack_error &err) { 150 if(errorOut) 151 *errorOut = err.what(); 152 153 return false; 154 } 155 } 156 157 auto VersionName::segment(const size_t index) const -> Segment 158 { 159 if(index < size()) 160 return m_segments[index]; 161 else 162 return {}; 163 } 164 165 int VersionName::compare(const VersionName &o) const 166 { 167 const size_t biggest = std::max(size(), o.size()); 168 169 switch(m_segments.empty() + o.m_segments.empty()) { 170 case 1: 171 return m_segments.empty() ? -1 : 1; 172 case 2: 173 return 0; 174 } 175 176 for(size_t i = 0; i < biggest; i++) { 177 const Segment &lseg = segment(i); 178 const Numeric *lnum = std::get_if<Numeric>(&lseg); 179 const std::string *lstr = std::get_if<std::string>(&lseg); 180 181 const Segment &rseg = o.segment(i); 182 const Numeric *rnum = std::get_if<Numeric>(&rseg); 183 const std::string *rstr = std::get_if<std::string>(&rseg); 184 185 if(lnum && rnum) { 186 if(*lnum < *rnum) 187 return -1; 188 else if(*lnum > *rnum) 189 return 1; 190 } 191 else if(lstr && rstr) { 192 if(*lstr < *rstr) 193 return -1; 194 else if(*lstr > *rstr) 195 return 1; 196 } 197 else if(lnum && rstr) 198 return 1; 199 else if(lstr && rnum) 200 return -1; 201 } 202 203 return 0; 204 }