diff --git a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj index 71e0701f1c..3556e9712a 100644 --- a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj +++ b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj @@ -121,6 +121,7 @@ + @@ -425,6 +426,12 @@ true true + + true + true + true + true + true true diff --git a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters index 873dc89ab2..7f33b0571d 100644 --- a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters +++ b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters @@ -734,6 +734,9 @@ beast_core\diagnostic + + beast_core\diagnostic + @@ -1147,6 +1150,9 @@ beast_core\diagnostic + + beast_core\diagnostic + diff --git a/Subtrees/beast/modules/beast_core/beast_core.cpp b/Subtrees/beast/modules/beast_core/beast_core.cpp index e3ae5c0f5b..b90b480d03 100644 --- a/Subtrees/beast/modules/beast_core/beast_core.cpp +++ b/Subtrees/beast/modules/beast_core/beast_core.cpp @@ -150,6 +150,7 @@ namespace beast #include "diagnostic/beast_FPUFlags.cpp" #include "diagnostic/beast_LeakChecked.cpp" #include "diagnostic/beast_ProtectedCall.cpp" +#include "diagnostic/beast_SemanticVersion.cpp" #include "diagnostic/beast_UnitTest.cpp" #include "diagnostic/beast_UnitTestUtilities.cpp" diff --git a/Subtrees/beast/modules/beast_core/beast_core.h b/Subtrees/beast/modules/beast_core/beast_core.h index fc0a336125..aff325acba 100644 --- a/Subtrees/beast/modules/beast_core/beast_core.h +++ b/Subtrees/beast/modules/beast_core/beast_core.h @@ -307,6 +307,7 @@ namespace beast #include "text/beast_LocalisedStrings.h" #include "text/beast_NewLine.h" #include "text/beast_StringArray.h" +#include "diagnostic/beast_SemanticVersion.h" #include "text/beast_StringPairArray.h" #include "text/beast_StringPool.h" #include "text/beast_TextDiff.h" diff --git a/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.cpp b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.cpp new file mode 100644 index 0000000000..2cf324a418 --- /dev/null +++ b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.cpp @@ -0,0 +1,519 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +SemanticVersion::SemanticVersion () + : majorVersion (0) + , minorVersion (0) + , patchVersion (0) +{ +} + +bool SemanticVersion::parse (String input) +{ + // May not have leading or trailing whitespace + if (input.trim () != input) + return false; + + // Must have major version number + if (! chopUInt (&majorVersion, std::numeric_limits ::max (), input)) + return false; + + if (! chop (".", input)) + return false; + + // Must have minor version number + if (! chopUInt (&minorVersion, std::numeric_limits ::max (), input)) + return false; + + if (! chop (".", input)) + return false; + + // Must have patch version number + if (! chopUInt (&patchVersion, std::numeric_limits ::max (), input)) + return false; + + // May have pre-release identifier list + if (chop ("-", input)) + { + chopIdentifiers (&preReleaseIdentifiers, false, input); + + // Must not be empty + if (preReleaseIdentifiers.size () <= 0) + return false; + } + + // May have metadata identifier list + if (chop ("+", input)) + { + chopIdentifiers (&metaData, true, input); + + // Must not be empty + if (metaData.size () <= 0) + return false; + } + + // May not have anything left + if (input.length () > 0) + return false; + + return true; +} + +String SemanticVersion::print () const +{ + String s; + + s << String (majorVersion) << "." << String (minorVersion) << "." << String (patchVersion); + + if (preReleaseIdentifiers.size () > 0) + s << "-" << printIdentifiers (preReleaseIdentifiers); + + if (metaData.size () > 0) + s << "+" << printIdentifiers (metaData); + + return s; +} + +int SemanticVersion::compare (SemanticVersion const& rhs) const noexcept +{ + if (majorVersion > rhs.majorVersion) + return 1; + else if (majorVersion < rhs.majorVersion) + return -1; + + if (minorVersion > rhs.minorVersion) + return 1; + else if (minorVersion < rhs.minorVersion) + return -1; + + if (patchVersion > rhs.patchVersion) + return 1; + else if (patchVersion < rhs.patchVersion) + return -1; + + if (isPreRelease () || rhs.isPreRelease ()) + { + // Pre-releases have a lower precedence + if (isRelease () && rhs.isPreRelease ()) + return 1; + else if (isPreRelease () && rhs.isRelease ()) + return -1; + + // Compare pre-release identifiers + for (int i = 0; i < bmax (preReleaseIdentifiers.size (), rhs.preReleaseIdentifiers.size ()); ++i) + { + // A larger list of identifiers has a higher precedence + if (i >= rhs.preReleaseIdentifiers.size ()) + return 1; + else if (i >= preReleaseIdentifiers.size ()) + return -1; + + String const& left (preReleaseIdentifiers [i]); + String const& right (rhs.preReleaseIdentifiers [i]); + + // Numeric identifiers have lower precedence + if (! isNumeric (left) && isNumeric (right)) + return 1; + else if (isNumeric (left) && ! isNumeric (right)) + return -1; + + if (isNumeric (left)) + { + bassert (isNumeric (right)); + + int const iLeft (left.getIntValue ()); + int const iRight (right.getIntValue ()); + + if (iLeft > iRight) + return 1; + else if (iLeft < iRight) + return -1; + } + else + { + bassert (! isNumeric (right)); + + int result = left.compareLexicographically (right); + + if (result != 0) + return result; + } + } + } + + // metadata is ignored + + return 0; +} + +bool SemanticVersion::isNumeric (String const& s) +{ + return String (s.getIntValue ()) == s; +} + +bool SemanticVersion::chop (String const& what, String& input) +{ + if (input.startsWith (what)) + { + input = input.substring (what.length ()); + + return true; + } + + return false; +} + +String SemanticVersion::printIdentifiers (StringArray const& list) +{ + String s; + + if (list.size () > 0) + { + s << list [0]; + + for (int i = 1; i < list.size (); ++i) + s << "." << list [i]; + } + + return s; +} + +bool SemanticVersion::chopUInt (int* value, int limit, String& input) +{ + // Must not be empty + if (input.length () <= 0) + return false; + + int firstNonDigit = 0; + for (; firstNonDigit < input.length (); ++firstNonDigit) + { + if (! CharacterFunctions::isDigit (input [firstNonDigit])) + break; + } + + String const s = input.substring (0, firstNonDigit); + + // Must not be empty + if (s.length () <= 0) + return false; + + int const n = s.getIntValue (); + + // Must not have leading zeroes + if (String (n) != s) + return false; + + // Must not be out of range + if (n < 0 || n > limit) + return false; + + input = input.substring (s.length ()); + + *value = n; + + return true; +} + +bool SemanticVersion::chopIdentifier (String* value, bool allowLeadingZeroes, String& input) +{ + // Must not be empty + if (input.length () <= 0) + return false; + + // Must not have a leading 0 + if (! allowLeadingZeroes && input [0] == '0') + return false; + + // Find the first character that cannot be part of an identifier + int i; + for (i = 0; i < input.length (); ++i) + { + static char const* validSet = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"; + + if (! String (validSet).contains (String::charToString (input [i]))) + break; + } + + // Must not be empty + if (i <= 0) + return false; + + *value = input.substring (0, i); + + input = input.substring (i); + + return true; +} + +bool SemanticVersion::chopIdentifiers (StringArray* value, bool allowLeadingZeroes, String& input) +{ + if (input.length () <= 0) + return false; + + for (;;) + { + String s; + + if (! chopIdentifier (&s, allowLeadingZeroes, input)) + return false; + + value->add (s); + + if (! chop (".", input)) + break; + } + + return true; +} + +//------------------------------------------------------------------------------ + +class SemanticVersionTests : public UnitTest +{ +public: + SemanticVersionTests () : UnitTest ("SemanticVersion", "beast") + { + } + + void checkPass (String const& input, bool shouldPass = true) + { + SemanticVersion v; + + if (shouldPass ) + { + expect (v.parse (input)); + expect (v.print () == input); + } + else + { + expect (! v.parse (input)); + } + } + + void checkFail (String const& input) + { + checkPass (input, false); + } + + // check input and input with appended metadata + void checkMeta (String const& input, bool shouldPass) + { + checkPass (input, shouldPass); + + checkPass (input + "+a", shouldPass); + checkPass (input + "+1", shouldPass); + checkPass (input + "+a.b", shouldPass); + checkPass (input + "+ab.cd", shouldPass); + + checkFail (input + "!"); + checkFail (input + "+"); + checkFail (input + "++"); + checkFail (input + "+!"); + checkFail (input + "+."); + checkFail (input + "+a.!"); + } + + void checkMetaFail (String const& input) + { + checkMeta (input, false); + } + + // check input, input with appended release data, + // input with appended metadata, and input with both + // appended release data and appended metadata + // + void checkRelease (String const& input, bool shouldPass = true) + { + checkMeta (input, shouldPass); + + checkMeta (input + "-1", shouldPass); + checkMeta (input + "-a", shouldPass); + checkMeta (input + "-a1", shouldPass); + checkMeta (input + "-a1.b1", shouldPass); + checkMeta (input + "-ab.cd", shouldPass); + checkMeta (input + "--", shouldPass); + + checkMetaFail (input + "+"); + checkMetaFail (input + "!"); + checkMetaFail (input + "-"); + checkMetaFail (input + "-!"); + checkMetaFail (input + "-."); + checkMetaFail (input + "-a.!"); + checkMetaFail (input + "-0.a"); + } + + // Checks the major.minor.version string alone and with all + // possible combinations of release identifiers and metadata. + // + void check (String const& input, bool shouldPass = true) + { + checkRelease (input, shouldPass); + } + + void ncheck (String const& input) + { + check (input, false); + } + + void testParse () + { + beginTestCase ("parse"); + + check ("0.0.0"); + check ("1.2.3"); + check ("2147483647.2147483647.2147483647"); // max int + + // negative values + ncheck ("-1.2.3"); + ncheck ("1.-2.3"); + ncheck ("1.2.-3"); + + // missing parts + ncheck (""); + ncheck ("1"); + ncheck ("1."); + ncheck ("1.2"); + ncheck ("1.2."); + ncheck (".2.3"); + + // whitespace + ncheck (" 1.2.3"); + ncheck ("1 .2.3"); + ncheck ("1.2 .3"); + ncheck ("1.2.3 "); + + // leading zeroes + ncheck ("01.2.3"); + ncheck ("1.02.3"); + ncheck ("1.2.03"); + } + + static StringArray ids () + { + return StringArray (); + } + + static StringArray ids (String const& s1) + { + StringArray v; + v.add (s1); + return v; + } + + static StringArray ids (String const& s1, String const& s2) + { + StringArray v; + v.add (s1); + v.add (s2); + return v; + } + + static StringArray ids (String const& s1, String const& s2, String const& s3) + { + StringArray v; + v.add (s1); + v.add (s2); + v.add (s3); + return v; + } + + // Checks the decomposition of the input into appropriate values + void checkValues (String const& input, + int majorVersion, + int minorVersion, + int patchVersion, + StringArray const& preReleaseIdentifiers = StringArray (), + StringArray const& metaData = StringArray ()) + { + SemanticVersion v; + + expect (v.parse (input)); + + expect (v.majorVersion == majorVersion); + expect (v.minorVersion == minorVersion); + expect (v.patchVersion == patchVersion); + + expect (v.preReleaseIdentifiers == preReleaseIdentifiers); + expect (v.metaData == metaData); + } + + void testValues () + { + checkValues ("0.1.2", 0, 1, 2); + checkValues ("1.2.3", 1, 2, 3); + checkValues ("1.2.3-rc1", 1, 2, 3, ids ("rc1")); + checkValues ("1.2.3-rc1.debug", 1, 2, 3, ids ("rc1", "debug")); + checkValues ("1.2.3-rc1.debug.asm", 1, 2, 3, ids ("rc1", "debug", "asm")); + checkValues ("1.2.3+full", 1, 2, 3, ids (), ids ("full")); + checkValues ("1.2.3+full.prod", 1, 2, 3, ids (), ids ("full", "prod")); + checkValues ("1.2.3+full.prod.x86", 1, 2, 3, ids (), ids ("full", "prod", "x86")); + checkValues ("1.2.3-rc1.debug.asm+full.prod.x86", 1, 2, 3, + ids ("rc1", "debug", "asm"), ids ("full", "prod", "x86")); + } + + // makes sure the left version is less than the right + void checkLessInternal (String const& lhs, String const& rhs) + { + SemanticVersion left; + SemanticVersion right; + + expect (left.parse (lhs)); + expect (right.parse (rhs)); + + expect (left.compare (left) == 0); + expect (right.compare (right) == 0); + expect (left.compare (right) < 0); + expect (right.compare (left) > 0); + + expect (left < right); + expect (right > left); + expect (left == left); + expect (right == right); + } + + void checkLess (String const& lhs, String const& rhs) + { + checkLessInternal (lhs, rhs); + checkLessInternal (lhs + "+meta", rhs); + checkLessInternal (lhs, rhs + "+meta"); + checkLessInternal (lhs + "+meta", rhs + "+meta"); + } + + void testCompare () + { + checkLess ("1.0.0-alpha", "1.0.0-alpha.1"); + checkLess ("1.0.0-alpha.1", "1.0.0-alpha.beta"); + checkLess ("1.0.0-alpha.beta", "1.0.0-beta"); + checkLess ("1.0.0-beta", "1.0.0-beta.2"); + checkLess ("1.0.0-beta.2", "1.0.0-beta.11"); + checkLess ("1.0.0-beta.11", "1.0.0-rc.1"); + checkLess ("1.0.0-rc.1", "1.0.0"); + checkLess ("0.9.9", "1.0.0"); + } + + void runTest () + { + testParse (); + testValues (); + testCompare (); + } +}; + +static SemanticVersionTests semanticVersionTests; diff --git a/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.h b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.h new file mode 100644 index 0000000000..e24d46f666 --- /dev/null +++ b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.h @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SEMANTICVERSION_H_INCLUDED +#define BEAST_SEMANTICVERSION_H_INCLUDED + +/** A Semantic Version number. + + Identifies the build of a particular version of software using + the Semantic Versioning Specification described here: + + http://semver.org/ +*/ +class BEAST_API SemanticVersion +{ +public: + int majorVersion; + int minorVersion; + int patchVersion; + StringArray preReleaseIdentifiers; + StringArray metaData; + + SemanticVersion (); + + /** Parse a semantic version string. + The parsing is as strict as possible. + @return `true` if the string was parsed. + */ + bool parse (String input); + + /** Produce a string from semantic version components. */ + String print () const; + + inline bool isRelease () const noexcept { return preReleaseIdentifiers.size () <= 0; } + inline bool isPreRelease () const noexcept { return ! isRelease (); } + + /** Compare this against another version. + The comparison follows the rules as per the specification. + */ + int compare (SemanticVersion const& rhs) const noexcept; + + inline bool operator== (SemanticVersion const& other) const noexcept { return compare (other) == 0; } + inline bool operator!= (SemanticVersion const& other) const noexcept { return compare (other) != 0; } + inline bool operator>= (SemanticVersion const& other) const noexcept { return compare (other) >= 0; } + inline bool operator<= (SemanticVersion const& other) const noexcept { return compare (other) <= 0; } + inline bool operator> (SemanticVersion const& other) const noexcept { return compare (other) > 0; } + inline bool operator< (SemanticVersion const& other) const noexcept { return compare (other) < 0; } + +private: + static bool isNumeric (String const& s); + static String printIdentifiers (StringArray const& list); + static bool chop (String const& what, String& input); + static bool chopUInt (int* value, int limit, String& input); + static bool chopIdentifier (String* value, bool allowLeadingZeroes, String& input); + static bool chopIdentifiers (StringArray* value, bool preRelease, String& input); +}; + +#endif