Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Version Parsing to handle more separators #751

Merged
merged 15 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
149 changes: 96 additions & 53 deletions launcher/Version.cpp
Original file line number Diff line number Diff line change
@@ -1,85 +1,128 @@
#include "Version.h"

#include <QStringList>
#include <QUrl>
#include <QDebug>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QUrl>

Version::Version(const QString &str) : m_string(str)
Version::Version(QString str) : m_string(std::move(str))
{
parse();
}

bool Version::operator<(const Version &other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
{
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2)
{
return sec1 < sec2;
}
#define VERSION_OPERATOR(return_on_different) \
bool exclude_our_sections = false; \
bool exclude_their_sections = false; \
\
const auto size = qMax(m_sections.size(), other.m_sections.size()); \
for (int i = 0; i < size; ++i) { \
Section sec1 = (i >= m_sections.size()) ? Section() : m_sections.at(i); \
Section sec2 = (i >= other.m_sections.size()) ? Section() : other.m_sections.at(i); \
\
{ /* Don't include appendixes in the comparison */ \
if (sec1.isAppendix()) \
exclude_our_sections = true; \
if (sec2.isAppendix()) \
exclude_their_sections = true; \
\
if (exclude_our_sections) { \
sec1 = Section(); \
if (sec2.m_isNull) \
break; \
} \
\
if (exclude_their_sections) { \
sec2 = Section(); \
if (sec1.m_isNull) \
break; \
} \
} \
\
if (sec1 != sec2) \
return return_on_different; \
}

bool Version::operator<(const Version& other) const
{
VERSION_OPERATOR(sec1 < sec2)

return false;
}
bool Version::operator<=(const Version &other) const
bool Version::operator==(const Version& other) const
{
VERSION_OPERATOR(false)

return true;
}
bool Version::operator!=(const Version& other) const
{
return !operator==(other);
}
bool Version::operator<=(const Version& other) const
{
return *this < other || *this == other;
}
bool Version::operator>(const Version &other) const
bool Version::operator>(const Version& other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
{
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2)
{
return sec1 > sec2;
}
}

return false;
return !(*this <= other);
}
bool Version::operator>=(const Version &other) const
bool Version::operator>=(const Version& other) const
{
return *this > other || *this == other;
return !(*this < other);
}
bool Version::operator==(const Version &other) const

void Version::parse()
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
{
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2)
{
m_sections.clear();
QString currentSection;

if (m_string.isEmpty())
return;

auto classChange = [&](QChar lastChar, QChar currentChar) {
if (lastChar.isNull())
return false;
if (lastChar.isDigit() != currentChar.isDigit())
return true;

const QList<QChar> s_separators{ '.', '-', '+' };
if (s_separators.contains(currentChar) && currentSection.at(0) != currentChar)
return true;

return false;
};

currentSection += m_string.at(0);
for (int i = 1; i < m_string.size(); ++i) {
const auto& current_char = m_string.at(i);
if (classChange(m_string.at(i - 1), current_char)) {
if (!currentSection.isEmpty())
m_sections.append(Section(currentSection));
currentSection = "";
}

currentSection += current_char;
}

return true;
}
bool Version::operator!=(const Version &other) const
{
return !operator==(other);
if (!currentSection.isEmpty())
m_sections.append(Section(currentSection));
}

