diff --git a/.github/actions/jdk/action.yml b/.github/actions/jdk/action.yml index 1acb43d..84230e9 100644 --- a/.github/actions/jdk/action.yml +++ b/.github/actions/jdk/action.yml @@ -5,7 +5,7 @@ inputs: java-version: description: 'Java version' required: false - default: '21' + default: '25' distribution: description: 'JDK Distribution' required: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 377a6ff..3ed6f23 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,10 @@ updates: commit-message: prefix: "ci:" target-branch: "main" + groups: + actions: + patterns: + - "*" - package-ecosystem: "gradle" directory: "/" schedule: @@ -15,12 +19,12 @@ updates: prefix: "build:" target-branch: "main" groups: - "org.flywaydb": - patterns: - - "org.flywaydb*" - "org.jooq": + "minor-and-patch": patterns: - - "org.jooq*" - "net.kyori": + - "*" + update-types: + - "minor" + - "patch" + "major": patterns: - - "net.kyori*" + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3da499..956eb30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ on: java: description: 'Java version' type: number - default: 21 + default: 25 os: description: 'OS' type: choice @@ -53,7 +53,7 @@ jobs: uses: ./.github/workflows/test.yml with: os: ${{ inputs.os || 'ubuntu-latest' }} - java: ${{ inputs.java || 21 }} + java: ${{ inputs.java || 25 }} fail_on_test_failure: true # Build and stage artifacts @@ -63,7 +63,7 @@ jobs: uses: ./.github/workflows/stage.yml with: os: ${{ inputs.os || 'ubuntu-latest' }} - java: ${{ inputs.java || 21 }} + java: ${{ inputs.java || 25 }} generate_attestations: ${{ inputs.generate_attestations || true }} retention_days: ${{ inputs.retention_days || 7 }} prerelease_suffix: ${{ inputs.prerelease_suffix || '-RC' }} diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index eacfb7a..b370427 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -9,7 +9,7 @@ on: java: description: 'Java version' type: number - default: 21 + default: 25 os: description: 'OS' type: choice @@ -29,5 +29,5 @@ jobs: uses: ./.github/workflows/test.yml with: os: ${{ inputs.os || 'ubuntu-latest' }} - java: ${{ inputs.java || 21 }} + java: ${{ inputs.java || 25 }} fail_on_test_failure: true diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml index 404f77b..bfebd6c 100644 --- a/.github/workflows/stage.yml +++ b/.github/workflows/stage.yml @@ -12,7 +12,7 @@ on: description: "The Java version the workflow should use" required: false type: number - default: 21 + default: 25 retention_days: description: "Artifact retention days (default: 7)" required: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5861ec5..1ac65dc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: description: "The Java version the workflow should use" required: false type: number - default: 21 + default: 25 retention_days: description: "Artifact retention days (default: 7)" required: false diff --git a/build.gradle.kts b/build.gradle.kts index 14a72dd..04dde99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,8 @@ subprojects { project.version = rootProject.version project.description = rootProject.description + base.archivesName.set("${rootProject.name}-${project.name}") + repositories { mavenCentral() } @@ -35,14 +37,14 @@ subprojects { } java { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) + toolchain.languageVersion.set(JavaLanguageVersion.of(25)) withJavadocJar() } tasks { compileJava { options.encoding = Charsets.UTF_8.name() - options.compilerArgs.addAll(arrayListOf("-Xlint:all", "-Xlint:-processing", "-Xdiags:verbose")) + options.compilerArgs.addAll(arrayListOf("-Xlint:all", "-Xlint:-processing", "-Xlint:-options", "-Xdiags:verbose")) options.release.set(8) } @@ -70,7 +72,9 @@ subprojects { fun applyCustomVersion() { // Apply custom version arg or append snapshot version - val ver = properties["altVer"]?.toString() ?: "${rootProject.version}-SNAPSHOT.${Instant.now().epochSecond}" + val ver = providers.gradleProperty("altVer") + .orElse(providers.provider { "${rootProject.version}-SNAPSHOT.${Instant.now().epochSecond}" }) + .get() // Strip prefixed "v" from version tag rootProject.version = (if (ver.first().equals('v', true)) ver.substring(1) else ver.uppercase()).uppercase() diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 3201aab..cdbc88e 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -3,6 +3,7 @@ import com.vanniktech.maven.publish.JavadocJar plugins { alias(libs.plugins.publisher) + signing } mavenPublishing { @@ -56,7 +57,8 @@ mavenPublishing { // Sign all publications signAllPublications() +} - // Skip signing for local tasks - tasks.withType().configureEach { onlyIf { !gradle.taskGraph.allTasks.any { it is PublishToMavenLocal } } } +signing { + isRequired = false // Skip signing if no credentials are provided, e.g. for local publishing } \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/Constraint.java b/common/src/main/java/io/github/milkdrinkers/javasemver/Constraint.java new file mode 100644 index 0000000..ca9a7e4 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/Constraint.java @@ -0,0 +1,197 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.Operator; +import io.github.milkdrinkers.javasemver.exception.RangeParseException; +import io.github.milkdrinkers.javasemver.internal.VersionComparison; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + * A single semver comparator e.g. {@code >=1.2.3} consisting of an {@link Operator} and a + * {@link Version}. Also supports wildcard comparator ({@code *} or {@code x}) which matches any version. + * + * @apiNote Implements {@link Predicate} so a constraint can be used directly as a filter e.g. {@code versions.stream().filter(Constraint.parse(">=1.2.3"))}. + */ +public class Constraint implements Predicate { + private final Operator operator; + private final Version version; + private final boolean any; + + private Constraint(Operator operator, Version version, boolean any) { + this.operator = operator; + this.version = version; + this.any = any; + } + + /** + * Creates a constraint from an explicit operator and version e.g. {@code new Constraint(Operator.GTE, someVersion)} to represent {@code >=1.2.3}. + * + * @param operator the comparison operator + * @param version the version to compare against + * @apiNote Intended for internal range-expansion logic (e.g. X-ranges, tilde/caret ranges) + * that needs to build constraints directly from already-parsed components rather than + * from a comparator string. + */ + public Constraint(@NotNull Operator operator, @NotNull Version version) { + this(Objects.requireNonNull(operator, "operator must not be null"), Objects.requireNonNull(version, "version must not be null"), false); + } + + /** + * Parses a single comparator string e.g. {@code >=1.2.3}, {@code 1.2.3} or {@code *}. + * + * @param input the comparator string to parse + * @return the parsed constraint + * @throws RangeParseException thrown if the string could not be parsed into a constraint + */ + public static @NotNull Constraint parse(String input) throws RangeParseException { + return parse(input, false); + } + + /** + * Parses a single comparator string e.g. {@code >=1.2.3}, {@code 1.2.3} or {@code *}. + * + * @param input the comparator string to parse + * @param loose whether the version part should be parsed leniently via {@link Version#parseLoose(String)} (tolerating a leading {@code v}/{@code =} and leading zeros) rather than strictly + * @return the parsed constraint + * @throws RangeParseException thrown if the string could not be parsed into a constraint + */ + public static @NotNull Constraint parse(String input, boolean loose) throws RangeParseException { + if (input == null) + throw new RangeParseException("Could not parse range constraint from: null"); + + final String trimmed = input.trim(); + + if (trimmed.isEmpty() || trimmed.equals("*") || trimmed.equalsIgnoreCase("x")) + return any(); + + Operator operator = null; + String versionPart = trimmed; + + if (trimmed.length() >= 2) { + final Operator twoChar = Operator.fromSymbol(trimmed.substring(0, 2)); + if (twoChar != null) { + operator = twoChar; + versionPart = trimmed.substring(2); + } + } + + if (operator == null) { + final Operator oneChar = Operator.fromSymbol(trimmed.substring(0, 1)); + if (oneChar != null) { + operator = oneChar; + versionPart = trimmed.substring(1); + } + } + + if (operator == null) + operator = Operator.EQ; + + try { + final Version parsedVersion = loose ? Version.parseLoose(versionPart.trim()) : Version.parse(versionPart.trim()); + return new Constraint(operator, parsedVersion, false); + } catch (RuntimeException e) { + throw new RangeParseException("Could not parse range constraint from: " + input, e); + } + } + + /** + * Creates a wildcard constraint, which matches any version. + * + * @return a wildcard constraint + */ + public static @NotNull Constraint any() { + return new Constraint(null, null, true); + } + + /** + * Checks whether a version satisfies this constraint. + * + * @param v the version to check + * @return true if the version satisfies this constraint + */ + public boolean contains(@NotNull Version v) { + if (any) + return true; + + final int comparison = VersionComparison.comparePrecedence(v, version); + + switch (operator) { + case GT: + return comparison > 0; + case GTE: + return comparison >= 0; + case LT: + return comparison < 0; + case LTE: + return comparison <= 0; + case EQ: + return comparison == 0; + default: + return false; + } + } + + /** + * Predicate implementation, delegating to {@link #contains(Version)}. + * + * @param v the version to test + * @return true if the version satisfies this constraint + */ + @Override + public boolean test(Version v) { + return contains(v); + } + + /** + * Gets the operator of this constraint. + * + * @return the operator, or {@code null} if this is the wildcard constraint + */ + public @Nullable Operator getOperator() { + return operator; + } + + /** + * Gets the version of this constraint. + * + * @return the version, or {@code null} if this is the wildcard constraint + */ + public @Nullable Version getVersion() { + return version; + } + + /** + * Checks whether this is the wildcard constraint. + * + * @return true if this constraint matches any version + */ + public boolean isAny() { + return any; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Constraint)) return false; + Constraint that = (Constraint) o; + return any == that.any + && operator == that.operator + && Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(operator, version, any); + } + + @Override + public String toString() { + if (any) + return "*"; + + return (operator == Operator.EQ ? "" : operator.getSymbol()) + version; + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/Range.java b/common/src/main/java/io/github/milkdrinkers/javasemver/Range.java new file mode 100644 index 0000000..ee3c88b --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/Range.java @@ -0,0 +1,311 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.exception.RangeParseException; +import io.github.milkdrinkers.javasemver.internal.RangeAlgebra; +import io.github.milkdrinkers.javasemver.internal.RangeParsing; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.Predicate; + +/** + * A semver range e.g. {@code >=1.2.3 <2.0.0 || 3.x}, consisting of one or more comparator sets + * joined by {@code ||} (OR), where each comparator set is one or more {@link Constraint}'s that + * must all match (AND). + * + * @apiNote Implements {@link Predicate} so a range can be used directly as a filter e.g. {@code versions.stream().filter(Range.parse(">=1.2.3"))}. + */ +public final class Range implements Predicate { + private final List> comparatorSets; + + private Range(List> comparatorSets) { + final List> copy = new ArrayList<>(); + for (List set : comparatorSets) + copy.add(Collections.unmodifiableList(new ArrayList<>(set))); + + this.comparatorSets = Collections.unmodifiableList(copy); + } + + /** + * Parses a range string e.g. {@code >=1.2.3 <2.0.0 || 3.x}. + * + * @param input the range string to parse + * @return the parsed range + * @throws RangeParseException thrown if the string could not be parsed into a range + */ + public static @NotNull Range parse(String input) throws RangeParseException { + return new Range(RangeParsing.parse(input)); + } + + /** + * Parses a range string leniently e.g. {@code >=v1.2.3 <2.0.0 || 3.x}, tolerating a leading {@code v}/{@code =} and leading zeros in each constraints version part. + * + * @param input the range string to parse + * @return the parsed range + * @throws RangeParseException thrown if the string could not be parsed into a range + * @apiNote Only the version part of primitive comparators (e.g. {@code >=v1.2.3}) is parsed leniently. X-ranges, tilde/caret and hyphen ranges already tolerate leading zeros. + */ + public static @NotNull Range parseLoose(String input) throws RangeParseException { + return new Range(RangeParsing.parse(input, true)); + } + + /** + * Parses a range string e.g. {@code >=1.2.3 <2.0.0 || 3.x}. + * + * @param input the range string to parse + * @return the parsed range wrapped in an optional, or an empty optional if parsing failed + */ + public static @NotNull Optional parseOptional(String input) { + try { + return Optional.of(parse(input)); + } catch (RuntimeException ignored) { + return Optional.empty(); + } + } + + /** + * Checks whether a range string is valid. + * + * @param input the range string to check + * @return whether the string could be parsed into a range + * @apiNote Uses {@link Range#parseOptional(String)} internally + */ + public static boolean isValid(String input) { + return parseOptional(input).isPresent(); + } + + /** + * Finds the highest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range to satisfy + * @return the highest satisfying version by natural order, or an empty optional if none satisfy the range + */ + public static @NotNull Optional maxSatisfying(Collection versions, Range range) { + return versions.stream() + .filter(range::contains) + .max(Comparator.naturalOrder()); + } + + /** + * Finds the highest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range string to parse and satisfy + * @return the highest satisfying version by natural order, or an empty optional if none satisfy the range + * @throws RangeParseException thrown if the range string could not be parsed + * @apiNote Delegates to {@link #parse(String)} to parse the range + */ + public static @NotNull Optional maxSatisfying(Collection versions, String range) { + return maxSatisfying(versions, parse(range)); + } + + /** + * Finds the lowest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range to satisfy + * @return the lowest satisfying version by natural order, or an empty optional if none satisfy the range + */ + public static @NotNull Optional minSatisfying(Collection versions, Range range) { + return versions.stream() + .filter(range::contains) + .min(Comparator.naturalOrder()); + } + + /** + * Finds the lowest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range string to parse and satisfy + * @return the lowest satisfying version by natural order, or an empty optional if none satisfy the range + * @throws RangeParseException thrown if the range string could not be parsed + * @apiNote Delegates to {@link #parse(String)} to parse the range + */ + public static @NotNull Optional minSatisfying(Collection versions, String range) { + return minSatisfying(versions, parse(range)); + } + + /** + * Checks whether a version satisfies this range. + *

+ * A version with a prerelease tag may only satisfy a comparator set if that set contains at least one constraint whose version also + * has a prerelease tag and shares the same {@code major.minor.patch} as the candidate. This + * keeps prerelease versions from matching ranges that don't mention that prerelease line. + * + * @param v the version to check + * @return true if the version satisfies at least one comparator set (OR), where a set is satisfied if the version satisfies all of its constraints (AND) and, when the version has a prerelease, the set opts into that prerelease line + */ + public boolean contains(@NotNull Version v) { + for (List comparatorSet : comparatorSets) { + if (!setAllowsPrerelease(comparatorSet, v)) + continue; + + boolean allMatch = true; + + for (Constraint constraint : comparatorSet) { + if (!constraint.contains(v)) { + allMatch = false; + break; + } + } + + if (allMatch) + return true; + } + + return false; + } + + /** + * Checks whether a comparator set is allowed to match a prerelease candidate. + *

