VersionConstraint.parse constructor

VersionConstraint.parse(String text)

Parses a version constraint.

This string is one of:

  • "any". any version.
  • "^" followed by a version string. Versions compatible with (VersionConstraint.compatibleWith) the version.
  • a series of version parts. Each part can be one of:
    • A version string like 1.2.3. In other words, anything that can be parsed by Version.parse().
    • A comparison operator (<, >, <=, or >=) followed by a version string.

Whitespace is ignored.

Examples:

any
^0.7.2
^1.0.0-alpha
1.2.3-alpha
<=5.1.4
>2.0.4 <= 2.4.6

Implementation

factory VersionConstraint.parse(String text) {
  var originalText = text;

  skipWhitespace() {
    text = text.trim();
  }

  skipWhitespace();

  // Handle the "any" constraint.
  if (text == "any") return any;

  // Try to parse and consume a version number.
  Version matchVersion() {
    var version = START_VERSION.firstMatch(text);
    if (version == null) return null;

    text = text.substring(version.end);
    return new Version.parse(version[0]);
  }

  // Try to parse and consume a comparison operator followed by a version.
  VersionRange matchComparison() {
    var comparison = START_COMPARISON.firstMatch(text);
    if (comparison == null) return null;

    var op = comparison[0];
    text = text.substring(comparison.end);
    skipWhitespace();

    var version = matchVersion();
    if (version == null) {
      throw new FormatException('Expected version number after "$op" in '
          '"$originalText", got "$text".');
    }

    switch (op) {
      case '<=':
        return new VersionRange(max: version, includeMax: true);
      case '<':
        return new VersionRange(
            max: version,
            includeMax: false,
            alwaysIncludeMaxPreRelease: true);
      case '>=':
        return new VersionRange(min: version, includeMin: true);
      case '>':
        return new VersionRange(min: version, includeMin: false);
    }
    throw "Unreachable.";
  }

  // Try to parse the "^" operator followed by a version.
  matchCompatibleWith() {
    if (!text.startsWith(COMPATIBLE_WITH)) return null;

    text = text.substring(COMPATIBLE_WITH.length);
    skipWhitespace();

    var version = matchVersion();
    if (version == null) {
      throw new FormatException('Expected version number after '
          '"$COMPATIBLE_WITH" in "$originalText", got "$text".');
    }

    if (text.isNotEmpty) {
      throw new FormatException('Cannot include other constraints with '
          '"$COMPATIBLE_WITH" constraint in "$originalText".');
    }

    return new VersionConstraint.compatibleWith(version);
  }

  var compatibleWith = matchCompatibleWith();
  if (compatibleWith != null) return compatibleWith;

  Version min;
  var includeMin = false;
  Version max;
  var includeMax = false;

  while (true) {
    skipWhitespace();

    if (text.isEmpty) break;

    var newRange = matchVersion() ?? matchComparison();
    if (newRange == null) {
      throw new FormatException('Could not parse version "$originalText". '
          'Unknown text at "$text".');
    }

    if (newRange.min != null) {
      if (min == null || newRange.min > min) {
        min = newRange.min;
        includeMin = newRange.includeMin;
      } else if (newRange.min == min && !newRange.includeMin) {
        includeMin = false;
      }
    }

    if (newRange.max != null) {
      if (max == null || newRange.max < max) {
        max = newRange.max;
        includeMax = newRange.includeMax;
      } else if (newRange.max == max && !newRange.includeMax) {
        includeMax = false;
      }
    }
  }

  if (min == null && max == null) {
    throw new FormatException('Cannot parse an empty string.');
  }

  if (min != null && max != null) {
    if (min > max) return VersionConstraint.empty;
    if (min == max) {
      if (includeMin && includeMax) return min;
      return VersionConstraint.empty;
    }
  }

  return new VersionRange(
      min: min, includeMin: includeMin, max: max, includeMax: includeMax);
}