void Version::parse()
/// qDebug print support for the Version class
QDebug operator<<(QDebug debug, const Version& v)
{
m_sections.clear();
QDebugStateSaver saver(debug);

// FIXME: this is bad. versions can contain a lot more separators...
QStringList parts = m_string.split('.');
debug.nospace() << "Version{ string: " << v.toString() << ", sections: [ ";

for (const auto& part : parts)
{
m_sections.append(Section(part));
bool first = true;
for (auto s : v.m_sections) {
if (!first) debug.nospace() << ", ";
debug.nospace() << s.m_fullString;
first = false;
}

debug.nospace() << " ]" << " }";

return debug;
}
135 changes: 78 additions & 57 deletions launcher/Version.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2023 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -35,17 +36,17 @@

#pragma once

#include <QDebug>
#include <QList>
#include <QString>
#include <QStringView>
#include <QList>

class QUrl;

class Version
{
public:
Version(const QString &str);
Version() {}
class Version {
public:
Version(QString str);
Version() = default;

bool operator<(const Version &other) const;
bool operator<=(const Version &other) const;
Expand All @@ -54,96 +55,116 @@ class Version
bool operator==(const Version &other) const;
bool operator!=(const Version &other) const;

QString toString() const
{
return m_string;
}
QString toString() const { return m_string; }

private:
QString m_string;
struct Section
{
explicit Section(const QString &fullString)
friend QDebug operator<<(QDebug debug, const Version& v);

private:
struct Section {
explicit Section(QString fullString) : m_fullString(std::move(fullString))
{
m_fullString = fullString;
int cutoff = m_fullString.size();
for(int i = 0; i < m_fullString.size(); i++)
{
if(!m_fullString[i].isDigit())
{
for (int i = 0; i < m_fullString.size(); i++) {
if (!m_fullString[i].isDigit()) {
cutoff = i;
break;
}
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto numPart = QStringView{m_fullString}.left(cutoff);
#else
auto numPart = m_fullString.leftRef(cutoff);
#endif
if(numPart.size())
{
numValid = true;

if (!numPart.isEmpty()) {
m_isNull = false;
m_numPart = numPart.toInt();
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto stringPart = QStringView{m_fullString}.mid(cutoff);
#else
auto stringPart = m_fullString.midRef(cutoff);
#endif
if(stringPart.size())
{

if (!stringPart.isEmpty()) {
m_isNull = false;
m_stringPart = stringPart.toString();
}
}
explicit Section() {}
bool numValid = false;

explicit Section() = default;

bool m_isNull = true;

int m_numPart = 0;
QString m_stringPart;

QString m_fullString;

inline bool operator!=(const Section &other) const
[[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
[[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }

inline bool operator==(const Section& other) const
{
if(numValid && other.numValid)
{
return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart;
}
else
{
return m_fullString != other.m_fullString;
if (m_isNull && !other.m_isNull)
return false;
Edgars-Cirulis marked this conversation as resolved.
Show resolved Hide resolved
if (!m_isNull && other.m_isNull)
return false;

if (!m_isNull && !other.m_isNull) {
return (m_numPart == other.m_numPart) && (m_stringPart == other.m_stringPart);
}

return true;
}
inline bool operator<(const Section &other) const
{
if(numValid && other.numValid)
{
if(m_numPart < other.m_numPart)

inline bool operator<(const Section& other) const
{
static auto unequal_is_less = [](Section const& non_null) -> bool {
if (non_null.m_stringPart.isEmpty())
return non_null.m_numPart == 0;
return (non_null.m_stringPart != QLatin1Char('.')) && non_null.isPreRelease();
};

if (!m_isNull && other.m_isNull)
return unequal_is_less(*this);
if (m_isNull && !other.m_isNull)
return !unequal_is_less(other);

if (!m_isNull && !other.m_isNull) {
if (m_numPart < other.m_numPart)
return true;
if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart)
if (m_numPart == other.m_numPart && m_stringPart < other.m_stringPart)
return true;

if (!m_stringPart.isEmpty() && other.m_stringPart.isEmpty())
return false;
if (m_stringPart.isEmpty() && !other.m_stringPart.isEmpty())
return true;

return false;
}
else
{
return m_fullString < other.m_fullString;
}

return m_fullString < other.m_fullString;
}

inline bool operator!=(const Section& other) const
{
return !(*this == other);
}
inline bool operator>(const Section &other) const
{
if(numValid && other.numValid)
{
if(m_numPart > other.m_numPart)
return true;
if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart)
return true;
return false;
}
else
{
return m_fullString > other.m_fullString;
}
return !(*this < other || *this == other);
}
};

private:
QString m_string;
QList<Section> m_sections;

void parse();
};


3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ ecm_add_test(Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR

ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Index)

ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Version)