// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:meta/meta.dart'; /// Represents the version of some piece of software. /// /// While a [Version] object has fields resembling semver, it does not /// necessarily represent a semver version. @immutable class Version implements Comparable<Version> { /// Creates a new [Version] object. /// /// A null [minor] or [patch] version is logically equivalent to 0. Using null /// for these parameters only affects the generation of [text], if no value /// for it is provided. factory Version(int major, int? minor, int? patch, {String? text}) { if (text == null) { text = '$major'; if (minor != null) { text = '$text.$minor'; } if (patch != null) { text = '$text.$patch'; } } return Version._(major, minor ?? 0, patch ?? 0, text); } /// Public constant constructor when all fields are non-null, without default value fallbacks. const Version.withText(this.major, this.minor, this.patch, this._text); Version._(this.major, this.minor, this.patch, this._text) { if (major < 0) { throw ArgumentError('Major version must be non-negative.'); } if (minor < 0) { throw ArgumentError('Minor version must be non-negative.'); } if (patch < 0) { throw ArgumentError('Patch version must be non-negative.'); } } /// Creates a new [Version] by parsing [text]. static Version? parse(String? text) { final Match? match = versionPattern.firstMatch(text ?? ''); if (match == null) { return null; } try { final int major = int.parse(match[1] ?? '0'); final int minor = int.parse(match[3] ?? '0'); final int patch = int.parse(match[5] ?? '0'); return Version._(major, minor, patch, text ?? ''); } on FormatException { return null; } } /// Returns the primary version out of a list of candidates. /// /// This is the highest-numbered stable version. static Version? primary(List<Version> versions) { Version? primary; for (final Version version in versions) { if (primary == null || (version > primary)) { primary = version; } } return primary; } /// The major version number: "1" in "1.2.3". final int major; /// The minor version number: "2" in "1.2.3". final int minor; /// The patch version number: "3" in "1.2.3". final int patch; /// The original string representation of the version number. /// /// This preserves textual artifacts like leading zeros that may be left out /// of the parsed version. final String _text; static final RegExp versionPattern = RegExp(r'^(\d+)(\.(\d+)(\.(\d+))?)?'); /// Two [Version]s are equal if their version numbers are. The version text /// is ignored. @override bool operator ==(Object other) { return other is Version && other.major == major && other.minor == minor && other.patch == patch; } @override int get hashCode => Object.hash(major, minor, patch); bool operator <(Version other) => compareTo(other) < 0; bool operator >(Version other) => compareTo(other) > 0; bool operator <=(Version other) => compareTo(other) <= 0; bool operator >=(Version other) => compareTo(other) >= 0; @override int compareTo(Version other) { if (major != other.major) { return major.compareTo(other.major); } if (minor != other.minor) { return minor.compareTo(other.minor); } return patch.compareTo(other.patch); } @override String toString() => _text; } /// Returns true if [targetVersion] is within the range [min] and [max] /// inclusive by default. /// /// [min] and [max] are evaluated by [Version.parse(text)]. /// /// Pass [inclusiveMin] = false for greater than and not equal to min. /// Pass [inclusiveMax] = false for less than and not equal to max. bool isWithinVersionRange( String targetVersion, { required String min, required String max, bool inclusiveMax = true, bool inclusiveMin = true, }) { final Version? parsedTargetVersion = Version.parse(targetVersion); final Version? minVersion = Version.parse(min); final Version? maxVersion = Version.parse(max); final bool withinMin = minVersion != null && parsedTargetVersion != null && (inclusiveMin ? parsedTargetVersion >= minVersion : parsedTargetVersion > minVersion); final bool withinMax = maxVersion != null && parsedTargetVersion != null && (inclusiveMax ? parsedTargetVersion <= maxVersion : parsedTargetVersion < maxVersion); return withinMin && withinMax; }