mirror of
https://github.com/DJDoubleD/refreezer.git
synced 2026-01-16 17:02:53 -03:00
291 lines
11 KiB
Dart
291 lines
11 KiB
Dart
/* Copyright (c) 2021, Matthew Barbour.
|
|
All rights reserved.
|
|
https://github.com/dartninja/version
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the <organization> nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
|
|
|
|
/// Provides version objects to enforce conformance to the Semantic Versioning 2.0 spec. The spec can be read at http://semver.org/
|
|
library;
|
|
|
|
/// Provides immutable storage and comparison of semantic version numbers.
|
|
class Version implements Comparable<Version> {
|
|
static final RegExp _versionRegex = RegExp(r'^([\d.]+)(-([0-9A-Za-z\-.]+))?(\+([0-9A-Za-z\-.]+))?$');
|
|
static final RegExp _buildRegex = RegExp(r'^[0-9A-Za-z\-.]+$');
|
|
static final RegExp _preReleaseRegex = RegExp(r'^[0-9A-Za-z\-]+$');
|
|
|
|
/// The major number of the version, incremented when making breaking changes.
|
|
final int major;
|
|
|
|
/// The minor number of the version, incremented when adding new functionality in a backwards-compatible manner.
|
|
final int minor;
|
|
|
|
/// The patch number of the version, incremented when making backwards-compatible bug fixes.
|
|
final int patch;
|
|
|
|
/// Build information relevant to the version. Does not contribute to sorting.
|
|
final String build;
|
|
|
|
final List<String> _preRelease;
|
|
|
|
/// Indicates that the version is a pre-release. Returns true if preRelease has any segments, otherwise false
|
|
bool get isPreRelease => _preRelease.isNotEmpty;
|
|
|
|
/// Creates a new instance of [Version].
|
|
///
|
|
/// [major], [minor], and [patch] are all required, all must be greater than 0 and not null, and at least one must be greater than 0.
|
|
/// [preRelease] is optional, but if specified must be a [List] of [String] and must not be null. Each element in the list represents one of the period-separated segments of the pre-release information, and may only contain [0-9A-Za-z-].
|
|
/// [build] is optional, but if specified must be a [String]. must contain only [0-9A-Za-z-.], and must not be null.
|
|
/// Throws a [FormatException] if the [String] content does not follow the character constraints defined above.
|
|
/// Throes an [ArgumentError] if any of the other conditions are violated.
|
|
Version(this.major, this.minor, this.patch, {List<String> preRelease = const <String>[], this.build = ''})
|
|
: _preRelease = preRelease {
|
|
for (int i = 0; i < _preRelease.length; i++) {
|
|
if (_preRelease[i].toString().trim().isEmpty) {
|
|
throw ArgumentError('preRelease segments must not be empty');
|
|
}
|
|
// Just in case
|
|
_preRelease[i] = _preRelease[i].toString();
|
|
if (!_preReleaseRegex.hasMatch(_preRelease[i])) {
|
|
throw const FormatException('preRelease segments must only contain [0-9A-Za-z-]');
|
|
}
|
|
}
|
|
if (build.isNotEmpty && !_buildRegex.hasMatch(build)) {
|
|
throw const FormatException('build must only contain [0-9A-Za-z-.]');
|
|
}
|
|
|
|
if (major < 0 || minor < 0 || patch < 0) {
|
|
throw ArgumentError('Version numbers must be greater than 0');
|
|
}
|
|
}
|
|
|
|
@override
|
|
int get hashCode => toString().hashCode;
|
|
|
|
/// Pre-release information segments.
|
|
List<String> get preRelease => List<String>.from(_preRelease);
|
|
|
|
/// Determines whether the left-hand [Version] represents a lower precedence than the right-hand [Version].
|
|
bool operator <(Object o) => o is Version && _compare(this, o) < 0;
|
|
|
|
/// Determines whether the left-hand [Version] represents an equal or lower precedence than the right-hand [Version].
|
|
bool operator <=(Object o) => o is Version && _compare(this, o) <= 0;
|
|
|
|
/// Determines whether the left-hand [Version] represents an equal precedence to the right-hand [Version].
|
|
@override
|
|
bool operator ==(Object o) => o is Version && _compare(this, o) == 0;
|
|
|
|
/// Determines whether the left-hand [Version] represents a greater precedence than the right-hand [Version].
|
|
bool operator >(Object o) => o is Version && _compare(this, o) > 0;
|
|
|
|
/// Determines whether the left-hand [Version] represents an equal or greater precedence than the right-hand [Version].
|
|
bool operator >=(Object o) => o is Version && _compare(this, o) >= 0;
|
|
|
|
@override
|
|
int compareTo(Version? other) {
|
|
if (other == null) {
|
|
throw ArgumentError.notNull('other');
|
|
}
|
|
|
|
return _compare(this, other);
|
|
}
|
|
|
|
/// Creates a new [Version] with the [major] version number incremented.
|
|
///
|
|
/// Also resets the [minor] and [patch] numbers to 0, and clears the [build] and [preRelease] information.
|
|
Version incrementMajor() => Version(major + 1, 0, 0);
|
|
|
|
/// Creates a new [Version] with the [minor] version number incremented.
|
|
///
|
|
/// Also resets the [patch] number to 0, and clears the [build] and [preRelease] information.
|
|
Version incrementMinor() => Version(major, minor + 1, 0);
|
|
|
|
/// Creates a new [Version] with the [patch] version number incremented.
|
|
///
|
|
/// Also clears the [build] and [preRelease] information.
|
|
Version incrementPatch() => Version(major, minor, patch + 1);
|
|
|
|
/// Creates a new [Version] with the right-most numeric [preRelease] segment incremented.
|
|
/// If no numeric segment is found, one will be added with the value "1".
|
|
///
|
|
/// If this [Version] is not a pre-release version, an Exception will be thrown.
|
|
Version incrementPreRelease() {
|
|
if (!isPreRelease) {
|
|
throw Exception('Cannot increment pre-release on a non-pre-release [Version]');
|
|
}
|
|
var newPreRelease = preRelease;
|
|
|
|
var found = false;
|
|
for (var i = newPreRelease.length - 1; i >= 0; i--) {
|
|
var segment = newPreRelease[i];
|
|
if (Version._isNumeric(segment)) {
|
|
var intVal = int.parse(segment);
|
|
intVal++;
|
|
newPreRelease[i] = intVal.toString();
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
newPreRelease.add('1');
|
|
}
|
|
|
|
return Version(major, minor, patch, preRelease: newPreRelease);
|
|
}
|
|
|
|
/// Returns a [String] representation of the [Version].
|
|
///
|
|
/// Uses the format "$major.$minor.$patch".
|
|
/// If [preRelease] has segments available they are appended as "-segmentOne.segmentTwo", with each segment separated by a period.
|
|
/// If [build] is specified, it is appended as "+build.info" where "build.info" is whatever value [build] is set to.
|
|
/// If all [preRelease] and [build] are specified, then both are appended, [preRelease] first and [build] second.
|
|
/// An example of such output would be "1.0.0-preRelease.segment+build.info".
|
|
@override
|
|
String toString() {
|
|
final StringBuffer output = StringBuffer('$major.$minor.$patch');
|
|
if (_preRelease.isNotEmpty) {
|
|
output.write("-${_preRelease.join('.')}");
|
|
}
|
|
if (build.trim().isNotEmpty) {
|
|
output.write('+${build.trim()}');
|
|
}
|
|
return output.toString();
|
|
}
|
|
|
|
/// Creates a [Version] instance from a string.
|
|
///
|
|
/// The string must conform to the specification at http://semver.org/
|
|
/// Throws [FormatException] if the string is empty or does not conform to the spec.
|
|
static Version parse(String versionString) {
|
|
if (versionString.trim().isEmpty) {
|
|
throw const FormatException('Cannot parse empty string into version');
|
|
}
|
|
if (!_versionRegex.hasMatch(versionString)) {
|
|
throw const FormatException('Not a properly formatted version string');
|
|
}
|
|
final Match m = _versionRegex.firstMatch(versionString)!;
|
|
final String version = m.group(1)!;
|
|
|
|
int? major, minor, patch;
|
|
final List<String> parts = version.split('.');
|
|
major = int.parse(parts[0]);
|
|
if (parts.length > 1) {
|
|
minor = int.parse(parts[1]);
|
|
if (parts.length > 2) {
|
|
patch = int.parse(parts[2]);
|
|
}
|
|
}
|
|
|
|
final String preReleaseString = m.group(3) ?? '';
|
|
List<String> preReleaseList = <String>[];
|
|
if (preReleaseString.trim().isNotEmpty) {
|
|
preReleaseList = preReleaseString.split('.');
|
|
}
|
|
final String build = m.group(5) ?? '';
|
|
|
|
return Version(major, minor ?? 0, patch ?? 0, build: build, preRelease: preReleaseList);
|
|
}
|
|
|
|
static int _compare(Version? a, Version? b) {
|
|
if (a == null) {
|
|
throw ArgumentError.notNull('a');
|
|
}
|
|
|
|
if (b == null) {
|
|
throw ArgumentError.notNull('b');
|
|
}
|
|
|
|
if (a.major > b.major) return 1;
|
|
if (a.major < b.major) return -1;
|
|
|
|
if (a.minor > b.minor) return 1;
|
|
if (a.minor < b.minor) return -1;
|
|
|
|
if (a.patch > b.patch) return 1;
|
|
if (a.patch < b.patch) return -1;
|
|
|
|
if (a.preRelease.isEmpty) {
|
|
if (b.preRelease.isEmpty) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
} else if (b.preRelease.isEmpty) {
|
|
return -1;
|
|
} else {
|
|
int preReleaseMax = a.preRelease.length;
|
|
if (b.preRelease.length > a.preRelease.length) {
|
|
preReleaseMax = b.preRelease.length;
|
|
}
|
|
|
|
for (int i = 0; i < preReleaseMax; i++) {
|
|
if (b.preRelease.length <= i) {
|
|
return 1;
|
|
} else if (a.preRelease.length <= i) {
|
|
return -1;
|
|
}
|
|
|
|
if (a.preRelease[i] == b.preRelease[i]) continue;
|
|
|
|
final bool aNumeric = _isNumeric(a.preRelease[i]);
|
|
final bool bNumeric = _isNumeric(b.preRelease[i]);
|
|
|
|
if (aNumeric && bNumeric) {
|
|
final double aNumber = double.parse(a.preRelease[i]);
|
|
final double bNumber = double.parse(b.preRelease[i]);
|
|
if (aNumber > bNumber) {
|
|
return 1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (bNumeric) {
|
|
return 1;
|
|
} else if (aNumeric) {
|
|
return -1;
|
|
} else {
|
|
return a.preRelease[i].compareTo(b.preRelease[i]);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Creates a [Version] instance from a string.
|
|
///
|
|
/// The string must conform to the specification at http://semver.org/
|
|
/// Returns null if the string is empty or does not conform to the spec.
|
|
static Version? tryParse(String source) {
|
|
try {
|
|
return Version.parse(source);
|
|
} on FormatException {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static bool _isNumeric(String? s) {
|
|
if (s == null) {
|
|
return false;
|
|
}
|
|
return double.tryParse(s) != null;
|
|
}
|
|
}
|