version.dart 4.65 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:meta/meta.dart';

7 8 9 10
/// Represents the version of some piece of software.
///
/// While a [Version] object has fields resembling semver, it does not
/// necessarily represent a semver version.
11
@immutable
12 13
class Version implements Comparable<Version> {
  /// Creates a new [Version] object.
14 15 16 17 18
  ///
  /// 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}) {
19
    if (text == null) {
20
      text = '$major';
21
      if (minor != null) {
22
        text = '$text.$minor';
23 24
      }
      if (patch != null) {
25
        text = '$text.$patch';
26
      }
27 28
    }

29
    return Version._(major, minor ?? 0, patch ?? 0, text);
30 31
  }

32 33 34
  /// Public constant constructor when all fields are non-null, without default value fallbacks.
  const Version.withText(this.major, this.minor, this.patch, this._text);

35
  Version._(this.major, this.minor, this.patch, this._text) {
36
    if (major < 0) {
37
      throw ArgumentError('Major version must be non-negative.');
38 39
    }
    if (minor < 0) {
40
      throw ArgumentError('Minor version must be non-negative.');
41 42
    }
    if (patch < 0) {
43
      throw ArgumentError('Patch version must be non-negative.');
44
    }
45 46 47
  }

  /// Creates a new [Version] by parsing [text].
48 49
  static Version? parse(String? text) {
    final Match? match = versionPattern.firstMatch(text ?? '');
50
    if (match == null) {
51
      return null;
52 53 54
    }

    try {
55 56 57
      final int major = int.parse(match[1] ?? '0');
      final int minor = int.parse(match[3] ?? '0');
      final int patch = int.parse(match[5] ?? '0');
58
      return Version._(major, minor, patch, text ?? '');
59
    } on FormatException {
60
      return null;
61 62 63
    }
  }

64 65 66
  /// Returns the primary version out of a list of candidates.
  ///
  /// This is the highest-numbered stable version.
67 68
  static Version? primary(List<Version> versions) {
    Version? primary;
69
    for (final Version version in versions) {
70 71 72 73 74 75 76
      if (primary == null || (version > primary)) {
        primary = version;
      }
    }
    return primary;
  }

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
  /// 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+))?)?');

95 96 97
  /// Two [Version]s are equal if their version numbers are. The version text
  /// is ignored.
  @override
98 99 100 101 102
  bool operator ==(Object other) {
    return other is Version
        && other.major == major
        && other.minor == minor
        && other.patch == patch;
103 104 105
  }

  @override
106
  int get hashCode => Object.hash(major, minor, patch);
107 108 109 110 111 112 113 114

  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) {
115
    if (major != other.major) {
116
      return major.compareTo(other.major);
117 118
    }
    if (minor != other.minor) {
119
      return minor.compareTo(other.minor);
120
    }
121 122 123 124 125 126
    return patch.compareTo(other.patch);
  }

  @override
  String toString() => _text;
}
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158

/// 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;
}