+ * Stable candidates are always allowed through. A prerelease candidate is only allowed + * through a set if the set contains a constraint whose version also carries a prerelease tag + * and shares the candidates exact {@code major.minor.patch}. + * + * @param comparatorSet the comparator set (AND-group) being evaluated + * @param candidate the version being tested against the range + * @return true if the set may be used to evaluate the candidate + */ + private static boolean setAllowsPrerelease(@NotNull List comparatorSet, @NotNull Version candidate) { + if (!candidate.hasPreRelease()) + return true; + + for (Constraint constraint : comparatorSet) { + final Version constraintVersion = constraint.getVersion(); + + if (constraintVersion == null || !constraintVersion.hasPreRelease()) + continue; + + if (constraintVersion.getMajor() == candidate.getMajor() + && constraintVersion.getMinor() == candidate.getMinor() + && constraintVersion.getPatch() == candidate.getPatch()) + return true; + } + + return false; + } + + /** + * Predicate implementation, delegating to {@link #contains(Version)}. + * + * @param v the version to test + * @return true if the version satisfies this range + */ + @Override + public boolean test(Version v) { + return contains(v); + } + + /** + * Gets the comparator sets making up this range. + * + * @return the unmodifiable list of comparator sets, joined by OR, each containing constraints joined by AND + */ + public @NotNull List> getComparatorSets() { + return comparatorSets; + } + + /** + * Checks whether this range matches any version. + * + * @return true if any comparator set is empty or contains the wildcard constraint + */ + public boolean isAny() { + for (List comparatorSet : comparatorSets) { + if (comparatorSet.isEmpty()) + return true; + + for (Constraint constraint : comparatorSet) { + if (constraint.isAny()) + return true; + } + } + + return false; + } + + /** + * Finds the lowest version this range can possibly satisfy. + * + * @return the lowest satisfying version by natural order, or an empty optional if this range admits no version at all (e.g. every comparator set is contradictory) + * @apiNote Delegates to {@link RangeAlgebra#minVersion(Range)} + */ + public @NotNull Optional minVersion() { + return RangeAlgebra.minVersion(this); + } + + /** + * Checks whether this range and another range intersect, e.g. share at least one version they could both possibly match. + * + * @param other the other range + * @return true if there exists a version that could satisfy both this range and {@code other} + * @apiNote Delegates to {@link RangeAlgebra#intersects(Range, Range)} + */ + public boolean intersects(@NotNull Range other) { + return RangeAlgebra.intersects(this, other); + } + + /** + * Checks whether this range is a subset of another range, e.g. every version this range could possibly match is also matched by {@code dom}. + * + * @param dom the candidate superset (dominating) range + * @return true if every version this range could match, {@code dom} could also match + * @apiNote Delegates to {@link RangeAlgebra#isSubsetOf(Range, Range)} + */ + public boolean isSubsetOf(@NotNull Range dom) { + return RangeAlgebra.isSubsetOf(this, dom); + } + + /** + * Finds the shortest range that has the same membership as {@code range} over a supplied set of versions. + *

+ * The versions are grouped into maximal contiguous runs of versions that satisfy + * {@code range}, and each run is collapsed to its tightest primitive form ({@code >=first + * <=last}, or a bare version for a single-element run), joined by {@code ||}. This preserves + * membership for every version in {@code versions}, though it isn't guaranteed to be the + * globally shortest possible string. + * + * @param versions the versions whose membership in {@code range} must be preserved + * @param range the range to simplify + * @return a range equivalent to {@code range} for every version in {@code versions}. If none of the supplied versions satisfy {@code range}, a range that matches nothing + * @apiNote Delegates to {@link RangeAlgebra#simplify(Collection, Range)} + */ + public static @NotNull Range simplify(@NotNull Collection versions, @NotNull Range range) { + return RangeAlgebra.simplify(versions, range); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Range)) return false; + Range range = (Range) o; + return Objects.equals(comparatorSets, range.comparatorSets); + } + + @Override + public int hashCode() { + return Objects.hash(comparatorSets); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < comparatorSets.size(); i++) { + if (i > 0) + sb.append(" || "); + + final List comparatorSet = comparatorSets.get(i); + for (int j = 0; j < comparatorSet.size(); j++) { + if (j > 0) + sb.append(" "); + + sb.append(comparatorSet.get(j)); + } + } + + return sb.toString(); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java b/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java index 0dad536..c6c6433 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java @@ -1,38 +1,44 @@ package io.github.milkdrinkers.javasemver; +import io.github.milkdrinkers.javasemver.enums.ReleaseType; import io.github.milkdrinkers.javasemver.exception.VersionBuildException; import io.github.milkdrinkers.javasemver.exception.VersionParseException; +import io.github.milkdrinkers.javasemver.internal.Coercion; +import io.github.milkdrinkers.javasemver.internal.RangeAlgebra; +import io.github.milkdrinkers.javasemver.internal.VersionComparison; +import io.github.milkdrinkers.javasemver.internal.VersionParsing; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; +import java.util.*; /** - * A class representing a Semantic Version. + * An immutable value object representing a Semantic Version, made up of a major, minor and + * patch component and optional pre-release and build-metadata. Implements {@link Comparable} for precedence ordering, + * and offers parsing/coercion factories along with comparison, increment and range operations. */ -public class Version extends VersionCompare implements Comparable { - // Base fields - private final long major; // The Major version - private final long minor; // The Minor version - private final long patch; // The Patch version - private final String preRelease; // The pre-release data like "SNAPSHOT-1" or "RC-3" - private final String[] preReleaseIdentifiers; // The pre-release data divided into strings by the seperator "." - private final String meta; // The build-metadata - - // Cached fields +public class Version implements Comparable { + + // Base + private final long major; + private final long minor; + private final long patch; + private final String preRelease; // pre-release data like "SNAPSHOT-1" or "RC-3" + private final List preReleaseIdentifiers; // pre-release data divided into strings by the seperator "." + private final String meta; // build-metadata + + // Cached private final boolean hasPreRelease; - private final boolean hasMeta; - private final String version; // The version consisting of only Major.Minor.Patch - private final String versionFull; // The entire version string + private final boolean hasBuildMetadata; + private final String version; // version consisting of only Major.Minor.Patch + private final String versionFull; // entire version string - // Cached fields - private final boolean isAlpha; // Whether the a pre-release contains "alpha" - private final boolean isBeta; // Whether the a pre-release contains "beta" - private final boolean isDevelopment; // Whether the a pre-release contains "dev", "develop" or "development" - private final boolean isReleaseCandidate; // Whether the a pre-release contains "rc" - private final boolean isSnapshot; // Whether the a pre-release contains "snapshot" + private final boolean isAlpha; // whether the pre-release contains "alpha" + private final boolean isBeta; // whether the pre-release contains "beta" + private final boolean isDevelopment; // whether the pre-release contains "dev", "develop" or "development" + private final boolean isReleaseCandidate; // whether the pre-release contains "rc" + private final boolean isSnapshot; // whether the pre-release contains "snapshot" /** * Instantiates a new version object. @@ -46,22 +52,25 @@ public class Version extends VersionCompare implements Comparable { */ @ApiStatus.Internal Version(long major, long minor, long patch, String preRelease, String meta) { - // Base fields + // Base this.major = major; this.minor = minor; this.patch = patch; this.preRelease = preRelease; - this.preReleaseIdentifiers = Arrays.stream(preRelease.split("\\.")) - .filter(string -> !string.isEmpty()) // Fixes bug where empty strings would count as identifiers - .toArray(String[]::new); + final List ids = new ArrayList<>(); + for (String s : preRelease.split("\\.")) { + if (!s.isEmpty()) // or empty strings may count as identifiers + ids.add(s); + } + this.preReleaseIdentifiers = Collections.unmodifiableList(ids); this.meta = meta; this.hasPreRelease = !getPreRelease().isEmpty(); - this.hasMeta = !getBuildMetadata().isEmpty(); + this.hasBuildMetadata = !getBuildMetadata().isEmpty(); this.version = concatenateVersionString(this.major, this.minor, this.patch); this.versionFull = concatenateVersionStringFull(this.major, this.minor, this.patch, this.preRelease, this.meta); - // Cached fields + // Cached this.isAlpha = preRelease.toLowerCase().contains("alpha"); this.isBeta = preRelease.toLowerCase().contains("beta"); this.isDevelopment = preRelease.toLowerCase().contains("dev") || preRelease.toLowerCase().contains("develop") || preRelease.toLowerCase().contains("development"); @@ -75,10 +84,10 @@ public class Version extends VersionCompare implements Comparable { * @param version a string containing a semantic version * @return a version object * @throws VersionParseException thrown if parsing the string into a version failed - * @apiNote Uses {@link VersionParser#parse(String)} internally + * @apiNote Uses {@link VersionParsing#parseStrict(String)} internally */ - public static @NotNull Version of(String version) throws VersionParseException { - return VersionParser.parse(version); + public static @NotNull Version parse(String version) throws VersionParseException { + return VersionParsing.parseStrict(version); } /** @@ -86,16 +95,64 @@ public class Version extends VersionCompare implements Comparable { * * @param version a string containing a semantic version * @return a version object wrapped in a optional - * @apiNote Uses {@link VersionParser#parse(String)} internally + * @apiNote Uses {@link VersionParsing#parseStrict(String)} internally */ - public static @NotNull Optional ofOptional(String version) { + public static @NotNull Optional parseOptional(String version) { try { - return Optional.of(VersionParser.parse(version)); - } catch (Exception ignored) { + return Optional.of(VersionParsing.parseStrict(version)); + } catch (RuntimeException ignored) { return Optional.empty(); } } + /** + * Create a Version object from a version string, tolerating a leading {@code v}/{@code V}, a leading {@code =}, surrounding whitespace, and leading zeros in numeric components. + * + * @param version a string containing a loosely formatted semantic version + * @return a version object + * @throws VersionParseException thrown if parsing the string into a version failed + * @apiNote Uses {@link VersionParsing#parseLoose(String)} internally + */ + public static @NotNull Version parseLoose(String version) throws VersionParseException { + return VersionParsing.parseLoose(version); + } + + /** + * Checks whether a version string is a valid semantic version. + * + * @param version a string containing a semantic version + * @return whether the string could be parsed into a version + * @apiNote Uses {@link Version#parseOptional(String)} internally + */ + public static boolean isValid(String version) { + return parseOptional(version).isPresent(); + } + + /** + * Scans an arbitrary string for the first thing that looks like a version and coerces it + * into a Version, defaulting any missing minor/patch component to {@code 0} and discarding + * any trailing pre-release/build-metadata data. + * + * @param input the string to scan + * @return the coerced version wrapped in an optional, or an empty optional if no version like data was found + * @apiNote Uses {@link Coercion#coerce(String)} internally + */ + public static Optional coerce(String input) { + return Coercion.coerce(input); + } + + /** + * Normalizes a loosely formatted version string into a strict semantic version string, + * tolerating surrounding whitespace, a leading {@code =} and a leading {@code v}/{@code V}. + * + * @param input the string to clean + * @return the cleaned version string wrapped in an optional, or an empty optional if the remainder was not a valid version + * @apiNote Uses {@link Coercion#clean(String)} internally + */ + public static Optional clean(String input) { + return Coercion.clean(input); + } + /** * Create a Version object from semantic version data. * @@ -104,7 +161,8 @@ public class Version extends VersionCompare implements Comparable { * @param patch the patch version * @return the semantic version * @throws VersionBuildException thrown if parsing the data into a version failed - * @apiNote Uses {@link VersionBuilder} internally + * @apiNote Uses {@link Builder} internally + * @see #builder() */ public static @NotNull Version of(long major, long minor, long patch) throws VersionBuildException { return of(major, minor, patch, ""); @@ -119,7 +177,8 @@ public class Version extends VersionCompare implements Comparable { * @param preRelease the pre-release version * @return the semantic version * @throws VersionBuildException thrown if parsing the data into a version failed - * @apiNote Uses {@link VersionBuilder} internally + * @apiNote Uses {@link Builder} internally + * @see #builder() */ public static @NotNull Version of(long major, long minor, long patch, String preRelease) throws VersionBuildException { return of(major, minor, patch, preRelease, ""); @@ -135,10 +194,11 @@ public class Version extends VersionCompare implements Comparable { * @param meta the build-meta * @return the semantic version * @throws VersionBuildException thrown if parsing the data into a version failed - * @apiNote Uses {@link VersionBuilder} internally + * @apiNote Uses {@link Builder} internally + * @see #builder() */ public static @NotNull Version of(long major, long minor, long patch, String preRelease, String meta) throws VersionBuildException { - return new VersionBuilder() + return builder() .withMajor(major) .withMinor(minor) .withPatch(patch) @@ -157,7 +217,7 @@ public long getMajor() { } /** - * Gets minor minor. + * Gets the minor version. * * @return the minor */ @@ -179,16 +239,16 @@ public long getPatch() { * * @return the pre release */ - public String getPreRelease() { + public @NotNull String getPreRelease() { return preRelease; } /** * Gets pre-release identifiers. * - * @return the array of identifiers + * @return the unmodifiable list of identifiers */ - public String[] getPreReleaseIdentifiers() { + public @NotNull List getPreReleaseIdentifiers() { return preReleaseIdentifiers; } @@ -197,7 +257,7 @@ public String[] getPreReleaseIdentifiers() { * * @return the metadata */ - public String getBuildMetadata() { + public @NotNull String getBuildMetadata() { return meta; } @@ -215,8 +275,8 @@ public boolean hasPreRelease() { * * @return boolean */ - public boolean hasMeta() { - return hasMeta; + public boolean hasBuildMetadata() { + return hasBuildMetadata; } /** @@ -287,6 +347,153 @@ public boolean isSnapshot() { return isSnapshot; } + /** + * Computes the next version for a given release type, using a default numeric + * pre-release identifier base of {@code 0} where a pre-release is introduced. + * + * @param release the type of increment to perform + * @return the incremented version + * @throws VersionBuildException thrown if the resulting version could not be built + * @apiNote Build metadata is never carried over to the result. + * @see #increment(ReleaseType, String) + */ + public @NotNull Version increment(@NotNull ReleaseType release) throws VersionBuildException { + return increment(release, null); + } + + /** + * Computes the next version for a given release type. + * + * @param release the type of increment to perform + * @param identifier the pre-release identifier to use for {@code PRE*} release types, may be {@code null} (or empty) to fall back to a numeric identifier of {@code 0} + * @return the incremented version + * @throws VersionBuildException thrown if the resulting version could not be built + * @apiNote Build metadata is never carried over to the result. + */ + public @NotNull Version increment(@NotNull ReleaseType release, @Nullable String identifier) throws VersionBuildException { + Objects.requireNonNull(release, "release type must not be null"); + + final Builder builder = Version.builder(); + + switch (release) { + case MAJOR: + if (hasPreRelease() && minor == 0L && patch == 0L) + return builder.withMajor(major).withMinor(minor).withPatch(patch).build(); + + return builder.withMajor(major + 1L).withMinor(0L).withPatch(0L).build(); + case MINOR: + if (hasPreRelease() && patch == 0L) + return builder.withMajor(major).withMinor(minor).withPatch(patch).build(); + + return builder.withMajor(major).withMinor(minor + 1L).withPatch(0L).build(); + case PATCH: + if (hasPreRelease()) + return builder.withMajor(major).withMinor(minor).withPatch(patch).build(); + + return builder.withMajor(major).withMinor(minor).withPatch(patch + 1L).build(); + case PREMAJOR: + return builder.withMajor(major + 1L).withMinor(0L).withPatch(0L).withPreRelease(nextPreReleaseBase(identifier)).build(); + case PREMINOR: + return builder.withMajor(major).withMinor(minor + 1L).withPatch(0L).withPreRelease(nextPreReleaseBase(identifier)).build(); + case PREPATCH: + return builder.withMajor(major).withMinor(minor).withPatch(patch + 1L).withPreRelease(nextPreReleaseBase(identifier)).build(); + case PRERELEASE: + if (!hasPreRelease()) + return builder.withMajor(major).withMinor(minor).withPatch(patch + 1L).withPreRelease(nextPreReleaseBase(identifier)).build(); + + return builder.withMajor(major).withMinor(minor).withPatch(patch).withPreRelease(nextPreReleaseIdentifier(identifier)).build(); + default: + throw new VersionBuildException("Unsupported release type: " + release); + } + } + + /** + * Shortcut for {@code increment(ReleaseType.MAJOR)}. + * + * @return the next major version + * @throws VersionBuildException thrown if the resulting version could not be built + */ + public @NotNull Version nextMajor() throws VersionBuildException { + return increment(ReleaseType.MAJOR); + } + + /** + * Shortcut for {@code increment(ReleaseType.MINOR)}. + * + * @return the next minor version + * @throws VersionBuildException thrown if the resulting version could not be built + */ + public @NotNull Version nextMinor() throws VersionBuildException { + return increment(ReleaseType.MINOR); + } + + /** + * Shortcut for {@code increment(ReleaseType.PATCH)}. + * + * @return the next patch version + * @throws VersionBuildException thrown if the resulting version could not be built + */ + public @NotNull Version nextPatch() throws VersionBuildException { + return increment(ReleaseType.PATCH); + } + + /** + * Builds the starting pre-release string for a fresh {@code PRE*} increment. + */ + private static String nextPreReleaseBase(String identifier) { + return identifier == null || identifier.isEmpty() ? "0" : identifier + ".0"; + } + + /** + * Computes the next pre-release string for a version that already has one, following the + * {@link ReleaseType#PRERELEASE} rules. An identifier matching the current one bumps its + * trailing numeric part, a different identifier restarts the pre-release, and no identifier + * just bumps the trailing numeric part of whatever is already there. + */ + private String nextPreReleaseIdentifier(String identifier) { + final List ids = getPreReleaseIdentifiers(); + + if (identifier != null && !identifier.isEmpty()) { + if (!ids.isEmpty() && ids.get(0).equals(identifier)) + return String.join(".", bumpTrailingNumericIdentifier(ids)); + + return identifier + ".0"; + } + + return String.join(".", bumpTrailingNumericIdentifier(ids)); + } + + /** + * Increments the trailing identifier if it's numeric, otherwise appends a fresh {@code 0} identifier. + */ + private static List bumpTrailingNumericIdentifier(List identifiers) { + final List bumped = new ArrayList<>(identifiers); + final String last = bumped.isEmpty() ? "" : bumped.get(bumped.size() - 1); + + if (isNumericIdentifier(last)) { + bumped.set(bumped.size() - 1, String.valueOf(Long.parseLong(last) + 1L)); + } else { + bumped.add("0"); + } + + return bumped; + } + + /** + * Checks whether a pre-release identifier consists only of digits. + */ + private static boolean isNumericIdentifier(String identifier) { + if (identifier.isEmpty()) + return false; + + for (int i = 0; i < identifier.length(); i++) { + if (!Character.isDigit(identifier.charAt(i))) + return false; + } + + return true; + } + /** * Concatenates to a Semantic versioning string. */ @@ -302,45 +509,302 @@ private static String concatenateVersionStringFull(long major, long minor, long } /** - * Compares this object with the specified object for order. Returns a + * Compares this object with the specified object for order. Returns a * negative integer, zero, or a positive integer as this object is less * than, equal to, or greater than the specified object. * * @param other the object to be compared. - * @return a negative integer, zero, or a positive integer as this object - * is less than, equal to, or greater than the specified object. + * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. * @throws NullPointerException if the specified object is null - * @throws ClassCastException if the specified object's type prevents it - * from being compared to this object. + * @throws ClassCastException if the specified object's type prevents it from being compared to this object. */ @Override public int compareTo(@NotNull Version other) { - final VersionCheckResult result = VersionCompare.compare(this, other); + return VersionComparison.comparePrecedence(this, other); + } - switch (result) { - case NEWER: - return -1; - case OLDER: - return 1; - default: - return 0; + /** + * Checks whether this version has a higher precedence than the other version. + * + * @param other the version to compare against + * @return true if this version is greater than the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isGreaterThan(Version other) { + return compareTo(other) > 0; + } + + /** + * Checks whether this version has a precedence greater than or equal to the other version. + * + * @param other the version to compare against + * @return true if this version is greater than or equal to the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isAtLeast(Version other) { + return compareTo(other) >= 0; + } + + /** + * Checks whether this version has a lower precedence than the other version. + * + * @param other the version to compare against + * @return true if this version is less than the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isLessThan(Version other) { + return compareTo(other) < 0; + } + + /** + * Checks whether this version has a precedence less than or equal to the other version. + * + * @param other the version to compare against + * @return true if this version is less than or equal to the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isAtMost(Version other) { + return compareTo(other) <= 0; + } + + /** + * Checks whether this version has the same precedence as the other version. + * + * @param other the version to compare against + * @return true if this version is equal in precedence to the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. Use {@link #equals(Object)} for strict equality. + */ + public boolean isEqualTo(Version other) { + return compareTo(other) == 0; + } + + /** + * Classifies the kind of change between this version and another version. + * + * @param other the version to compare against + * @return the release type describing the difference, or an empty optional if the two versions have equal precedence + */ + public @NotNull Optional difference(@NotNull Version other) { + final int comparison = compareTo(other); + if (comparison == 0) + return Optional.empty(); + + final Version high = comparison > 0 ? this : other; + final Version low = comparison > 0 ? other : this; + final boolean highHasPre = high.hasPreRelease(); + final boolean lowHasPre = low.hasPreRelease(); + + if (lowHasPre && !highHasPre) { + // a prerelease of a bare major (X.0.0) is always a major level change like: 1.0.0-1 -> 1.0.0 / 1.1.0 / 2.0.0 are all "major" + if (low.getMinor() == 0L && low.getPatch() == 0L) + return Optional.of(ReleaseType.MAJOR); + + // same major.minor.patch. the prerelease was simply dropped, so the change is at the lowest non zero component of the low version + if (low.getMajor() == high.getMajor() && low.getMinor() == high.getMinor() && low.getPatch() == high.getPatch()) { + if (low.getMinor() != 0L && low.getPatch() == 0L) + return Optional.of(ReleaseType.MINOR); + + return Optional.of(ReleaseType.PATCH); + } } + + // pre* prefix only applies when higher version is itself a prerelease + final boolean pre = highHasPre; + + if (getMajor() != other.getMajor()) + return Optional.of(pre ? ReleaseType.PREMAJOR : ReleaseType.MAJOR); + + if (getMinor() != other.getMinor()) + return Optional.of(pre ? ReleaseType.PREMINOR : ReleaseType.MINOR); + + if (getPatch() != other.getPatch()) + return Optional.of(pre ? ReleaseType.PREPATCH : ReleaseType.PATCH); + + return Optional.of(ReleaseType.PRERELEASE); + } + + /** + * Checks whether this version satisfies a range. + * + * @param range the range to check against + * @return true if this version satisfies the range + * @apiNote Shorthand for {@code range.contains(this)}. + */ + public boolean satisfies(@NotNull Range range) { + return range.contains(this); + } + + /** + * Checks whether this version satisfies a range string. + * + * @param range the range string to check against + * @return true if this version satisfies the range + * @throws io.github.milkdrinkers.javasemver.exception.RangeParseException thrown if the range string could not be parsed + * @apiNote Shorthand for {@code Range.parse(range).contains(this)}. + */ + public boolean satisfies(@NotNull String range) { + return Range.parse(range).contains(this); + } + + /** + * Checks whether this version's precedence is greater than everything a range could possibly match. + * + * @param range the range to check against + * @return true if this version is above every version the range could match + */ + public boolean isAbove(@NotNull Range range) { + return RangeAlgebra.isAbove(this, range); } + /** + * Checks whether this versions precedence is less than everything a range could possibly match. + * + * @param range the range to check against + * @return true if this version is below every version the range could match + */ + public boolean isBelow(@NotNull Range range) { + return RangeAlgebra.isBelow(this, range); + } + + /** + * A comparator that orders versions by Semantic Versioning precedence using build metadata as a tiebreak when precedence is otherwise equal. + * + * @apiNote A version with build metadata sorts as greater than an otherwise equal version without any. + */ + public static final Comparator BUILD_AWARE = VersionComparison::compareBuild; + @Override public boolean equals(Object o) { + if (this == o) return true; if (!(o instanceof Version)) return false; Version version = (Version) o; - return getMajor() == version.getMajor() && getMinor() == version.getMinor() && getPatch() == version.getPatch() && Objects.equals(hasPreRelease(), version.hasPreRelease()) && Objects.equals(getBuildMetadata(), version.getBuildMetadata()); + + return getMajor() == version.getMajor() + && getMinor() == version.getMinor() + && getPatch() == version.getPatch() + && Objects.equals(getPreRelease(), version.getPreRelease()) + && Objects.equals(getBuildMetadata(), version.getBuildMetadata()); } @Override public int hashCode() { - return Objects.hash(getMajor(), getMinor(), getPatch(), hasPreRelease(), getBuildMetadata()); + return Objects.hash(getMajor(), getMinor(), getPatch(), getPreRelease(), getBuildMetadata()); } @Override public String toString() { return getVersionFull(); } -} + + /** + * Creates a new {@link Builder} for constructing a {@link Version} object. + * + * @return the version builder + */ + public static @NotNull Builder builder() { + return new Builder(); + } + + /** + * A class allowing the creation of {@link Version} objects through this builder. + * + * @implSpec Major, Minor and Patch are required in a valid semantic version. + */ + public static final class Builder { + private Long major; // The Major version + private Long minor; // The Minor version + private Long patch; // The Patch version + private String preRelease; // The pre-release data like "SNAPSHOT-1" or "RC-3" + private String meta; // The build-metadata + + /** + * Set major version for builder. + * + * @param major the major + * @return the version builder + */ + public Builder withMajor(long major) { + this.major = major; + return this; + } + + /** + * Set minor version for builder. + * + * @param minor the minor + * @return the version builder + */ + public Builder withMinor(long minor) { + this.minor = minor; + return this; + } + + /** + * Set patch version for builder. + * + * @param patch the patch + * @return the version builder + */ + public Builder withPatch(long patch) { + this.patch = patch; + return this; + } + + /** + * Set pre-release version for builder. + * + * @param preRelease the pre-release + * @return the version builder + */ + public Builder withPreRelease(String preRelease) { + this.preRelease = preRelease; + return this; + } + + /** + * Set build-metadata version for builder. + * + * @param meta the build-metadata + * @return the version builder + */ + public Builder withMeta(String meta) { + this.meta = meta; + return this; + } + + /** + * Build version. + * + * @return the {@link Version} + * @throws VersionBuildException thrown if the version could not be built + * @implSpec Major, Minor and Patch are required when building a valid semantic version. + */ + public Version build() throws VersionBuildException { + if (major == null) + throw new VersionBuildException("Major version needs to be specified."); + + if (minor == null) + throw new VersionBuildException("Minor version needs to be specified."); + + if (patch == null) + throw new VersionBuildException("Patch version needs to be specified."); + + if (major < 0L) + throw new VersionBuildException(String.format("Major version \"%s\" can't be less than 0.", major)); + + if (minor < 0L) + throw new VersionBuildException(String.format("Minor version \"%s\" can't be less than 0.", minor)); + + if (patch < 0L) + throw new VersionBuildException(String.format("Patch version \"%s\" can't be less than 0.", patch)); + + if (preRelease == null) + preRelease = ""; + + if (meta == null) + meta = ""; + + return new Version(major, minor, patch, preRelease, meta); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionBuilder.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionBuilder.java deleted file mode 100644 index 423f764..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionBuilder.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -import io.github.milkdrinkers.javasemver.exception.VersionBuildException; - -/** - * A class allowing the creation of {@link Version} objects through this builder. - * - * @implSpec Major, Minor and Patch are required in a valid semantic version. - */ -public final class VersionBuilder { - private Long major; // The Major version - private Long minor; // The Minor version - private Long patch; // The Patch version - private String preRelease; // The pre-release data like "SNAPSHOT-1" or "RC-3" - private String meta; // The build-metadata - - /** - * Set major version for builder. - * - * @param major the major - * @return the version builder - */ - public VersionBuilder withMajor(long major) { - this.major = major; - return this; - } - - /** - * Set minor version for builder. - * - * @param minor the minor - * @return the version builder - */ - public VersionBuilder withMinor(long minor) { - this.minor = minor; - return this; - } - - /** - * Set patch version for builder. - * - * @param patch the patch - * @return the version builder - */ - public VersionBuilder withPatch(long patch) { - this.patch = patch; - return this; - } - - /** - * Set pre-release version for builder. - * - * @param preRelease the pre-release - * @return the version builder - */ - public VersionBuilder withPreRelease(String preRelease) { - this.preRelease = preRelease; - return this; - } - - /** - * Set build-metadata version for builder. - * - * @param meta the build-metadata - * @return the version builder - */ - public VersionBuilder withMeta(String meta) { - this.meta = meta; - return this; - } - - /** - * Build version. - * - * @return the {@link Version} - * @throws VersionBuildException thrown if the version could not be built - * @implSpec Major, Minor and Patch are required when building a valid semantic version. - */ - public Version build() throws VersionBuildException { - if (major == null) - throw new VersionBuildException("Major version needs to be specified."); - - if (minor == null) - throw new VersionBuildException("Minor version needs to be specified."); - - if (patch == null) - throw new VersionBuildException("Patch version needs to be specified."); - - if (major < 0L) - throw new VersionBuildException(String.format("Major version \"%s\" can't be less than 0.", major)); - - if (minor < 0L) - throw new VersionBuildException(String.format("Minor version \"%s\" can't be less than 0.", minor)); - - if (patch < 0L) - throw new VersionBuildException(String.format("Patch version \"%s\" can't be less than 0.", patch)); - - if (preRelease == null) - preRelease = ""; - - if (meta == null) - meta = ""; - - return new Version(major, minor, patch, preRelease, meta); - } -} \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCheckResult.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCheckResult.java deleted file mode 100644 index cc62050..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCheckResult.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -/** - * Contains version comparison results. - */ -public enum VersionCheckResult { - /** - * Newer version check result. - */ - NEWER, - /** - * Equal version check result. - */ - EQUAL, - /** - * Older version check result. - */ - OLDER -} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCompare.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCompare.java deleted file mode 100644 index 2547dfa..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCompare.java +++ /dev/null @@ -1,209 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Contains all methods used for comparing two {@link Version} objects according to the Semantic Versioning 2.0 specification. - */ -public abstract class VersionCompare { - /** - * Takes a result from Java compare methods and returns the equivalent {@link VersionCheckResult}. - * - * @param result any integer - * @return {@link VersionCheckResult#EQUAL} if 0, {@link VersionCheckResult#OLDER} if less than 0, {@link VersionCheckResult#NEWER} if greater than 0. - */ - @ApiStatus.Internal - private static VersionCheckResult result(long result) { - if (result == 0D) - return VersionCheckResult.EQUAL; - - return result < 0D ? VersionCheckResult.OLDER : VersionCheckResult.NEWER; - } - - /** - * Compare major, minor, patch and then pre-release data of two versions. - * - * @param current the current version to compare with - * @param other the other version to compare against - * @return the version check result - * @apiNote Follows Semver spec such that this is always true: {@code 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1}. - */ - public static @NotNull VersionCheckResult compare(@NotNull Version current, @NotNull Version other) { - final long majorChange = current.getMajor() - other.getMajor(); - final long minorChange = current.getMinor() - other.getMinor(); - final long patchChange = current.getPatch() - other.getPatch(); - - // If there are any changes in versioning we know whether the version is newer or older - if (majorChange != 0) - return result(majorChange); - - if (minorChange != 0) - return result(minorChange); - - if (patchChange != 0) - return result(patchChange); - - // No change in major, minor or patch, check pre-release identifiers - return comparePreRelease(current, other); - } - - /** - * Compare pre-release data of two versions. - * - * @param current the current version to compare with - * @param other the other version to compare against - * @return the version check result - * @apiNote Follows Semver spec such that this is always true: {@code 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0}. - */ - @SuppressWarnings("ConstantValue") - public static @NotNull VersionCheckResult comparePreRelease(@NotNull Version current, @NotNull Version other) { - final String[] currentIdentifiers = current.getPreReleaseIdentifiers(); - final String[] otherIdentifiers = other.getPreReleaseIdentifiers(); - - // If one array is empty and the other isn't, the version with a pre-release has lower precedence - if (currentIdentifiers == null || currentIdentifiers.length == 0) { - if (otherIdentifiers == null || otherIdentifiers.length == 0) { - return VersionCheckResult.EQUAL; - } - return VersionCheckResult.NEWER; // No pre-release has higher precedence - } else if (otherIdentifiers == null || otherIdentifiers.length == 0) { - return VersionCheckResult.OLDER; // identifiers1 has pre-release, identifiers2 doesn't - } - - // Compare each identifier in sequence - final int minLength = Math.min(currentIdentifiers.length, otherIdentifiers.length); - - for (int i = 0; i < minLength; i++) { - final String currentId = currentIdentifiers[i]; - final String otherId = otherIdentifiers[i]; - - // Check if both identifiers are numeric - final boolean isCurrentNumeric = isNumeric(currentId); - final boolean isOtherNumeric = isNumeric(otherId); - - // Rule 3: Numeric identifiers have lower precedence than non-numeric identifiers - if (isCurrentNumeric && !isOtherNumeric) { - return VersionCheckResult.OLDER; - } else if (!isCurrentNumeric && isOtherNumeric) { - return VersionCheckResult.NEWER; - } else if (isCurrentNumeric && isOtherNumeric) { - // Rule 1: Numeric comparison for numeric identifiers - int num1 = Integer.parseInt(currentId); - int num2 = Integer.parseInt(otherId); - - if (num1 < num2) { - return VersionCheckResult.OLDER; - } else if (num1 > num2) { - return VersionCheckResult.NEWER; - } - // Equal, continue iter to next id - } else { - // Rule 2: Lexical comparison for non-numeric identifiers - int comparison = currentId.compareTo(otherId); - - if (comparison < 0) { - return VersionCheckResult.OLDER; - } else if (comparison > 0) { - return VersionCheckResult.NEWER; - } - // Equal, continue iter to next id - } - } - - // Rule 4: If all identifiers so far are equal, the longer array has higher precedence - if (currentIdentifiers.length < otherIdentifiers.length) { - return VersionCheckResult.OLDER; - } else if (currentIdentifiers.length > otherIdentifiers.length) { - return VersionCheckResult.NEWER; - } - - // All identifiers are equal - return VersionCheckResult.EQUAL; - } - - /** - * Checks if a string is a numeric identifier according to SemVer rules. - * A numeric identifier consists of only digits with no leading zeros (except for "0" itself). - * - * @param identifier The identifier to check - * @return true if the identifier is numeric, false otherwise - * @apiNote Follows Semver spec such that this is always true: {@code 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0}. - */ - @SuppressWarnings("RedundantIfStatement") - private static boolean isNumeric(@Nullable String identifier) { - if (identifier == null || identifier.isEmpty()) - return false; - - // Check if the string consists of only digits - if (!identifier.matches("\\d+")) - return false; - - // Check for leading zeros (but "0" by itself is fine) - if (identifier.length() > 1 && identifier.startsWith("0")) - return false; - - return true; - } - - /** - * Check if two versions are the same. - * - * @param current the current version - * @param other the other version - * @return true if current version is same as other version - */ - @SuppressWarnings("unused") - public static boolean isEqual(@NotNull Version current, @NotNull Version other) { - return compare(current, other).equals(VersionCheckResult.EQUAL); - } - - /** - * Check if one version is newer. - * - * @param current the current version - * @param other the other version - * @return true if current version is newer than other version - */ - @SuppressWarnings("unused") - public static boolean isNewer(@NotNull Version current, @NotNull Version other) { - return compare(current, other).equals(VersionCheckResult.NEWER); - } - - /** - * Check if one version is older. - * - * @param current the current version - * @param other the other version - * @return true if current version is older than other version - */ - @SuppressWarnings("unused") - public static boolean isOlder(@NotNull Version current, @NotNull Version other) { - return compare(current, other).equals(VersionCheckResult.OLDER); - } - - /** - * Check if one version is newer or same. - * - * @param current the current version - * @param other the other version - * @return true if current version is newer or equal to other version - */ - @SuppressWarnings("unused") - public static boolean isNewerOrEqual(@NotNull Version current, @NotNull Version other) { - return isNewer(current, other) || isEqual(current, other); - } - - /** - * Check if one version is older or same. - * - * @param current the current version - * @param other the other version - * @return true if current version is older or equal to other version - */ - @SuppressWarnings("unused") - public static boolean isOlderOrEqual(@NotNull Version current, @NotNull Version other) { - return isOlder(current, other) || isEqual(current, other); - } -} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionParser.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionParser.java deleted file mode 100644 index 4af71fa..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionParser.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -import io.github.milkdrinkers.javasemver.exception.VersionBuildException; -import io.github.milkdrinkers.javasemver.exception.VersionParseException; - -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Contains parsing logic for {@link Version} objects. - */ -public abstract class VersionParser { - private final static Pattern SEMVER_REGEX = Pattern.compile("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); // Extract named fields for semantic version - - /** - * Parse a {@link String} into a {@link Version}. - * - * @param unparsedVersion the unparsed version string - * @return the resulting {@link Version} - * @throws VersionParseException thrown if a valid semantic version could not be parsed from the string - * @apiNote Uses {@link VersionBuilder} internally - * @implNote Preceding "V" or "v" characters are stripped from the unparsedVersion - */ - public static Version parse(String unparsedVersion) throws VersionParseException { - // Check if version meets minimal requirements (if it doesn't have 4+ characters it can't be valid semver) - if (unparsedVersion.length() < 5) - throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", unparsedVersion)); - - // Strip leading "V" before version - if (unparsedVersion.toUpperCase().startsWith("V")) - unparsedVersion = unparsedVersion.substring(1); - - try { - // Grab version details from string - final Matcher matcher = SEMVER_REGEX.matcher(unparsedVersion); - if (!matcher.matches()) - throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", unparsedVersion)); - - // Grap values from matcher groups and put in optionals - final Optional major = Optional.ofNullable(matcher.group("major")).map(Long::parseLong); - final Optional minor = Optional.ofNullable(matcher.group("minor")).map(Long::parseLong); - final Optional patch = Optional.ofNullable(matcher.group("patch")).map(Long::parseLong); - final Optional preRelease = Optional.ofNullable(matcher.group("prerelease")); - final Optional meta = Optional.ofNullable(matcher.group("meta")); - - // Check for missing data - if (!major.isPresent()) - throw new VersionParseException(String.format("Major version could not be parsed from version string \"%s\" when constructing Version object.", unparsedVersion)); - - if (!minor.isPresent()) - throw new VersionParseException(String.format("Minor version could not be parsed from version string \"%s\" when constructing Version object.", unparsedVersion)); - - if (!patch.isPresent()) - throw new VersionParseException(String.format("Patch version could not be parsed from version string \"%s\" when constructing Version object.", unparsedVersion)); - - return new VersionBuilder() - .withMajor(major.get()) - .withMinor(minor.get()) - .withPatch(patch.get()) - .withPreRelease(preRelease.orElse("")) - .withMeta(meta.orElse("")) - .build(); - } catch (IllegalStateException e) { - throw new VersionParseException(String.format("Match operation failed parsing version from string \"%s\" when constructing Version object.", unparsedVersion)); - } catch (IllegalArgumentException e) { - throw new VersionParseException(String.format("Regex groups were not found while parsing version from string \"%s\" when constructing Version object.", unparsedVersion)); - } catch (VersionBuildException e) { - throw new VersionParseException(String.format("Builder failed while parsing version from string \"%s\" when constructing Version object.", unparsedVersion), e); - } - } -} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/enums/Operator.java b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/Operator.java new file mode 100644 index 0000000..2297305 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/Operator.java @@ -0,0 +1,61 @@ +package io.github.milkdrinkers.javasemver.enums; + +/** + * Represents a comparison operator used by a single semver range comparator e.g. the {@code >=} in {@code >=1.2.3}. + */ +public enum Operator { + /** + * Greater than ({@code >}). + */ + GT(">"), + + /** + * Greater than or equal to ({@code >=}). + */ + GTE(">="), + + /** + * Less than ({@code <}). + */ + LT("<"), + + /** + * Less than or equal to ({@code <=}). + */ + LTE("<="), + + /** + * Equal to ({@code =}). + */ + EQ("="); + + private final String symbol; + + Operator(String symbol) { + this.symbol = symbol; + } + + /** + * Gets the textual symbol representing this operator e.g. {@code ">="}. + * + * @return the symbol + */ + public String getSymbol() { + return symbol; + } + + /** + * Finds the operator matching the given symbol. + * + * @param symbol the symbol to look up e.g. {@code ">="} + * @return the matching operator, or {@code null} if no operator has that symbol + */ + public static Operator fromSymbol(String symbol) { + for (Operator operator : values()) { + if (operator.symbol.equals(symbol)) + return operator; + } + + return null; + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/enums/ReleaseType.java b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/ReleaseType.java new file mode 100644 index 0000000..1d697cd --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/ReleaseType.java @@ -0,0 +1,48 @@ +package io.github.milkdrinkers.javasemver.enums; + +/** + * Represents the type of version increment to perform. + */ +public enum ReleaseType { + /** + * Increment the major version number. + * Example: 1.2.3 becomes 2.0.0 + */ + MAJOR, + + /** + * Increment the minor version number. + * Example: 1.2.3 becomes 1.3.0 + */ + MINOR, + + /** + * Increment the patch version number. + * Example: 1.2.3 becomes 1.2.4 + */ + PATCH, + + /** + * Increment to the next major version with pre-release. + * Example: 1.2.3 becomes 2.0.0-0 + */ + PREMAJOR, + + /** + * Increment to the next minor version with pre-release. + * Example: 1.2.3 becomes 1.3.0-0 + */ + PREMINOR, + + /** + * Increment to the next patch version with pre-release. + * Example: 1.2.3 becomes 1.2.4-0 + */ + PREPATCH, + + /** + * Increment the pre-release version. + * Example: 1.2.3-0 becomes 1.2.3-1, 1.2.3 becomes 1.2.4-0 + */ + PRERELEASE +} \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/RangeParseException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/RangeParseException.java new file mode 100644 index 0000000..78f7c37 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/RangeParseException.java @@ -0,0 +1,27 @@ +package io.github.milkdrinkers.javasemver.exception; + +/** + * Thrown when a version range cannot be parsed. + */ +public class RangeParseException extends VersionException { + private static final long serialVersionUID = 5693929286274031511L; + + /** + * Constructs a new range parse exception with the specified message. + * + * @param message the detail message + */ + public RangeParseException(String message) { + super(message); + } + + /** + * Constructs a new range parse exception with the specified message and cause. + * + * @param message the detail message + * @param cause the cause + */ + public RangeParseException(String message, Exception cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java index ff81a3e..27ba4ab 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java @@ -1,7 +1,7 @@ package io.github.milkdrinkers.javasemver.exception; public class VersionBuildException extends VersionException { - private static final long serialVersionUID = 6995079221117712762L; + private static final long serialVersionUID = 983442101168699796L; public VersionBuildException(String message) { super(message); diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java index b08a876..f5e08be 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java @@ -1,7 +1,7 @@ package io.github.milkdrinkers.javasemver.exception; public class VersionException extends RuntimeException { - private static final long serialVersionUID = 5869428421273062603L; + private static final long serialVersionUID = 2025937154918639292L; public VersionException(String message) { super(message); diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java index d5d2f17..1429979 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java @@ -1,7 +1,7 @@ package io.github.milkdrinkers.javasemver.exception; public class VersionParseException extends VersionException { - private static final long serialVersionUID = 3454425882326722240L; + private static final long serialVersionUID = 5172097489090152114L; public VersionParseException(String message) { super(message); diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Coercion.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Coercion.java new file mode 100644 index 0000000..be7901e --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Coercion.java @@ -0,0 +1,60 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Version; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * {@code Coerce}/{@code Clean} logic used by {@link Version}. + */ +@ApiStatus.Internal +public final class Coercion { + private static final Pattern COERCE_REGEX = Pattern.compile("(^|[^\\d])(\\d{1,16})(?:\\.(\\d{1,16}))?(?:\\.(\\d{1,16}))?(?:$|[^\\d])"); // up to three digit groups of at most 16 digits each, each delimited by a non digit or a string boundary (so over long numbers are skipped) + + private Coercion() { + } + + /** + * Scans an arbitrary string for the first thing that looks like a version and coerces it + * into a {@link Version}, defaulting any missing minor/patch component to {@code 0} and + * discarding any trailing pre release/build metadata data. + * + * @param input the string to scan + * @return the coerced version, or {@link Optional#empty()} if no version like data was found + * @apiNote The extracted {@code major.minor.patch} is parsed strictly, so an extracted component with leading zeros (e.g. {@code 01.02.03}) yields an empty result, and each component is capped at 16 digits. + */ + public static @NotNull Optional coerce(String input) { + if (input == null) + return Optional.empty(); + + final Matcher m = COERCE_REGEX.matcher(input); + if (!m.find()) + return Optional.empty(); + + final String major = m.group(2); + final String minor = m.group(3) != null ? m.group(3) : "0"; + final String patch = m.group(4) != null ? m.group(4) : "0"; + + return Version.parseOptional(major + "." + minor + "." + patch); + } + + /** + * Normalizes a loosely formatted version string into a strict semantic version string, tolerating surrounding whitespace, a leading {@code =} and a leading {@code v}/{@code V}. + * + * @param input the string to clean + * @return the cleaned version string, or {@link Optional#empty()} if the remainder was not a valid version + */ + public static @NotNull Optional clean(String input) { + if (input == null) + return Optional.empty(); + + // trip leading run of =/v characters + final String s = input.trim().replaceFirst("^[=vV]+", ""); + + return Version.parseOptional(s).map(Version::toString); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Interval.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Interval.java new file mode 100644 index 0000000..66b6a58 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Interval.java @@ -0,0 +1,250 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.Operator; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Reduces one AND-set of {@link Constraint}s (e.g. the constraints parsed from a single OR-part of + * a range, such as {@code >=1.2.0 <2.0.0}) to a single interval over the version total order. + * + *

The interval is represented as a lower and an upper bound, each of which is either a + * {@link Version} paired with an inclusive/exclusive flag, or {@code null} meaning unbounded + * ({@code -} for the lower bound, {@code +} for the upper bound). This normalized form makes it + * cheap to test two AND-sets for overlap ({@link #overlaps(Interval, Interval)}) without having to + * reason about each constraint individually.

+ * + *

All version comparisons are done via {@link VersionComparison#comparePrecedence(Version, Version)}, e.g. build metadata is ignored, matching the rest of the range matching logic.

+ */ +@ApiStatus.Internal +public final class Interval { + private final Version lower; + private final boolean lowerInclusive; + private final Version upper; + private final boolean upperInclusive; + + private Interval(@Nullable Version lower, boolean lowerInclusive, @Nullable Version upper, boolean upperInclusive) { + this.lower = lower; + this.lowerInclusive = lowerInclusive; + this.upper = upper; + this.upperInclusive = upperInclusive; + } + + /** + * Combines an AND-set of constraints into the single interval that is their intersection. + * + *

Each constraint contributes a half bounded (or, for {@code EQ}, fully bounded) interval:

+ *
    + *
  • {@code any} → {@code (-, +)}, contributes nothing.
  • + *
  • {@code EQ v} → lower = {@code v} inclusive, upper = {@code v} inclusive.
  • + *
  • {@code GT v} → lower = {@code v} exclusive.
  • + *
  • {@code GTE v} → lower = {@code v} inclusive.
  • + *
  • {@code LT v} → upper = {@code v} exclusive.
  • + *
  • {@code LTE v} → upper = {@code v} inclusive.
  • + *
+ * + *

Each contribution is intersected into a running result that starts fully unbounded + * ({@code (-, +)}):

+ *
    + *
  • A candidate lower bound replaces the running lower bound only if it is strictly + * tighter, e.g. strictly greater (a {@code -} running lower bound is always beaten + * by any real version). If the candidate's version equals the running lower bound's version, + * the version is kept but the bound becomes inclusive only if both contributions + * were inclusive, the exclusive (tighter) side wins.
  • + *
  • Uppers are handled symmetrically: a candidate replaces the running upper bound only if + * it is strictly lesser, and ties keep the exclusive side.
  • + *
+ * + * @param andSet the AND-ed constraints to combine e.g. the constraints of one OR-part of a range + * @return the interval that is the intersection of all of {@code andSet}'s constraints + */ + public static @NotNull Interval of(@NotNull List andSet) { + Bound lower = new Bound(null, false); + Bound upper = new Bound(null, false); + + for (final Constraint constraint : andSet) { + if (constraint.isAny()) + continue; + + final Operator operator = constraint.getOperator(); + final Version version = constraint.getVersion(); + + switch (operator) { + case GT: + lower = lower.tightenLower(version, false); + break; + case GTE: + lower = lower.tightenLower(version, true); + break; + case LT: + upper = upper.tightenUpper(version, false); + break; + case LTE: + upper = upper.tightenUpper(version, true); + break; + case EQ: + lower = lower.tightenLower(version, true); + upper = upper.tightenUpper(version, true); + break; + default: + break; + } + } + + return new Interval(lower.version, lower.inclusive, upper.version, upper.inclusive); + } + + /** + * An immutable (version, inclusive) pair representing one side of a running interval bound, + * with {@code version == null} meaning unbounded. Kept as its own tiny type so that tightening + * a bound is a single atomic "replace with the new (version, inclusive) pair or keep the old + * one" decision, never a partial update where the version and inclusivity are computed + * against inconsistent snapshots of each other. + */ + private static final class Bound { + private final Version version; + private final boolean inclusive; + + private Bound(@Nullable Version version, boolean inclusive) { + this.version = version; + this.inclusive = inclusive; + } + + /** + * Intersects this lower bound with a candidate {@code (candidate, candidateInclusive)} lower bound, keeping whichever is strictly tighter (greater), or, on a tie, the exclusive one. + */ + private Bound tightenLower(@NotNull Version candidate, boolean candidateInclusive) { + if (this.version == null) + return new Bound(candidate, candidateInclusive); + + final int c = VersionComparison.comparePrecedence(candidate, this.version); + if (c > 0) + return new Bound(candidate, candidateInclusive); + if (c < 0) + return this; + + return new Bound(this.version, this.inclusive && candidateInclusive); + } + + /** + * Intersects this upper bound with a candidate {@code (candidate, candidateInclusive)} upper bound, keeping whichever is strictly tighter (lesser), or, on a tie, the exclusive one. Symmetric to {@link #tightenLower}. + */ + private Bound tightenUpper(@NotNull Version candidate, boolean candidateInclusive) { + if (this.version == null) + return new Bound(candidate, candidateInclusive); + + final int c = VersionComparison.comparePrecedence(candidate, this.version); + if (c < 0) + return new Bound(candidate, candidateInclusive); + if (c > 0) + return this; + + return new Bound(this.version, this.inclusive && candidateInclusive); + } + } + + /** + * Gets the lower bound of this interval. + * + * @return the lower bound, or {@code null} meaning unbounded ({@code -}) + */ + public @Nullable Version getLower() { + return lower; + } + + /** + * Checks whether the lower bound is inclusive. + * + * @return true if the lower bound (when non null) is included in the interval + */ + public boolean isLowerInclusive() { + return lowerInclusive; + } + + /** + * Gets the upper bound of this interval. + * + * @return the upper bound, or {@code null} meaning unbounded ({@code +}) + */ + public @Nullable Version getUpper() { + return upper; + } + + /** + * Checks whether the upper bound is inclusive. + * + * @return true if the upper bound (when non null) is included in the interval + */ + public boolean isUpperInclusive() { + return upperInclusive; + } + + /** + * Checks whether this interval contains no versions at all. + * + *

When both bounds are present: empty if {@code lower > upper}, or if {@code lower == upper} + * and either end is exclusive (a single point interval {@code [v, v]} is non empty only when + * both ends include {@code v}). If either bound is unbounded ({@code null}), the interval can + * never be empty on account of that side.

+ * + * @return true if this interval is empty + */ + public boolean isEmpty() { + if (lower == null || upper == null) + return false; + + final int c = VersionComparison.comparePrecedence(lower, upper); + if (c > 0) + return true; + if (c == 0) + return !(lowerInclusive && upperInclusive); + + return false; + } + + /** + * Checks whether two intervals overlap, e.g. share at least one version. + * + *

An empty interval never overlaps anything. Otherwise, two non empty intervals overlap iff + * each one's lower endpoint is at or before the other's upper endpoint, where endpoints are only + * allowed to "touch" (equal versions) if both sides that meet there actually include that point.

+ * + * @param a the first interval + * @param b the second interval + * @return true if {@code a} and {@code b} share at least one version + */ + public static boolean overlaps(@NotNull Interval a, @NotNull Interval b) { + if (a.isEmpty() || b.isEmpty()) + return false; + + return lowerLessOrEqualUpper(a.lower, a.lowerInclusive, b.upper, b.upperInclusive) + && lowerLessOrEqualUpper(b.lower, b.lowerInclusive, a.upper, a.upperInclusive); + } + + /** + * Checks whether a lower endpoint is less than or equal to an upper endpoint, where the endpoints are only allowed to be equal ("touch") if both sides include that point. + * + * @param lowerVersion the lower endpoint's version, or {@code null} for {@code -} + * @param lowerInclusive whether the lower endpoint includes {@code lowerVersion} + * @param upperVersion the upper endpoint's version, or {@code null} for {@code +} + * @param upperInclusive whether the upper endpoint includes {@code upperVersion} + * @return true if the lower endpoint is at or before the upper endpoint + */ + private static boolean lowerLessOrEqualUpper(@Nullable Version lowerVersion, boolean lowerInclusive, @Nullable Version upperVersion, boolean upperInclusive) { + if (lowerVersion == null || upperVersion == null) + return true; + + final int c = VersionComparison.comparePrecedence(lowerVersion, upperVersion); + if (c < 0) + return true; + if (c > 0) + return false; + + return lowerInclusive && upperInclusive; + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeAlgebra.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeAlgebra.java new file mode 100644 index 0000000..ba50793 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeAlgebra.java @@ -0,0 +1,433 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Range; +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.Operator; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * Algebraic operations over {@link Range} built on top of the per comparator set {@link Interval} representation e.g. computing the lowest version a range can possibly admit. + */ +@ApiStatus.Internal +public final class RangeAlgebra { + private RangeAlgebra() { + } + + /** + * Finds the lowest version a range can possibly satisfy. + *

+ * Each comparator set (an AND-group joined by OR at the {@link Range} level) is reduced to an + * {@link Interval}. Empty intervals contribute nothing. For every other interval, the interval's + * own minimum is computed, {@code 0.0.0} for an unbounded ({@code -}) lower bound, the + * lower bound itself when inclusive, or the next version after it when exclusive, and kept + * only if the range as a whole actually admits it, since the interval's minimum can still be + * excluded by another AND-ed upper bound or by the range's prerelease rule. The least of the + * surviving candidates is the range's minimum. + * + * @param range the range to inspect + * @return the lowest satisfying version by natural order, or an empty optional if the range admits no version at all + */ + public static @NotNull Optional minVersion(@NotNull Range range) { + final List candidates = new ArrayList<>(); + + for (final List comparatorSet : range.getComparatorSets()) { + final Interval interval = Interval.of(comparatorSet); + if (interval.isEmpty()) + continue; + + final Version candidate = intervalMinimum(interval); + if (range.contains(candidate)) + candidates.add(candidate); + } + + return candidates.stream().min(Comparator.naturalOrder()); + } + + /** + * Checks whether two ranges intersect, e.g. share at least one version they could both possibly match. + *

+ * Each range is reduced to its list of non empty {@link Interval}s, one per comparator set + * (an AND-group joined by OR at the {@link Range} level). Comparator sets that reduce to an + * empty interval (contradictory AND-ed constraints) are dropped, since they can never + * contribute a version. The two ranges intersect iff some interval of {@code a} overlaps some interval of {@code b}. + * + * @param a the first range + * @param b the second range + * @return true if the ranges share at least one version they could both match + */ + public static boolean intersects(@NotNull Range a, @NotNull Range b) { + final List intervalsA = nonEmptyIntervals(a); + final List intervalsB = nonEmptyIntervals(b); + + for (final Interval intervalA : intervalsA) { + for (final Interval intervalB : intervalsB) { + if (Interval.overlaps(intervalA, intervalB)) + return true; + } + } + + return false; + } + + /** + * An unbounded lower bound comparator, standing in for the {@code *} wildcard. + */ + private static final Constraint GTE_ZERO = new Constraint(Operator.GTE, Version.of(0L, 0L, 0L)); + + /** + * Checks whether a version's precedence is greater than everything a range could possibly match. + * + * @param v the version to check + * @param range the range to check against + * @return true if {@code v} is above every version {@code range} could match + */ + public static boolean isAbove(@NotNull Version v, @NotNull Range range) { + return outside(v, range, true); + } + + /** + * Checks whether a version's precedence is less than everything a range could possibly match. + * + * @param v the version to check + * @param range the range to check against + * @return true if {@code v} is below every version {@code range} could match + */ + public static boolean isBelow(@NotNull Version v, @NotNull Range range) { + return outside(v, range, false); + } + + /** + * Whether {@code version} lies entirely above ({@code above == true}, e.g. {@code gtr}) or below ({@code above == false}, e.g. {@code ltr}) + * every version {@code range} could match. For each OR-ed comparator set the highest and + * lowest semver comparators are found, then the version is judged against those endpoints and their operators. + * + * @param version the version to check + * @param range the range to check against + * @param above {@code true} for {@code gtr}, {@code false} for {@code ltr} + * @return true if {@code version} is entirely outside {@code range} in the requested direction + */ + private static boolean outside(@NotNull Version version, @NotNull Range range, boolean above) { + if (range.contains(version)) + return false; + + for (final List set : range.getComparatorSets()) { + Constraint high = null; + Constraint low = null; + for (final Constraint raw : set) { + final Constraint c = raw.isAny() ? GTE_ZERO : raw; + if (high == null) { + high = c; + low = c; + } else if (edgeGreater(c.getVersion(), high.getVersion(), above)) { + high = c; + } else if (edgeLess(c.getVersion(), low.getVersion(), above)) { + low = c; + } + } + + if (high == null) + continue; + + final Operator hop = high.getOperator(); + final Operator lop = low.getOperator(); + + if (above) { + if (hop == Operator.GT || hop == Operator.GTE) + return false; + if ((lop == Operator.EQ || lop == Operator.GT) && lte(version, low.getVersion())) + return false; + else if (lop == Operator.GTE && lt(version, low.getVersion())) + return false; + } else { + if (hop == Operator.LT || hop == Operator.LTE) + return false; + if ((lop == Operator.EQ || lop == Operator.LT) && gte(version, low.getVersion())) + return false; + else if (lop == Operator.LTE && gt(version, low.getVersion())) + return false; + } + } + + return true; + } + + /** + * greater than for {@code gtr}, less than for {@code ltr}. + */ + private static boolean edgeGreater(Version a, Version b, boolean above) { + return above ? gt(a, b) : lt(a, b); + } + + /** + * less than for {@code gtr}, greater than for {@code ltr}. + */ + private static boolean edgeLess(Version a, Version b, boolean above) { + return above ? lt(a, b) : gt(a, b); + } + + private static boolean gt(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) > 0; + } + + private static boolean lt(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) < 0; + } + + private static boolean gte(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) >= 0; + } + + private static boolean lte(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) <= 0; + } + + /** + * Checks whether every version {@code sub} could possibly match is also matched by {@code dom}, e.g. whether {@code sub} is a subset of {@code dom}. + *

+ * {@code *} is the universal set, so anything is a subset of it, and it is a subset of nothing + * else (unless the other side is also {@code *}). Otherwise both ranges are reduced to their + * non empty {@link Interval}s (one per comparator set, dropping contradictory AND-groups). A + * range that reduces to no intervals at all matches nothing, and the empty set is trivially a + * subset of anything. Otherwise {@code sub} is a subset of {@code dom} iff every one of + * {@code sub}'s intervals is wholly contained within a single one of {@code dom}'s intervals + * a sub interval that straddles two dom intervals (with a gap of unmatched versions between them) is not contained in either. + * + * @param sub the candidate subset range + * @param dom the candidate superset (dominating) range + * @return true if every version {@code sub} could match, {@code dom} could also match + */ + public static boolean isSubsetOf(@NotNull Range sub, @NotNull Range dom) { + if (dom.isAny()) + return true; + + if (sub.isAny()) + return false; + + for (final List subSet : sub.getComparatorSets()) { + final Interval subInterval = Interval.of(subSet); + if (subInterval.isEmpty()) + continue; + + boolean contained = false; + for (final List domSet : dom.getComparatorSets()) { + final Interval domInterval = Interval.of(domSet); + if (domInterval.isEmpty()) + continue; + + if (contains(domInterval, subInterval) && prereleaseCovered(subInterval, domSet)) { + contained = true; + break; + } + } + + if (!contained) + return false; + } + + return true; + } + + /** + * If the sub range's lower or upper bound + * carries a prerelease, the matched dominator set must contain a comparator that also has a + * prerelease and shares that bound's major.minor.patch tuple, otherwise the sub range + * admits prereleases in that tuple the dominator does not. The exclusive {@code domSet) { + final Version lower = subInterval.getLower(); + if (lower != null && lower.hasPreRelease() && !domHasPrereleaseAtTuple(domSet, lower)) + return false; + + final Version upper = subInterval.getUpper(); + return upper == null || !upper.hasPreRelease() || isExclusiveZeroPrerelease(subInterval) || domHasPrereleaseAtTuple(domSet, upper); + } + + /** + * Whether the interval's upper bound is an exclusive {@code domSet, @NotNull Version tuple) { + for (final Constraint c : domSet) { + final Version cv = c.getVersion(); + if (cv != null && cv.hasPreRelease() + && cv.getMajor() == tuple.getMajor() + && cv.getMinor() == tuple.getMinor() + && cv.getPatch() == tuple.getPatch()) + return true; + } + + return false; + } + + /** + * Checks whether a (contiguous) interval {@code subI} is wholly contained within another + * (contiguous) interval {@code domI}, e.g. {@code domI}'s lower bound is at or before + * {@code subI}'s lower bound, and {@code domI}'s upper bound is at or after {@code subI}'s + * upper bound, both honoring inclusivity at the boundary. + * + * @param domI the (candidate) containing interval + * @param subI the (candidate) contained interval + * @return true if every version {@code subI} admits is also admitted by {@code domI} + */ + private static boolean contains(@NotNull Interval domI, @NotNull Interval subI) { + final boolean lowerCovered; + if (domI.getLower() == null) { + lowerCovered = true; + } else if (subI.getLower() == null) { + lowerCovered = false; + } else { + final int c = VersionComparison.comparePrecedence(domI.getLower(), subI.getLower()); + if (c < 0) + lowerCovered = true; + else if (c > 0) + lowerCovered = false; + else + lowerCovered = domI.isLowerInclusive() || !subI.isLowerInclusive(); + } + + final boolean upperCovered; + if (domI.getUpper() == null) { + upperCovered = true; + } else if (subI.getUpper() == null) { + upperCovered = false; + } else { + final int c = VersionComparison.comparePrecedence(subI.getUpper(), domI.getUpper()); + if (c < 0) + upperCovered = true; + else if (c > 0) + upperCovered = false; + else + upperCovered = domI.isUpperInclusive() || !subI.isUpperInclusive(); + } + + return lowerCovered && upperCovered; + } + + /** + * Reduces a range's comparator sets to the list of their non empty {@link Interval}s. + * + * @param range the range to inspect + * @return the non empty intervals of {@code range}, one per comparator set that isn't contradictory + */ + private static @NotNull List nonEmptyIntervals(@NotNull Range range) { + final List intervals = new ArrayList<>(); + + for (final List comparatorSet : range.getComparatorSets()) { + final Interval interval = Interval.of(comparatorSet); + if (!interval.isEmpty()) + intervals.add(interval); + } + + return intervals; + } + + /** + * Finds the shortest range that has the same membership as {@code range} over a supplied set of versions. + *

+ * The versions are sorted ascending and walked in order, grouping them into maximal + * contiguous runs of versions that satisfy {@code range}, a run breaks the moment a + * version fails {@link Range#contains(Version)}. Each run of satisfying versions is then + * collapsed to its tightest primitive form: a single satisfying version becomes that bare + * version, and a run spanning several becomes {@code >=first <=last}. The resulting forms are + * OR-ed together. This does not attempt to find the globally shortest string it only + * guarantees membership over the versions supplied is preserved. + * + * @param versions the versions whose membership in {@code range} must be preserved + * @param range the range to simplify + * @return a range equivalent to {@code range} for every version in {@code versions}, if none of the supplied versions satisfy {@code range}, a range that matches nothing + */ + public static @NotNull Range simplify(@NotNull Collection versions, @NotNull Range range) { + final List sorted = new ArrayList<>(versions); + Collections.sort(sorted); + + final List runs = new ArrayList<>(); + Version runFirst = null; + Version runLast = null; + + for (final Version v : sorted) { + if (range.contains(v)) { + if (runFirst == null) + runFirst = v; + + runLast = v; + } else if (runFirst != null) { + runs.add(formatRun(runFirst, runLast)); + runFirst = null; + runLast = null; + } + } + + if (runFirst != null) + runs.add(formatRun(runFirst, runLast)); + + if (runs.isEmpty()) + return Range.parse("<0.0.0-0"); + + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < runs.size(); i++) { + if (i > 0) + sb.append(" || "); + + sb.append(runs.get(i)); + } + + return Range.parse(sb.toString()); + } + + /** + * Formats a single contiguous run of satisfying versions as its tightest primitive range form. + * + * @param first the first (lowest) version of the run + * @param last the last (highest) version of the run + * @return {@code first.toString()} if the run is a single version, otherwise {@code >=first <=last} + */ + private static @NotNull String formatRun(@NotNull Version first, @NotNull Version last) { + if (first.equals(last)) + return first.toString(); + + return ">=" + first + " <=" + last; + } + + /** + * Computes the lowest version an individual (non empty) interval could admit, ignoring + * anything outside of the interval itself (e.g. not yet checked against the owning range's + * other comparator sets or prerelease rule). + * + * @param interval the interval to inspect, must not be empty + * @return the interval's own lower bound candidate + */ + private static @NotNull Version intervalMinimum(@NotNull Interval interval) { + final Version lower = interval.getLower(); + + if (lower == null) + return Version.of(0, 0, 0); + + if (interval.isLowerInclusive()) + return lower; + + if (lower.hasPreRelease()) + return Version.of(lower.getMajor(), lower.getMinor(), lower.getPatch(), lower.getPreRelease() + ".0"); + + return Version.of(lower.getMajor(), lower.getMinor(), lower.getPatch() + 1); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeParsing.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeParsing.java new file mode 100644 index 0000000..c5243f3 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeParsing.java @@ -0,0 +1,379 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.Operator; +import io.github.milkdrinkers.javasemver.exception.RangeParseException; +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Range parsing logic used by {@code Range}. + * + * @apiNote Handles the OR-split ({@code ||}), hyphen ranges (e.g. {@code 1.2.3 - 2.3.4}), + * space separated primitive comparators (plus the {@code *}/empty wildcard), X-ranges/partial + * versions (e.g. {@code 1.x}, {@code 1.2.*}, {@code 1}, {@code 1.2}), and tilde/caret ranges (e.g. {@code ~1.2.3}, {@code ^1.2.3}) within each OR-part. + */ +@ApiStatus.Internal +public final class RangeParsing { + /** + * Matches a bare (no leading operator) X-range or partial version token e.g. {@code 1}, + * {@code 1.2}, {@code 1.2.3}, {@code 1.x}, {@code 1.2.*}. Each of the up to three dot separated + * components is either a non negative integer or a wildcard marker ({@code x}, {@code X} or {@code *}), trailing components may be omitted entirely. + */ + private static final Pattern X_RANGE_PATTERN = Pattern.compile("^([0-9]+|[xX*])(\\.([0-9]+|[xX*])(\\.([0-9]+|[xX*]))?)?$"); + + /** + * Matches an entire hyphen range OR-part e.g. {@code 1.2.3 - 2.3.4} a token, then a hyphen + * surrounded by whitespace, then a token. Must be checked before the OR-part is split on + * whitespace, since the from/to tokens themselves contain no spaces but are separated by them. + */ + private static final Pattern HYPHEN_RANGE_PATTERN = Pattern.compile("^\\s*(\\S+)\\s+-\\s+(\\S+)\\s*$"); + + private RangeParsing() { + } + + /** + * Parses a range string into a list of comparator sets, where the outer list represents an OR relationship between sets and the inner list represents an AND relationship between the constraints of a set. + * + * @param rangeStr the range string to parse + * @return the parsed comparator sets + * @throws RangeParseException thrown if the range string could not be parsed + */ + public static List> parse(String rangeStr) throws RangeParseException { + return parse(rangeStr, false); + } + + /** + * Parses a range string into a list of comparator sets, where the outer list represents an + * OR relationship between sets and the inner list represents an AND relationship between the constraints of a set. + * + * @param rangeStr the range string to parse + * @param loose whether primitive comparator versions (e.g. {@code >=v1.2.0}) should be parsed leniently via {@link io.github.milkdrinkers.javasemver.Version#parseLoose(String)} rather than strictly + * @return the parsed comparator sets + * @throws RangeParseException thrown if the range string could not be parsed + */ + public static List> parse(String rangeStr, boolean loose) throws RangeParseException { + if (rangeStr == null) + throw new RangeParseException("Could not parse range from: null"); + + final String trimmed = rangeStr.trim(); + + if (trimmed.isEmpty() || trimmed.equals("*")) { + final List anySet = new ArrayList<>(); + anySet.add(Constraint.any()); + final List> comparatorSets = new ArrayList<>(); + comparatorSets.add(anySet); + return comparatorSets; + } + + final List> comparatorSets = new ArrayList<>(); + for (String orPart : trimmed.split("\\|\\|")) { + if (orPart.trim().isEmpty()) + continue; + + final List comparatorSet = parseComparatorSet(orPart, loose); + if (!comparatorSet.isEmpty()) + comparatorSets.add(comparatorSet); + } + + if (comparatorSets.isEmpty()) + throw new RangeParseException("Could not parse range from: " + rangeStr); + + return comparatorSets; + } + + /** + * Parses a single OR-part (a space separated series of comparators that must all match, e.g. an AND relationship) into a comparator set. + * + *

A hyphen range (e.g. {@code 1.2.3 - 2.3.4}) is detected first, before the OR-part is + * split on whitespace, since it is itself whitespace separated (unlike every other supported + * token). Anything that is not a hyphen range falls through to the original token by token handling.

+ */ + private static List parseComparatorSet(String part, boolean loose) { + final Matcher hyphenMatcher = HYPHEN_RANGE_PATTERN.matcher(part); + if (hyphenMatcher.matches()) + return expandHyphenRange(part, hyphenMatcher.group(1), hyphenMatcher.group(2)); + + final List constraints = new ArrayList<>(); + + for (String token : part.trim().split("\\s+")) { + if (token.isEmpty()) + continue; + + if (token.charAt(0) == '~') { + constraints.addAll(expandTildeRange(token)); + continue; + } + + if (token.charAt(0) == '^') { + constraints.addAll(expandCaretRange(token)); + continue; + } + + if (X_RANGE_PATTERN.matcher(token).matches()) { + constraints.addAll(expandXRange(token)); + continue; + } + + constraints.add(Constraint.parse(token, loose)); + } + + return constraints; + } + + /** + * Expands a hyphen range (e.g. {@code 1.2.3 - 2.3.4}), already split into its {@code from} and {@code to} tokens by {@link #HYPHEN_RANGE_PATTERN}, into the equivalent {@code >=lower <=upper} (or {@code + *
  • {@code from} is partial → missing components are filled with zero for the lower + * bound e.g. {@code 1.2 - ...} → {@code >=1.2.0 ...}, {@code 1 - ...} → + * {@code >=1.0.0 ...}.
  • + *
  • {@code to} is fully specified → {@code <=} that version (prerelease preserved).
  • + *
  • {@code to} is partial → the next component up is bumped and the bound becomes + * exclusive e.g. {@code ... - 2.3} → {@code ... <2.4.0-0}, {@code ... - 2} → + * {@code ... <3.0.0-0}.
  • + * + */ + private static List expandHyphenRange(String part, String fromToken, String toToken) { + final PartialVersion from = parsePartial(part, fromToken); + final PartialVersion to = parsePartial(part, toToken); + + final List constraints = new ArrayList<>(); + + if (!from.any) + constraints.add(new Constraint(Operator.GTE, from.toLowerVersion())); + + if (to.any) + return constraints; + + if (to.specified >= 3) { + constraints.add(new Constraint(Operator.LTE, to.toLowerVersion())); + return constraints; + } + + final Version upper = to.specified == 2 + ? Version.of(to.major, to.minor + 1L, 0L, "0") + : Version.of(to.major + 1L, 0L, 0L, "0"); + constraints.add(new Constraint(Operator.LT, upper)); + + return constraints; + } + + /** + * Expands a bare X-range/partial version token (already validated against {@link #X_RANGE_PATTERN}) into the equivalent list of AND-ed constraints. + * + *
      + *
    • major is a wildcard (e.g. {@code x}, {@code *}) → a single {@link Constraint#any()}
    • + *
    • minor is a wildcard or absent (e.g. {@code 1}, {@code 1.x}) → {@code >=M.0.0 <(M+1).0.0}
    • + *
    • patch is a wildcard or absent (e.g. {@code 1.2}, {@code 1.2.x}) → {@code >=M.m.0 + *
    • fully specified (e.g. {@code 1.2.3}) → a single {@code =M.m.p} equality
    • + *
    + */ + private static List expandXRange(String token) { + final String[] parts = token.split("\\.", -1); + final String majorPart = parts[0]; + final String minorPart = parts.length > 1 ? parts[1] : null; + final String patchPart = parts.length > 2 ? parts[2] : null; + + final List constraints = new ArrayList<>(); + + if (isWildcardComponent(majorPart)) { + constraints.add(Constraint.any()); + return constraints; + } + + try { + final long major = Long.parseLong(majorPart); + + if (isWildcardComponent(minorPart)) { + constraints.add(new Constraint(Operator.GTE, Version.of(major, 0L, 0L))); + constraints.add(new Constraint(Operator.LT, Version.of(major + 1L, 0L, 0L, "0"))); + return constraints; + } + + final long minor = Long.parseLong(minorPart); + + if (isWildcardComponent(patchPart)) { + constraints.add(new Constraint(Operator.GTE, Version.of(major, minor, 0L))); + constraints.add(new Constraint(Operator.LT, Version.of(major, minor + 1L, 0L, "0"))); + return constraints; + } + + final long patch = Long.parseLong(patchPart); + constraints.add(new Constraint(Operator.EQ, Version.of(major, minor, patch))); + return constraints; + } catch (NumberFormatException e) { + throw new RangeParseException("Could not parse range from: " + token, e); + } + } + + /** + * Expands a tilde range token (e.g. {@code ~1.2.3}), which already carries its leading {@code ~}, into the equivalent {@code >=lower Allows patch level changes if minor is specified, otherwise minor level changes:

    + *
      + *
    • {@code ~M.m.p} → {@code >=M.m.p + *
    • {@code ~M.m} → {@code >=M.m.0 + *
    • {@code ~M} → {@code >=M.0.0 <(M+1).0.0-0}
    • + *
    + * + *

    A prerelease on the version part (e.g. {@code ~1.2.3-beta}) is preserved on the lower bound but never affects the upper bound.

    + */ + private static List expandTildeRange(String token) { + final PartialVersion partial = parsePartial(token, token.substring(1)); + + final List constraints = new ArrayList<>(); + if (partial.any) { + constraints.add(Constraint.any()); + return constraints; + } + + constraints.add(new Constraint(Operator.GTE, partial.toLowerVersion())); + + final Version upper = partial.specified >= 2 + ? Version.of(partial.major, partial.minor + 1L, 0L, "0") + : Version.of(partial.major + 1L, 0L, 0L, "0"); + constraints.add(new Constraint(Operator.LT, upper)); + + return constraints; + } + + /** + * Expands a caret range token (e.g. {@code ^1.2.3}), which already carries its leading {@code ^}, into the equivalent {@code >=lower Allows changes that do not modify the left most non zero of the specified + * components e.g. {@code ^1.2.3} → {@code >=1.2.3 <2.0.0-0}, {@code ^0.2.3} → + * {@code >=0.2.3 <0.3.0-0}, {@code ^0.0.3} → {@code >=0.0.3 <0.0.4-0}. If every specified + * component is zero (e.g. {@code ^0.0}), the least significant specified component is bumped + * instead (e.g. {@code ^0.0} → {@code <0.1.0-0}, {@code ^0} → {@code <1.0.0-0}).

    + * + *

    A prerelease on the version part is preserved on the lower bound but never affects the upper bound.

    + */ + private static List expandCaretRange(String token) { + final PartialVersion partial = parsePartial(token, token.substring(1)); + + final List constraints = new ArrayList<>(); + if (partial.any) { + constraints.add(Constraint.any()); + return constraints; + } + + constraints.add(new Constraint(Operator.GTE, partial.toLowerVersion())); + constraints.add(new Constraint(Operator.LT, caretUpperBound(partial))); + + return constraints; + } + + /** + * Computes the caret upper bound: the left most non zero of the specified major/minor/patch + * components is incremented and everything to its right is zeroed. If every specified + * component is zero, the least significant specified component is incremented instead. + */ + private static Version caretUpperBound(PartialVersion partial) { + if (partial.major > 0L) + return Version.of(partial.major + 1L, 0L, 0L, "0"); + + if (partial.specified >= 2 && partial.minor > 0L) + return Version.of(0L, partial.minor + 1L, 0L, "0"); + + if (partial.specified >= 3 && partial.patch > 0L) + return Version.of(0L, 0L, partial.patch + 1L, "0"); + + switch (partial.specified) { + case 1: + return Version.of(1L, 0L, 0L, "0"); + case 2: + return Version.of(0L, 1L, 0L, "0"); + default: + return Version.of(0L, 0L, 1L, "0"); + } + } + + /** + * Parses the version part following a stripped {@code ~}/{@code ^} prefix into a + * {@link PartialVersion}, tolerating X-range wildcards/absent trailing components (e.g. + * {@code 1}, {@code 1.2}, {@code 1.x}) and a full version carrying a prerelease (e.g. {@code 1.2.3-beta}). + * + * @param token the original token (including its {@code ~}/{@code ^} prefix), used only for error messages + * @param remainder the version part with the {@code ~}/{@code ^} prefix already stripped + */ + private static PartialVersion parsePartial(String token, String remainder) { + try { + // build metadata never affects, strip here + final int plusIndex = remainder.indexOf('+'); + final String noBuild = plusIndex >= 0 ? remainder.substring(0, plusIndex) : remainder; + + if (noBuild.contains("-")) { + final Version v = Version.parse(noBuild); + return new PartialVersion(v.getMajor(), v.getMinor(), v.getPatch(), 3, v.getPreRelease(), false); + } + + if (!X_RANGE_PATTERN.matcher(noBuild).matches()) + throw new RangeParseException("Could not parse range from: " + token); + + final String[] parts = noBuild.split("\\.", -1); + final String majorPart = parts[0]; + final String minorPart = parts.length > 1 ? parts[1] : null; + final String patchPart = parts.length > 2 ? parts[2] : null; + + if (isWildcardComponent(majorPart)) + return new PartialVersion(0L, 0L, 0L, 0, "", true); + + final long major = Long.parseLong(majorPart); + + if (isWildcardComponent(minorPart)) + return new PartialVersion(major, 0L, 0L, 1, "", false); + + final long minor = Long.parseLong(minorPart); + + if (isWildcardComponent(patchPart)) + return new PartialVersion(major, minor, 0L, 2, "", false); + + final long patch = Long.parseLong(patchPart); + return new PartialVersion(major, minor, patch, 3, "", false); + } catch (RangeParseException e) { + throw e; + } catch (RuntimeException e) { + throw new RangeParseException("Could not parse range from: " + token, e); + } + } + + /** + * Checks whether an X-range component is a wildcard, e.g. {@code x}, {@code X}, {@code *}, or absent (null). + */ + private static boolean isWildcardComponent(String component) { + return component == null || component.equals("x") || component.equals("X") || component.equals("*"); + } + + /** + * The decomposed major/minor/patch/prerelease of a tilde/caret version part, along with how + * many of the leading components were actually specified (as opposed to wildcard/absent) and + * whether the whole thing was a bare wildcard (e.g. {@code ~x}, {@code ^*}). + */ + private static final class PartialVersion { + private final long major; + private final long minor; + private final long patch; + private final int specified; + private final String preRelease; + private final boolean any; + + private PartialVersion(long major, long minor, long patch, int specified, String preRelease, boolean any) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.specified = specified; + this.preRelease = preRelease; + this.any = any; + } + + private Version toLowerVersion() { + return preRelease.isEmpty() ? Version.of(major, minor, patch) : Version.of(major, minor, patch, preRelease); + } + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionComparison.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionComparison.java new file mode 100644 index 0000000..4b48f8d --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionComparison.java @@ -0,0 +1,142 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Version; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Precedence and build aware comparison logic used by {@link Version}. + */ +@ApiStatus.Internal +public final class VersionComparison { + private VersionComparison() { + } + + /** + * Compares two versions by Semantic Versioning precedence: major, minor, patch and then + * pre release identifiers. Build metadata plays no role in this comparison. + * + * @param a the first version + * @param b the second version + * @return {@code -1}, {@code 0} or {@code 1} as {@code a} is less than, equal to, or greater than {@code b} + */ + public static int comparePrecedence(@NotNull Version a, @NotNull Version b) { + int result = signum(a.getMajor() - b.getMajor()); + if (result != 0) + return result; + + result = signum(a.getMinor() - b.getMinor()); + if (result != 0) + return result; + + result = signum(a.getPatch() - b.getPatch()); + if (result != 0) + return result; + + return comparePreRelease(a.getPreReleaseIdentifiers(), b.getPreReleaseIdentifiers()); + } + + /** + * Compares two versions by Semantic Versioning precedence and then by build metadata as a tiebreak. + * A version with build metadata is considered greater than an otherwise equal version without any. This is the opposite of how missing pre release identifiers are treated. + * + * @param a the first version + * @param b the second version + * @return {@code -1}, {@code 0} or {@code 1} as {@code a} is less than, equal to, or greater than {@code b} + */ + public static int compareBuild(@NotNull Version a, @NotNull Version b) { + final int p = comparePrecedence(a, b); + if (p != 0) + return p; + + return compareBuildIdentifiers(splitDot(a.getBuildMetadata()), splitDot(b.getBuildMetadata())); + } + + /** + * Compares pre release identifier lists. + */ + private static int comparePreRelease(@NotNull List currentIdentifiers, @NotNull List otherIdentifiers) { + if (currentIdentifiers.isEmpty()) { + return otherIdentifiers.isEmpty() ? 0 : 1; + } else if (otherIdentifiers.isEmpty()) { + return -1; + } + + final int minLength = Math.min(currentIdentifiers.size(), otherIdentifiers.size()); + + for (int i = 0; i < minLength; i++) { + final int comparison = compareIdentifier(currentIdentifiers.get(i), otherIdentifiers.get(i)); + if (comparison != 0) + return comparison; + } + + return signum(currentIdentifiers.size() - otherIdentifiers.size()); + } + + /** + * Compares build metadata identifier lists. Unlike pre release identifiers, an empty (missing) build metadata identifier list sorts lower, not higher. + */ + private static int compareBuildIdentifiers(@NotNull List currentIdentifiers, @NotNull List otherIdentifiers) { + final int minLength = Math.min(currentIdentifiers.size(), otherIdentifiers.size()); + + for (int i = 0; i < minLength; i++) { + final int comparison = compareIdentifier(currentIdentifiers.get(i), otherIdentifiers.get(i)); + if (comparison != 0) + return comparison; + } + + return signum(currentIdentifiers.size() - otherIdentifiers.size()); + } + + /** + * Compares a single pair of dot separated identifiers using the numeric vs alphanumeric rule. + * Numeric identifiers are compared numerically and are always lower than alphanumeric ones, which are compared lexically. + */ + private static int compareIdentifier(@NotNull String currentId, @NotNull String otherId) { + final boolean isCurrentNumeric = isNumeric(currentId); + final boolean isOtherNumeric = isNumeric(otherId); + + if (isCurrentNumeric && !isOtherNumeric) { + return -1; + } else if (!isCurrentNumeric && isOtherNumeric) { + return 1; + } else if (isCurrentNumeric) { + return signum(Long.parseLong(currentId) - Long.parseLong(otherId)); + } else { + return signum(currentId.compareTo(otherId)); + } + } + + /** + * Checks if an identifier is numeric, e.g. consists only of digits. Leading zeros are allowed. Strictly parsed pre release identifiers can never carry them, but build metadata identifiers may. + * + * @param identifier The identifier to check + * @return true if the identifier is numeric, false otherwise + */ + private static boolean isNumeric(@Nullable String identifier) { + return identifier != null && !identifier.isEmpty() && identifier.matches("\\d+"); + } + + /** + * Splits a dot separated identifier string into its component identifiers, treating an empty string as an empty list rather than a single empty identifier. + */ + private static @NotNull List splitDot(@NotNull String value) { + final List ids = new ArrayList<>(); + for (String s : value.split("\\.")) { + if (!s.isEmpty()) + ids.add(s); + } + return ids; + } + + /** + * Clamps an arbitrary integer difference to {@code -1}, {@code 0} or {@code 1}. + */ + private static int signum(long value) { + return Long.compare(value, 0L); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionParsing.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionParsing.java new file mode 100644 index 0000000..acc7082 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionParsing.java @@ -0,0 +1,86 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.exception.VersionParseException; +import org.jetbrains.annotations.ApiStatus; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Strict semantic version parsing logic used by {@link Version}. + */ +@ApiStatus.Internal +public final class VersionParsing { + private static final Pattern SEMVER_REGEX = Pattern.compile( + "^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)" + + "(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" + + "(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + + "(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + + private static final Pattern LOOSE_SEMVER_REGEX = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z-.]+))?(?:\\+([0-9A-Za-z-.]+))?$"); + + private VersionParsing() { + } + + public static Version parseStrict(String input) { + if (input == null) + throw new VersionParseException("Version string was null."); + + String s = input.trim(); + + if (s.length() >= 1 && (s.charAt(0) == 'v' || s.charAt(0) == 'V')) + s = s.substring(1); + + final Matcher m = SEMVER_REGEX.matcher(s); + if (!m.matches()) + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input)); + + final String pre = m.group("prerelease"); + final String meta = m.group("meta"); + + try { + return Version.of( + Long.parseLong(m.group("major")), + Long.parseLong(m.group("minor")), + Long.parseLong(m.group("patch")), + pre == null ? "" : pre, + meta == null ? "" : meta + ); + } catch (NumberFormatException e) { + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input), e); + } + } + + public static Version parseLoose(String input) { + if (input == null) + throw new VersionParseException("Version string was null."); + + String s = input.trim(); + + if (s.length() >= 1 && s.charAt(0) == '=') + s = s.substring(1); + + if (s.length() >= 1 && (s.charAt(0) == 'v' || s.charAt(0) == 'V')) + s = s.substring(1); + + final Matcher m = LOOSE_SEMVER_REGEX.matcher(s); + if (!m.matches()) + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input)); + + final String pre = m.group(4); + final String meta = m.group(5); + + try { + return Version.of( + Long.parseLong(m.group(1)), + Long.parseLong(m.group(2)), + Long.parseLong(m.group(3)), + pre == null ? "" : pre, + meta == null ? "" : meta + ); + } catch (NumberFormatException e) { + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input), e); + } + } +} diff --git a/common/src/test/java/SemVerTest.java b/common/src/test/java/SemVerTest.java index 14a1f26..26c95be 100644 --- a/common/src/test/java/SemVerTest.java +++ b/common/src/test/java/SemVerTest.java @@ -16,7 +16,7 @@ public class SemVerTest { class ParsingTests { @Test public void testBasicParsing() { - Version v = Version.of("1.2.3"); + Version v = Version.parse("1.2.3"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -26,7 +26,7 @@ public void testBasicParsing() { @Test public void testParsingWithPreRelease() { - Version v = Version.of("1.2.3-alpha.1"); + Version v = Version.parse("1.2.3-alpha.1"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -36,7 +36,7 @@ public void testParsingWithPreRelease() { @Test public void testParsingWithBuildMetadata() { - Version v = Version.of("1.2.3+build.20230101"); + Version v = Version.parse("1.2.3+build.20230101"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -46,7 +46,7 @@ public void testParsingWithBuildMetadata() { @Test public void testParsingWithPreReleaseAndBuildMetadata() { - Version v = Version.of("1.2.3-alpha.1+build.20230101"); + Version v = Version.parse("1.2.3-alpha.1+build.20230101"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -56,7 +56,7 @@ public void testParsingWithPreReleaseAndBuildMetadata() { @Test public void testParsingWithLeadingZeros() { - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.02.03")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.02.03")); } @ParameterizedTest @@ -73,7 +73,7 @@ public void testParsingWithLeadingZeros() { "-1.2.3" }) public void testInvalidVersions(String version) { - Assertions.assertThrows(VersionParseException.class, () -> Version.of(version)); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse(version)); } @ParameterizedTest @@ -86,7 +86,7 @@ public void testInvalidVersions(String version) { "1.2.3-rc.1+build.123" }) public void testValidVersions(String version) { - Assertions.assertDoesNotThrow(() -> Version.of(version)); + Assertions.assertDoesNotThrow(() -> Version.parse(version)); } } @@ -94,81 +94,81 @@ public void testValidVersions(String version) { class ComparisonTests { @Test public void testReleaseVsPreRelease() { - Version release = Version.of("1.0.0"); - Version preRelease = Version.of("1.0.0-alpha"); + Version release = Version.parse("1.0.0"); + Version preRelease = Version.parse("1.0.0-alpha"); - Assertions.assertTrue(Version.isNewer(release, preRelease)); - Assertions.assertTrue(Version.isNewerOrEqual(release, preRelease)); - Assertions.assertFalse(Version.isEqual(release, preRelease)); - Assertions.assertFalse(Version.isOlderOrEqual(release, preRelease)); - Assertions.assertFalse(Version.isOlder(release, preRelease)); + Assertions.assertTrue(release.isGreaterThan(preRelease)); + Assertions.assertTrue(release.isAtLeast(preRelease)); + Assertions.assertFalse(release.isEqualTo(preRelease)); + Assertions.assertFalse(release.isAtMost(preRelease)); + Assertions.assertFalse(release.isLessThan(preRelease)); } @Test public void testPreReleaseComparisons() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-alpha.1"); - Version v3 = Version.of("1.0.0-alpha.beta"); - Version v4 = Version.of("1.0.0-beta"); - Version v5 = Version.of("1.0.0-beta.2"); - Version v6 = Version.of("1.0.0-beta.11"); - Version v7 = Version.of("1.0.0-rc.1"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-alpha.1"); + Version v3 = Version.parse("1.0.0-alpha.beta"); + Version v4 = Version.parse("1.0.0-beta"); + Version v5 = Version.parse("1.0.0-beta.2"); + Version v6 = Version.parse("1.0.0-beta.11"); + Version v7 = Version.parse("1.0.0-rc.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); - Assertions.assertTrue(Version.isOlder(v2, v3)); - Assertions.assertTrue(Version.isOlder(v3, v4)); - Assertions.assertTrue(Version.isOlder(v4, v5)); - Assertions.assertTrue(Version.isOlder(v5, v6)); - Assertions.assertTrue(Version.isOlder(v6, v7)); + Assertions.assertTrue(v1.isLessThan(v2)); + Assertions.assertTrue(v2.isLessThan(v3)); + Assertions.assertTrue(v3.isLessThan(v4)); + Assertions.assertTrue(v4.isLessThan(v5)); + Assertions.assertTrue(v5.isLessThan(v6)); + Assertions.assertTrue(v6.isLessThan(v7)); } @Test public void testBuildMetadataIgnored() { - Version v1 = Version.of("1.0.0+build.1"); - Version v2 = Version.of("1.0.0+build.2"); + Version v1 = Version.parse("1.0.0+build.1"); + Version v2 = Version.parse("1.0.0+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); - Assertions.assertTrue(Version.isNewerOrEqual(v1, v2)); - Assertions.assertTrue(Version.isOlderOrEqual(v1, v2)); - Assertions.assertFalse(Version.isNewer(v1, v2)); - Assertions.assertFalse(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); + Assertions.assertTrue(v1.isAtLeast(v2)); + Assertions.assertTrue(v1.isAtMost(v2)); + Assertions.assertFalse(v1.isGreaterThan(v2)); + Assertions.assertFalse(v1.isLessThan(v2)); } @Test public void testPreReleaseWithBuildMetadata() { - Version v1 = Version.of("1.0.0-alpha+build.1"); - Version v2 = Version.of("1.0.0-alpha+build.2"); + Version v1 = Version.parse("1.0.0-alpha+build.1"); + Version v2 = Version.parse("1.0.0-alpha+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); - Version v3 = Version.of("1.0.0-alpha.1+build.1"); - Assertions.assertTrue(Version.isNewer(v3, v1)); + Version v3 = Version.parse("1.0.0-alpha.1+build.1"); + Assertions.assertTrue(v3.isGreaterThan(v1)); } @Test public void testNumericVsAlphabeticPreRelease() { - Version v1 = Version.of("1.0.0-alpha.1"); - Version v2 = Version.of("1.0.0-alpha.beta"); + Version v1 = Version.parse("1.0.0-alpha.1"); + Version v2 = Version.parse("1.0.0-alpha.beta"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-beta.2"); - Version v4 = Version.of("1.0.0-beta.11"); + Version v3 = Version.parse("1.0.0-beta.2"); + Version v4 = Version.parse("1.0.0-beta.11"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } @ParameterizedTest @MethodSource("provideVersionPairs") public void testVersionOrder(String olderVersion, String newerVersion) { - Version older = Version.of(olderVersion); - Version newer = Version.of(newerVersion); + Version older = Version.parse(olderVersion); + Version newer = Version.parse(newerVersion); - Assertions.assertTrue(Version.isOlder(older, newer), + Assertions.assertTrue(older.isLessThan(newer), olderVersion + " should be older than " + newerVersion); - Assertions.assertTrue(Version.isNewer(newer, older), + Assertions.assertTrue(newer.isGreaterThan(older), newerVersion + " should be newer than " + olderVersion); - Assertions.assertFalse(Version.isEqual(older, newer)); + Assertions.assertFalse(older.isEqualTo(newer)); } static Stream provideVersionPairs() { @@ -195,38 +195,38 @@ static Stream provideVersionPairs() { class EquivalenceTests { @Test public void testIdenticalVersionsEqual() { - Version v1 = Version.of("1.2.3"); - Version v2 = Version.of("1.2.3"); + Version v1 = Version.parse("1.2.3"); + Version v2 = Version.parse("1.2.3"); - Assertions.assertTrue(Version.isEqual(v1, v2)); - Assertions.assertTrue(Version.isNewerOrEqual(v1, v2)); - Assertions.assertTrue(Version.isOlderOrEqual(v1, v2)); - Assertions.assertFalse(Version.isNewer(v1, v2)); - Assertions.assertFalse(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); + Assertions.assertTrue(v1.isAtLeast(v2)); + Assertions.assertTrue(v1.isAtMost(v2)); + Assertions.assertFalse(v1.isGreaterThan(v2)); + Assertions.assertFalse(v1.isLessThan(v2)); } @Test public void testVersionsWithDifferentBuildMetadataEqual() { - Version v1 = Version.of("1.2.3+build.1"); - Version v2 = Version.of("1.2.3+build.2"); + Version v1 = Version.parse("1.2.3+build.1"); + Version v2 = Version.parse("1.2.3+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); } @Test public void testVersionsWithSamePreReleaseEqual() { - Version v1 = Version.of("1.2.3-alpha"); - Version v2 = Version.of("1.2.3-alpha"); + Version v1 = Version.parse("1.2.3-alpha"); + Version v2 = Version.parse("1.2.3-alpha"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); } @Test public void testVersionsWithSamePreReleaseAndDifferentBuildMetadataEqual() { - Version v1 = Version.of("1.2.3-alpha+build.1"); - Version v2 = Version.of("1.2.3-alpha+build.2"); + Version v1 = Version.parse("1.2.3-alpha+build.1"); + Version v2 = Version.parse("1.2.3-alpha+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); } } @@ -234,52 +234,52 @@ public void testVersionsWithSamePreReleaseAndDifferentBuildMetadataEqual() { class EdgeCaseTests { @Test public void testZeroVersions() { - Version v1 = Version.of("0.0.0"); - Version v2 = Version.of("0.0.1"); + Version v1 = Version.parse("0.0.0"); + Version v2 = Version.parse("0.0.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testVeryLargeNumbers() { - Version v1 = Version.of("999999.999999.999999"); - Version v2 = Version.of("1000000.0.0"); + Version v1 = Version.parse("999999.999999.999999"); + Version v2 = Version.parse("1000000.0.0"); - Assertions.assertTrue(Version.isOlderOrEqual(v1, v2)); + Assertions.assertTrue(v1.isAtMost(v2)); } @Test public void testNumericPreReleaseIdentifiers() { - Version v1 = Version.of("1.0.0-1"); - Version v2 = Version.of("1.0.0-2"); + Version v1 = Version.parse("1.0.0-1"); + Version v2 = Version.parse("1.0.0-2"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testAlphanumericPreReleaseIdentifiers() { - Version v1 = Version.of("1.0.0-a1"); - Version v2 = Version.of("1.0.0-a2"); + Version v1 = Version.parse("1.0.0-a1"); + Version v2 = Version.parse("1.0.0-a2"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testDifferentLengthPreReleaseIdentifiers() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-alpha.1"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-alpha.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testSpecialPreReleaseIdentifiers() { - Version snapshot = Version.of("2.0.0-snapshot"); - Version beta = Version.of("2.0.0-beta.2"); - Version release = Version.of("2.0.0"); + Version snapshot = Version.parse("2.0.0-snapshot"); + Version beta = Version.parse("2.0.0-beta.2"); + Version release = Version.parse("2.0.0"); - Assertions.assertTrue(Version.isNewer(snapshot, beta)); - Assertions.assertTrue(Version.isNewer(release, snapshot)); + Assertions.assertTrue(snapshot.isGreaterThan(beta)); + Assertions.assertTrue(release.isGreaterThan(snapshot)); } } @@ -287,41 +287,41 @@ public void testSpecialPreReleaseIdentifiers() { class PreReleaseIdentifierOrderingTests { @Test public void testNumericIdentifiersComparedNumerically() { - Version v1 = Version.of("1.0.0-1"); - Version v2 = Version.of("1.0.0-alpha"); + Version v1 = Version.parse("1.0.0-1"); + Version v2 = Version.parse("1.0.0-alpha"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-2"); - Version v4 = Version.of("1.0.0-10"); + Version v3 = Version.parse("1.0.0-2"); + Version v4 = Version.parse("1.0.0-10"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } @Test public void testIdentifiersWithLettersComparedLexically() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-beta"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-beta"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-abc"); - Version v4 = Version.of("1.0.0-abd"); + Version v3 = Version.parse("1.0.0-abc"); + Version v4 = Version.parse("1.0.0-abd"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } @Test public void testIdentifiersWithSamePrefixComparedByLength() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-alpha.1"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-alpha.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-alpha.beta"); - Version v4 = Version.of("1.0.0-alpha.beta.1"); + Version v3 = Version.parse("1.0.0-alpha.beta"); + Version v4 = Version.parse("1.0.0-alpha.beta.1"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } } @@ -330,18 +330,18 @@ class ComplexComparisonTests { @Test public void testMixedVersionComparisons() { Version[] versions = new Version[]{ - Version.of("1.0.0"), - Version.of("1.0.1"), - Version.of("1.1.0"), - Version.of("1.1.1"), - Version.of("2.0.0"), - Version.of("2.1.0") + Version.parse("1.0.0"), + Version.parse("1.0.1"), + Version.parse("1.1.0"), + Version.parse("1.1.1"), + Version.parse("2.0.0"), + Version.parse("2.1.0") }; for (int i = 0; i < versions.length - 1; i++) { for (int j = i + 1; j < versions.length; j++) { - Assertions.assertTrue(Version.isOlder(versions[i], versions[j])); - Assertions.assertTrue(Version.isNewer(versions[j], versions[i])); + Assertions.assertTrue(versions[i].isLessThan(versions[j])); + Assertions.assertTrue(versions[j].isGreaterThan(versions[i])); } } } @@ -349,31 +349,31 @@ public void testMixedVersionComparisons() { @Test public void testPreReleaseVsReleaseForSameVersion() { Version[] versions = new Version[]{ - Version.of("1.0.0-alpha"), - Version.of("1.0.0-alpha.1"), - Version.of("1.0.0-beta"), - Version.of("1.0.0-beta.2"), - Version.of("1.0.0-beta.11"), - Version.of("1.0.0-rc.1"), - Version.of("1.0.0") + Version.parse("1.0.0-alpha"), + Version.parse("1.0.0-alpha.1"), + Version.parse("1.0.0-beta"), + Version.parse("1.0.0-beta.2"), + Version.parse("1.0.0-beta.11"), + Version.parse("1.0.0-rc.1"), + Version.parse("1.0.0") }; for (int i = 0; i < versions.length - 1; i++) { for (int j = i + 1; j < versions.length; j++) { - Assertions.assertTrue(Version.isOlder(versions[i], versions[j])); - Assertions.assertTrue(Version.isNewer(versions[j], versions[i])); + Assertions.assertTrue(versions[i].isLessThan(versions[j])); + Assertions.assertTrue(versions[j].isGreaterThan(versions[i])); } } } @Test public void testPreReleaseVsHigherVersion() { - Version preRelease = Version.of("2.0.0-alpha"); - Version lowerVersion = Version.of("1.9.9"); - Version higherVersion = Version.of("2.0.1"); + Version preRelease = Version.parse("2.0.0-alpha"); + Version lowerVersion = Version.parse("1.9.9"); + Version higherVersion = Version.parse("2.0.1"); - Assertions.assertTrue(Version.isNewer(preRelease, lowerVersion)); - Assertions.assertTrue(Version.isOlder(preRelease, higherVersion)); + Assertions.assertTrue(preRelease.isGreaterThan(lowerVersion)); + Assertions.assertTrue(preRelease.isLessThan(higherVersion)); } } @@ -381,16 +381,16 @@ public void testPreReleaseVsHigherVersion() { class FormattingTests { @Test public void testToString() { - Version v1 = Version.of("1.2.3"); + Version v1 = Version.parse("1.2.3"); Assertions.assertEquals("1.2.3", v1.toString()); - Version v2 = Version.of("1.2.3-alpha"); + Version v2 = Version.parse("1.2.3-alpha"); Assertions.assertEquals("1.2.3-alpha", v2.toString()); - Version v3 = Version.of("1.2.3+build.1"); + Version v3 = Version.parse("1.2.3+build.1"); Assertions.assertEquals("1.2.3+build.1", v3.toString()); - Version v4 = Version.of("1.2.3-alpha+build.1"); + Version v4 = Version.parse("1.2.3-alpha+build.1"); Assertions.assertEquals("1.2.3-alpha+build.1", v4.toString()); } } @@ -418,28 +418,28 @@ class CommonPreReleasePatternTests { @Test public void testCommonPreReleasePatterns() { Version[] versions = new Version[]{ - Version.of("1.0.0-alpha"), - Version.of("1.0.0-alpha.1"), - Version.of("1.0.0-beta"), - Version.of("1.0.0-beta.1"), - Version.of("1.0.0-rc"), - Version.of("1.0.0-rc.1"), - Version.of("1.0.0") + Version.parse("1.0.0-alpha"), + Version.parse("1.0.0-alpha.1"), + Version.parse("1.0.0-beta"), + Version.parse("1.0.0-beta.1"), + Version.parse("1.0.0-rc"), + Version.parse("1.0.0-rc.1"), + Version.parse("1.0.0") }; for (int i = 0; i < versions.length - 1; i++) { - Assertions.assertTrue(Version.isOlder(versions[i], versions[i + 1])); + Assertions.assertTrue(versions[i].isLessThan(versions[i + 1])); } } @Test public void testBuildNumberPreReleases() { - Version build1 = Version.of("1.0.0-build.1"); - Version build2 = Version.of("1.0.0-build.2"); - Version build10 = Version.of("1.0.0-build.10"); + Version build1 = Version.parse("1.0.0-build.1"); + Version build2 = Version.parse("1.0.0-build.2"); + Version build10 = Version.parse("1.0.0-build.10"); - Assertions.assertTrue(Version.isOlder(build1, build2)); - Assertions.assertTrue(Version.isOlder(build2, build10)); + Assertions.assertTrue(build1.isLessThan(build2)); + Assertions.assertTrue(build2.isLessThan(build10)); } } @@ -447,19 +447,19 @@ public void testBuildNumberPreReleases() { class StandardComplianceTests { @Test public void testStartWithV() { - Assertions.assertDoesNotThrow(() -> Version.of("v1.0.0")); + Assertions.assertDoesNotThrow(() -> Version.parse("v1.0.0")); } @Test public void testNoLeadingZeros() { - Assertions.assertThrows(VersionParseException.class, () -> Version.of("01.0.0")); - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.00.0")); - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.0.00")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("01.0.0")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.00.0")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.0.00")); } @Test public void testLeadingZerosInPreRelease() { - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.0.0-01")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.0.0-01")); } } } \ No newline at end of file diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/AboveBelowTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/AboveBelowTest.java new file mode 100644 index 0000000..ecae461 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/AboveBelowTest.java @@ -0,0 +1,14 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class AboveBelowTest { + @Test + void aboveAndBelow() { + Assertions.assertTrue(Version.parse("3.0.0").isAbove(Range.parse("^1.0.0"))); + Assertions.assertTrue(Version.parse("0.5.0").isBelow(Range.parse("^1.0.0"))); + Assertions.assertFalse(Version.parse("1.5.0").isAbove(Range.parse("^1.0.0"))); + Assertions.assertFalse(Version.parse("1.5.0").isBelow(Range.parse("^1.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/BuildOrderingTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/BuildOrderingTest.java new file mode 100644 index 0000000..d3da097 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/BuildOrderingTest.java @@ -0,0 +1,16 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class BuildOrderingTest { + /** + * Compares build identifiers with leading zeros vs non leading zeros + */ + @Test + void leadingZerosCompareNumerically() { + Assertions.assertEquals(0, Version.BUILD_AWARE.compare(Version.parse("1.0.0+007"), Version.parse("1.0.0+7"))); + Assertions.assertTrue(Version.BUILD_AWARE.compare(Version.parse("1.0.0+007"), Version.parse("1.0.0+10")) < 0); + Assertions.assertTrue(Version.BUILD_AWARE.compare(Version.parse("1.0.0+010"), Version.parse("1.0.0+9")) > 0); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/CleanPrefixTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/CleanPrefixTest.java new file mode 100644 index 0000000..0be7dc7 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/CleanPrefixTest.java @@ -0,0 +1,13 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CleanPrefixTest { + @Test + void cleanStripsLeadingEqualsAndVRun() { + Assertions.assertEquals("1.2.3", Version.clean("==v1.2.3").get()); + Assertions.assertEquals("1.2.3", Version.clean("v=1.2.3").get()); + Assertions.assertEquals("1.2.3", Version.clean(" =v1.2.3 ").get()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceCleanTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceCleanTest.java new file mode 100644 index 0000000..7c9b21d --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceCleanTest.java @@ -0,0 +1,28 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CoerceCleanTest { + @Test + void coerceExtractsVersions() { + Assertions.assertEquals(Version.of(1, 2, 0), Version.coerce("v1.2").get()); + Assertions.assertEquals(Version.of(1, 2, 3), Version.coerce("app-1.2.3.jar").get()); + Assertions.assertEquals(Version.of(4, 0, 0), Version.coerce("release 4").get()); + Assertions.assertFalse(Version.coerce("no numbers here").isPresent()); + } + + @Test + void cleanNormalizesValidStrings() { + Assertions.assertEquals("1.2.3", Version.clean(" =v1.2.3 ").get()); + Assertions.assertFalse(Version.clean("not a version").isPresent()); + } + + /** + * Coercion fails on overflowing numbers returns empty optional. + */ + @Test + void coerceOverflowingNumber() { + Assertions.assertFalse(Version.coerce("build-99999999999999999999.jar").isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceParityTest.java new file mode 100644 index 0000000..cdca4ac --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceParityTest.java @@ -0,0 +1,38 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CoerceParityTest { + private static String coerce(String s) { + return Version.coerce(s).map(Version::toString).orElse(null); + } + + @Test + void matchesNodeCoerce() { + Assertions.assertEquals("1.2.0", coerce("v1.2")); + Assertions.assertEquals("1.2.3", coerce("1.2.3.4.5")); + Assertions.assertEquals("2.3.0", coerce(".2.3")); + Assertions.assertEquals("10.0.0", coerce("10.0.0.0")); + Assertions.assertEquals("1.0.0", coerce("a1b2c3")); + Assertions.assertEquals("2.3.0", coerce("version 2.3")); + Assertions.assertEquals("1.2.0", coerce("1.2.x")); + } + + @Test + void leadingZerosMakeCoerceFail() { + Assertions.assertNull(coerce("01.02.03")); + } + + @Test + void overlongNumbersAreSkipped() { + Assertions.assertEquals("5.6.0", coerce("123456789012345678.5.6")); + Assertions.assertEquals("0.0.0", coerce("99999999999999999999.0.0")); + } + + @Test + void noDigitsYieldsEmpty() { + Assertions.assertNull(coerce("x.y.z")); + Assertions.assertNull(coerce("no numbers here")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ConstraintTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ConstraintTest.java new file mode 100644 index 0000000..531e146 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ConstraintTest.java @@ -0,0 +1,26 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ConstraintTest { + @Test + void parsesAndTests() { + Constraint c = Constraint.parse(">=1.2.3"); + Assertions.assertTrue(c.contains(Version.parse("1.2.3"))); + Assertions.assertTrue(c.contains(Version.parse("2.0.0"))); + Assertions.assertFalse(c.contains(Version.parse("1.0.0"))); + Assertions.assertEquals(">=1.2.3", c.toString()); + } + + @Test + void bareVersionIsEquality() { + Assertions.assertTrue(Constraint.parse("1.2.3").contains(Version.parse("1.2.3"))); + Assertions.assertFalse(Constraint.parse("1.2.3").contains(Version.parse("1.2.4"))); + } + + @Test + void wildcardMatchesAnyRelease() { + Assertions.assertTrue(Constraint.any().contains(Version.parse("9.9.9"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/DiffParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/DiffParityTest.java new file mode 100644 index 0000000..b1294d1 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/DiffParityTest.java @@ -0,0 +1,45 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DiffParityTest { + @Test + void prereleaseOfMajorMilestoneToReleaseIsMajor() { + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0-a").difference(Version.parse("1.0.0")).get()); + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0-a").difference(Version.parse("1.1.0")).get()); + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0-a").difference(Version.parse("2.0.0")).get()); + } + + @Test + void prereleaseToSameCoreReleaseUsesLowestChangedComponent() { + Assertions.assertEquals(ReleaseType.MINOR, Version.parse("1.2.0-a").difference(Version.parse("1.2.0")).get()); + Assertions.assertEquals(ReleaseType.PATCH, Version.parse("1.2.3-a").difference(Version.parse("1.2.3")).get()); + } + + @Test + void prereleaseToHigherReleaseUsesPlainReleaseType() { + Assertions.assertEquals(ReleaseType.MINOR, Version.parse("1.2.0-a").difference(Version.parse("1.3.0")).get()); + Assertions.assertEquals(ReleaseType.PATCH, Version.parse("1.2.3-a").difference(Version.parse("1.2.4")).get()); + } + + @Test + void prePrefixOnlyWhenHigherVersionIsPrerelease() { + Assertions.assertEquals(ReleaseType.PREMAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0-a")).get()); + Assertions.assertEquals(ReleaseType.PREMINOR, Version.parse("1.0.0").difference(Version.parse("1.1.0-a")).get()); + Assertions.assertEquals(ReleaseType.PREPATCH, Version.parse("1.0.0").difference(Version.parse("1.0.1-a")).get()); + } + + @Test + void differenceIsSymmetric() { + Assertions.assertEquals(Version.parse("1.0.0-a").difference(Version.parse("1.0.0")), Version.parse("1.0.0").difference(Version.parse("1.0.0-a"))); + } + + @Test + void plainAndPrereleaseOnlyCasesUnchanged() { + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0")).get()); + Assertions.assertEquals(ReleaseType.PRERELEASE, Version.parse("1.0.0-a").difference(Version.parse("1.0.0-b")).get()); + Assertions.assertFalse(Version.parse("1.2.3").difference(Version.parse("1.2.3")).isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/DifferenceTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/DifferenceTest.java new file mode 100644 index 0000000..62b8b3f --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/DifferenceTest.java @@ -0,0 +1,17 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DifferenceTest { + @Test + void classifiesDifference() { + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0")).get()); + Assertions.assertEquals(ReleaseType.MINOR, Version.parse("1.0.0").difference(Version.parse("1.1.0")).get()); + Assertions.assertEquals(ReleaseType.PATCH, Version.parse("1.0.0").difference(Version.parse("1.0.1")).get()); + Assertions.assertEquals(ReleaseType.PREMAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0-a")).get()); + Assertions.assertEquals(ReleaseType.PRERELEASE, Version.parse("1.0.0-a").difference(Version.parse("1.0.0-b")).get()); + Assertions.assertFalse(Version.parse("1.0.0").difference(Version.parse("1.0.0")).isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/GtrLtrParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/GtrLtrParityTest.java new file mode 100644 index 0000000..0df672b --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/GtrLtrParityTest.java @@ -0,0 +1,47 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class GtrLtrParityTest { + private static boolean gtr(String v, String r) { + return Version.parse(v).isAbove(Range.parse(r)); + } + + private static boolean ltr(String v, String r) { + return Version.parse(v).isBelow(Range.parse(r)); + } + + @Test + void prereleaseInsideRangeIsReportedBothAboveAndBelow() { + Assertions.assertTrue(gtr("1.5.0-alpha", "^1.0.0")); + Assertions.assertTrue(ltr("1.5.0-alpha", "^1.0.0")); + } + + @Test + void ordinaryCasesMatchNode() { + Assertions.assertTrue(gtr("3.0.0", "^1.0.0")); + Assertions.assertFalse(ltr("3.0.0", "^1.0.0")); + Assertions.assertFalse(gtr("0.5.0", "^1.0.0")); + Assertions.assertTrue(ltr("0.5.0", "^1.0.0")); + Assertions.assertTrue(gtr("2.0.0", "^1.0.0")); + Assertions.assertTrue(gtr("2.0.0-0", "^1.0.0")); + Assertions.assertTrue(gtr("1.3.0", "~1.2.3")); + Assertions.assertFalse(gtr("1.2.2", "~1.2.3")); + Assertions.assertTrue(ltr("1.2.2", "~1.2.3")); + } + + @Test + void satisfyingVersionIsNeitherAboveNorBelow() { + Assertions.assertFalse(gtr("2.0.0", ">=1.0.0")); + Assertions.assertFalse(ltr("2.0.0", ">=1.0.0")); + Assertions.assertFalse(gtr("0.5.0", "<2.0.0")); + } + + @Test + void versionInGapBetweenOrSetsIsNeitherAboveNorBelow() { + Assertions.assertFalse(gtr("2.0.0", "1.x || 3.x")); + Assertions.assertFalse(ltr("2.0.0", "1.x || 3.x")); + Assertions.assertFalse(gtr("2.5.0", "1.x || 3.x")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/IncrementTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/IncrementTest.java new file mode 100644 index 0000000..02b0e77 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/IncrementTest.java @@ -0,0 +1,26 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class IncrementTest { + @Test + void plainIncrements() { + Assertions.assertEquals("2.0.0", Version.parse("1.2.3").increment(ReleaseType.MAJOR).toString()); + Assertions.assertEquals("1.3.0", Version.parse("1.2.3").increment(ReleaseType.MINOR).toString()); + Assertions.assertEquals("1.2.4", Version.parse("1.2.3").increment(ReleaseType.PATCH).toString()); + } + + @Test + void prereleaseIncrements() { + Assertions.assertEquals("1.2.4-0", Version.parse("1.2.3").increment(ReleaseType.PRERELEASE).toString()); + Assertions.assertEquals("1.2.4-alpha.0", Version.parse("1.2.3").increment(ReleaseType.PREPATCH, "alpha").toString()); + Assertions.assertEquals("1.2.3-alpha.2", Version.parse("1.2.3-alpha.1").increment(ReleaseType.PRERELEASE, "alpha").toString()); + } + + @Test + void majorOfPrereleaseAtZeroDropsPrerelease() { + Assertions.assertEquals("2.0.0", Version.parse("2.0.0-alpha.1").increment(ReleaseType.MAJOR).toString()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/IntersectsTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/IntersectsTest.java new file mode 100644 index 0000000..d3e1ae4 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/IntersectsTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class IntersectsTest { + @Test + void overlapping() { + Assertions.assertTrue(Range.parse(">=1.0.0 <2.0.0").intersects(Range.parse("^1.5.0"))); + Assertions.assertTrue(Range.parse("*").intersects(Range.parse("^1.0.0"))); + } + + @Test + void disjoint() { + Assertions.assertFalse(Range.parse("^1.0.0").intersects(Range.parse("^2.0.0"))); + Assertions.assertFalse(Range.parse("<1.0.0").intersects(Range.parse(">=2.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/MaxMinSatisfyingTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/MaxMinSatisfyingTest.java new file mode 100644 index 0000000..1408d71 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/MaxMinSatisfyingTest.java @@ -0,0 +1,24 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +class MaxMinSatisfyingTest { + private final List versions = Arrays.asList( + Version.parse("1.0.0"), Version.parse("1.2.0"), + Version.parse("1.9.0"), Version.parse("2.0.0")); + + @Test + void maxAndMin() { + Assertions.assertEquals(Version.parse("1.9.0"), Range.maxSatisfying(versions, "^1.0.0").get()); + Assertions.assertEquals(Version.parse("1.0.0"), Range.minSatisfying(versions, "^1.0.0").get()); + } + + @Test + void emptyWhenNoneMatch() { + Assertions.assertFalse(Range.maxSatisfying(versions, ">=3.0.0").isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/MinVersionTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/MinVersionTest.java new file mode 100644 index 0000000..e9af9a0 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/MinVersionTest.java @@ -0,0 +1,28 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class MinVersionTest { + @Test + void lowestSatisfying() { + Assertions.assertEquals(Version.parse("1.2.0"), Range.parse("^1.2.0").minVersion().get()); + Assertions.assertEquals(Version.parse("1.2.4"), Range.parse(">1.2.3").minVersion().get()); + Assertions.assertEquals(Version.parse("0.0.0"), Range.parse("*").minVersion().get()); + } + + @Test + void emptyRangeHasNoMin() { + Assertions.assertFalse(Range.parse(">=2.0.0 <1.0.0").minVersion().isPresent()); + } + + @Test + void exclusiveLowerBoundWithPrereleaseAppendsZeroIdentifier() { + Assertions.assertEquals(Version.parse("1.2.3-alpha.0"), Range.parse(">1.2.3-alpha").minVersion().get()); + } + + @Test + void exclusiveLowerBoundWithoutPrereleaseStillBumpsPatch() { + Assertions.assertEquals(Version.parse("1.2.4"), Range.parse(">1.2.3").minVersion().get()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/NodeParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/NodeParityTest.java new file mode 100644 index 0000000..c49df27 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/NodeParityTest.java @@ -0,0 +1,75 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Test class running tests ensuring full project parity with node-semver 7.8.5. + * A result file is generated from node-semver's tests and stored in test resources as {@code node_parity.txt}, and this test runs the same tests with java-semver and fails on mismatch in results. + */ +class NodeParityTest { + @Test + void matchesNodeSemver() throws Exception { + final List mismatches = new ArrayList<>(); + int checked = 0; + + try (InputStream in = getClass().getResourceAsStream("/node_parity.txt")) { + Assertions.assertNotNull(in, "test resource file /node_parity.txt not found on the test classpath!"); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) + continue; + + final String[] parts = line.split("\t", -1); + final String method = parts[0]; + final String a = parts[1]; + final String b = parts[2]; + final String expected = parts[3]; + + checked++; + String actual; + try { + actual = compute(method, a, b); + } catch (RuntimeException e) { + actual = "THREW:" + e.getClass().getSimpleName(); + } + + if (!expected.equals(actual)) + mismatches.add(method + "(" + a + (b.isEmpty() ? "" : " , " + b) + ") expected=" + expected + " actual=" + actual); + } + } + } + + if (!mismatches.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + sb.append(mismatches.size()).append(" of ").append(checked).append(" node parity checks deviated:\n"); + for (final String m : mismatches) + sb.append(" ").append(m).append('\n'); + Assertions.fail(sb.toString()); + } + } + + private static String compute(String method, String a, String b) { + return switch (method) { + case "satisfies" -> String.valueOf(Version.parse(a).satisfies(b)); + case "gtr" -> String.valueOf(Version.parse(a).isAbove(Range.parse(b))); + case "ltr" -> String.valueOf(Version.parse(a).isBelow(Range.parse(b))); + case "intersects" -> String.valueOf(Range.parse(a).intersects(Range.parse(b))); + case "compare" -> String.valueOf(Integer.signum(Version.parse(a).compareTo(Version.parse(b)))); + case "diff" -> Version.parse(a).difference(Version.parse(b)).map(rt -> rt.name().toLowerCase()).orElse("NULL"); + case "inc" -> Version.parse(a).increment(ReleaseType.valueOf(b.toUpperCase())).toString(); + case "coerce" -> Version.coerce(a).map(Version::toString).orElse("NULL"); + default -> throw new IllegalArgumentException("unknown method: " + method); + }; + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityComparisonTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityComparisonTest.java new file mode 100644 index 0000000..ebcc7f6 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityComparisonTest.java @@ -0,0 +1,40 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +class ParityComparisonTest { + @ParameterizedTest(name = "[{index}] \"{0}\" < \"{1}\"") + @MethodSource("pairs") + void lowerIsLessThanHigher(String lower, String higher) { + final Version lowerVersion = Version.parse(lower); + final Version higherVersion = Version.parse(higher); + + Assertions.assertTrue(lowerVersion.isLessThan(higherVersion), () -> "expected \"" + lower + "\" to be less than \"" + higher + "\""); + Assertions.assertTrue(higherVersion.isGreaterThan(lowerVersion), () -> "expected \"" + higher + "\" to be greater than \"" + lower + "\""); + } + + static Stream pairs() { + return Stream.of( + Arguments.of("1.0.0-alpha", "1.0.0-alpha.1"), + Arguments.of("1.0.0-alpha.1", "1.0.0-alpha.beta"), + Arguments.of("1.0.0-alpha.beta", "1.0.0-beta"), + Arguments.of("1.0.0-beta", "1.0.0-beta.2"), + Arguments.of("1.0.0-beta.2", "1.0.0-beta.11"), + Arguments.of("1.0.0-beta.11", "1.0.0-rc.1"), + Arguments.of("1.0.0-rc.1", "1.0.0"), + Arguments.of("1.0.0", "2.0.0"), + Arguments.of("1.0.0-1", "1.0.0-alpha"), + Arguments.of("1.0.0-2", "1.0.0-10"), + Arguments.of("0.0.1", "0.0.2"), + Arguments.of("0.1.0", "0.2.0"), + Arguments.of("1.2.3", "1.2.4"), + Arguments.of("1.2.3", "1.3.0"), + Arguments.of("1.2.3", "2.0.0") + ); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityIncrementTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityIncrementTest.java new file mode 100644 index 0000000..b370b06 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityIncrementTest.java @@ -0,0 +1,44 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +class ParityIncrementTest { + @ParameterizedTest(name = "[{index}] \"{0}\".increment({1}) = \"{2}\"") + @MethodSource("noIdentifierCases") + void incrementWithoutIdentifier(String input, ReleaseType release, String expected) { + Assertions.assertEquals(expected, Version.parse(input).increment(release).toString()); + } + + @ParameterizedTest(name = "[{index}] \"{0}\".increment({1}, \"{2}\") = \"{3}\"") + @MethodSource("withIdentifierCases") + void incrementWithIdentifier(String input, ReleaseType release, String identifier, String expected) { + Assertions.assertEquals(expected, Version.parse(input).increment(release, identifier).toString()); + } + + static Stream noIdentifierCases() { + return Stream.of( + Arguments.of("1.2.3", ReleaseType.MAJOR, "2.0.0"), + Arguments.of("1.2.3", ReleaseType.MINOR, "1.3.0"), + Arguments.of("1.2.3", ReleaseType.PATCH, "1.2.4"), + Arguments.of("1.2.3", ReleaseType.PREMAJOR, "2.0.0-0"), + Arguments.of("1.2.3", ReleaseType.PREMINOR, "1.3.0-0"), + Arguments.of("1.2.3", ReleaseType.PREPATCH, "1.2.4-0"), + Arguments.of("1.2.3", ReleaseType.PRERELEASE, "1.2.4-0"), + Arguments.of("1.2.3-alpha.1", ReleaseType.PRERELEASE, "1.2.3-alpha.2"), + Arguments.of("2.0.0-alpha.1", ReleaseType.MAJOR, "2.0.0") + ); + } + + static Stream withIdentifierCases() { + return Stream.of( + Arguments.of("1.2.3", ReleaseType.PREPATCH, "alpha", "1.2.4-alpha.0"), + Arguments.of("1.2.3-alpha.1", ReleaseType.PRERELEASE, "alpha", "1.2.3-alpha.2") + ); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeExcludeTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeExcludeTest.java new file mode 100644 index 0000000..7d4fcc4 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeExcludeTest.java @@ -0,0 +1,30 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ParityRangeExcludeTest { + @ParameterizedTest(name = "[{index}] \"{1}\" does not satisfy \"{0}\"") + @CsvSource({ + "^1.2.3, 2.0.0", + "^1.2.3, 1.2.2", + "^0.2.3, 0.3.0", + "~1.2.3, 1.3.0", + "~1.2.3, 1.2.2", + "1.2.3 - 2.3.4, 2.3.5", + "1.2.3 - 2.3.4, 1.2.2", + "1.2.3 - 2.3, 2.4.0", + "1.2 - 2.3.4, 1.1.9", + "1.x, 2.0.0", + "1.2.x, 1.3.0", + "1.2.x, 1.1.9", + ">=1.2.3 <2.0.0, 2.0.0", + ">=1.2.3 <2.0.0, 1.0.0", + "1.2.3 || >=2.0.0, 1.5.0", + ">=1.0.0 <2.0.0, 1.2.3-alpha.4" + }) + void doesNotSatisfy(String range, String version) { + Assertions.assertFalse(Version.parse(version).satisfies(range), () -> "expected \"" + version + "\" NOT to satisfy \"" + range + "\""); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeIncludeTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeIncludeTest.java new file mode 100644 index 0000000..aa9d210 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeIncludeTest.java @@ -0,0 +1,32 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ParityRangeIncludeTest { + @ParameterizedTest(name = "[{index}] \"{1}\" satisfies \"{0}\"") + @CsvSource({ + "^1.2.3, 1.2.3", + "^1.2.3, 1.9.9", + "^0.2.3, 0.2.9", + "~1.2.3, 1.2.3", + "~1.2.3, 1.2.9", + "1.2.3 - 2.3.4, 1.2.3", + "1.2.3 - 2.3.4, 2.3.4", + "1.2.3 - 2.3, 2.3.9", + "1.2 - 2.3.4, 1.2.0", + "1.x, 1.9.9", + "1.2.x, 1.2.5", + "*, 3.1.4", + "1, 1.4.2", + "1.2, 1.2.7", + ">=1.0.0 <2.0.0, 1.5.0", + "1.2.3 || >=2.0.0, 1.2.3", + "1.2.3 || >=2.0.0, 2.5.0", + ">=1.2.3-alpha <2.0.0, 1.2.3-alpha.4" + }) + void satisfies(String range, String version) { + Assertions.assertTrue(Version.parse(version).satisfies(range), () -> "expected \"" + version + "\" to satisfy \"" + range + "\""); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParseTrimTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParseTrimTest.java new file mode 100644 index 0000000..d59d975 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParseTrimTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ParseTrimTest { + @Test + void strictParseTrimsSurroundingWhitespace() { + Assertions.assertEquals(Version.of(1, 2, 3), Version.parse(" 1.2.3 ")); + Assertions.assertEquals(Version.of(1, 2, 3), Version.parse("\t1.2.3\n")); + Assertions.assertEquals(Version.of(1, 2, 3), Version.parse(" v1.2.3 ")); + } + + @Test + void isValidAcceptsPaddedVersion() { + Assertions.assertTrue(Version.isValid(" 1.2.3 ")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeBuildMetadataTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeBuildMetadataTest.java new file mode 100644 index 0000000..4ab2342 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeBuildMetadataTest.java @@ -0,0 +1,33 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Build metadata never affects matching in ranges so range comparators with it must parse and behave as if it were absent. + */ +class RangeBuildMetadataTest { + @Test + void tildeToleratesBuildMetadata() { + Assertions.assertTrue(Version.parse("1.2.9").satisfies("~1.2.3+build")); + Assertions.assertFalse(Version.parse("1.3.0").satisfies("~1.2.3+build")); + } + + @Test + void caretToleratesBuildMetadata() { + Assertions.assertTrue(Version.parse("1.9.0").satisfies("^1.2.3+build")); + Assertions.assertFalse(Version.parse("2.0.0").satisfies("^1.2.3+build")); + } + + @Test + void hyphenToleratesBuildMetadataOnEitherBound() { + Assertions.assertTrue(Version.parse("2.3.4").satisfies("1.2.3+build - 2.3.4")); + Assertions.assertTrue(Version.parse("2.3.4").satisfies("1.2.3 - 2.3.4+build")); + Assertions.assertFalse(Version.parse("2.3.5").satisfies("1.2.3 - 2.3.4+build")); + } + + @Test + void caretWithPrereleaseAndBuildMetadata() { + Assertions.assertTrue(Version.parse("1.2.3-beta.2").satisfies("^1.2.3-beta+build")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeCoreTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeCoreTest.java new file mode 100644 index 0000000..533f94f --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeCoreTest.java @@ -0,0 +1,29 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeCoreTest { + @Test + void andSet() { + Range r = Range.parse(">=1.2.3 <2.0.0"); + Assertions.assertTrue(r.contains(Version.parse("1.5.0"))); + Assertions.assertFalse(r.contains(Version.parse("2.0.0"))); + Assertions.assertFalse(r.contains(Version.parse("1.0.0"))); + } + + @Test + void orSets() { + Range r = Range.parse("1.2.3 || >=2.0.0"); + Assertions.assertTrue(r.contains(Version.parse("1.2.3"))); + Assertions.assertTrue(r.contains(Version.parse("3.0.0"))); + Assertions.assertFalse(r.contains(Version.parse("1.5.0"))); + } + + @Test + void starIsAny() { + Assertions.assertTrue(Range.parse("*").isAny()); + Assertions.assertTrue(Range.parse("").isAny()); + Assertions.assertTrue(Range.parse("*").contains(Version.parse("1.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeHyphenTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeHyphenTest.java new file mode 100644 index 0000000..dbaec0b --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeHyphenTest.java @@ -0,0 +1,28 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeHyphenTest { + @Test + void fullHyphen() { + Range r = Range.parse("1.2.3 - 2.3.4"); + Assertions.assertTrue(r.contains(Version.parse("1.2.3"))); + Assertions.assertTrue(r.contains(Version.parse("2.3.4"))); + Assertions.assertFalse(r.contains(Version.parse("2.3.5"))); + } + + @Test + void partialUpperBoundExpands() { + Range r = Range.parse("1.2.3 - 2.3"); + Assertions.assertTrue(r.contains(Version.parse("2.3.9"))); + Assertions.assertFalse(r.contains(Version.parse("2.4.0"))); + } + + @Test + void partialLowerBound() { + Range r = Range.parse("1.2 - 2.3.4"); + Assertions.assertTrue(r.contains(Version.parse("1.2.0"))); + Assertions.assertFalse(r.contains(Version.parse("1.1.9"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangePrereleaseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangePrereleaseTest.java new file mode 100644 index 0000000..df2c63c --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangePrereleaseTest.java @@ -0,0 +1,17 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangePrereleaseTest { + @Test + void prereleaseMatchesOnlyWhenNamed() { + Assertions.assertTrue(Range.parse(">=1.2.3-alpha <2.0.0").contains(Version.parse("1.2.3-alpha.4"))); + Assertions.assertFalse(Range.parse(">=1.0.0 <2.0.0").contains(Version.parse("1.2.3-alpha.4"))); + } + + @Test + void stableUnaffected() { + Assertions.assertTrue(Range.parse(">=1.0.0 <2.0.0").contains(Version.parse("1.5.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeTildeCaretTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeTildeCaretTest.java new file mode 100644 index 0000000..373ccf7 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeTildeCaretTest.java @@ -0,0 +1,27 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeTildeCaretTest { + @Test + void tilde() { + Range r = Range.parse("~1.2.3"); + Assertions.assertTrue(r.contains(Version.parse("1.2.9"))); + Assertions.assertFalse(r.contains(Version.parse("1.3.0"))); + } + + @Test + void caretAboveZero() { + Range r = Range.parse("^1.2.3"); + Assertions.assertTrue(r.contains(Version.parse("1.9.0"))); + Assertions.assertFalse(r.contains(Version.parse("2.0.0"))); + } + + @Test + void caretZeroMinor() { + Range r = Range.parse("^0.2.3"); + Assertions.assertTrue(r.contains(Version.parse("0.2.9"))); + Assertions.assertFalse(r.contains(Version.parse("0.3.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeXTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeXTest.java new file mode 100644 index 0000000..cb513a6 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeXTest.java @@ -0,0 +1,26 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeXTest { + @Test + void minorWildcard() { + Range r = Range.parse("1.x"); + Assertions.assertTrue(r.contains(Version.parse("1.9.9"))); + Assertions.assertFalse(r.contains(Version.parse("2.0.0"))); + } + + @Test + void patchWildcard() { + Range r = Range.parse("1.2.x"); + Assertions.assertTrue(r.contains(Version.parse("1.2.9"))); + Assertions.assertFalse(r.contains(Version.parse("1.3.0"))); + } + + @Test + void partialMeansWildcard() { + Assertions.assertTrue(Range.parse("1").contains(Version.parse("1.4.2"))); + Assertions.assertTrue(Range.parse("1.2").contains(Version.parse("1.2.7"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SatisfiesTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SatisfiesTest.java new file mode 100644 index 0000000..6d43eaf --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SatisfiesTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SatisfiesTest { + @Test + void satisfiesFromVersionSide() { + Assertions.assertTrue(Version.parse("1.2.3").satisfies("^1.0.0")); + Assertions.assertTrue(Version.parse("1.2.3").satisfies(Range.parse("~1.2.0"))); + Assertions.assertFalse(Version.parse("2.0.0").satisfies("^1.0.0")); + } + + @Test + void rangeParseLooseAcceptsVPrefixedBounds() { + Assertions.assertTrue(Range.parseLoose(">=v1.0.0").contains(Version.parse("1.2.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SimplifyTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SimplifyTest.java new file mode 100644 index 0000000..6fba613 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SimplifyTest.java @@ -0,0 +1,39 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +class SimplifyTest { + @Test + void collapsesContiguousRun() { + List versions = Arrays.asList( + Version.parse("1.0.0"), + Version.parse("1.1.0"), + Version.parse("1.2.0"), + Version.parse("1.3.0"), + Version.parse("1.4.0") + ); + Range simplified = Range.simplify(versions, Range.parse(">=1.0.0 <2.0.0")); + for (Version v : versions) { + Assertions.assertEquals(Range.parse(">=1.0.0 <2.0.0").contains(v), simplified.contains(v), "membership must be preserved for " + v); + } + } + + @Test + void equivalenceOverTheSet() { + List versions = Arrays.asList( + Version.parse("0.9.0"), + Version.parse("1.0.0"), + Version.parse("1.5.0"), + Version.parse("2.0.0") + ); + Range input = Range.parse(">=1.0.0 <2.0.0"); + Range simplified = Range.simplify(versions, input); + for (Version v : versions) { + Assertions.assertEquals(input.contains(v), simplified.contains(v)); + } + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetPrereleaseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetPrereleaseTest.java new file mode 100644 index 0000000..7967dbb --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetPrereleaseTest.java @@ -0,0 +1,23 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SubsetPrereleaseTest { + @Test + void prereleaseBoundRequiresMatchingTupleInDominator() { + Assertions.assertFalse(Range.parse(">=1.2.3-pre").isSubsetOf(Range.parse(">=1.0.0"))); + Assertions.assertFalse(Range.parse("^1.2.3-beta").isSubsetOf(Range.parse(">=1.0.0 <2.0.0"))); + } + + @Test + void prereleaseBoundSubsetWhenDominatorNamesSameTuple() { + Assertions.assertTrue(Range.parse(">=1.2.3-pre <2.0.0").isSubsetOf(Range.parse(">=1.2.3-pre <3.0.0"))); + } + + @Test + void caretUpperZeroPrereleaseBoundDoesNotTriggerRequirement() { + Assertions.assertTrue(Range.parse("^1.2.3").isSubsetOf(Range.parse(">=1.0.0 <2.0.0"))); + Assertions.assertTrue(Range.parse("~1.2.3").isSubsetOf(Range.parse(">=1.2.0 <1.3.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetTest.java new file mode 100644 index 0000000..9eeae78 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetTest.java @@ -0,0 +1,19 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SubsetTest { + @Test + void containment() { + Assertions.assertTrue(Range.parse("^1.2.0").isSubsetOf(Range.parse(">=1.0.0 <2.0.0"))); + Assertions.assertTrue(Range.parse(">=1.5.0 <1.8.0").isSubsetOf(Range.parse("^1.0.0"))); + Assertions.assertTrue(Range.parse("^1.0.0").isSubsetOf(Range.parse("*"))); + } + + @Test + void notContained() { + Assertions.assertFalse(Range.parse("^1.0.0").isSubsetOf(Range.parse(">=1.5.0 <2.0.0"))); + Assertions.assertFalse(Range.parse("*").isSubsetOf(Range.parse("^1.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionComparisonTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionComparisonTest.java new file mode 100644 index 0000000..67fe860 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionComparisonTest.java @@ -0,0 +1,31 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class VersionComparisonTest { + @Test + void precedenceOrdering() { + Assertions.assertTrue(Version.parse("1.0.0-alpha").isLessThan(Version.parse("1.0.0-alpha.1"))); + Assertions.assertTrue(Version.parse("1.0.0-rc.1").isLessThan(Version.parse("1.0.0"))); + Assertions.assertTrue(Version.parse("2.0.0").isGreaterThan(Version.parse("1.9.9"))); + } + + @Test + void predicates() { + Version a = Version.parse("1.2.3"); + Assertions.assertTrue(a.isAtLeast(Version.parse("1.2.3"))); + Assertions.assertTrue(a.isAtMost(Version.parse("1.2.3"))); + Assertions.assertTrue(a.isEqualTo(Version.parse("1.2.3"))); + Assertions.assertFalse(a.isGreaterThan(Version.parse("1.2.3"))); + } + + @Test + void buildMetadataIgnoredForPrecedenceButUsedByBuildAware() { + Version a = Version.parse("1.0.0+build.1"); + Version b = Version.parse("1.0.0+build.2"); + Assertions.assertEquals(0, a.compareTo(b)); + Assertions.assertTrue(a.isEqualTo(b)); + Assertions.assertTrue(Version.BUILD_AWARE.compare(a, b) < 0); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionLooseParseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionLooseParseTest.java new file mode 100644 index 0000000..299b78d --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionLooseParseTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class VersionLooseParseTest { + @Test + void tolerantOfWhitespacePrefixAndLeadingZeros() { + Assertions.assertEquals(Version.of(1, 2, 3), Version.parseLoose(" =v1.2.3 ")); + Assertions.assertEquals(Version.of(1, 2, 3), Version.parseLoose("01.02.03")); + Assertions.assertEquals("1.2.3-beta", Version.parseLoose("v1.2.3-beta").toString()); + } + + @Test + void strictStillRejectsLeadingZeros() { + Assertions.assertFalse(Version.isValid("01.02.03")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionParseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionParseTest.java new file mode 100644 index 0000000..bdcc331 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionParseTest.java @@ -0,0 +1,34 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.exception.VersionParseException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class VersionParseTest { + @Test + void parsesFullVersion() { + Version v = Version.parse("1.2.3-alpha.1+build.7"); + Assertions.assertEquals("1.2.3-alpha.1+build.7", v.toString()); + } + + @Test + void stripsLeadingV() { + Assertions.assertEquals(Version.of(1, 0, 0), Version.parse("v1.0.0")); + } + + @ParameterizedTest + @ValueSource(strings = {"1.02.03", "1.a.3", "1.2", "1.2.3-", "1.2.3-alpha..1", "1.0.0-01"}) + void rejectsInvalid(String s) { + Assertions.assertThrows(VersionParseException.class, () -> Version.parse(s)); + Assertions.assertFalse(Version.isValid(s)); + Assertions.assertFalse(Version.parseOptional(s).isPresent()); + } + + @Test + void optionalAndValidAgreeOnGood() { + Assertions.assertTrue(Version.isValid("1.2.3")); + Assertions.assertTrue(Version.parseOptional("1.2.3").isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionValueTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionValueTest.java new file mode 100644 index 0000000..a9c6aff --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionValueTest.java @@ -0,0 +1,36 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +class VersionValueTest { + @Test + void exposesComponents() { + Version v = Version.of(1, 2, 3, "alpha.1", "build.7"); + Assertions.assertEquals(1, v.getMajor()); + Assertions.assertEquals(2, v.getMinor()); + Assertions.assertEquals(3, v.getPatch()); + Assertions.assertEquals("alpha.1", v.getPreRelease()); + Assertions.assertEquals(Arrays.asList("alpha", "1"), v.getPreReleaseIdentifiers()); + Assertions.assertEquals("build.7", v.getBuildMetadata()); + Assertions.assertTrue(v.hasPreRelease()); + Assertions.assertTrue(v.hasBuildMetadata()); + Assertions.assertEquals("1.2.3-alpha.1+build.7", v.toString()); + } + + @Test + void identifiersListIsUnmodifiable() { + Version v = Version.of(1, 0, 0, "a.b"); + Assertions.assertThrows(UnsupportedOperationException.class, () -> v.getPreReleaseIdentifiers().add("c")); + } + + @Test + void structuralEqualityIncludesBuildMetadata() { + Version a = Version.of(1, 0, 0, "", "build.1"); + Version b = Version.of(1, 0, 0, "", "build.2"); + Assertions.assertNotEquals(a, b); + Assertions.assertEquals(Version.of(1, 0, 0), Version.of(1, 0, 0)); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/internal/IntervalTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/internal/IntervalTest.java new file mode 100644 index 0000000..9f72e0e --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/internal/IntervalTest.java @@ -0,0 +1,35 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Version; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +class IntervalTest { + @Test + void combinesBounds() { + Interval i = Interval.of(Arrays.asList(Constraint.parse(">=1.2.0"), Constraint.parse("<2.0.0"))); + Assertions.assertEquals(Version.parse("1.2.0"), i.getLower()); + Assertions.assertTrue(i.isLowerInclusive()); + Assertions.assertEquals(Version.parse("2.0.0"), i.getUpper()); + Assertions.assertFalse(i.isUpperInclusive()); + Assertions.assertFalse(i.isEmpty()); + } + + @Test + void contradictionIsEmpty() { + Interval i = Interval.of(Arrays.asList(Constraint.parse(">=2.0.0"), Constraint.parse("<1.0.0"))); + Assertions.assertTrue(i.isEmpty()); + } + + @Test + void overlapDetection() { + Interval a = Interval.of(Arrays.asList(Constraint.parse(">=1.0.0"), Constraint.parse("<2.0.0"))); + Interval b = Interval.of(Arrays.asList(Constraint.parse(">=1.5.0"), Constraint.parse("<3.0.0"))); + Interval c = Interval.of(Arrays.asList(Constraint.parse(">=2.0.0"), Constraint.parse("<3.0.0"))); + Assertions.assertTrue(Interval.overlaps(a, b)); + Assertions.assertFalse(Interval.overlaps(a, c)); + } +} diff --git a/common/src/test/resources/node_parity.txt b/common/src/test/resources/node_parity.txt new file mode 100644 index 0000000..030f162 --- /dev/null +++ b/common/src/test/resources/node_parity.txt @@ -0,0 +1,2183 @@ +satisfies 0.0.0 1.2.3 false +gtr 0.0.0 1.2.3 false +ltr 0.0.0 1.2.3 true +satisfies 0.0.0 =1.2.3 false +gtr 0.0.0 =1.2.3 false +ltr 0.0.0 =1.2.3 true +satisfies 0.0.0 >1.2.3 false +gtr 0.0.0 >1.2.3 false +ltr 0.0.0 >1.2.3 true +satisfies 0.0.0 >=1.2.3 false +gtr 0.0.0 >=1.2.3 false +ltr 0.0.0 >=1.2.3 true +satisfies 0.0.0 <1.2.3 true +gtr 0.0.0 <1.2.3 false +ltr 0.0.0 <1.2.3 false +satisfies 0.0.0 <=1.2.3 true +gtr 0.0.0 <=1.2.3 false +ltr 0.0.0 <=1.2.3 false +satisfies 0.0.0 ^1.2.3 false +gtr 0.0.0 ^1.2.3 false +ltr 0.0.0 ^1.2.3 true +satisfies 0.0.0 ~1.2.3 false +gtr 0.0.0 ~1.2.3 false +ltr 0.0.0 ~1.2.3 true +satisfies 0.0.0 ^0.2.3 false +gtr 0.0.0 ^0.2.3 false +ltr 0.0.0 ^0.2.3 true +satisfies 0.0.0 ^0.0.3 false +gtr 0.0.0 ^0.0.3 false +ltr 0.0.0 ^0.0.3 true +satisfies 0.0.0 1.2.x false +gtr 0.0.0 1.2.x false +ltr 0.0.0 1.2.x true +satisfies 0.0.0 1.x false +gtr 0.0.0 1.x false +ltr 0.0.0 1.x true +satisfies 0.0.0 * true +gtr 0.0.0 * false +ltr 0.0.0 * false +satisfies 0.0.0 >=1.2.3 <2.0.0 false +gtr 0.0.0 >=1.2.3 <2.0.0 false +ltr 0.0.0 >=1.2.3 <2.0.0 true +satisfies 0.0.0 1.2.3 - 2.3.4 false +gtr 0.0.0 1.2.3 - 2.3.4 false +ltr 0.0.0 1.2.3 - 2.3.4 true +satisfies 0.0.0 1.2.3 || >=2.0.0 false +gtr 0.0.0 1.2.3 || >=2.0.0 false +ltr 0.0.0 1.2.3 || >=2.0.0 true +satisfies 0.0.0 ^1.2.3-alpha false +gtr 0.0.0 ^1.2.3-alpha false +ltr 0.0.0 ^1.2.3-alpha true +satisfies 0.0.0 ~1.2 false +gtr 0.0.0 ~1.2 false +ltr 0.0.0 ~1.2 true +satisfies 0.0.0 ^1 false +gtr 0.0.0 ^1 false +ltr 0.0.0 ^1 true +satisfies 1.0.0 1.2.3 false +gtr 1.0.0 1.2.3 false +ltr 1.0.0 1.2.3 true +satisfies 1.0.0 =1.2.3 false +gtr 1.0.0 =1.2.3 false +ltr 1.0.0 =1.2.3 true +satisfies 1.0.0 >1.2.3 false +gtr 1.0.0 >1.2.3 false +ltr 1.0.0 >1.2.3 true +satisfies 1.0.0 >=1.2.3 false +gtr 1.0.0 >=1.2.3 false +ltr 1.0.0 >=1.2.3 true +satisfies 1.0.0 <1.2.3 true +gtr 1.0.0 <1.2.3 false +ltr 1.0.0 <1.2.3 false +satisfies 1.0.0 <=1.2.3 true +gtr 1.0.0 <=1.2.3 false +ltr 1.0.0 <=1.2.3 false +satisfies 1.0.0 ^1.2.3 false +gtr 1.0.0 ^1.2.3 false +ltr 1.0.0 ^1.2.3 true +satisfies 1.0.0 ~1.2.3 false +gtr 1.0.0 ~1.2.3 false +ltr 1.0.0 ~1.2.3 true +satisfies 1.0.0 ^0.2.3 false +gtr 1.0.0 ^0.2.3 true +ltr 1.0.0 ^0.2.3 false +satisfies 1.0.0 ^0.0.3 false +gtr 1.0.0 ^0.0.3 true +ltr 1.0.0 ^0.0.3 false +satisfies 1.0.0 1.2.x false +gtr 1.0.0 1.2.x false +ltr 1.0.0 1.2.x true +satisfies 1.0.0 1.x true +gtr 1.0.0 1.x false +ltr 1.0.0 1.x false +satisfies 1.0.0 * true +gtr 1.0.0 * false +ltr 1.0.0 * false +satisfies 1.0.0 >=1.2.3 <2.0.0 false +gtr 1.0.0 >=1.2.3 <2.0.0 false +ltr 1.0.0 >=1.2.3 <2.0.0 true +satisfies 1.0.0 1.2.3 - 2.3.4 false +gtr 1.0.0 1.2.3 - 2.3.4 false +ltr 1.0.0 1.2.3 - 2.3.4 true +satisfies 1.0.0 1.2.3 || >=2.0.0 false +gtr 1.0.0 1.2.3 || >=2.0.0 false +ltr 1.0.0 1.2.3 || >=2.0.0 true +satisfies 1.0.0 ^1.2.3-alpha false +gtr 1.0.0 ^1.2.3-alpha false +ltr 1.0.0 ^1.2.3-alpha true +satisfies 1.0.0 ~1.2 false +gtr 1.0.0 ~1.2 false +ltr 1.0.0 ~1.2 true +satisfies 1.0.0 ^1 true +gtr 1.0.0 ^1 false +ltr 1.0.0 ^1 false +satisfies 1.2.3 1.2.3 true +gtr 1.2.3 1.2.3 false +ltr 1.2.3 1.2.3 false +satisfies 1.2.3 =1.2.3 true +gtr 1.2.3 =1.2.3 false +ltr 1.2.3 =1.2.3 false +satisfies 1.2.3 >1.2.3 false +gtr 1.2.3 >1.2.3 false +ltr 1.2.3 >1.2.3 true +satisfies 1.2.3 >=1.2.3 true +gtr 1.2.3 >=1.2.3 false +ltr 1.2.3 >=1.2.3 false +satisfies 1.2.3 <1.2.3 false +gtr 1.2.3 <1.2.3 true +ltr 1.2.3 <1.2.3 false +satisfies 1.2.3 <=1.2.3 true +gtr 1.2.3 <=1.2.3 false +ltr 1.2.3 <=1.2.3 false +satisfies 1.2.3 ^1.2.3 true +gtr 1.2.3 ^1.2.3 false +ltr 1.2.3 ^1.2.3 false +satisfies 1.2.3 ~1.2.3 true +gtr 1.2.3 ~1.2.3 false +ltr 1.2.3 ~1.2.3 false +satisfies 1.2.3 ^0.2.3 false +gtr 1.2.3 ^0.2.3 true +ltr 1.2.3 ^0.2.3 false +satisfies 1.2.3 ^0.0.3 false +gtr 1.2.3 ^0.0.3 true +ltr 1.2.3 ^0.0.3 false +satisfies 1.2.3 1.2.x true +gtr 1.2.3 1.2.x false +ltr 1.2.3 1.2.x false +satisfies 1.2.3 1.x true +gtr 1.2.3 1.x false +ltr 1.2.3 1.x false +satisfies 1.2.3 * true +gtr 1.2.3 * false +ltr 1.2.3 * false +satisfies 1.2.3 >=1.2.3 <2.0.0 true +gtr 1.2.3 >=1.2.3 <2.0.0 false +ltr 1.2.3 >=1.2.3 <2.0.0 false +satisfies 1.2.3 1.2.3 - 2.3.4 true +gtr 1.2.3 1.2.3 - 2.3.4 false +ltr 1.2.3 1.2.3 - 2.3.4 false +satisfies 1.2.3 1.2.3 || >=2.0.0 true +gtr 1.2.3 1.2.3 || >=2.0.0 false +ltr 1.2.3 1.2.3 || >=2.0.0 false +satisfies 1.2.3 ^1.2.3-alpha true +gtr 1.2.3 ^1.2.3-alpha false +ltr 1.2.3 ^1.2.3-alpha false +satisfies 1.2.3 ~1.2 true +gtr 1.2.3 ~1.2 false +ltr 1.2.3 ~1.2 false +satisfies 1.2.3 ^1 true +gtr 1.2.3 ^1 false +ltr 1.2.3 ^1 false +satisfies 1.2.4 1.2.3 false +gtr 1.2.4 1.2.3 true +ltr 1.2.4 1.2.3 false +satisfies 1.2.4 =1.2.3 false +gtr 1.2.4 =1.2.3 true +ltr 1.2.4 =1.2.3 false +satisfies 1.2.4 >1.2.3 true +gtr 1.2.4 >1.2.3 false +ltr 1.2.4 >1.2.3 false +satisfies 1.2.4 >=1.2.3 true +gtr 1.2.4 >=1.2.3 false +ltr 1.2.4 >=1.2.3 false +satisfies 1.2.4 <1.2.3 false +gtr 1.2.4 <1.2.3 true +ltr 1.2.4 <1.2.3 false +satisfies 1.2.4 <=1.2.3 false +gtr 1.2.4 <=1.2.3 true +ltr 1.2.4 <=1.2.3 false +satisfies 1.2.4 ^1.2.3 true +gtr 1.2.4 ^1.2.3 false +ltr 1.2.4 ^1.2.3 false +satisfies 1.2.4 ~1.2.3 true +gtr 1.2.4 ~1.2.3 false +ltr 1.2.4 ~1.2.3 false +satisfies 1.2.4 ^0.2.3 false +gtr 1.2.4 ^0.2.3 true +ltr 1.2.4 ^0.2.3 false +satisfies 1.2.4 ^0.0.3 false +gtr 1.2.4 ^0.0.3 true +ltr 1.2.4 ^0.0.3 false +satisfies 1.2.4 1.2.x true +gtr 1.2.4 1.2.x false +ltr 1.2.4 1.2.x false +satisfies 1.2.4 1.x true +gtr 1.2.4 1.x false +ltr 1.2.4 1.x false +satisfies 1.2.4 * true +gtr 1.2.4 * false +ltr 1.2.4 * false +satisfies 1.2.4 >=1.2.3 <2.0.0 true +gtr 1.2.4 >=1.2.3 <2.0.0 false +ltr 1.2.4 >=1.2.3 <2.0.0 false +satisfies 1.2.4 1.2.3 - 2.3.4 true +gtr 1.2.4 1.2.3 - 2.3.4 false +ltr 1.2.4 1.2.3 - 2.3.4 false +satisfies 1.2.4 1.2.3 || >=2.0.0 false +gtr 1.2.4 1.2.3 || >=2.0.0 false +ltr 1.2.4 1.2.3 || >=2.0.0 false +satisfies 1.2.4 ^1.2.3-alpha true +gtr 1.2.4 ^1.2.3-alpha false +ltr 1.2.4 ^1.2.3-alpha false +satisfies 1.2.4 ~1.2 true +gtr 1.2.4 ~1.2 false +ltr 1.2.4 ~1.2 false +satisfies 1.2.4 ^1 true +gtr 1.2.4 ^1 false +ltr 1.2.4 ^1 false +satisfies 1.3.0 1.2.3 false +gtr 1.3.0 1.2.3 true +ltr 1.3.0 1.2.3 false +satisfies 1.3.0 =1.2.3 false +gtr 1.3.0 =1.2.3 true +ltr 1.3.0 =1.2.3 false +satisfies 1.3.0 >1.2.3 true +gtr 1.3.0 >1.2.3 false +ltr 1.3.0 >1.2.3 false +satisfies 1.3.0 >=1.2.3 true +gtr 1.3.0 >=1.2.3 false +ltr 1.3.0 >=1.2.3 false +satisfies 1.3.0 <1.2.3 false +gtr 1.3.0 <1.2.3 true +ltr 1.3.0 <1.2.3 false +satisfies 1.3.0 <=1.2.3 false +gtr 1.3.0 <=1.2.3 true +ltr 1.3.0 <=1.2.3 false +satisfies 1.3.0 ^1.2.3 true +gtr 1.3.0 ^1.2.3 false +ltr 1.3.0 ^1.2.3 false +satisfies 1.3.0 ~1.2.3 false +gtr 1.3.0 ~1.2.3 true +ltr 1.3.0 ~1.2.3 false +satisfies 1.3.0 ^0.2.3 false +gtr 1.3.0 ^0.2.3 true +ltr 1.3.0 ^0.2.3 false +satisfies 1.3.0 ^0.0.3 false +gtr 1.3.0 ^0.0.3 true +ltr 1.3.0 ^0.0.3 false +satisfies 1.3.0 1.2.x false +gtr 1.3.0 1.2.x true +ltr 1.3.0 1.2.x false +satisfies 1.3.0 1.x true +gtr 1.3.0 1.x false +ltr 1.3.0 1.x false +satisfies 1.3.0 * true +gtr 1.3.0 * false +ltr 1.3.0 * false +satisfies 1.3.0 >=1.2.3 <2.0.0 true +gtr 1.3.0 >=1.2.3 <2.0.0 false +ltr 1.3.0 >=1.2.3 <2.0.0 false +satisfies 1.3.0 1.2.3 - 2.3.4 true +gtr 1.3.0 1.2.3 - 2.3.4 false +ltr 1.3.0 1.2.3 - 2.3.4 false +satisfies 1.3.0 1.2.3 || >=2.0.0 false +gtr 1.3.0 1.2.3 || >=2.0.0 false +ltr 1.3.0 1.2.3 || >=2.0.0 false +satisfies 1.3.0 ^1.2.3-alpha true +gtr 1.3.0 ^1.2.3-alpha false +ltr 1.3.0 ^1.2.3-alpha false +satisfies 1.3.0 ~1.2 false +gtr 1.3.0 ~1.2 true +ltr 1.3.0 ~1.2 false +satisfies 1.3.0 ^1 true +gtr 1.3.0 ^1 false +ltr 1.3.0 ^1 false +satisfies 2.0.0 1.2.3 false +gtr 2.0.0 1.2.3 true +ltr 2.0.0 1.2.3 false +satisfies 2.0.0 =1.2.3 false +gtr 2.0.0 =1.2.3 true +ltr 2.0.0 =1.2.3 false +satisfies 2.0.0 >1.2.3 true +gtr 2.0.0 >1.2.3 false +ltr 2.0.0 >1.2.3 false +satisfies 2.0.0 >=1.2.3 true +gtr 2.0.0 >=1.2.3 false +ltr 2.0.0 >=1.2.3 false +satisfies 2.0.0 <1.2.3 false +gtr 2.0.0 <1.2.3 true +ltr 2.0.0 <1.2.3 false +satisfies 2.0.0 <=1.2.3 false +gtr 2.0.0 <=1.2.3 true +ltr 2.0.0 <=1.2.3 false +satisfies 2.0.0 ^1.2.3 false +gtr 2.0.0 ^1.2.3 true +ltr 2.0.0 ^1.2.3 false +satisfies 2.0.0 ~1.2.3 false +gtr 2.0.0 ~1.2.3 true +ltr 2.0.0 ~1.2.3 false +satisfies 2.0.0 ^0.2.3 false +gtr 2.0.0 ^0.2.3 true +ltr 2.0.0 ^0.2.3 false +satisfies 2.0.0 ^0.0.3 false +gtr 2.0.0 ^0.0.3 true +ltr 2.0.0 ^0.0.3 false +satisfies 2.0.0 1.2.x false +gtr 2.0.0 1.2.x true +ltr 2.0.0 1.2.x false +satisfies 2.0.0 1.x false +gtr 2.0.0 1.x true +ltr 2.0.0 1.x false +satisfies 2.0.0 * true +gtr 2.0.0 * false +ltr 2.0.0 * false +satisfies 2.0.0 >=1.2.3 <2.0.0 false +gtr 2.0.0 >=1.2.3 <2.0.0 true +ltr 2.0.0 >=1.2.3 <2.0.0 false +satisfies 2.0.0 1.2.3 - 2.3.4 true +gtr 2.0.0 1.2.3 - 2.3.4 false +ltr 2.0.0 1.2.3 - 2.3.4 false +satisfies 2.0.0 1.2.3 || >=2.0.0 true +gtr 2.0.0 1.2.3 || >=2.0.0 false +ltr 2.0.0 1.2.3 || >=2.0.0 false +satisfies 2.0.0 ^1.2.3-alpha false +gtr 2.0.0 ^1.2.3-alpha true +ltr 2.0.0 ^1.2.3-alpha false +satisfies 2.0.0 ~1.2 false +gtr 2.0.0 ~1.2 true +ltr 2.0.0 ~1.2 false +satisfies 2.0.0 ^1 false +gtr 2.0.0 ^1 true +ltr 2.0.0 ^1 false +satisfies 1.0.0-alpha 1.2.3 false +gtr 1.0.0-alpha 1.2.3 false +ltr 1.0.0-alpha 1.2.3 true +satisfies 1.0.0-alpha =1.2.3 false +gtr 1.0.0-alpha =1.2.3 false +ltr 1.0.0-alpha =1.2.3 true +satisfies 1.0.0-alpha >1.2.3 false +gtr 1.0.0-alpha >1.2.3 false +ltr 1.0.0-alpha >1.2.3 true +satisfies 1.0.0-alpha >=1.2.3 false +gtr 1.0.0-alpha >=1.2.3 false +ltr 1.0.0-alpha >=1.2.3 true +satisfies 1.0.0-alpha <1.2.3 false +gtr 1.0.0-alpha <1.2.3 true +ltr 1.0.0-alpha <1.2.3 false +satisfies 1.0.0-alpha <=1.2.3 false +gtr 1.0.0-alpha <=1.2.3 true +ltr 1.0.0-alpha <=1.2.3 false +satisfies 1.0.0-alpha ^1.2.3 false +gtr 1.0.0-alpha ^1.2.3 false +ltr 1.0.0-alpha ^1.2.3 true +satisfies 1.0.0-alpha ~1.2.3 false +gtr 1.0.0-alpha ~1.2.3 false +ltr 1.0.0-alpha ~1.2.3 true +satisfies 1.0.0-alpha ^0.2.3 false +gtr 1.0.0-alpha ^0.2.3 true +ltr 1.0.0-alpha ^0.2.3 false +satisfies 1.0.0-alpha ^0.0.3 false +gtr 1.0.0-alpha ^0.0.3 true +ltr 1.0.0-alpha ^0.0.3 false +satisfies 1.0.0-alpha 1.2.x false +gtr 1.0.0-alpha 1.2.x false +ltr 1.0.0-alpha 1.2.x true +satisfies 1.0.0-alpha 1.x false +gtr 1.0.0-alpha 1.x false +ltr 1.0.0-alpha 1.x true +satisfies 1.0.0-alpha * false +gtr 1.0.0-alpha * false +ltr 1.0.0-alpha * true +satisfies 1.0.0-alpha >=1.2.3 <2.0.0 false +gtr 1.0.0-alpha >=1.2.3 <2.0.0 false +ltr 1.0.0-alpha >=1.2.3 <2.0.0 true +satisfies 1.0.0-alpha 1.2.3 - 2.3.4 false +gtr 1.0.0-alpha 1.2.3 - 2.3.4 false +ltr 1.0.0-alpha 1.2.3 - 2.3.4 true +satisfies 1.0.0-alpha 1.2.3 || >=2.0.0 false +gtr 1.0.0-alpha 1.2.3 || >=2.0.0 false +ltr 1.0.0-alpha 1.2.3 || >=2.0.0 true +satisfies 1.0.0-alpha ^1.2.3-alpha false +gtr 1.0.0-alpha ^1.2.3-alpha false +ltr 1.0.0-alpha ^1.2.3-alpha true +satisfies 1.0.0-alpha ~1.2 false +gtr 1.0.0-alpha ~1.2 false +ltr 1.0.0-alpha ~1.2 true +satisfies 1.0.0-alpha ^1 false +gtr 1.0.0-alpha ^1 false +ltr 1.0.0-alpha ^1 true +satisfies 1.0.0-alpha.1 1.2.3 false +gtr 1.0.0-alpha.1 1.2.3 false +ltr 1.0.0-alpha.1 1.2.3 true +satisfies 1.0.0-alpha.1 =1.2.3 false +gtr 1.0.0-alpha.1 =1.2.3 false +ltr 1.0.0-alpha.1 =1.2.3 true +satisfies 1.0.0-alpha.1 >1.2.3 false +gtr 1.0.0-alpha.1 >1.2.3 false +ltr 1.0.0-alpha.1 >1.2.3 true +satisfies 1.0.0-alpha.1 >=1.2.3 false +gtr 1.0.0-alpha.1 >=1.2.3 false +ltr 1.0.0-alpha.1 >=1.2.3 true +satisfies 1.0.0-alpha.1 <1.2.3 false +gtr 1.0.0-alpha.1 <1.2.3 true +ltr 1.0.0-alpha.1 <1.2.3 false +satisfies 1.0.0-alpha.1 <=1.2.3 false +gtr 1.0.0-alpha.1 <=1.2.3 true +ltr 1.0.0-alpha.1 <=1.2.3 false +satisfies 1.0.0-alpha.1 ^1.2.3 false +gtr 1.0.0-alpha.1 ^1.2.3 false +ltr 1.0.0-alpha.1 ^1.2.3 true +satisfies 1.0.0-alpha.1 ~1.2.3 false +gtr 1.0.0-alpha.1 ~1.2.3 false +ltr 1.0.0-alpha.1 ~1.2.3 true +satisfies 1.0.0-alpha.1 ^0.2.3 false +gtr 1.0.0-alpha.1 ^0.2.3 true +ltr 1.0.0-alpha.1 ^0.2.3 false +satisfies 1.0.0-alpha.1 ^0.0.3 false +gtr 1.0.0-alpha.1 ^0.0.3 true +ltr 1.0.0-alpha.1 ^0.0.3 false +satisfies 1.0.0-alpha.1 1.2.x false +gtr 1.0.0-alpha.1 1.2.x false +ltr 1.0.0-alpha.1 1.2.x true +satisfies 1.0.0-alpha.1 1.x false +gtr 1.0.0-alpha.1 1.x false +ltr 1.0.0-alpha.1 1.x true +satisfies 1.0.0-alpha.1 * false +gtr 1.0.0-alpha.1 * false +ltr 1.0.0-alpha.1 * true +satisfies 1.0.0-alpha.1 >=1.2.3 <2.0.0 false +gtr 1.0.0-alpha.1 >=1.2.3 <2.0.0 false +ltr 1.0.0-alpha.1 >=1.2.3 <2.0.0 true +satisfies 1.0.0-alpha.1 1.2.3 - 2.3.4 false +gtr 1.0.0-alpha.1 1.2.3 - 2.3.4 false +ltr 1.0.0-alpha.1 1.2.3 - 2.3.4 true +satisfies 1.0.0-alpha.1 1.2.3 || >=2.0.0 false +gtr 1.0.0-alpha.1 1.2.3 || >=2.0.0 false +ltr 1.0.0-alpha.1 1.2.3 || >=2.0.0 true +satisfies 1.0.0-alpha.1 ^1.2.3-alpha false +gtr 1.0.0-alpha.1 ^1.2.3-alpha false +ltr 1.0.0-alpha.1 ^1.2.3-alpha true +satisfies 1.0.0-alpha.1 ~1.2 false +gtr 1.0.0-alpha.1 ~1.2 false +ltr 1.0.0-alpha.1 ~1.2 true +satisfies 1.0.0-alpha.1 ^1 false +gtr 1.0.0-alpha.1 ^1 false +ltr 1.0.0-alpha.1 ^1 true +satisfies 1.0.0-alpha.beta 1.2.3 false +gtr 1.0.0-alpha.beta 1.2.3 false +ltr 1.0.0-alpha.beta 1.2.3 true +satisfies 1.0.0-alpha.beta =1.2.3 false +gtr 1.0.0-alpha.beta =1.2.3 false +ltr 1.0.0-alpha.beta =1.2.3 true +satisfies 1.0.0-alpha.beta >1.2.3 false +gtr 1.0.0-alpha.beta >1.2.3 false +ltr 1.0.0-alpha.beta >1.2.3 true +satisfies 1.0.0-alpha.beta >=1.2.3 false +gtr 1.0.0-alpha.beta >=1.2.3 false +ltr 1.0.0-alpha.beta >=1.2.3 true +satisfies 1.0.0-alpha.beta <1.2.3 false +gtr 1.0.0-alpha.beta <1.2.3 true +ltr 1.0.0-alpha.beta <1.2.3 false +satisfies 1.0.0-alpha.beta <=1.2.3 false +gtr 1.0.0-alpha.beta <=1.2.3 true +ltr 1.0.0-alpha.beta <=1.2.3 false +satisfies 1.0.0-alpha.beta ^1.2.3 false +gtr 1.0.0-alpha.beta ^1.2.3 false +ltr 1.0.0-alpha.beta ^1.2.3 true +satisfies 1.0.0-alpha.beta ~1.2.3 false +gtr 1.0.0-alpha.beta ~1.2.3 false +ltr 1.0.0-alpha.beta ~1.2.3 true +satisfies 1.0.0-alpha.beta ^0.2.3 false +gtr 1.0.0-alpha.beta ^0.2.3 true +ltr 1.0.0-alpha.beta ^0.2.3 false +satisfies 1.0.0-alpha.beta ^0.0.3 false +gtr 1.0.0-alpha.beta ^0.0.3 true +ltr 1.0.0-alpha.beta ^0.0.3 false +satisfies 1.0.0-alpha.beta 1.2.x false +gtr 1.0.0-alpha.beta 1.2.x false +ltr 1.0.0-alpha.beta 1.2.x true +satisfies 1.0.0-alpha.beta 1.x false +gtr 1.0.0-alpha.beta 1.x false +ltr 1.0.0-alpha.beta 1.x true +satisfies 1.0.0-alpha.beta * false +gtr 1.0.0-alpha.beta * false +ltr 1.0.0-alpha.beta * true +satisfies 1.0.0-alpha.beta >=1.2.3 <2.0.0 false +gtr 1.0.0-alpha.beta >=1.2.3 <2.0.0 false +ltr 1.0.0-alpha.beta >=1.2.3 <2.0.0 true +satisfies 1.0.0-alpha.beta 1.2.3 - 2.3.4 false +gtr 1.0.0-alpha.beta 1.2.3 - 2.3.4 false +ltr 1.0.0-alpha.beta 1.2.3 - 2.3.4 true +satisfies 1.0.0-alpha.beta 1.2.3 || >=2.0.0 false +gtr 1.0.0-alpha.beta 1.2.3 || >=2.0.0 false +ltr 1.0.0-alpha.beta 1.2.3 || >=2.0.0 true +satisfies 1.0.0-alpha.beta ^1.2.3-alpha false +gtr 1.0.0-alpha.beta ^1.2.3-alpha false +ltr 1.0.0-alpha.beta ^1.2.3-alpha true +satisfies 1.0.0-alpha.beta ~1.2 false +gtr 1.0.0-alpha.beta ~1.2 false +ltr 1.0.0-alpha.beta ~1.2 true +satisfies 1.0.0-alpha.beta ^1 false +gtr 1.0.0-alpha.beta ^1 false +ltr 1.0.0-alpha.beta ^1 true +satisfies 1.0.0-beta 1.2.3 false +gtr 1.0.0-beta 1.2.3 false +ltr 1.0.0-beta 1.2.3 true +satisfies 1.0.0-beta =1.2.3 false +gtr 1.0.0-beta =1.2.3 false +ltr 1.0.0-beta =1.2.3 true +satisfies 1.0.0-beta >1.2.3 false +gtr 1.0.0-beta >1.2.3 false +ltr 1.0.0-beta >1.2.3 true +satisfies 1.0.0-beta >=1.2.3 false +gtr 1.0.0-beta >=1.2.3 false +ltr 1.0.0-beta >=1.2.3 true +satisfies 1.0.0-beta <1.2.3 false +gtr 1.0.0-beta <1.2.3 true +ltr 1.0.0-beta <1.2.3 false +satisfies 1.0.0-beta <=1.2.3 false +gtr 1.0.0-beta <=1.2.3 true +ltr 1.0.0-beta <=1.2.3 false +satisfies 1.0.0-beta ^1.2.3 false +gtr 1.0.0-beta ^1.2.3 false +ltr 1.0.0-beta ^1.2.3 true +satisfies 1.0.0-beta ~1.2.3 false +gtr 1.0.0-beta ~1.2.3 false +ltr 1.0.0-beta ~1.2.3 true +satisfies 1.0.0-beta ^0.2.3 false +gtr 1.0.0-beta ^0.2.3 true +ltr 1.0.0-beta ^0.2.3 false +satisfies 1.0.0-beta ^0.0.3 false +gtr 1.0.0-beta ^0.0.3 true +ltr 1.0.0-beta ^0.0.3 false +satisfies 1.0.0-beta 1.2.x false +gtr 1.0.0-beta 1.2.x false +ltr 1.0.0-beta 1.2.x true +satisfies 1.0.0-beta 1.x false +gtr 1.0.0-beta 1.x false +ltr 1.0.0-beta 1.x true +satisfies 1.0.0-beta * false +gtr 1.0.0-beta * false +ltr 1.0.0-beta * true +satisfies 1.0.0-beta >=1.2.3 <2.0.0 false +gtr 1.0.0-beta >=1.2.3 <2.0.0 false +ltr 1.0.0-beta >=1.2.3 <2.0.0 true +satisfies 1.0.0-beta 1.2.3 - 2.3.4 false +gtr 1.0.0-beta 1.2.3 - 2.3.4 false +ltr 1.0.0-beta 1.2.3 - 2.3.4 true +satisfies 1.0.0-beta 1.2.3 || >=2.0.0 false +gtr 1.0.0-beta 1.2.3 || >=2.0.0 false +ltr 1.0.0-beta 1.2.3 || >=2.0.0 true +satisfies 1.0.0-beta ^1.2.3-alpha false +gtr 1.0.0-beta ^1.2.3-alpha false +ltr 1.0.0-beta ^1.2.3-alpha true +satisfies 1.0.0-beta ~1.2 false +gtr 1.0.0-beta ~1.2 false +ltr 1.0.0-beta ~1.2 true +satisfies 1.0.0-beta ^1 false +gtr 1.0.0-beta ^1 false +ltr 1.0.0-beta ^1 true +satisfies 1.0.0-beta.2 1.2.3 false +gtr 1.0.0-beta.2 1.2.3 false +ltr 1.0.0-beta.2 1.2.3 true +satisfies 1.0.0-beta.2 =1.2.3 false +gtr 1.0.0-beta.2 =1.2.3 false +ltr 1.0.0-beta.2 =1.2.3 true +satisfies 1.0.0-beta.2 >1.2.3 false +gtr 1.0.0-beta.2 >1.2.3 false +ltr 1.0.0-beta.2 >1.2.3 true +satisfies 1.0.0-beta.2 >=1.2.3 false +gtr 1.0.0-beta.2 >=1.2.3 false +ltr 1.0.0-beta.2 >=1.2.3 true +satisfies 1.0.0-beta.2 <1.2.3 false +gtr 1.0.0-beta.2 <1.2.3 true +ltr 1.0.0-beta.2 <1.2.3 false +satisfies 1.0.0-beta.2 <=1.2.3 false +gtr 1.0.0-beta.2 <=1.2.3 true +ltr 1.0.0-beta.2 <=1.2.3 false +satisfies 1.0.0-beta.2 ^1.2.3 false +gtr 1.0.0-beta.2 ^1.2.3 false +ltr 1.0.0-beta.2 ^1.2.3 true +satisfies 1.0.0-beta.2 ~1.2.3 false +gtr 1.0.0-beta.2 ~1.2.3 false +ltr 1.0.0-beta.2 ~1.2.3 true +satisfies 1.0.0-beta.2 ^0.2.3 false +gtr 1.0.0-beta.2 ^0.2.3 true +ltr 1.0.0-beta.2 ^0.2.3 false +satisfies 1.0.0-beta.2 ^0.0.3 false +gtr 1.0.0-beta.2 ^0.0.3 true +ltr 1.0.0-beta.2 ^0.0.3 false +satisfies 1.0.0-beta.2 1.2.x false +gtr 1.0.0-beta.2 1.2.x false +ltr 1.0.0-beta.2 1.2.x true +satisfies 1.0.0-beta.2 1.x false +gtr 1.0.0-beta.2 1.x false +ltr 1.0.0-beta.2 1.x true +satisfies 1.0.0-beta.2 * false +gtr 1.0.0-beta.2 * false +ltr 1.0.0-beta.2 * true +satisfies 1.0.0-beta.2 >=1.2.3 <2.0.0 false +gtr 1.0.0-beta.2 >=1.2.3 <2.0.0 false +ltr 1.0.0-beta.2 >=1.2.3 <2.0.0 true +satisfies 1.0.0-beta.2 1.2.3 - 2.3.4 false +gtr 1.0.0-beta.2 1.2.3 - 2.3.4 false +ltr 1.0.0-beta.2 1.2.3 - 2.3.4 true +satisfies 1.0.0-beta.2 1.2.3 || >=2.0.0 false +gtr 1.0.0-beta.2 1.2.3 || >=2.0.0 false +ltr 1.0.0-beta.2 1.2.3 || >=2.0.0 true +satisfies 1.0.0-beta.2 ^1.2.3-alpha false +gtr 1.0.0-beta.2 ^1.2.3-alpha false +ltr 1.0.0-beta.2 ^1.2.3-alpha true +satisfies 1.0.0-beta.2 ~1.2 false +gtr 1.0.0-beta.2 ~1.2 false +ltr 1.0.0-beta.2 ~1.2 true +satisfies 1.0.0-beta.2 ^1 false +gtr 1.0.0-beta.2 ^1 false +ltr 1.0.0-beta.2 ^1 true +satisfies 1.0.0-beta.11 1.2.3 false +gtr 1.0.0-beta.11 1.2.3 false +ltr 1.0.0-beta.11 1.2.3 true +satisfies 1.0.0-beta.11 =1.2.3 false +gtr 1.0.0-beta.11 =1.2.3 false +ltr 1.0.0-beta.11 =1.2.3 true +satisfies 1.0.0-beta.11 >1.2.3 false +gtr 1.0.0-beta.11 >1.2.3 false +ltr 1.0.0-beta.11 >1.2.3 true +satisfies 1.0.0-beta.11 >=1.2.3 false +gtr 1.0.0-beta.11 >=1.2.3 false +ltr 1.0.0-beta.11 >=1.2.3 true +satisfies 1.0.0-beta.11 <1.2.3 false +gtr 1.0.0-beta.11 <1.2.3 true +ltr 1.0.0-beta.11 <1.2.3 false +satisfies 1.0.0-beta.11 <=1.2.3 false +gtr 1.0.0-beta.11 <=1.2.3 true +ltr 1.0.0-beta.11 <=1.2.3 false +satisfies 1.0.0-beta.11 ^1.2.3 false +gtr 1.0.0-beta.11 ^1.2.3 false +ltr 1.0.0-beta.11 ^1.2.3 true +satisfies 1.0.0-beta.11 ~1.2.3 false +gtr 1.0.0-beta.11 ~1.2.3 false +ltr 1.0.0-beta.11 ~1.2.3 true +satisfies 1.0.0-beta.11 ^0.2.3 false +gtr 1.0.0-beta.11 ^0.2.3 true +ltr 1.0.0-beta.11 ^0.2.3 false +satisfies 1.0.0-beta.11 ^0.0.3 false +gtr 1.0.0-beta.11 ^0.0.3 true +ltr 1.0.0-beta.11 ^0.0.3 false +satisfies 1.0.0-beta.11 1.2.x false +gtr 1.0.0-beta.11 1.2.x false +ltr 1.0.0-beta.11 1.2.x true +satisfies 1.0.0-beta.11 1.x false +gtr 1.0.0-beta.11 1.x false +ltr 1.0.0-beta.11 1.x true +satisfies 1.0.0-beta.11 * false +gtr 1.0.0-beta.11 * false +ltr 1.0.0-beta.11 * true +satisfies 1.0.0-beta.11 >=1.2.3 <2.0.0 false +gtr 1.0.0-beta.11 >=1.2.3 <2.0.0 false +ltr 1.0.0-beta.11 >=1.2.3 <2.0.0 true +satisfies 1.0.0-beta.11 1.2.3 - 2.3.4 false +gtr 1.0.0-beta.11 1.2.3 - 2.3.4 false +ltr 1.0.0-beta.11 1.2.3 - 2.3.4 true +satisfies 1.0.0-beta.11 1.2.3 || >=2.0.0 false +gtr 1.0.0-beta.11 1.2.3 || >=2.0.0 false +ltr 1.0.0-beta.11 1.2.3 || >=2.0.0 true +satisfies 1.0.0-beta.11 ^1.2.3-alpha false +gtr 1.0.0-beta.11 ^1.2.3-alpha false +ltr 1.0.0-beta.11 ^1.2.3-alpha true +satisfies 1.0.0-beta.11 ~1.2 false +gtr 1.0.0-beta.11 ~1.2 false +ltr 1.0.0-beta.11 ~1.2 true +satisfies 1.0.0-beta.11 ^1 false +gtr 1.0.0-beta.11 ^1 false +ltr 1.0.0-beta.11 ^1 true +satisfies 1.0.0-rc.1 1.2.3 false +gtr 1.0.0-rc.1 1.2.3 false +ltr 1.0.0-rc.1 1.2.3 true +satisfies 1.0.0-rc.1 =1.2.3 false +gtr 1.0.0-rc.1 =1.2.3 false +ltr 1.0.0-rc.1 =1.2.3 true +satisfies 1.0.0-rc.1 >1.2.3 false +gtr 1.0.0-rc.1 >1.2.3 false +ltr 1.0.0-rc.1 >1.2.3 true +satisfies 1.0.0-rc.1 >=1.2.3 false +gtr 1.0.0-rc.1 >=1.2.3 false +ltr 1.0.0-rc.1 >=1.2.3 true +satisfies 1.0.0-rc.1 <1.2.3 false +gtr 1.0.0-rc.1 <1.2.3 true +ltr 1.0.0-rc.1 <1.2.3 false +satisfies 1.0.0-rc.1 <=1.2.3 false +gtr 1.0.0-rc.1 <=1.2.3 true +ltr 1.0.0-rc.1 <=1.2.3 false +satisfies 1.0.0-rc.1 ^1.2.3 false +gtr 1.0.0-rc.1 ^1.2.3 false +ltr 1.0.0-rc.1 ^1.2.3 true +satisfies 1.0.0-rc.1 ~1.2.3 false +gtr 1.0.0-rc.1 ~1.2.3 false +ltr 1.0.0-rc.1 ~1.2.3 true +satisfies 1.0.0-rc.1 ^0.2.3 false +gtr 1.0.0-rc.1 ^0.2.3 true +ltr 1.0.0-rc.1 ^0.2.3 false +satisfies 1.0.0-rc.1 ^0.0.3 false +gtr 1.0.0-rc.1 ^0.0.3 true +ltr 1.0.0-rc.1 ^0.0.3 false +satisfies 1.0.0-rc.1 1.2.x false +gtr 1.0.0-rc.1 1.2.x false +ltr 1.0.0-rc.1 1.2.x true +satisfies 1.0.0-rc.1 1.x false +gtr 1.0.0-rc.1 1.x false +ltr 1.0.0-rc.1 1.x true +satisfies 1.0.0-rc.1 * false +gtr 1.0.0-rc.1 * false +ltr 1.0.0-rc.1 * true +satisfies 1.0.0-rc.1 >=1.2.3 <2.0.0 false +gtr 1.0.0-rc.1 >=1.2.3 <2.0.0 false +ltr 1.0.0-rc.1 >=1.2.3 <2.0.0 true +satisfies 1.0.0-rc.1 1.2.3 - 2.3.4 false +gtr 1.0.0-rc.1 1.2.3 - 2.3.4 false +ltr 1.0.0-rc.1 1.2.3 - 2.3.4 true +satisfies 1.0.0-rc.1 1.2.3 || >=2.0.0 false +gtr 1.0.0-rc.1 1.2.3 || >=2.0.0 false +ltr 1.0.0-rc.1 1.2.3 || >=2.0.0 true +satisfies 1.0.0-rc.1 ^1.2.3-alpha false +gtr 1.0.0-rc.1 ^1.2.3-alpha false +ltr 1.0.0-rc.1 ^1.2.3-alpha true +satisfies 1.0.0-rc.1 ~1.2 false +gtr 1.0.0-rc.1 ~1.2 false +ltr 1.0.0-rc.1 ~1.2 true +satisfies 1.0.0-rc.1 ^1 false +gtr 1.0.0-rc.1 ^1 false +ltr 1.0.0-rc.1 ^1 true +satisfies 1.2.3-0 1.2.3 false +gtr 1.2.3-0 1.2.3 false +ltr 1.2.3-0 1.2.3 true +satisfies 1.2.3-0 =1.2.3 false +gtr 1.2.3-0 =1.2.3 false +ltr 1.2.3-0 =1.2.3 true +satisfies 1.2.3-0 >1.2.3 false +gtr 1.2.3-0 >1.2.3 false +ltr 1.2.3-0 >1.2.3 true +satisfies 1.2.3-0 >=1.2.3 false +gtr 1.2.3-0 >=1.2.3 false +ltr 1.2.3-0 >=1.2.3 true +satisfies 1.2.3-0 <1.2.3 false +gtr 1.2.3-0 <1.2.3 true +ltr 1.2.3-0 <1.2.3 false +satisfies 1.2.3-0 <=1.2.3 false +gtr 1.2.3-0 <=1.2.3 true +ltr 1.2.3-0 <=1.2.3 false +satisfies 1.2.3-0 ^1.2.3 false +gtr 1.2.3-0 ^1.2.3 false +ltr 1.2.3-0 ^1.2.3 true +satisfies 1.2.3-0 ~1.2.3 false +gtr 1.2.3-0 ~1.2.3 false +ltr 1.2.3-0 ~1.2.3 true +satisfies 1.2.3-0 ^0.2.3 false +gtr 1.2.3-0 ^0.2.3 true +ltr 1.2.3-0 ^0.2.3 false +satisfies 1.2.3-0 ^0.0.3 false +gtr 1.2.3-0 ^0.0.3 true +ltr 1.2.3-0 ^0.0.3 false +satisfies 1.2.3-0 1.2.x false +gtr 1.2.3-0 1.2.x true +ltr 1.2.3-0 1.2.x true +satisfies 1.2.3-0 1.x false +gtr 1.2.3-0 1.x true +ltr 1.2.3-0 1.x true +satisfies 1.2.3-0 * false +gtr 1.2.3-0 * false +ltr 1.2.3-0 * true +satisfies 1.2.3-0 >=1.2.3 <2.0.0 false +gtr 1.2.3-0 >=1.2.3 <2.0.0 false +ltr 1.2.3-0 >=1.2.3 <2.0.0 true +satisfies 1.2.3-0 1.2.3 - 2.3.4 false +gtr 1.2.3-0 1.2.3 - 2.3.4 false +ltr 1.2.3-0 1.2.3 - 2.3.4 true +satisfies 1.2.3-0 1.2.3 || >=2.0.0 false +gtr 1.2.3-0 1.2.3 || >=2.0.0 false +ltr 1.2.3-0 1.2.3 || >=2.0.0 true +satisfies 1.2.3-0 ^1.2.3-alpha false +gtr 1.2.3-0 ^1.2.3-alpha false +ltr 1.2.3-0 ^1.2.3-alpha true +satisfies 1.2.3-0 ~1.2 false +gtr 1.2.3-0 ~1.2 true +ltr 1.2.3-0 ~1.2 true +satisfies 1.2.3-0 ^1 false +gtr 1.2.3-0 ^1 true +ltr 1.2.3-0 ^1 true +satisfies 2.0.0-0 1.2.3 false +gtr 2.0.0-0 1.2.3 true +ltr 2.0.0-0 1.2.3 false +satisfies 2.0.0-0 =1.2.3 false +gtr 2.0.0-0 =1.2.3 true +ltr 2.0.0-0 =1.2.3 false +satisfies 2.0.0-0 >1.2.3 false +gtr 2.0.0-0 >1.2.3 false +ltr 2.0.0-0 >1.2.3 true +satisfies 2.0.0-0 >=1.2.3 false +gtr 2.0.0-0 >=1.2.3 false +ltr 2.0.0-0 >=1.2.3 true +satisfies 2.0.0-0 <1.2.3 false +gtr 2.0.0-0 <1.2.3 true +ltr 2.0.0-0 <1.2.3 false +satisfies 2.0.0-0 <=1.2.3 false +gtr 2.0.0-0 <=1.2.3 true +ltr 2.0.0-0 <=1.2.3 false +satisfies 2.0.0-0 ^1.2.3 false +gtr 2.0.0-0 ^1.2.3 true +ltr 2.0.0-0 ^1.2.3 false +satisfies 2.0.0-0 ~1.2.3 false +gtr 2.0.0-0 ~1.2.3 true +ltr 2.0.0-0 ~1.2.3 false +satisfies 2.0.0-0 ^0.2.3 false +gtr 2.0.0-0 ^0.2.3 true +ltr 2.0.0-0 ^0.2.3 false +satisfies 2.0.0-0 ^0.0.3 false +gtr 2.0.0-0 ^0.0.3 true +ltr 2.0.0-0 ^0.0.3 false +satisfies 2.0.0-0 1.2.x false +gtr 2.0.0-0 1.2.x true +ltr 2.0.0-0 1.2.x false +satisfies 2.0.0-0 1.x false +gtr 2.0.0-0 1.x true +ltr 2.0.0-0 1.x false +satisfies 2.0.0-0 * false +gtr 2.0.0-0 * false +ltr 2.0.0-0 * true +satisfies 2.0.0-0 >=1.2.3 <2.0.0 false +gtr 2.0.0-0 >=1.2.3 <2.0.0 true +ltr 2.0.0-0 >=1.2.3 <2.0.0 true +satisfies 2.0.0-0 1.2.3 - 2.3.4 false +gtr 2.0.0-0 1.2.3 - 2.3.4 true +ltr 2.0.0-0 1.2.3 - 2.3.4 true +satisfies 2.0.0-0 1.2.3 || >=2.0.0 false +gtr 2.0.0-0 1.2.3 || >=2.0.0 false +ltr 2.0.0-0 1.2.3 || >=2.0.0 false +satisfies 2.0.0-0 ^1.2.3-alpha false +gtr 2.0.0-0 ^1.2.3-alpha true +ltr 2.0.0-0 ^1.2.3-alpha false +satisfies 2.0.0-0 ~1.2 false +gtr 2.0.0-0 ~1.2 true +ltr 2.0.0-0 ~1.2 false +satisfies 2.0.0-0 ^1 false +gtr 2.0.0-0 ^1 true +ltr 2.0.0-0 ^1 false +satisfies 1.2.3+build 1.2.3 true +gtr 1.2.3+build 1.2.3 false +ltr 1.2.3+build 1.2.3 false +satisfies 1.2.3+build =1.2.3 true +gtr 1.2.3+build =1.2.3 false +ltr 1.2.3+build =1.2.3 false +satisfies 1.2.3+build >1.2.3 false +gtr 1.2.3+build >1.2.3 false +ltr 1.2.3+build >1.2.3 true +satisfies 1.2.3+build >=1.2.3 true +gtr 1.2.3+build >=1.2.3 false +ltr 1.2.3+build >=1.2.3 false +satisfies 1.2.3+build <1.2.3 false +gtr 1.2.3+build <1.2.3 true +ltr 1.2.3+build <1.2.3 false +satisfies 1.2.3+build <=1.2.3 true +gtr 1.2.3+build <=1.2.3 false +ltr 1.2.3+build <=1.2.3 false +satisfies 1.2.3+build ^1.2.3 true +gtr 1.2.3+build ^1.2.3 false +ltr 1.2.3+build ^1.2.3 false +satisfies 1.2.3+build ~1.2.3 true +gtr 1.2.3+build ~1.2.3 false +ltr 1.2.3+build ~1.2.3 false +satisfies 1.2.3+build ^0.2.3 false +gtr 1.2.3+build ^0.2.3 true +ltr 1.2.3+build ^0.2.3 false +satisfies 1.2.3+build ^0.0.3 false +gtr 1.2.3+build ^0.0.3 true +ltr 1.2.3+build ^0.0.3 false +satisfies 1.2.3+build 1.2.x true +gtr 1.2.3+build 1.2.x false +ltr 1.2.3+build 1.2.x false +satisfies 1.2.3+build 1.x true +gtr 1.2.3+build 1.x false +ltr 1.2.3+build 1.x false +satisfies 1.2.3+build * true +gtr 1.2.3+build * false +ltr 1.2.3+build * false +satisfies 1.2.3+build >=1.2.3 <2.0.0 true +gtr 1.2.3+build >=1.2.3 <2.0.0 false +ltr 1.2.3+build >=1.2.3 <2.0.0 false +satisfies 1.2.3+build 1.2.3 - 2.3.4 true +gtr 1.2.3+build 1.2.3 - 2.3.4 false +ltr 1.2.3+build 1.2.3 - 2.3.4 false +satisfies 1.2.3+build 1.2.3 || >=2.0.0 true +gtr 1.2.3+build 1.2.3 || >=2.0.0 false +ltr 1.2.3+build 1.2.3 || >=2.0.0 false +satisfies 1.2.3+build ^1.2.3-alpha true +gtr 1.2.3+build ^1.2.3-alpha false +ltr 1.2.3+build ^1.2.3-alpha false +satisfies 1.2.3+build ~1.2 true +gtr 1.2.3+build ~1.2 false +ltr 1.2.3+build ~1.2 false +satisfies 1.2.3+build ^1 true +gtr 1.2.3+build ^1 false +ltr 1.2.3+build ^1 false +satisfies 1.0.0+007 1.2.3 false +gtr 1.0.0+007 1.2.3 false +ltr 1.0.0+007 1.2.3 true +satisfies 1.0.0+007 =1.2.3 false +gtr 1.0.0+007 =1.2.3 false +ltr 1.0.0+007 =1.2.3 true +satisfies 1.0.0+007 >1.2.3 false +gtr 1.0.0+007 >1.2.3 false +ltr 1.0.0+007 >1.2.3 true +satisfies 1.0.0+007 >=1.2.3 false +gtr 1.0.0+007 >=1.2.3 false +ltr 1.0.0+007 >=1.2.3 true +satisfies 1.0.0+007 <1.2.3 true +gtr 1.0.0+007 <1.2.3 false +ltr 1.0.0+007 <1.2.3 false +satisfies 1.0.0+007 <=1.2.3 true +gtr 1.0.0+007 <=1.2.3 false +ltr 1.0.0+007 <=1.2.3 false +satisfies 1.0.0+007 ^1.2.3 false +gtr 1.0.0+007 ^1.2.3 false +ltr 1.0.0+007 ^1.2.3 true +satisfies 1.0.0+007 ~1.2.3 false +gtr 1.0.0+007 ~1.2.3 false +ltr 1.0.0+007 ~1.2.3 true +satisfies 1.0.0+007 ^0.2.3 false +gtr 1.0.0+007 ^0.2.3 true +ltr 1.0.0+007 ^0.2.3 false +satisfies 1.0.0+007 ^0.0.3 false +gtr 1.0.0+007 ^0.0.3 true +ltr 1.0.0+007 ^0.0.3 false +satisfies 1.0.0+007 1.2.x false +gtr 1.0.0+007 1.2.x false +ltr 1.0.0+007 1.2.x true +satisfies 1.0.0+007 1.x true +gtr 1.0.0+007 1.x false +ltr 1.0.0+007 1.x false +satisfies 1.0.0+007 * true +gtr 1.0.0+007 * false +ltr 1.0.0+007 * false +satisfies 1.0.0+007 >=1.2.3 <2.0.0 false +gtr 1.0.0+007 >=1.2.3 <2.0.0 false +ltr 1.0.0+007 >=1.2.3 <2.0.0 true +satisfies 1.0.0+007 1.2.3 - 2.3.4 false +gtr 1.0.0+007 1.2.3 - 2.3.4 false +ltr 1.0.0+007 1.2.3 - 2.3.4 true +satisfies 1.0.0+007 1.2.3 || >=2.0.0 false +gtr 1.0.0+007 1.2.3 || >=2.0.0 false +ltr 1.0.0+007 1.2.3 || >=2.0.0 true +satisfies 1.0.0+007 ^1.2.3-alpha false +gtr 1.0.0+007 ^1.2.3-alpha false +ltr 1.0.0+007 ^1.2.3-alpha true +satisfies 1.0.0+007 ~1.2 false +gtr 1.0.0+007 ~1.2 false +ltr 1.0.0+007 ~1.2 true +satisfies 1.0.0+007 ^1 true +gtr 1.0.0+007 ^1 false +ltr 1.0.0+007 ^1 false +satisfies 10.0.0 1.2.3 false +gtr 10.0.0 1.2.3 true +ltr 10.0.0 1.2.3 false +satisfies 10.0.0 =1.2.3 false +gtr 10.0.0 =1.2.3 true +ltr 10.0.0 =1.2.3 false +satisfies 10.0.0 >1.2.3 true +gtr 10.0.0 >1.2.3 false +ltr 10.0.0 >1.2.3 false +satisfies 10.0.0 >=1.2.3 true +gtr 10.0.0 >=1.2.3 false +ltr 10.0.0 >=1.2.3 false +satisfies 10.0.0 <1.2.3 false +gtr 10.0.0 <1.2.3 true +ltr 10.0.0 <1.2.3 false +satisfies 10.0.0 <=1.2.3 false +gtr 10.0.0 <=1.2.3 true +ltr 10.0.0 <=1.2.3 false +satisfies 10.0.0 ^1.2.3 false +gtr 10.0.0 ^1.2.3 true +ltr 10.0.0 ^1.2.3 false +satisfies 10.0.0 ~1.2.3 false +gtr 10.0.0 ~1.2.3 true +ltr 10.0.0 ~1.2.3 false +satisfies 10.0.0 ^0.2.3 false +gtr 10.0.0 ^0.2.3 true +ltr 10.0.0 ^0.2.3 false +satisfies 10.0.0 ^0.0.3 false +gtr 10.0.0 ^0.0.3 true +ltr 10.0.0 ^0.0.3 false +satisfies 10.0.0 1.2.x false +gtr 10.0.0 1.2.x true +ltr 10.0.0 1.2.x false +satisfies 10.0.0 1.x false +gtr 10.0.0 1.x true +ltr 10.0.0 1.x false +satisfies 10.0.0 * true +gtr 10.0.0 * false +ltr 10.0.0 * false +satisfies 10.0.0 >=1.2.3 <2.0.0 false +gtr 10.0.0 >=1.2.3 <2.0.0 true +ltr 10.0.0 >=1.2.3 <2.0.0 false +satisfies 10.0.0 1.2.3 - 2.3.4 false +gtr 10.0.0 1.2.3 - 2.3.4 true +ltr 10.0.0 1.2.3 - 2.3.4 false +satisfies 10.0.0 1.2.3 || >=2.0.0 true +gtr 10.0.0 1.2.3 || >=2.0.0 false +ltr 10.0.0 1.2.3 || >=2.0.0 false +satisfies 10.0.0 ^1.2.3-alpha false +gtr 10.0.0 ^1.2.3-alpha true +ltr 10.0.0 ^1.2.3-alpha false +satisfies 10.0.0 ~1.2 false +gtr 10.0.0 ~1.2 true +ltr 10.0.0 ~1.2 false +satisfies 10.0.0 ^1 false +gtr 10.0.0 ^1 true +ltr 10.0.0 ^1 false +intersects 1.2.3 1.2.3 true +intersects 1.2.3 =1.2.3 true +intersects 1.2.3 >1.2.3 false +intersects 1.2.3 >=1.2.3 true +intersects 1.2.3 <1.2.3 false +intersects 1.2.3 <=1.2.3 true +intersects 1.2.3 ^1.2.3 true +intersects 1.2.3 ~1.2.3 true +intersects 1.2.3 ^0.2.3 false +intersects 1.2.3 ^0.0.3 false +intersects 1.2.3 1.2.x true +intersects 1.2.3 1.x true +intersects 1.2.3 * true +intersects 1.2.3 >=1.2.3 <2.0.0 true +intersects 1.2.3 1.2.3 - 2.3.4 true +intersects 1.2.3 1.2.3 || >=2.0.0 true +intersects 1.2.3 ^1.2.3-alpha true +intersects 1.2.3 ~1.2 true +intersects 1.2.3 ^1 true +intersects =1.2.3 1.2.3 true +intersects =1.2.3 =1.2.3 true +intersects =1.2.3 >1.2.3 false +intersects =1.2.3 >=1.2.3 true +intersects =1.2.3 <1.2.3 false +intersects =1.2.3 <=1.2.3 true +intersects =1.2.3 ^1.2.3 true +intersects =1.2.3 ~1.2.3 true +intersects =1.2.3 ^0.2.3 false +intersects =1.2.3 ^0.0.3 false +intersects =1.2.3 1.2.x true +intersects =1.2.3 1.x true +intersects =1.2.3 * true +intersects =1.2.3 >=1.2.3 <2.0.0 true +intersects =1.2.3 1.2.3 - 2.3.4 true +intersects =1.2.3 1.2.3 || >=2.0.0 true +intersects =1.2.3 ^1.2.3-alpha true +intersects =1.2.3 ~1.2 true +intersects =1.2.3 ^1 true +intersects >1.2.3 1.2.3 false +intersects >1.2.3 =1.2.3 false +intersects >1.2.3 >1.2.3 true +intersects >1.2.3 >=1.2.3 true +intersects >1.2.3 <1.2.3 false +intersects >1.2.3 <=1.2.3 false +intersects >1.2.3 ^1.2.3 true +intersects >1.2.3 ~1.2.3 true +intersects >1.2.3 ^0.2.3 false +intersects >1.2.3 ^0.0.3 false +intersects >1.2.3 1.2.x true +intersects >1.2.3 1.x true +intersects >1.2.3 * true +intersects >1.2.3 >=1.2.3 <2.0.0 true +intersects >1.2.3 1.2.3 - 2.3.4 true +intersects >1.2.3 1.2.3 || >=2.0.0 true +intersects >1.2.3 ^1.2.3-alpha true +intersects >1.2.3 ~1.2 true +intersects >1.2.3 ^1 true +intersects >=1.2.3 1.2.3 true +intersects >=1.2.3 =1.2.3 true +intersects >=1.2.3 >1.2.3 true +intersects >=1.2.3 >=1.2.3 true +intersects >=1.2.3 <1.2.3 false +intersects >=1.2.3 <=1.2.3 true +intersects >=1.2.3 ^1.2.3 true +intersects >=1.2.3 ~1.2.3 true +intersects >=1.2.3 ^0.2.3 false +intersects >=1.2.3 ^0.0.3 false +intersects >=1.2.3 1.2.x true +intersects >=1.2.3 1.x true +intersects >=1.2.3 * true +intersects >=1.2.3 >=1.2.3 <2.0.0 true +intersects >=1.2.3 1.2.3 - 2.3.4 true +intersects >=1.2.3 1.2.3 || >=2.0.0 true +intersects >=1.2.3 ^1.2.3-alpha true +intersects >=1.2.3 ~1.2 true +intersects >=1.2.3 ^1 true +intersects <1.2.3 1.2.3 false +intersects <1.2.3 =1.2.3 false +intersects <1.2.3 >1.2.3 false +intersects <1.2.3 >=1.2.3 false +intersects <1.2.3 <1.2.3 true +intersects <1.2.3 <=1.2.3 true +intersects <1.2.3 ^1.2.3 false +intersects <1.2.3 ~1.2.3 false +intersects <1.2.3 ^0.2.3 true +intersects <1.2.3 ^0.0.3 true +intersects <1.2.3 1.2.x true +intersects <1.2.3 1.x true +intersects <1.2.3 * true +intersects <1.2.3 >=1.2.3 <2.0.0 false +intersects <1.2.3 1.2.3 - 2.3.4 false +intersects <1.2.3 1.2.3 || >=2.0.0 false +intersects <1.2.3 ^1.2.3-alpha true +intersects <1.2.3 ~1.2 true +intersects <1.2.3 ^1 true +intersects <=1.2.3 1.2.3 true +intersects <=1.2.3 =1.2.3 true +intersects <=1.2.3 >1.2.3 false +intersects <=1.2.3 >=1.2.3 true +intersects <=1.2.3 <1.2.3 true +intersects <=1.2.3 <=1.2.3 true +intersects <=1.2.3 ^1.2.3 true +intersects <=1.2.3 ~1.2.3 true +intersects <=1.2.3 ^0.2.3 true +intersects <=1.2.3 ^0.0.3 true +intersects <=1.2.3 1.2.x true +intersects <=1.2.3 1.x true +intersects <=1.2.3 * true +intersects <=1.2.3 >=1.2.3 <2.0.0 true +intersects <=1.2.3 1.2.3 - 2.3.4 true +intersects <=1.2.3 1.2.3 || >=2.0.0 true +intersects <=1.2.3 ^1.2.3-alpha true +intersects <=1.2.3 ~1.2 true +intersects <=1.2.3 ^1 true +intersects ^1.2.3 1.2.3 true +intersects ^1.2.3 =1.2.3 true +intersects ^1.2.3 >1.2.3 true +intersects ^1.2.3 >=1.2.3 true +intersects ^1.2.3 <1.2.3 false +intersects ^1.2.3 <=1.2.3 true +intersects ^1.2.3 ^1.2.3 true +intersects ^1.2.3 ~1.2.3 true +intersects ^1.2.3 ^0.2.3 false +intersects ^1.2.3 ^0.0.3 false +intersects ^1.2.3 1.2.x true +intersects ^1.2.3 1.x true +intersects ^1.2.3 * true +intersects ^1.2.3 >=1.2.3 <2.0.0 true +intersects ^1.2.3 1.2.3 - 2.3.4 true +intersects ^1.2.3 1.2.3 || >=2.0.0 true +intersects ^1.2.3 ^1.2.3-alpha true +intersects ^1.2.3 ~1.2 true +intersects ^1.2.3 ^1 true +intersects ~1.2.3 1.2.3 true +intersects ~1.2.3 =1.2.3 true +intersects ~1.2.3 >1.2.3 true +intersects ~1.2.3 >=1.2.3 true +intersects ~1.2.3 <1.2.3 false +intersects ~1.2.3 <=1.2.3 true +intersects ~1.2.3 ^1.2.3 true +intersects ~1.2.3 ~1.2.3 true +intersects ~1.2.3 ^0.2.3 false +intersects ~1.2.3 ^0.0.3 false +intersects ~1.2.3 1.2.x true +intersects ~1.2.3 1.x true +intersects ~1.2.3 * true +intersects ~1.2.3 >=1.2.3 <2.0.0 true +intersects ~1.2.3 1.2.3 - 2.3.4 true +intersects ~1.2.3 1.2.3 || >=2.0.0 true +intersects ~1.2.3 ^1.2.3-alpha true +intersects ~1.2.3 ~1.2 true +intersects ~1.2.3 ^1 true +intersects ^0.2.3 1.2.3 false +intersects ^0.2.3 =1.2.3 false +intersects ^0.2.3 >1.2.3 false +intersects ^0.2.3 >=1.2.3 false +intersects ^0.2.3 <1.2.3 true +intersects ^0.2.3 <=1.2.3 true +intersects ^0.2.3 ^1.2.3 false +intersects ^0.2.3 ~1.2.3 false +intersects ^0.2.3 ^0.2.3 true +intersects ^0.2.3 ^0.0.3 false +intersects ^0.2.3 1.2.x false +intersects ^0.2.3 1.x false +intersects ^0.2.3 * true +intersects ^0.2.3 >=1.2.3 <2.0.0 false +intersects ^0.2.3 1.2.3 - 2.3.4 false +intersects ^0.2.3 1.2.3 || >=2.0.0 false +intersects ^0.2.3 ^1.2.3-alpha false +intersects ^0.2.3 ~1.2 false +intersects ^0.2.3 ^1 false +intersects ^0.0.3 1.2.3 false +intersects ^0.0.3 =1.2.3 false +intersects ^0.0.3 >1.2.3 false +intersects ^0.0.3 >=1.2.3 false +intersects ^0.0.3 <1.2.3 true +intersects ^0.0.3 <=1.2.3 true +intersects ^0.0.3 ^1.2.3 false +intersects ^0.0.3 ~1.2.3 false +intersects ^0.0.3 ^0.2.3 false +intersects ^0.0.3 ^0.0.3 true +intersects ^0.0.3 1.2.x false +intersects ^0.0.3 1.x false +intersects ^0.0.3 * true +intersects ^0.0.3 >=1.2.3 <2.0.0 false +intersects ^0.0.3 1.2.3 - 2.3.4 false +intersects ^0.0.3 1.2.3 || >=2.0.0 false +intersects ^0.0.3 ^1.2.3-alpha false +intersects ^0.0.3 ~1.2 false +intersects ^0.0.3 ^1 false +intersects 1.2.x 1.2.3 true +intersects 1.2.x =1.2.3 true +intersects 1.2.x >1.2.3 true +intersects 1.2.x >=1.2.3 true +intersects 1.2.x <1.2.3 true +intersects 1.2.x <=1.2.3 true +intersects 1.2.x ^1.2.3 true +intersects 1.2.x ~1.2.3 true +intersects 1.2.x ^0.2.3 false +intersects 1.2.x ^0.0.3 false +intersects 1.2.x 1.2.x true +intersects 1.2.x 1.x true +intersects 1.2.x * true +intersects 1.2.x >=1.2.3 <2.0.0 true +intersects 1.2.x 1.2.3 - 2.3.4 true +intersects 1.2.x 1.2.3 || >=2.0.0 true +intersects 1.2.x ^1.2.3-alpha true +intersects 1.2.x ~1.2 true +intersects 1.2.x ^1 true +intersects 1.x 1.2.3 true +intersects 1.x =1.2.3 true +intersects 1.x >1.2.3 true +intersects 1.x >=1.2.3 true +intersects 1.x <1.2.3 true +intersects 1.x <=1.2.3 true +intersects 1.x ^1.2.3 true +intersects 1.x ~1.2.3 true +intersects 1.x ^0.2.3 false +intersects 1.x ^0.0.3 false +intersects 1.x 1.2.x true +intersects 1.x 1.x true +intersects 1.x * true +intersects 1.x >=1.2.3 <2.0.0 true +intersects 1.x 1.2.3 - 2.3.4 true +intersects 1.x 1.2.3 || >=2.0.0 true +intersects 1.x ^1.2.3-alpha true +intersects 1.x ~1.2 true +intersects 1.x ^1 true +intersects * 1.2.3 true +intersects * =1.2.3 true +intersects * >1.2.3 true +intersects * >=1.2.3 true +intersects * <1.2.3 true +intersects * <=1.2.3 true +intersects * ^1.2.3 true +intersects * ~1.2.3 true +intersects * ^0.2.3 true +intersects * ^0.0.3 true +intersects * 1.2.x true +intersects * 1.x true +intersects * * true +intersects * >=1.2.3 <2.0.0 true +intersects * 1.2.3 - 2.3.4 true +intersects * 1.2.3 || >=2.0.0 true +intersects * ^1.2.3-alpha true +intersects * ~1.2 true +intersects * ^1 true +intersects >=1.2.3 <2.0.0 1.2.3 true +intersects >=1.2.3 <2.0.0 =1.2.3 true +intersects >=1.2.3 <2.0.0 >1.2.3 true +intersects >=1.2.3 <2.0.0 >=1.2.3 true +intersects >=1.2.3 <2.0.0 <1.2.3 false +intersects >=1.2.3 <2.0.0 <=1.2.3 true +intersects >=1.2.3 <2.0.0 ^1.2.3 true +intersects >=1.2.3 <2.0.0 ~1.2.3 true +intersects >=1.2.3 <2.0.0 ^0.2.3 false +intersects >=1.2.3 <2.0.0 ^0.0.3 false +intersects >=1.2.3 <2.0.0 1.2.x true +intersects >=1.2.3 <2.0.0 1.x true +intersects >=1.2.3 <2.0.0 * true +intersects >=1.2.3 <2.0.0 >=1.2.3 <2.0.0 true +intersects >=1.2.3 <2.0.0 1.2.3 - 2.3.4 true +intersects >=1.2.3 <2.0.0 1.2.3 || >=2.0.0 true +intersects >=1.2.3 <2.0.0 ^1.2.3-alpha true +intersects >=1.2.3 <2.0.0 ~1.2 true +intersects >=1.2.3 <2.0.0 ^1 true +intersects 1.2.3 - 2.3.4 1.2.3 true +intersects 1.2.3 - 2.3.4 =1.2.3 true +intersects 1.2.3 - 2.3.4 >1.2.3 true +intersects 1.2.3 - 2.3.4 >=1.2.3 true +intersects 1.2.3 - 2.3.4 <1.2.3 false +intersects 1.2.3 - 2.3.4 <=1.2.3 true +intersects 1.2.3 - 2.3.4 ^1.2.3 true +intersects 1.2.3 - 2.3.4 ~1.2.3 true +intersects 1.2.3 - 2.3.4 ^0.2.3 false +intersects 1.2.3 - 2.3.4 ^0.0.3 false +intersects 1.2.3 - 2.3.4 1.2.x true +intersects 1.2.3 - 2.3.4 1.x true +intersects 1.2.3 - 2.3.4 * true +intersects 1.2.3 - 2.3.4 >=1.2.3 <2.0.0 true +intersects 1.2.3 - 2.3.4 1.2.3 - 2.3.4 true +intersects 1.2.3 - 2.3.4 1.2.3 || >=2.0.0 true +intersects 1.2.3 - 2.3.4 ^1.2.3-alpha true +intersects 1.2.3 - 2.3.4 ~1.2 true +intersects 1.2.3 - 2.3.4 ^1 true +intersects 1.2.3 || >=2.0.0 1.2.3 true +intersects 1.2.3 || >=2.0.0 =1.2.3 true +intersects 1.2.3 || >=2.0.0 >1.2.3 true +intersects 1.2.3 || >=2.0.0 >=1.2.3 true +intersects 1.2.3 || >=2.0.0 <1.2.3 false +intersects 1.2.3 || >=2.0.0 <=1.2.3 true +intersects 1.2.3 || >=2.0.0 ^1.2.3 true +intersects 1.2.3 || >=2.0.0 ~1.2.3 true +intersects 1.2.3 || >=2.0.0 ^0.2.3 false +intersects 1.2.3 || >=2.0.0 ^0.0.3 false +intersects 1.2.3 || >=2.0.0 1.2.x true +intersects 1.2.3 || >=2.0.0 1.x true +intersects 1.2.3 || >=2.0.0 * true +intersects 1.2.3 || >=2.0.0 >=1.2.3 <2.0.0 true +intersects 1.2.3 || >=2.0.0 1.2.3 - 2.3.4 true +intersects 1.2.3 || >=2.0.0 1.2.3 || >=2.0.0 true +intersects 1.2.3 || >=2.0.0 ^1.2.3-alpha true +intersects 1.2.3 || >=2.0.0 ~1.2 true +intersects 1.2.3 || >=2.0.0 ^1 true +intersects ^1.2.3-alpha 1.2.3 true +intersects ^1.2.3-alpha =1.2.3 true +intersects ^1.2.3-alpha >1.2.3 true +intersects ^1.2.3-alpha >=1.2.3 true +intersects ^1.2.3-alpha <1.2.3 true +intersects ^1.2.3-alpha <=1.2.3 true +intersects ^1.2.3-alpha ^1.2.3 true +intersects ^1.2.3-alpha ~1.2.3 true +intersects ^1.2.3-alpha ^0.2.3 false +intersects ^1.2.3-alpha ^0.0.3 false +intersects ^1.2.3-alpha 1.2.x true +intersects ^1.2.3-alpha 1.x true +intersects ^1.2.3-alpha * true +intersects ^1.2.3-alpha >=1.2.3 <2.0.0 true +intersects ^1.2.3-alpha 1.2.3 - 2.3.4 true +intersects ^1.2.3-alpha 1.2.3 || >=2.0.0 true +intersects ^1.2.3-alpha ^1.2.3-alpha true +intersects ^1.2.3-alpha ~1.2 true +intersects ^1.2.3-alpha ^1 true +intersects ~1.2 1.2.3 true +intersects ~1.2 =1.2.3 true +intersects ~1.2 >1.2.3 true +intersects ~1.2 >=1.2.3 true +intersects ~1.2 <1.2.3 true +intersects ~1.2 <=1.2.3 true +intersects ~1.2 ^1.2.3 true +intersects ~1.2 ~1.2.3 true +intersects ~1.2 ^0.2.3 false +intersects ~1.2 ^0.0.3 false +intersects ~1.2 1.2.x true +intersects ~1.2 1.x true +intersects ~1.2 * true +intersects ~1.2 >=1.2.3 <2.0.0 true +intersects ~1.2 1.2.3 - 2.3.4 true +intersects ~1.2 1.2.3 || >=2.0.0 true +intersects ~1.2 ^1.2.3-alpha true +intersects ~1.2 ~1.2 true +intersects ~1.2 ^1 true +intersects ^1 1.2.3 true +intersects ^1 =1.2.3 true +intersects ^1 >1.2.3 true +intersects ^1 >=1.2.3 true +intersects ^1 <1.2.3 true +intersects ^1 <=1.2.3 true +intersects ^1 ^1.2.3 true +intersects ^1 ~1.2.3 true +intersects ^1 ^0.2.3 false +intersects ^1 ^0.0.3 false +intersects ^1 1.2.x true +intersects ^1 1.x true +intersects ^1 * true +intersects ^1 >=1.2.3 <2.0.0 true +intersects ^1 1.2.3 - 2.3.4 true +intersects ^1 1.2.3 || >=2.0.0 true +intersects ^1 ^1.2.3-alpha true +intersects ^1 ~1.2 true +intersects ^1 ^1 true +compare 0.0.0 0.0.0 0 +compare 0.0.0 1.0.0 -1 +compare 0.0.0 1.2.3 -1 +compare 0.0.0 1.2.4 -1 +compare 0.0.0 1.3.0 -1 +compare 0.0.0 2.0.0 -1 +compare 0.0.0 1.0.0-alpha -1 +compare 0.0.0 1.0.0-alpha.1 -1 +compare 0.0.0 1.0.0-alpha.beta -1 +compare 0.0.0 1.0.0-beta -1 +compare 0.0.0 1.0.0-beta.2 -1 +compare 0.0.0 1.0.0-beta.11 -1 +compare 0.0.0 1.0.0-rc.1 -1 +compare 0.0.0 1.2.3-0 -1 +compare 0.0.0 2.0.0-0 -1 +compare 0.0.0 1.2.3+build -1 +compare 0.0.0 1.0.0+007 -1 +compare 0.0.0 10.0.0 -1 +compare 1.0.0 0.0.0 1 +compare 1.0.0 1.0.0 0 +compare 1.0.0 1.2.3 -1 +compare 1.0.0 1.2.4 -1 +compare 1.0.0 1.3.0 -1 +compare 1.0.0 2.0.0 -1 +compare 1.0.0 1.0.0-alpha 1 +compare 1.0.0 1.0.0-alpha.1 1 +compare 1.0.0 1.0.0-alpha.beta 1 +compare 1.0.0 1.0.0-beta 1 +compare 1.0.0 1.0.0-beta.2 1 +compare 1.0.0 1.0.0-beta.11 1 +compare 1.0.0 1.0.0-rc.1 1 +compare 1.0.0 1.2.3-0 -1 +compare 1.0.0 2.0.0-0 -1 +compare 1.0.0 1.2.3+build -1 +compare 1.0.0 1.0.0+007 0 +compare 1.0.0 10.0.0 -1 +compare 1.2.3 0.0.0 1 +compare 1.2.3 1.0.0 1 +compare 1.2.3 1.2.3 0 +compare 1.2.3 1.2.4 -1 +compare 1.2.3 1.3.0 -1 +compare 1.2.3 2.0.0 -1 +compare 1.2.3 1.0.0-alpha 1 +compare 1.2.3 1.0.0-alpha.1 1 +compare 1.2.3 1.0.0-alpha.beta 1 +compare 1.2.3 1.0.0-beta 1 +compare 1.2.3 1.0.0-beta.2 1 +compare 1.2.3 1.0.0-beta.11 1 +compare 1.2.3 1.0.0-rc.1 1 +compare 1.2.3 1.2.3-0 1 +compare 1.2.3 2.0.0-0 -1 +compare 1.2.3 1.2.3+build 0 +compare 1.2.3 1.0.0+007 1 +compare 1.2.3 10.0.0 -1 +compare 1.2.4 0.0.0 1 +compare 1.2.4 1.0.0 1 +compare 1.2.4 1.2.3 1 +compare 1.2.4 1.2.4 0 +compare 1.2.4 1.3.0 -1 +compare 1.2.4 2.0.0 -1 +compare 1.2.4 1.0.0-alpha 1 +compare 1.2.4 1.0.0-alpha.1 1 +compare 1.2.4 1.0.0-alpha.beta 1 +compare 1.2.4 1.0.0-beta 1 +compare 1.2.4 1.0.0-beta.2 1 +compare 1.2.4 1.0.0-beta.11 1 +compare 1.2.4 1.0.0-rc.1 1 +compare 1.2.4 1.2.3-0 1 +compare 1.2.4 2.0.0-0 -1 +compare 1.2.4 1.2.3+build 1 +compare 1.2.4 1.0.0+007 1 +compare 1.2.4 10.0.0 -1 +compare 1.3.0 0.0.0 1 +compare 1.3.0 1.0.0 1 +compare 1.3.0 1.2.3 1 +compare 1.3.0 1.2.4 1 +compare 1.3.0 1.3.0 0 +compare 1.3.0 2.0.0 -1 +compare 1.3.0 1.0.0-alpha 1 +compare 1.3.0 1.0.0-alpha.1 1 +compare 1.3.0 1.0.0-alpha.beta 1 +compare 1.3.0 1.0.0-beta 1 +compare 1.3.0 1.0.0-beta.2 1 +compare 1.3.0 1.0.0-beta.11 1 +compare 1.3.0 1.0.0-rc.1 1 +compare 1.3.0 1.2.3-0 1 +compare 1.3.0 2.0.0-0 -1 +compare 1.3.0 1.2.3+build 1 +compare 1.3.0 1.0.0+007 1 +compare 1.3.0 10.0.0 -1 +compare 2.0.0 0.0.0 1 +compare 2.0.0 1.0.0 1 +compare 2.0.0 1.2.3 1 +compare 2.0.0 1.2.4 1 +compare 2.0.0 1.3.0 1 +compare 2.0.0 2.0.0 0 +compare 2.0.0 1.0.0-alpha 1 +compare 2.0.0 1.0.0-alpha.1 1 +compare 2.0.0 1.0.0-alpha.beta 1 +compare 2.0.0 1.0.0-beta 1 +compare 2.0.0 1.0.0-beta.2 1 +compare 2.0.0 1.0.0-beta.11 1 +compare 2.0.0 1.0.0-rc.1 1 +compare 2.0.0 1.2.3-0 1 +compare 2.0.0 2.0.0-0 1 +compare 2.0.0 1.2.3+build 1 +compare 2.0.0 1.0.0+007 1 +compare 2.0.0 10.0.0 -1 +compare 1.0.0-alpha 0.0.0 1 +compare 1.0.0-alpha 1.0.0 -1 +compare 1.0.0-alpha 1.2.3 -1 +compare 1.0.0-alpha 1.2.4 -1 +compare 1.0.0-alpha 1.3.0 -1 +compare 1.0.0-alpha 2.0.0 -1 +compare 1.0.0-alpha 1.0.0-alpha 0 +compare 1.0.0-alpha 1.0.0-alpha.1 -1 +compare 1.0.0-alpha 1.0.0-alpha.beta -1 +compare 1.0.0-alpha 1.0.0-beta -1 +compare 1.0.0-alpha 1.0.0-beta.2 -1 +compare 1.0.0-alpha 1.0.0-beta.11 -1 +compare 1.0.0-alpha 1.0.0-rc.1 -1 +compare 1.0.0-alpha 1.2.3-0 -1 +compare 1.0.0-alpha 2.0.0-0 -1 +compare 1.0.0-alpha 1.2.3+build -1 +compare 1.0.0-alpha 1.0.0+007 -1 +compare 1.0.0-alpha 10.0.0 -1 +compare 1.0.0-alpha.1 0.0.0 1 +compare 1.0.0-alpha.1 1.0.0 -1 +compare 1.0.0-alpha.1 1.2.3 -1 +compare 1.0.0-alpha.1 1.2.4 -1 +compare 1.0.0-alpha.1 1.3.0 -1 +compare 1.0.0-alpha.1 2.0.0 -1 +compare 1.0.0-alpha.1 1.0.0-alpha 1 +compare 1.0.0-alpha.1 1.0.0-alpha.1 0 +compare 1.0.0-alpha.1 1.0.0-alpha.beta -1 +compare 1.0.0-alpha.1 1.0.0-beta -1 +compare 1.0.0-alpha.1 1.0.0-beta.2 -1 +compare 1.0.0-alpha.1 1.0.0-beta.11 -1 +compare 1.0.0-alpha.1 1.0.0-rc.1 -1 +compare 1.0.0-alpha.1 1.2.3-0 -1 +compare 1.0.0-alpha.1 2.0.0-0 -1 +compare 1.0.0-alpha.1 1.2.3+build -1 +compare 1.0.0-alpha.1 1.0.0+007 -1 +compare 1.0.0-alpha.1 10.0.0 -1 +compare 1.0.0-alpha.beta 0.0.0 1 +compare 1.0.0-alpha.beta 1.0.0 -1 +compare 1.0.0-alpha.beta 1.2.3 -1 +compare 1.0.0-alpha.beta 1.2.4 -1 +compare 1.0.0-alpha.beta 1.3.0 -1 +compare 1.0.0-alpha.beta 2.0.0 -1 +compare 1.0.0-alpha.beta 1.0.0-alpha 1 +compare 1.0.0-alpha.beta 1.0.0-alpha.1 1 +compare 1.0.0-alpha.beta 1.0.0-alpha.beta 0 +compare 1.0.0-alpha.beta 1.0.0-beta -1 +compare 1.0.0-alpha.beta 1.0.0-beta.2 -1 +compare 1.0.0-alpha.beta 1.0.0-beta.11 -1 +compare 1.0.0-alpha.beta 1.0.0-rc.1 -1 +compare 1.0.0-alpha.beta 1.2.3-0 -1 +compare 1.0.0-alpha.beta 2.0.0-0 -1 +compare 1.0.0-alpha.beta 1.2.3+build -1 +compare 1.0.0-alpha.beta 1.0.0+007 -1 +compare 1.0.0-alpha.beta 10.0.0 -1 +compare 1.0.0-beta 0.0.0 1 +compare 1.0.0-beta 1.0.0 -1 +compare 1.0.0-beta 1.2.3 -1 +compare 1.0.0-beta 1.2.4 -1 +compare 1.0.0-beta 1.3.0 -1 +compare 1.0.0-beta 2.0.0 -1 +compare 1.0.0-beta 1.0.0-alpha 1 +compare 1.0.0-beta 1.0.0-alpha.1 1 +compare 1.0.0-beta 1.0.0-alpha.beta 1 +compare 1.0.0-beta 1.0.0-beta 0 +compare 1.0.0-beta 1.0.0-beta.2 -1 +compare 1.0.0-beta 1.0.0-beta.11 -1 +compare 1.0.0-beta 1.0.0-rc.1 -1 +compare 1.0.0-beta 1.2.3-0 -1 +compare 1.0.0-beta 2.0.0-0 -1 +compare 1.0.0-beta 1.2.3+build -1 +compare 1.0.0-beta 1.0.0+007 -1 +compare 1.0.0-beta 10.0.0 -1 +compare 1.0.0-beta.2 0.0.0 1 +compare 1.0.0-beta.2 1.0.0 -1 +compare 1.0.0-beta.2 1.2.3 -1 +compare 1.0.0-beta.2 1.2.4 -1 +compare 1.0.0-beta.2 1.3.0 -1 +compare 1.0.0-beta.2 2.0.0 -1 +compare 1.0.0-beta.2 1.0.0-alpha 1 +compare 1.0.0-beta.2 1.0.0-alpha.1 1 +compare 1.0.0-beta.2 1.0.0-alpha.beta 1 +compare 1.0.0-beta.2 1.0.0-beta 1 +compare 1.0.0-beta.2 1.0.0-beta.2 0 +compare 1.0.0-beta.2 1.0.0-beta.11 -1 +compare 1.0.0-beta.2 1.0.0-rc.1 -1 +compare 1.0.0-beta.2 1.2.3-0 -1 +compare 1.0.0-beta.2 2.0.0-0 -1 +compare 1.0.0-beta.2 1.2.3+build -1 +compare 1.0.0-beta.2 1.0.0+007 -1 +compare 1.0.0-beta.2 10.0.0 -1 +compare 1.0.0-beta.11 0.0.0 1 +compare 1.0.0-beta.11 1.0.0 -1 +compare 1.0.0-beta.11 1.2.3 -1 +compare 1.0.0-beta.11 1.2.4 -1 +compare 1.0.0-beta.11 1.3.0 -1 +compare 1.0.0-beta.11 2.0.0 -1 +compare 1.0.0-beta.11 1.0.0-alpha 1 +compare 1.0.0-beta.11 1.0.0-alpha.1 1 +compare 1.0.0-beta.11 1.0.0-alpha.beta 1 +compare 1.0.0-beta.11 1.0.0-beta 1 +compare 1.0.0-beta.11 1.0.0-beta.2 1 +compare 1.0.0-beta.11 1.0.0-beta.11 0 +compare 1.0.0-beta.11 1.0.0-rc.1 -1 +compare 1.0.0-beta.11 1.2.3-0 -1 +compare 1.0.0-beta.11 2.0.0-0 -1 +compare 1.0.0-beta.11 1.2.3+build -1 +compare 1.0.0-beta.11 1.0.0+007 -1 +compare 1.0.0-beta.11 10.0.0 -1 +compare 1.0.0-rc.1 0.0.0 1 +compare 1.0.0-rc.1 1.0.0 -1 +compare 1.0.0-rc.1 1.2.3 -1 +compare 1.0.0-rc.1 1.2.4 -1 +compare 1.0.0-rc.1 1.3.0 -1 +compare 1.0.0-rc.1 2.0.0 -1 +compare 1.0.0-rc.1 1.0.0-alpha 1 +compare 1.0.0-rc.1 1.0.0-alpha.1 1 +compare 1.0.0-rc.1 1.0.0-alpha.beta 1 +compare 1.0.0-rc.1 1.0.0-beta 1 +compare 1.0.0-rc.1 1.0.0-beta.2 1 +compare 1.0.0-rc.1 1.0.0-beta.11 1 +compare 1.0.0-rc.1 1.0.0-rc.1 0 +compare 1.0.0-rc.1 1.2.3-0 -1 +compare 1.0.0-rc.1 2.0.0-0 -1 +compare 1.0.0-rc.1 1.2.3+build -1 +compare 1.0.0-rc.1 1.0.0+007 -1 +compare 1.0.0-rc.1 10.0.0 -1 +compare 1.2.3-0 0.0.0 1 +compare 1.2.3-0 1.0.0 1 +compare 1.2.3-0 1.2.3 -1 +compare 1.2.3-0 1.2.4 -1 +compare 1.2.3-0 1.3.0 -1 +compare 1.2.3-0 2.0.0 -1 +compare 1.2.3-0 1.0.0-alpha 1 +compare 1.2.3-0 1.0.0-alpha.1 1 +compare 1.2.3-0 1.0.0-alpha.beta 1 +compare 1.2.3-0 1.0.0-beta 1 +compare 1.2.3-0 1.0.0-beta.2 1 +compare 1.2.3-0 1.0.0-beta.11 1 +compare 1.2.3-0 1.0.0-rc.1 1 +compare 1.2.3-0 1.2.3-0 0 +compare 1.2.3-0 2.0.0-0 -1 +compare 1.2.3-0 1.2.3+build -1 +compare 1.2.3-0 1.0.0+007 1 +compare 1.2.3-0 10.0.0 -1 +compare 2.0.0-0 0.0.0 1 +compare 2.0.0-0 1.0.0 1 +compare 2.0.0-0 1.2.3 1 +compare 2.0.0-0 1.2.4 1 +compare 2.0.0-0 1.3.0 1 +compare 2.0.0-0 2.0.0 -1 +compare 2.0.0-0 1.0.0-alpha 1 +compare 2.0.0-0 1.0.0-alpha.1 1 +compare 2.0.0-0 1.0.0-alpha.beta 1 +compare 2.0.0-0 1.0.0-beta 1 +compare 2.0.0-0 1.0.0-beta.2 1 +compare 2.0.0-0 1.0.0-beta.11 1 +compare 2.0.0-0 1.0.0-rc.1 1 +compare 2.0.0-0 1.2.3-0 1 +compare 2.0.0-0 2.0.0-0 0 +compare 2.0.0-0 1.2.3+build 1 +compare 2.0.0-0 1.0.0+007 1 +compare 2.0.0-0 10.0.0 -1 +compare 1.2.3+build 0.0.0 1 +compare 1.2.3+build 1.0.0 1 +compare 1.2.3+build 1.2.3 0 +compare 1.2.3+build 1.2.4 -1 +compare 1.2.3+build 1.3.0 -1 +compare 1.2.3+build 2.0.0 -1 +compare 1.2.3+build 1.0.0-alpha 1 +compare 1.2.3+build 1.0.0-alpha.1 1 +compare 1.2.3+build 1.0.0-alpha.beta 1 +compare 1.2.3+build 1.0.0-beta 1 +compare 1.2.3+build 1.0.0-beta.2 1 +compare 1.2.3+build 1.0.0-beta.11 1 +compare 1.2.3+build 1.0.0-rc.1 1 +compare 1.2.3+build 1.2.3-0 1 +compare 1.2.3+build 2.0.0-0 -1 +compare 1.2.3+build 1.2.3+build 0 +compare 1.2.3+build 1.0.0+007 1 +compare 1.2.3+build 10.0.0 -1 +compare 1.0.0+007 0.0.0 1 +compare 1.0.0+007 1.0.0 0 +compare 1.0.0+007 1.2.3 -1 +compare 1.0.0+007 1.2.4 -1 +compare 1.0.0+007 1.3.0 -1 +compare 1.0.0+007 2.0.0 -1 +compare 1.0.0+007 1.0.0-alpha 1 +compare 1.0.0+007 1.0.0-alpha.1 1 +compare 1.0.0+007 1.0.0-alpha.beta 1 +compare 1.0.0+007 1.0.0-beta 1 +compare 1.0.0+007 1.0.0-beta.2 1 +compare 1.0.0+007 1.0.0-beta.11 1 +compare 1.0.0+007 1.0.0-rc.1 1 +compare 1.0.0+007 1.2.3-0 -1 +compare 1.0.0+007 2.0.0-0 -1 +compare 1.0.0+007 1.2.3+build -1 +compare 1.0.0+007 1.0.0+007 0 +compare 1.0.0+007 10.0.0 -1 +compare 10.0.0 0.0.0 1 +compare 10.0.0 1.0.0 1 +compare 10.0.0 1.2.3 1 +compare 10.0.0 1.2.4 1 +compare 10.0.0 1.3.0 1 +compare 10.0.0 2.0.0 1 +compare 10.0.0 1.0.0-alpha 1 +compare 10.0.0 1.0.0-alpha.1 1 +compare 10.0.0 1.0.0-alpha.beta 1 +compare 10.0.0 1.0.0-beta 1 +compare 10.0.0 1.0.0-beta.2 1 +compare 10.0.0 1.0.0-beta.11 1 +compare 10.0.0 1.0.0-rc.1 1 +compare 10.0.0 1.2.3-0 1 +compare 10.0.0 2.0.0-0 1 +compare 10.0.0 1.2.3+build 1 +compare 10.0.0 1.0.0+007 1 +compare 10.0.0 10.0.0 0 +diff 0.0.0 0.0.0 NULL +diff 0.0.0 1.0.0 major +diff 0.0.0 1.2.3 major +diff 0.0.0 1.2.4 major +diff 0.0.0 1.3.0 major +diff 0.0.0 2.0.0 major +diff 0.0.0 1.0.0-alpha premajor +diff 0.0.0 1.0.0-alpha.1 premajor +diff 0.0.0 1.0.0-alpha.beta premajor +diff 0.0.0 1.0.0-beta premajor +diff 0.0.0 1.0.0-beta.2 premajor +diff 0.0.0 1.0.0-beta.11 premajor +diff 0.0.0 1.0.0-rc.1 premajor +diff 0.0.0 1.2.3-0 premajor +diff 0.0.0 2.0.0-0 premajor +diff 0.0.0 1.2.3+build major +diff 0.0.0 1.0.0+007 major +diff 0.0.0 10.0.0 major +diff 1.0.0 0.0.0 major +diff 1.0.0 1.0.0 NULL +diff 1.0.0 1.2.3 minor +diff 1.0.0 1.2.4 minor +diff 1.0.0 1.3.0 minor +diff 1.0.0 2.0.0 major +diff 1.0.0 1.0.0-alpha major +diff 1.0.0 1.0.0-alpha.1 major +diff 1.0.0 1.0.0-alpha.beta major +diff 1.0.0 1.0.0-beta major +diff 1.0.0 1.0.0-beta.2 major +diff 1.0.0 1.0.0-beta.11 major +diff 1.0.0 1.0.0-rc.1 major +diff 1.0.0 1.2.3-0 preminor +diff 1.0.0 2.0.0-0 premajor +diff 1.0.0 1.2.3+build minor +diff 1.0.0 1.0.0+007 NULL +diff 1.0.0 10.0.0 major +diff 1.2.3 0.0.0 major +diff 1.2.3 1.0.0 minor +diff 1.2.3 1.2.3 NULL +diff 1.2.3 1.2.4 patch +diff 1.2.3 1.3.0 minor +diff 1.2.3 2.0.0 major +diff 1.2.3 1.0.0-alpha major +diff 1.2.3 1.0.0-alpha.1 major +diff 1.2.3 1.0.0-alpha.beta major +diff 1.2.3 1.0.0-beta major +diff 1.2.3 1.0.0-beta.2 major +diff 1.2.3 1.0.0-beta.11 major +diff 1.2.3 1.0.0-rc.1 major +diff 1.2.3 1.2.3-0 patch +diff 1.2.3 2.0.0-0 premajor +diff 1.2.3 1.2.3+build NULL +diff 1.2.3 1.0.0+007 minor +diff 1.2.3 10.0.0 major +diff 1.2.4 0.0.0 major +diff 1.2.4 1.0.0 minor +diff 1.2.4 1.2.3 patch +diff 1.2.4 1.2.4 NULL +diff 1.2.4 1.3.0 minor +diff 1.2.4 2.0.0 major +diff 1.2.4 1.0.0-alpha major +diff 1.2.4 1.0.0-alpha.1 major +diff 1.2.4 1.0.0-alpha.beta major +diff 1.2.4 1.0.0-beta major +diff 1.2.4 1.0.0-beta.2 major +diff 1.2.4 1.0.0-beta.11 major +diff 1.2.4 1.0.0-rc.1 major +diff 1.2.4 1.2.3-0 patch +diff 1.2.4 2.0.0-0 premajor +diff 1.2.4 1.2.3+build patch +diff 1.2.4 1.0.0+007 minor +diff 1.2.4 10.0.0 major +diff 1.3.0 0.0.0 major +diff 1.3.0 1.0.0 minor +diff 1.3.0 1.2.3 minor +diff 1.3.0 1.2.4 minor +diff 1.3.0 1.3.0 NULL +diff 1.3.0 2.0.0 major +diff 1.3.0 1.0.0-alpha major +diff 1.3.0 1.0.0-alpha.1 major +diff 1.3.0 1.0.0-alpha.beta major +diff 1.3.0 1.0.0-beta major +diff 1.3.0 1.0.0-beta.2 major +diff 1.3.0 1.0.0-beta.11 major +diff 1.3.0 1.0.0-rc.1 major +diff 1.3.0 1.2.3-0 minor +diff 1.3.0 2.0.0-0 premajor +diff 1.3.0 1.2.3+build minor +diff 1.3.0 1.0.0+007 minor +diff 1.3.0 10.0.0 major +diff 2.0.0 0.0.0 major +diff 2.0.0 1.0.0 major +diff 2.0.0 1.2.3 major +diff 2.0.0 1.2.4 major +diff 2.0.0 1.3.0 major +diff 2.0.0 2.0.0 NULL +diff 2.0.0 1.0.0-alpha major +diff 2.0.0 1.0.0-alpha.1 major +diff 2.0.0 1.0.0-alpha.beta major +diff 2.0.0 1.0.0-beta major +diff 2.0.0 1.0.0-beta.2 major +diff 2.0.0 1.0.0-beta.11 major +diff 2.0.0 1.0.0-rc.1 major +diff 2.0.0 1.2.3-0 major +diff 2.0.0 2.0.0-0 major +diff 2.0.0 1.2.3+build major +diff 2.0.0 1.0.0+007 major +diff 2.0.0 10.0.0 major +diff 1.0.0-alpha 0.0.0 premajor +diff 1.0.0-alpha 1.0.0 major +diff 1.0.0-alpha 1.2.3 major +diff 1.0.0-alpha 1.2.4 major +diff 1.0.0-alpha 1.3.0 major +diff 1.0.0-alpha 2.0.0 major +diff 1.0.0-alpha 1.0.0-alpha NULL +diff 1.0.0-alpha 1.0.0-alpha.1 prerelease +diff 1.0.0-alpha 1.0.0-alpha.beta prerelease +diff 1.0.0-alpha 1.0.0-beta prerelease +diff 1.0.0-alpha 1.0.0-beta.2 prerelease +diff 1.0.0-alpha 1.0.0-beta.11 prerelease +diff 1.0.0-alpha 1.0.0-rc.1 prerelease +diff 1.0.0-alpha 1.2.3-0 preminor +diff 1.0.0-alpha 2.0.0-0 premajor +diff 1.0.0-alpha 1.2.3+build major +diff 1.0.0-alpha 1.0.0+007 major +diff 1.0.0-alpha 10.0.0 major +diff 1.0.0-alpha.1 0.0.0 premajor +diff 1.0.0-alpha.1 1.0.0 major +diff 1.0.0-alpha.1 1.2.3 major +diff 1.0.0-alpha.1 1.2.4 major +diff 1.0.0-alpha.1 1.3.0 major +diff 1.0.0-alpha.1 2.0.0 major +diff 1.0.0-alpha.1 1.0.0-alpha prerelease +diff 1.0.0-alpha.1 1.0.0-alpha.1 NULL +diff 1.0.0-alpha.1 1.0.0-alpha.beta prerelease +diff 1.0.0-alpha.1 1.0.0-beta prerelease +diff 1.0.0-alpha.1 1.0.0-beta.2 prerelease +diff 1.0.0-alpha.1 1.0.0-beta.11 prerelease +diff 1.0.0-alpha.1 1.0.0-rc.1 prerelease +diff 1.0.0-alpha.1 1.2.3-0 preminor +diff 1.0.0-alpha.1 2.0.0-0 premajor +diff 1.0.0-alpha.1 1.2.3+build major +diff 1.0.0-alpha.1 1.0.0+007 major +diff 1.0.0-alpha.1 10.0.0 major +diff 1.0.0-alpha.beta 0.0.0 premajor +diff 1.0.0-alpha.beta 1.0.0 major +diff 1.0.0-alpha.beta 1.2.3 major +diff 1.0.0-alpha.beta 1.2.4 major +diff 1.0.0-alpha.beta 1.3.0 major +diff 1.0.0-alpha.beta 2.0.0 major +diff 1.0.0-alpha.beta 1.0.0-alpha prerelease +diff 1.0.0-alpha.beta 1.0.0-alpha.1 prerelease +diff 1.0.0-alpha.beta 1.0.0-alpha.beta NULL +diff 1.0.0-alpha.beta 1.0.0-beta prerelease +diff 1.0.0-alpha.beta 1.0.0-beta.2 prerelease +diff 1.0.0-alpha.beta 1.0.0-beta.11 prerelease +diff 1.0.0-alpha.beta 1.0.0-rc.1 prerelease +diff 1.0.0-alpha.beta 1.2.3-0 preminor +diff 1.0.0-alpha.beta 2.0.0-0 premajor +diff 1.0.0-alpha.beta 1.2.3+build major +diff 1.0.0-alpha.beta 1.0.0+007 major +diff 1.0.0-alpha.beta 10.0.0 major +diff 1.0.0-beta 0.0.0 premajor +diff 1.0.0-beta 1.0.0 major +diff 1.0.0-beta 1.2.3 major +diff 1.0.0-beta 1.2.4 major +diff 1.0.0-beta 1.3.0 major +diff 1.0.0-beta 2.0.0 major +diff 1.0.0-beta 1.0.0-alpha prerelease +diff 1.0.0-beta 1.0.0-alpha.1 prerelease +diff 1.0.0-beta 1.0.0-alpha.beta prerelease +diff 1.0.0-beta 1.0.0-beta NULL +diff 1.0.0-beta 1.0.0-beta.2 prerelease +diff 1.0.0-beta 1.0.0-beta.11 prerelease +diff 1.0.0-beta 1.0.0-rc.1 prerelease +diff 1.0.0-beta 1.2.3-0 preminor +diff 1.0.0-beta 2.0.0-0 premajor +diff 1.0.0-beta 1.2.3+build major +diff 1.0.0-beta 1.0.0+007 major +diff 1.0.0-beta 10.0.0 major +diff 1.0.0-beta.2 0.0.0 premajor +diff 1.0.0-beta.2 1.0.0 major +diff 1.0.0-beta.2 1.2.3 major +diff 1.0.0-beta.2 1.2.4 major +diff 1.0.0-beta.2 1.3.0 major +diff 1.0.0-beta.2 2.0.0 major +diff 1.0.0-beta.2 1.0.0-alpha prerelease +diff 1.0.0-beta.2 1.0.0-alpha.1 prerelease +diff 1.0.0-beta.2 1.0.0-alpha.beta prerelease +diff 1.0.0-beta.2 1.0.0-beta prerelease +diff 1.0.0-beta.2 1.0.0-beta.2 NULL +diff 1.0.0-beta.2 1.0.0-beta.11 prerelease +diff 1.0.0-beta.2 1.0.0-rc.1 prerelease +diff 1.0.0-beta.2 1.2.3-0 preminor +diff 1.0.0-beta.2 2.0.0-0 premajor +diff 1.0.0-beta.2 1.2.3+build major +diff 1.0.0-beta.2 1.0.0+007 major +diff 1.0.0-beta.2 10.0.0 major +diff 1.0.0-beta.11 0.0.0 premajor +diff 1.0.0-beta.11 1.0.0 major +diff 1.0.0-beta.11 1.2.3 major +diff 1.0.0-beta.11 1.2.4 major +diff 1.0.0-beta.11 1.3.0 major +diff 1.0.0-beta.11 2.0.0 major +diff 1.0.0-beta.11 1.0.0-alpha prerelease +diff 1.0.0-beta.11 1.0.0-alpha.1 prerelease +diff 1.0.0-beta.11 1.0.0-alpha.beta prerelease +diff 1.0.0-beta.11 1.0.0-beta prerelease +diff 1.0.0-beta.11 1.0.0-beta.2 prerelease +diff 1.0.0-beta.11 1.0.0-beta.11 NULL +diff 1.0.0-beta.11 1.0.0-rc.1 prerelease +diff 1.0.0-beta.11 1.2.3-0 preminor +diff 1.0.0-beta.11 2.0.0-0 premajor +diff 1.0.0-beta.11 1.2.3+build major +diff 1.0.0-beta.11 1.0.0+007 major +diff 1.0.0-beta.11 10.0.0 major +diff 1.0.0-rc.1 0.0.0 premajor +diff 1.0.0-rc.1 1.0.0 major +diff 1.0.0-rc.1 1.2.3 major +diff 1.0.0-rc.1 1.2.4 major +diff 1.0.0-rc.1 1.3.0 major +diff 1.0.0-rc.1 2.0.0 major +diff 1.0.0-rc.1 1.0.0-alpha prerelease +diff 1.0.0-rc.1 1.0.0-alpha.1 prerelease +diff 1.0.0-rc.1 1.0.0-alpha.beta prerelease +diff 1.0.0-rc.1 1.0.0-beta prerelease +diff 1.0.0-rc.1 1.0.0-beta.2 prerelease +diff 1.0.0-rc.1 1.0.0-beta.11 prerelease +diff 1.0.0-rc.1 1.0.0-rc.1 NULL +diff 1.0.0-rc.1 1.2.3-0 preminor +diff 1.0.0-rc.1 2.0.0-0 premajor +diff 1.0.0-rc.1 1.2.3+build major +diff 1.0.0-rc.1 1.0.0+007 major +diff 1.0.0-rc.1 10.0.0 major +diff 1.2.3-0 0.0.0 premajor +diff 1.2.3-0 1.0.0 preminor +diff 1.2.3-0 1.2.3 patch +diff 1.2.3-0 1.2.4 patch +diff 1.2.3-0 1.3.0 minor +diff 1.2.3-0 2.0.0 major +diff 1.2.3-0 1.0.0-alpha preminor +diff 1.2.3-0 1.0.0-alpha.1 preminor +diff 1.2.3-0 1.0.0-alpha.beta preminor +diff 1.2.3-0 1.0.0-beta preminor +diff 1.2.3-0 1.0.0-beta.2 preminor +diff 1.2.3-0 1.0.0-beta.11 preminor +diff 1.2.3-0 1.0.0-rc.1 preminor +diff 1.2.3-0 1.2.3-0 NULL +diff 1.2.3-0 2.0.0-0 premajor +diff 1.2.3-0 1.2.3+build patch +diff 1.2.3-0 1.0.0+007 preminor +diff 1.2.3-0 10.0.0 major +diff 2.0.0-0 0.0.0 premajor +diff 2.0.0-0 1.0.0 premajor +diff 2.0.0-0 1.2.3 premajor +diff 2.0.0-0 1.2.4 premajor +diff 2.0.0-0 1.3.0 premajor +diff 2.0.0-0 2.0.0 major +diff 2.0.0-0 1.0.0-alpha premajor +diff 2.0.0-0 1.0.0-alpha.1 premajor +diff 2.0.0-0 1.0.0-alpha.beta premajor +diff 2.0.0-0 1.0.0-beta premajor +diff 2.0.0-0 1.0.0-beta.2 premajor +diff 2.0.0-0 1.0.0-beta.11 premajor +diff 2.0.0-0 1.0.0-rc.1 premajor +diff 2.0.0-0 1.2.3-0 premajor +diff 2.0.0-0 2.0.0-0 NULL +diff 2.0.0-0 1.2.3+build premajor +diff 2.0.0-0 1.0.0+007 premajor +diff 2.0.0-0 10.0.0 major +diff 1.2.3+build 0.0.0 major +diff 1.2.3+build 1.0.0 minor +diff 1.2.3+build 1.2.3 NULL +diff 1.2.3+build 1.2.4 patch +diff 1.2.3+build 1.3.0 minor +diff 1.2.3+build 2.0.0 major +diff 1.2.3+build 1.0.0-alpha major +diff 1.2.3+build 1.0.0-alpha.1 major +diff 1.2.3+build 1.0.0-alpha.beta major +diff 1.2.3+build 1.0.0-beta major +diff 1.2.3+build 1.0.0-beta.2 major +diff 1.2.3+build 1.0.0-beta.11 major +diff 1.2.3+build 1.0.0-rc.1 major +diff 1.2.3+build 1.2.3-0 patch +diff 1.2.3+build 2.0.0-0 premajor +diff 1.2.3+build 1.2.3+build NULL +diff 1.2.3+build 1.0.0+007 minor +diff 1.2.3+build 10.0.0 major +diff 1.0.0+007 0.0.0 major +diff 1.0.0+007 1.0.0 NULL +diff 1.0.0+007 1.2.3 minor +diff 1.0.0+007 1.2.4 minor +diff 1.0.0+007 1.3.0 minor +diff 1.0.0+007 2.0.0 major +diff 1.0.0+007 1.0.0-alpha major +diff 1.0.0+007 1.0.0-alpha.1 major +diff 1.0.0+007 1.0.0-alpha.beta major +diff 1.0.0+007 1.0.0-beta major +diff 1.0.0+007 1.0.0-beta.2 major +diff 1.0.0+007 1.0.0-beta.11 major +diff 1.0.0+007 1.0.0-rc.1 major +diff 1.0.0+007 1.2.3-0 preminor +diff 1.0.0+007 2.0.0-0 premajor +diff 1.0.0+007 1.2.3+build minor +diff 1.0.0+007 1.0.0+007 NULL +diff 1.0.0+007 10.0.0 major +diff 10.0.0 0.0.0 major +diff 10.0.0 1.0.0 major +diff 10.0.0 1.2.3 major +diff 10.0.0 1.2.4 major +diff 10.0.0 1.3.0 major +diff 10.0.0 2.0.0 major +diff 10.0.0 1.0.0-alpha major +diff 10.0.0 1.0.0-alpha.1 major +diff 10.0.0 1.0.0-alpha.beta major +diff 10.0.0 1.0.0-beta major +diff 10.0.0 1.0.0-beta.2 major +diff 10.0.0 1.0.0-beta.11 major +diff 10.0.0 1.0.0-rc.1 major +diff 10.0.0 1.2.3-0 major +diff 10.0.0 2.0.0-0 major +diff 10.0.0 1.2.3+build major +diff 10.0.0 1.0.0+007 major +diff 10.0.0 10.0.0 NULL +inc 0.0.0 major 1.0.0 +inc 0.0.0 minor 0.1.0 +inc 0.0.0 patch 0.0.1 +inc 0.0.0 premajor 1.0.0-0 +inc 0.0.0 preminor 0.1.0-0 +inc 0.0.0 prepatch 0.0.1-0 +inc 0.0.0 prerelease 0.0.1-0 +inc 1.0.0 major 2.0.0 +inc 1.0.0 minor 1.1.0 +inc 1.0.0 patch 1.0.1 +inc 1.0.0 premajor 2.0.0-0 +inc 1.0.0 preminor 1.1.0-0 +inc 1.0.0 prepatch 1.0.1-0 +inc 1.0.0 prerelease 1.0.1-0 +inc 1.2.3 major 2.0.0 +inc 1.2.3 minor 1.3.0 +inc 1.2.3 patch 1.2.4 +inc 1.2.3 premajor 2.0.0-0 +inc 1.2.3 preminor 1.3.0-0 +inc 1.2.3 prepatch 1.2.4-0 +inc 1.2.3 prerelease 1.2.4-0 +inc 1.2.4 major 2.0.0 +inc 1.2.4 minor 1.3.0 +inc 1.2.4 patch 1.2.5 +inc 1.2.4 premajor 2.0.0-0 +inc 1.2.4 preminor 1.3.0-0 +inc 1.2.4 prepatch 1.2.5-0 +inc 1.2.4 prerelease 1.2.5-0 +inc 1.3.0 major 2.0.0 +inc 1.3.0 minor 1.4.0 +inc 1.3.0 patch 1.3.1 +inc 1.3.0 premajor 2.0.0-0 +inc 1.3.0 preminor 1.4.0-0 +inc 1.3.0 prepatch 1.3.1-0 +inc 1.3.0 prerelease 1.3.1-0 +inc 2.0.0 major 3.0.0 +inc 2.0.0 minor 2.1.0 +inc 2.0.0 patch 2.0.1 +inc 2.0.0 premajor 3.0.0-0 +inc 2.0.0 preminor 2.1.0-0 +inc 2.0.0 prepatch 2.0.1-0 +inc 2.0.0 prerelease 2.0.1-0 +inc 1.0.0-alpha major 1.0.0 +inc 1.0.0-alpha minor 1.0.0 +inc 1.0.0-alpha patch 1.0.0 +inc 1.0.0-alpha premajor 2.0.0-0 +inc 1.0.0-alpha preminor 1.1.0-0 +inc 1.0.0-alpha prepatch 1.0.1-0 +inc 1.0.0-alpha prerelease 1.0.0-alpha.0 +inc 1.0.0-alpha.1 major 1.0.0 +inc 1.0.0-alpha.1 minor 1.0.0 +inc 1.0.0-alpha.1 patch 1.0.0 +inc 1.0.0-alpha.1 premajor 2.0.0-0 +inc 1.0.0-alpha.1 preminor 1.1.0-0 +inc 1.0.0-alpha.1 prepatch 1.0.1-0 +inc 1.0.0-alpha.1 prerelease 1.0.0-alpha.2 +inc 1.0.0-alpha.beta major 1.0.0 +inc 1.0.0-alpha.beta minor 1.0.0 +inc 1.0.0-alpha.beta patch 1.0.0 +inc 1.0.0-alpha.beta premajor 2.0.0-0 +inc 1.0.0-alpha.beta preminor 1.1.0-0 +inc 1.0.0-alpha.beta prepatch 1.0.1-0 +inc 1.0.0-alpha.beta prerelease 1.0.0-alpha.beta.0 +inc 1.0.0-beta major 1.0.0 +inc 1.0.0-beta minor 1.0.0 +inc 1.0.0-beta patch 1.0.0 +inc 1.0.0-beta premajor 2.0.0-0 +inc 1.0.0-beta preminor 1.1.0-0 +inc 1.0.0-beta prepatch 1.0.1-0 +inc 1.0.0-beta prerelease 1.0.0-beta.0 +inc 1.0.0-beta.2 major 1.0.0 +inc 1.0.0-beta.2 minor 1.0.0 +inc 1.0.0-beta.2 patch 1.0.0 +inc 1.0.0-beta.2 premajor 2.0.0-0 +inc 1.0.0-beta.2 preminor 1.1.0-0 +inc 1.0.0-beta.2 prepatch 1.0.1-0 +inc 1.0.0-beta.2 prerelease 1.0.0-beta.3 +inc 1.0.0-beta.11 major 1.0.0 +inc 1.0.0-beta.11 minor 1.0.0 +inc 1.0.0-beta.11 patch 1.0.0 +inc 1.0.0-beta.11 premajor 2.0.0-0 +inc 1.0.0-beta.11 preminor 1.1.0-0 +inc 1.0.0-beta.11 prepatch 1.0.1-0 +inc 1.0.0-beta.11 prerelease 1.0.0-beta.12 +inc 1.0.0-rc.1 major 1.0.0 +inc 1.0.0-rc.1 minor 1.0.0 +inc 1.0.0-rc.1 patch 1.0.0 +inc 1.0.0-rc.1 premajor 2.0.0-0 +inc 1.0.0-rc.1 preminor 1.1.0-0 +inc 1.0.0-rc.1 prepatch 1.0.1-0 +inc 1.0.0-rc.1 prerelease 1.0.0-rc.2 +inc 1.2.3-0 major 2.0.0 +inc 1.2.3-0 minor 1.3.0 +inc 1.2.3-0 patch 1.2.3 +inc 1.2.3-0 premajor 2.0.0-0 +inc 1.2.3-0 preminor 1.3.0-0 +inc 1.2.3-0 prepatch 1.2.4-0 +inc 1.2.3-0 prerelease 1.2.3-1 +inc 2.0.0-0 major 2.0.0 +inc 2.0.0-0 minor 2.0.0 +inc 2.0.0-0 patch 2.0.0 +inc 2.0.0-0 premajor 3.0.0-0 +inc 2.0.0-0 preminor 2.1.0-0 +inc 2.0.0-0 prepatch 2.0.1-0 +inc 2.0.0-0 prerelease 2.0.0-1 +inc 1.2.3+build major 2.0.0 +inc 1.2.3+build minor 1.3.0 +inc 1.2.3+build patch 1.2.4 +inc 1.2.3+build premajor 2.0.0-0 +inc 1.2.3+build preminor 1.3.0-0 +inc 1.2.3+build prepatch 1.2.4-0 +inc 1.2.3+build prerelease 1.2.4-0 +inc 1.0.0+007 major 2.0.0 +inc 1.0.0+007 minor 1.1.0 +inc 1.0.0+007 patch 1.0.1 +inc 1.0.0+007 premajor 2.0.0-0 +inc 1.0.0+007 preminor 1.1.0-0 +inc 1.0.0+007 prepatch 1.0.1-0 +inc 1.0.0+007 prerelease 1.0.1-0 +inc 10.0.0 major 11.0.0 +inc 10.0.0 minor 10.1.0 +inc 10.0.0 patch 10.0.1 +inc 10.0.0 premajor 11.0.0-0 +inc 10.0.0 preminor 10.1.0-0 +inc 10.0.0 prepatch 10.0.1-0 +inc 10.0.0 prerelease 10.0.1-0 +coerce v1.2 1.2.0 +coerce app-1.2.3.jar 1.2.3 +coerce release 4 4.0.0 +coerce 1.2.3.4.5 1.2.3 +coerce .2.3 2.3.0 +coerce 1.2.3-alpha 1.2.3 +coerce =1.2.3 1.2.3 +coerce 10.0.0.0 10.0.0 +coerce 1.2.3.4 1.2.3 +coerce a1b2c3 1.0.0 +coerce 01.02.03 NULL +coerce 1.2 1.2.0 +coerce 123456789012345678.5.6 5.6.0 +coerce 99999999999999999999.0.0 0.0.0 +coerce version 2.3 2.3.0 +coerce x.y.z NULL +coerce 2 2.0.0 +coerce 1.2.x 1.2.0 +coerce 1.0.0 1.0.0 +coerce v0.0.1 0.0.1 +coerce =v1.1.1 1.1.1 +coerce 1.2.3.4.5.6 1.2.3 diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md deleted file mode 100644 index e148814..0000000 --- a/docs/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -GITHUB_CONTACT_EMAIL. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 5151a9e..f5739fa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@

    Java-Semver

    - _**Java-Semver** is a lightweight, zero-dependency Java library that provides full support for **Semantic Versioning 2.0.0**. Designed for simplicity and reliability, it enables parsing and comparing semantic versions (`major.minor.patch-preRelease+build`) effortlessly. Perfect for applications requiring precise version management._ + _A modern & lightweight Java library providing full **Semantic Versioning 2.0.0** specification compliance. Designed as a port of the javascript ecosystems [Node Semver](https://github.com/npm/node-semver), it ports all major features of [Node Semver](https://github.com/npm/node-semver) to native Java._
    @@ -11,7 +11,7 @@ Maven Central Version - + Documentation @@ -32,15 +32,20 @@ --- +![code image](image.png) + ## 🌟 Features -- **Full SemVer 2.0.0 Compliance**: Strict adherence to the official specification. +- **Full SemVer 2.0.0 Compliance**: Adherence to the official specification. - **Zero Dependencies**: Lightweight and self-contained. - **Java 8+ compatible**: Compatible with legacy and modern Java projects. -- **Simple API**: Intuitive methods for parsing and comparing versions. -- **Error Handling**: Gracefully handles invalid versions through exceptions. -- **Well-tested**: Robust JUnit test coverage ensures reliability. -- **Pre-release & Build Metadata**: Supports `1.0.0-alpha+001` and other complex formats. +- **Node Semver Features Ported:** + - Parsing + - Comparisons + - Ranges + - Incrementing + - Coercion +- **Well-tested**: Extensive test coverage, including parity tests with node-semver. ## 📦 Installation @@ -78,25 +83,36 @@ dependencies { -## Usage Example 🚀 +## Usage Example ```java import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.ReleaseType; + +// Parsing and comparisons +final Version current = Version.parse("1.0.0-rc.1+5"); +final Version latest = Version.parse("2.0.0-beta+exp.sha.5114f85"); + +current.isLessThan(latest); // true +current.isGreaterThan(latest); // false +current.isEqualTo(latest); // false + +// Ranges +Version.parse("1.2.3").satisfies("^1.0.0"); // true +Version.parse("2.0.0").satisfies(">=1.0.0 <2.0.0"); // false -final Version currentVersion = Version.of("1.0.0-RC.1+5"); -final Version latestVersion = Version.of("2.0.0-beta+exp.sha.5114f85"); +// Incrementing +Version.parse("1.2.3").increment(ReleaseType.MINOR); // 1.3.0 +Version.parse("1.2.3").nextPatch(); // 1.2.4 -Version.isNewer(currentVersion, latestVersion); // false -Version.isNewerOrEqual(currentVersion, latestVersion); // false -Version.isEqual(currentVersion, latestVersion); // false -Version.isOlderOrEqual(currentVersion, latestVersion); // true -Version.isOlder(currentVersion, latestVersion); // true +// Coerceion +Version.coerce("release-v1.2"); // Optional wrapped 1.2.0 ``` ## 📚 Documentation - [Full Javadoc Documentation](https://javadoc.io/doc/io.github.milkdrinkers/javasemver) -- [Documentation](https://milkdrinkers.athyrium.eu/javasemver) +- [Documentation](https://docs.milkdrinkers.dev/javasemver) - [Maven Central](https://central.sonatype.com/artifact/io.github.milkdrinkers/javasemver) --- diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000..9816edc Binary files /dev/null and b/docs/image.png differ diff --git a/gradle.properties b/gradle.properties index f56a202..c596976 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,5 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xmx2048M group=io.github.milkdrinkers version=1.0.2 -description=A project providing Semantic Versioning utilities for Java 8+ +description=A project providing node-semvers Semantic Versioning tookit, natively implemented Java for Java 8+. SONATYPE_CLOSE_TIMEOUT_SECONDS=1800 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b1b8ef5..eddabd2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a9db115..69dd0d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/gradlew.bat b/gradlew.bat index 8508ef6..a51ec4f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,82 +1,82 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem gradlew startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables, and ensure extensions are enabled -setlocal EnableExtensions - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -"%COMSPEC%" /c exit 1 - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -"%COMSPEC%" /c exit 1 - -:execute -@rem Setup the command line - - - -@rem Execute gradlew -@rem endlocal doesn't take effect until after the line is parsed and variables are expanded -@rem which allows us to clear the local environment before executing the java command -endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel - -:exitWithErrorLevel -@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts -"%COMSPEC%" /c exit %ERRORLEVEL% +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%