dexed/src/u_semver.pas

214 lines
5.1 KiB
Plaintext

unit u_semver;
{$I u_defines.inc}
interface
uses
SysUtils, u_stringrange;
type
// Record used to split the text of a semantic version into components.
// Special helpers exit for the additional labels "alpha", "beta" or
// "rc" as used.
TSemVer = record
strict private
fAdditional: string;
fMajor, fMinor, fPatch: word;
fValid: boolean;
public
// Initializes with the semVer text.
// When throw is set to true an Exception is raised if the format is not compliant.
procedure init(const text: string; throw: boolean);
// Indicates wether the version is not a final release.
function isPreRelease: boolean;
// Indicates wether the version is an alpha ("v1.0.0-alpha", "v1.0.0-alpha.1").
function isAlpha: boolean;
// Indicates wether the version is a beta ("v1.0.0-beta", "v1.0.0-beta.1").
function isBeta: boolean;
// Indicates wether the version is a release candidate ("v1.0.0-rc", "v1.0.0-rc.1").
function isRc: boolean;
// Rebuild and Returns the SemVer string.
function asString: string;
// The major version.
property major: word read fMajor write fMajor;
// The minor version.
property minor: word read fMinor write fMinor;
// The patch.
property patch: word read fPatch write fPatch;
// The additional labels.
property additional: string read fAdditional write fAdditional;
// Indicates if the init has succeeded.
property valid: boolean read fValid;
end;
PSemVer = ^TSemVer;
operator > (constref lhs, rhs: TSemVer): boolean;
operator < (constref lhs, rhs: TSemVer): boolean;
operator = (constref lhs, rhs: TSemVer): boolean;
implementation
{$IFDEF DEBUG}
var v1, v2: TSemVer;
{$ENDIF}
procedure TSemVer.init(const text: string; throw: boolean);
procedure resetFields();
begin
fMajor := 0;
fMinor := 0;
fPatch := 0;
fAdditional := '';
end;
procedure error(const message: string);
begin
resetFields();
fAdditional := '';
fValid := false;
raise Exception.Create(message);
end;
var
r: TStringRange = (ptr:nil; pos:0; len: 0);
begin
resetFields();
r.init(text);
if throw and (r.front <> 'v') then
error('Invalid semVer format, the text must start with "v"');
r.popFront;
fMajor := StrToInt64Def(r.takeUntil('.').yield, 0);
if throw and r.empty then
error('Invalid semVer format, minor and patch miss');
fMinor := StrToInt64Def(r.popFront^.takeUntil('.').yield, 0);
if not r.empty then
fPatch := StrToInt64Def(r.popFront^.takeWhile(['0'..'9']).yield, 0);
if not r.empty then
fAdditional := r.popFront^.takeUntil(#0).yield;
fValid := true;
end;
function TSemVer.isPreRelease: boolean;
begin
result := isAlpha() or isBeta() or isRc();
end;
function TSemVer.isAlpha: boolean;
begin
result := (additional = 'alpha') or (additional.StartsWith('alpha.'));
end;
function TSemVer.isBeta: boolean;
begin
result := (additional = 'beta') or (additional.StartsWith('beta.'));
end;
function TSemVer.isRc: boolean;
begin
result := (additional = 'rc') or (additional.StartsWith('rc.'));
end;
function TSemVer.asString: string;
begin
result := format('%d.%d.%d', [fMajor, fMinor, fPatch]);
if fAdditional <> '' then
result += '-' + fAdditional;
end;
operator > (constref lhs, rhs: TSemVer): boolean;
begin
result := (lhs.major > rhs.major) or
((lhs.major = rhs.major) and (lhs.minor > rhs.minor)) or
((lhs.major = rhs.major) and (lhs.minor = rhs.minor)
and (lhs.patch > rhs.patch)) or
((lhs.major = rhs.major) and (lhs.minor = rhs.minor)
and (lhs.patch = rhs.patch) and (lhs.additional > rhs.additional));
end;
operator < (constref lhs, rhs: TSemVer): boolean;
begin
result := rhs > lhs;
end;
operator = (constref lhs, rhs: TSemVer): boolean;
begin
result := (lhs.major = rhs.major) and (lhs.minor = rhs.minor)
and (lhs.patch = rhs.patch) and (lhs.additional = rhs.additional);
end;
{$IFDEF DEBUG}
begin
v1.init('v1.0.0');
v2.init('v1.0.0');
assert(v1 = v2);
v1.init('v2.0.0');
v2.init('v1.0.0');
assert(v1 > v2);
assert(v2 < v1);
v1.init('v1.1.0');
v2.init('v1.0.0');
assert(v1 > v2);
assert(v2 < v1);
v1.init('v1.1.1');
v2.init('v1.1.0');
assert(v1 > v2);
assert(v2 < v1);
v1.init('v1.1.1');
v2.init('v1.0.1');
assert(v1 > v2);
assert(v2 < v1);
v1.init('v1.1.1-alpha.2');
v2.init('v1.1.1-alpha.1');
assert(v1 > v2);
assert(v1.isAlpha);
assert(v2.isAlpha);
assert(v2.asString = '1.1.1-alpha.1');
v1.init('v1.1.1-beta.1');
v2.init('v1.1.1-alpha.8');
assert(v1 > v2);
assert(v1.isBeta);
assert(v2.isAlpha);
assert(v1.isPreRelease);
v1.init('v1.2.3');
v2.init('v1.22.33');
assert(v1.major = 1);
assert(v1.minor = 2);
assert(v1.patch = 3);
assert(v2.major = 1);
assert(v2.minor = 22);
assert(v2.patch = 33);
assert(v2.asString = '1.22.33');
v1.init('v0.0.2060');
assert(v1.major = 0);
assert(v1.minor = 0);
assert(v1.patch = 2060);
v2 := v1;
assert(v2.major = 0);
assert(v2.minor = 0);
assert(v2.patch = 2060);
v1.init('v0.6.0');
assert(v1.major = 0);
assert(v1.minor = 6);
assert(v1.patch = 0);
{$ENDIF}
end.