From 05361b937a7d8631dca09f2d8ac843e3d37ee292 Mon Sep 17 00:00:00 2001 From: Linas Valiukas Date: Thu, 31 May 2012 17:33:15 +0300 Subject: [PATCH] version comparator from Sparkle --- fvversioncomparator.cpp | 164 ++++++++++++++++++++++++++++++ fvversioncomparator.h | 36 +++++++ tests/fvversioncomparatortest.cpp | 44 ++++++++ tests/fvversioncomparatortest.h | 21 ++++ 4 files changed, 265 insertions(+) create mode 100644 fvversioncomparator.cpp create mode 100644 fvversioncomparator.h create mode 100644 tests/fvversioncomparatortest.cpp create mode 100644 tests/fvversioncomparatortest.h diff --git a/fvversioncomparator.cpp b/fvversioncomparator.cpp new file mode 100644 index 0000000..dc4da8a --- /dev/null +++ b/fvversioncomparator.cpp @@ -0,0 +1,164 @@ +#include "fvversioncomparator.h" +#include +#include +#include + +// +// Clone of Sparkle's SUStandardVersionComparator.m, so here's original author's +// copyright too: +// +// Copyright 2007 Andy Matuschak. All rights reserved. +// +// Everything's the same except for TypeOfCharacter() +// (because who knows how Foundation does isdigit() and such.) +// + + +FvVersionComparator::FvVersionComparator() +{ + // noop +} + +FvVersionComparator::CharacterType FvVersionComparator::TypeOfCharacter(std::string character) +{ + if (character == ".") { + return kSeparatorType; + } else if (isdigit(character[0])) { + return kNumberType; + } else if (isspace(character[0])) { + return kSeparatorType; + } else if (ispunct(character[0])) { + return kSeparatorType; + } else { + return kStringType; + } + +} + +std::vector FvVersionComparator::SplitVersionString(std::string version) +{ + std::string character; + std::string s; + unsigned long i = 0, n = 0; + CharacterType oldType, newType; + std::vector parts; + + if (version.length() == 0) { + // Nothing to do here + return parts; + } + + s = version.substr(0, 1); + oldType = TypeOfCharacter(s); + n = version.length() - 1; + for (i = 1; i <= n; ++i) { + character = version.substr(i, 1)[0]; + newType = TypeOfCharacter(character); + if (oldType != newType || oldType == kSeparatorType) { + // We've reached a new segment + std::string aPart = s; + parts.push_back(aPart); + s = character; + } else { + // Add character to string and continue + s.append(character); + } + oldType = newType; + } + + // Add the last part onto the array + parts.push_back(s); + return parts; +} + + +FvVersionComparator::ComparatorResult FvVersionComparator::CompareVersions(std::string versionA, + std::string versionB) +{ + std::vector partsA = SplitVersionString(versionA); + std::vector partsB = SplitVersionString(versionB); + + std::string partA = std::string(""), partB = std::string(""); + unsigned long i = 0, n = 0; + int intA, intB; + CharacterType typeA, typeB; + + n = std::min(partsA.size(), partsB.size()); + for (i = 0; i < n; ++i) { + partA = partsA.at(i); + partB = partsB.at(i); + + typeA = TypeOfCharacter(partA); + typeB = TypeOfCharacter(partB); + + // Compare types + if (typeA == typeB) { + // Same type; we can compare + if (typeA == kNumberType) { + intA = atoi(partA.c_str()); + intB = atoi(partB.c_str()); + + if (intA > intB) { + return kDescending; + } else if (intA < intB) { + return kAscending; + } + } else if (typeA == kStringType) { + short result = partA.compare(partB); + switch (result) { + case -1: return kAscending; break; + case 1: return kDescending; break; + case 0: /* do nothing */ break; + }; + } + } else { + // Not the same type? Now we have to do some validity checking + if (typeA != kStringType && typeB == kStringType) { + // typeA wins + return kDescending; + } else if (typeA == kStringType && typeB != kStringType) { + // typeB wins + return kAscending; + } else { + // One is a number and the other is a period. The period is invalid + if (typeA == kNumberType) { + return kDescending; + } else { + return kAscending; + } + } + } + } + // The versions are equal up to the point where they both still have parts + // Lets check to see if one is larger than the other + if (partsA.size() != partsB.size()) { + // Yep. Lets get the next part of the larger + // n holds the index of the part we want. + std::string missingPart = std::string(""); + CharacterType missingType; + ComparatorResult shorterResult, largerResult; + + if (partsA.size() > partsB.size()) { + missingPart = partsA.at(n); + shorterResult = kAscending; + largerResult = kDescending; + } else { + missingPart = partsB.at(n); + shorterResult = kDescending; + largerResult = kAscending; + } + + missingType = TypeOfCharacter(missingPart); + // Check the type + if (missingType == kStringType) { + // It's a string. Shorter version wins + return shorterResult; + } else { + // It's a number/period. Larger version wins + return largerResult; + } + } + + // The 2 strings are identical + return kSame; +} diff --git a/fvversioncomparator.h b/fvversioncomparator.h new file mode 100644 index 0000000..f083fdf --- /dev/null +++ b/fvversioncomparator.h @@ -0,0 +1,36 @@ +#ifndef FVVERSIONCOMPARATOR_H +#define FVVERSIONCOMPARATOR_H + +#include +#include + + +class FvVersionComparator +{ +public: + + typedef enum { + kSame = 0, + kDescending = 1, + kAscending = -1 + } ComparatorResult; + + static ComparatorResult CompareVersions(std::string versionA, + std::string versionB); + +private: + + FvVersionComparator(); + + typedef enum { + kNumberType, + kStringType, + kSeparatorType + } CharacterType; + + static CharacterType TypeOfCharacter(std::string character); + static std::vector SplitVersionString(std::string version); + +}; + +#endif // FVVERSIONCOMPARATOR_H diff --git a/tests/fvversioncomparatortest.cpp b/tests/fvversioncomparatortest.cpp new file mode 100644 index 0000000..d6dc833 --- /dev/null +++ b/tests/fvversioncomparatortest.cpp @@ -0,0 +1,44 @@ +#include "fvversioncomparatortest.h" +#include "fvversioncomparator.h" + + +void FvVersionComparatorTest::runAll() +{ + testNumbers(); + testPrereleases(); + testVersionsWithBuildNumbers(); +} + +void FvVersionComparatorTest::testNumbers() +{ + QVERIFY(FvVersionComparator::CompareVersions("1.0", "1.1") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0", "1.0") == FvVersionComparator::kSame); + QVERIFY(FvVersionComparator::CompareVersions("2.0", "1.1") == FvVersionComparator::kDescending); + QVERIFY(FvVersionComparator::CompareVersions("0.1", "0.0.1") == FvVersionComparator::kDescending); + QVERIFY(FvVersionComparator::CompareVersions("0.1", "0.1.2") == FvVersionComparator::kAscending); +} + +void FvVersionComparatorTest::testPrereleases() +{ + QVERIFY(FvVersionComparator::CompareVersions("1.0a1", "1.0b1") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b1", "1.0") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("0.9", "1.0a1") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b", "1.0b2") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b10", "1.0b11") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b9", "1.0b10") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0rc", "1.0") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b", "1.0") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0pre1", "1.0") == FvVersionComparator::kAscending); + + QVERIFY(FvVersionComparator::CompareVersions("1.0", "1.0pre1") == FvVersionComparator::kDescending); +} + +void FvVersionComparatorTest::testVersionsWithBuildNumbers() +{ + QVERIFY(FvVersionComparator::CompareVersions("1.0 (1234)", "1.0 (1235)") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b1 (1234)", "1.0 (1234)") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b5 (1234)", "1.0b5 (1235)") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0b5 (1234)", "1.0.1b5 (1234)") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("1.0.1b5 (1234)", "1.0.1b6 (1234)") == FvVersionComparator::kAscending); + QVERIFY(FvVersionComparator::CompareVersions("3.3 (5847)", "3.3.1b1 (5902)") == FvVersionComparator::kAscending); +} diff --git a/tests/fvversioncomparatortest.h b/tests/fvversioncomparatortest.h new file mode 100644 index 0000000..515a58b --- /dev/null +++ b/tests/fvversioncomparatortest.h @@ -0,0 +1,21 @@ +#ifndef FVVERSIONCOMPARATORTEST_H +#define FVVERSIONCOMPARATORTEST_H + +#include + + +class FvVersionComparatorTest : public QObject +{ + Q_OBJECT + +public slots: + + void runAll(); + + void testNumbers(); + void testPrereleases(); + void testVersionsWithBuildNumbers(); + +}; + +#endif // FVVERSIONCOMPARATORTEST_H