mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 02:55:50 +00:00
520 lines
14 KiB
C++
520 lines
14 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of Beast: https://github.com/vinniefalco/Beast
|
|
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
|
|
|
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 <int>::max (), input))
|
|
return false;
|
|
|
|
if (! chop (".", input))
|
|
return false;
|
|
|
|
// Must have minor version number
|
|
if (! chopUInt (&minorVersion, std::numeric_limits <int>::max (), input))
|
|
return false;
|
|
|
|
if (! chop (".", input))
|
|
return false;
|
|
|
|
// Must have patch version number
|
|
if (! chopUInt (&patchVersion, std::numeric_limits <int>::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 negcheck (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
|
|
negcheck ("-1.2.3");
|
|
negcheck ("1.-2.3");
|
|
negcheck ("1.2.-3");
|
|
|
|
// missing parts
|
|
negcheck ("");
|
|
negcheck ("1");
|
|
negcheck ("1.");
|
|
negcheck ("1.2");
|
|
negcheck ("1.2.");
|
|
negcheck (".2.3");
|
|
|
|
// whitespace
|
|
negcheck (" 1.2.3");
|
|
negcheck ("1 .2.3");
|
|
negcheck ("1.2 .3");
|
|
negcheck ("1.2.3 ");
|
|
|
|
// leading zeroes
|
|
negcheck ("01.2.3");
|
|
negcheck ("1.02.3");
|
|
negcheck ("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;
|