Create SemVer Expression parser
This commit is contained in:
parent
2dc8bd8930
commit
388f94915f
90
README.md
90
README.md
@ -1,12 +1,10 @@
|
|||||||
Java SemVer v0.7.0 (SemVer 2) [![Build Status](https://travis-ci.org/zafarkhaja/java-semver.png)](https://travis-ci.org/zafarkhaja/java-semver)
|
Java SemVer v0.7.0 (SemVer 2) [![Build Status](https://travis-ci.org/zafarkhaja/java-semver.png)](https://travis-ci.org/zafarkhaja/java-semver)
|
||||||
==================
|
=============================
|
||||||
|
|
||||||
Java SemVer is a Java implementation of the Semantic Versioning Specification
|
Java SemVer is a Java implementation of the Semantic Versioning Specification
|
||||||
(http://semver.org/).
|
(http://semver.org/).
|
||||||
|
|
||||||
|
### Versioning ###
|
||||||
Versioning
|
|
||||||
----------
|
|
||||||
Java SemVer is versioned according to the SemVer Specification.
|
Java SemVer is versioned according to the SemVer Specification.
|
||||||
|
|
||||||
**NOTE**: The current release of the Java SemVer library has a major version of
|
**NOTE**: The current release of the Java SemVer library has a major version of
|
||||||
@ -19,13 +17,13 @@ Usage
|
|||||||
Below are some common use cases for the Java SemVer library.
|
Below are some common use cases for the Java SemVer library.
|
||||||
|
|
||||||
### Creating Versions ###
|
### Creating Versions ###
|
||||||
Java SemVer library is composed of one small package which contains a few
|
The main class of the Java SemVer library is `Version` which implements the
|
||||||
classes. All the classes but one are package-private and are not accessible
|
Facade design pattern. By design, the `Version` class is made immutable by
|
||||||
outside the package. The only public class is `Version` which acts as a
|
making its constructors package-private, so that it can not be subclassed or
|
||||||
_facade_ for the client code. By design, the `Version` class is made immutable
|
directly instantiated. Instead of public constructors, the `Version` class
|
||||||
by making its constructor package-private, so that it can not be subclassed or
|
provides few _static factory methods_.
|
||||||
directly instantiated. Instead of public constructor, the `Version` class
|
|
||||||
provides a _static factory method_, `Version.valueOf(String value)`.
|
One of the methods is the `Version.valueOf` method.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.github.zafarkhaja.semver.Version;
|
import com.github.zafarkhaja.semver.Version;
|
||||||
@ -43,6 +41,17 @@ String build = v.getBuildMetadata(); // "build.1"
|
|||||||
String str = v.toString(); // "1.0.0-rc.1+build.1"
|
String str = v.toString(); // "1.0.0-rc.1+build.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The other static factory method is `Version.forIntegers` which is also
|
||||||
|
overloaded to allow fewer arguments.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.github.zafarkhaja.semver.Version;
|
||||||
|
|
||||||
|
Version v1 = Version.forIntegers(1);
|
||||||
|
Version v2 = Version.forIntegers(1, 2);
|
||||||
|
Version v3 = Version.forIntegers(1, 2, 3);
|
||||||
|
```
|
||||||
|
|
||||||
Another way to create a `Version` is to use a _builder_ class `Version.Builder`.
|
Another way to create a `Version` is to use a _builder_ class `Version.Builder`.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@ -76,15 +85,15 @@ import com.github.zafarkhaja.semver.Version;
|
|||||||
|
|
||||||
Version v1 = Version.valueOf("1.2.3");
|
Version v1 = Version.valueOf("1.2.3");
|
||||||
|
|
||||||
// Incrementing major version
|
// Incrementing the major version
|
||||||
Version v2 = v1.incrementMajorVersion(); // "2.0.0"
|
Version v2 = v1.incrementMajorVersion(); // "2.0.0"
|
||||||
Version v2 = v1.incrementMajorVersion("alpha"); // "2.0.0-alpha"
|
Version v2 = v1.incrementMajorVersion("alpha"); // "2.0.0-alpha"
|
||||||
|
|
||||||
// Incrementing minor version
|
// Incrementing the minor version
|
||||||
Version v3 = v1.incrementMinorVersion(); // "1.3.0"
|
Version v3 = v1.incrementMinorVersion(); // "1.3.0"
|
||||||
Version v3 = v1.incrementMinorVersion("alpha"); // "1.3.0-alpha"
|
Version v3 = v1.incrementMinorVersion("alpha"); // "1.3.0-alpha"
|
||||||
|
|
||||||
// Incrementing patch version
|
// Incrementing the patch version
|
||||||
Version v4 = v1.incrementPatchVersion(); // "1.2.4"
|
Version v4 = v1.incrementPatchVersion(); // "1.2.4"
|
||||||
Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha"
|
Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha"
|
||||||
|
|
||||||
@ -92,30 +101,32 @@ Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha"
|
|||||||
String str = v1.toString(); // "1.2.3"
|
String str = v1.toString(); // "1.2.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
There are also incrementor methods for pre-release version and build metadata.
|
There are also incrementor methods for the pre-release version and the build
|
||||||
|
metadata.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.github.zafarkhaja.semver.Version;
|
import com.github.zafarkhaja.semver.Version;
|
||||||
|
|
||||||
// Incrementing pre-release version
|
// Incrementing the pre-release version
|
||||||
Version v1 = Version.valueOf("1.2.3-rc"); // considered as "rc.0"
|
Version v1 = Version.valueOf("1.2.3-rc"); // considered as "rc.0"
|
||||||
Version v2 = v1.incrementPreReleaseVersion(); // "1.2.3-rc.1"
|
Version v2 = v1.incrementPreReleaseVersion(); // "1.2.3-rc.1"
|
||||||
Version v3 = v2.incrementPreReleaseVersion(); // "1.2.3-rc.2"
|
Version v3 = v2.incrementPreReleaseVersion(); // "1.2.3-rc.2"
|
||||||
|
|
||||||
// Incrementing build metadata
|
// Incrementing the build metadata
|
||||||
Version v1 = Version.valueOf("1.2.3-rc+build"); // considered as "build.0"
|
Version v1 = Version.valueOf("1.2.3-rc+build"); // considered as "build.0"
|
||||||
Version v2 = v1.incrementBuildMetadata(); // "1.2.3-rc+build.1"
|
Version v2 = v1.incrementBuildMetadata(); // "1.2.3-rc+build.1"
|
||||||
Version v3 = v2.incrementBuildMetadata(); // "1.2.3-rc+build.2"
|
Version v3 = v2.incrementBuildMetadata(); // "1.2.3-rc+build.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
When incrementing normal or pre-release versions build metadata is always dropped.
|
When incrementing the normal or pre-release versions the build metadata is
|
||||||
|
always dropped.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.github.zafarkhaja.semver.Version;
|
import com.github.zafarkhaja.semver.Version;
|
||||||
|
|
||||||
Version v1 = Version.valueOf("1.2.3-beta+build");
|
Version v1 = Version.valueOf("1.2.3-beta+build");
|
||||||
|
|
||||||
// Incrementing normal version
|
// Incrementing the normal version
|
||||||
Version v2 = v1.incrementMajorVersion(); // "2.0.0"
|
Version v2 = v1.incrementMajorVersion(); // "2.0.0"
|
||||||
Version v2 = v1.incrementMajorVersion("alpha"); // "2.0.0-alpha"
|
Version v2 = v1.incrementMajorVersion("alpha"); // "2.0.0-alpha"
|
||||||
|
|
||||||
@ -125,7 +136,7 @@ Version v3 = v1.incrementMinorVersion("alpha"); // "1.3.0-alpha"
|
|||||||
Version v4 = v1.incrementPatchVersion(); // "1.2.4"
|
Version v4 = v1.incrementPatchVersion(); // "1.2.4"
|
||||||
Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha"
|
Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha"
|
||||||
|
|
||||||
// Incrementing pre-release version
|
// Incrementing the pre-release version
|
||||||
Version v2 = v1.incrementPreReleaseVersion(); // "1.2.3-beta.1"
|
Version v2 = v1.incrementPreReleaseVersion(); // "1.2.3-beta.1"
|
||||||
```
|
```
|
||||||
**NOTE**: The discussion page https://github.com/mojombo/semver/issues/60 might
|
**NOTE**: The discussion page https://github.com/mojombo/semver/issues/60 might
|
||||||
@ -134,8 +145,8 @@ incrementor methods.
|
|||||||
|
|
||||||
### Comparing Versions ###
|
### Comparing Versions ###
|
||||||
Comparing versions with Java SemVer is easy. The `Version` class implements the
|
Comparing versions with Java SemVer is easy. The `Version` class implements the
|
||||||
`Comparable` interface, it also overrides the `Object.equals(Object obj)` method
|
`Comparable` interface, it also overrides the `Object.equals` method and provides
|
||||||
and provides some more methods for convenient comparing.
|
some more methods for convenient comparing.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.github.zafarkhaja.semver.Version;
|
import com.github.zafarkhaja.semver.Version;
|
||||||
@ -152,7 +163,7 @@ boolean result = v1.lessThan(v2); // true
|
|||||||
boolean result = v1.lessThanOrEqualTo(v2); // true
|
boolean result = v1.lessThanOrEqualTo(v2); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
When determining version precedence build metadata is ignored (SemVer p.10).
|
When determining version precedence the build metadata is ignored (SemVer p.10).
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.github.zafarkhaja.semver.Version;
|
import com.github.zafarkhaja.semver.Version;
|
||||||
@ -164,9 +175,9 @@ int result = v1.compareTo(v2); // = 0
|
|||||||
boolean result = v1.equals(v2); // true
|
boolean result = v1.equals(v2); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
Sometimes, however, you might want to compare versions with build metadata in
|
Sometimes, however, you might want to compare versions with the build metadata
|
||||||
mind. For such cases Java SemVer provides a _comparator_ `Version.BUILD_AWARE_ORDER`
|
in mind. For such cases Java SemVer provides a _comparator_ `Version.BUILD_AWARE_ORDER`
|
||||||
and a convenience method `Version.compareWithBuildsTo(Version other)`.
|
and a convenience method `Version.compareWithBuildsTo`.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.github.zafarkhaja.semver.Version;
|
import com.github.zafarkhaja.semver.Version;
|
||||||
@ -181,9 +192,34 @@ boolean result = v1.equals(v2); // false
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
SemVer Expressions API (Ranges)
|
||||||
|
----------------------
|
||||||
|
Since version 0.7.0 Java SemVer supports the SemVer Expressions API which is
|
||||||
|
implemented as an external DSL. The BNF grammar for the SemVer Expressions DSL
|
||||||
|
can be found in the corresponding issue
|
||||||
|
"[Implement the SemVer Expressions API](https://github.com/zafarkhaja/java-semver/issues/1)".
|
||||||
|
|
||||||
|
The entry point for the API is the `Version.satisfies` method.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.github.zafarkhaja.semver.Version;
|
||||||
|
|
||||||
|
Version v = Version.valueOf("1.0.0-beta");
|
||||||
|
boolean result = v.satisfies(">=1.0.0 & <2.0.0"); // false
|
||||||
|
```
|
||||||
|
|
||||||
|
Below are examples of some common use cases, as well as syntactic sugar and some
|
||||||
|
other interesting capabilities of the SemVer Expressions DSL.
|
||||||
|
* Wildcard - `1.*` which is equivalent to `>=1.0.0 & <2.0.0`
|
||||||
|
* Tilde operator - `~1.5` which is equivalent to `>=1.5.0 & <2.0.0`
|
||||||
|
* Range - `1.0-2.0` which is equivalent to `>=1.0.0 & <=2.0.0`
|
||||||
|
* Negation operator - `!(1.*)` which is equivalent to `<1.0.0 & >=2.0.0`
|
||||||
|
* Short notation - `1` which is equivalent to `=1.0.0`
|
||||||
|
* Parenthesized expression - `~1.3 | (1.4.* & !=1.4.5) | ~2`
|
||||||
|
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
----
|
----
|
||||||
* [Implement ranges](https://github.com/zafarkhaja/java-semver/issues/1)
|
|
||||||
* [Write doc comments for all API classes and methods](https://github.com/zafarkhaja/java-semver/issues/2)
|
* [Write doc comments for all API classes and methods](https://github.com/zafarkhaja/java-semver/issues/2)
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.github.zafarkhaja.semver;
|
package com.github.zafarkhaja.semver;
|
||||||
|
|
||||||
|
import com.github.zafarkhaja.semver.expr.Expression;
|
||||||
|
import com.github.zafarkhaja.semver.expr.ExpressionParser;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,6 +118,23 @@ public class Version implements Comparable<Version> {
|
|||||||
return VersionParser.parseValidSemVer(version);
|
return VersionParser.parseValidSemVer(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Version forIntegers(int major) {
|
||||||
|
return new Version(new NormalVersion(major, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Version forIntegers(int major, int minor) {
|
||||||
|
return new Version(new NormalVersion(major, minor, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Version forIntegers(int major, int minor, int patch) {
|
||||||
|
return new Version(new NormalVersion(major, minor, patch));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean satisfies(String expr) {
|
||||||
|
Parser<Expression> parser = ExpressionParser.newInstance();
|
||||||
|
return parser.parse(expr).interpret(this);
|
||||||
|
}
|
||||||
|
|
||||||
public Version incrementMajorVersion() {
|
public Version incrementMajorVersion() {
|
||||||
return new Version(normal.incrementMajor());
|
return new Version(normal.incrementMajor());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 Zafar Khaja <zafarkhaja@gmail.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.github.zafarkhaja.semver.expr;
|
||||||
|
|
||||||
|
import com.github.zafarkhaja.semver.Parser;
|
||||||
|
import com.github.zafarkhaja.semver.Version;
|
||||||
|
import com.github.zafarkhaja.semver.expr.Lexer.Token;
|
||||||
|
import com.github.zafarkhaja.semver.util.Stream;
|
||||||
|
import com.github.zafarkhaja.semver.util.Stream.ElementType;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
public class ExpressionParser implements Parser<Expression> {
|
||||||
|
|
||||||
|
private final Lexer lexer;
|
||||||
|
private Stream<Token> tokens;
|
||||||
|
|
||||||
|
ExpressionParser(Lexer lexer) {
|
||||||
|
this.lexer = lexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Parser<Expression> newInstance() {
|
||||||
|
return new ExpressionParser(new Lexer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Expression parse(String input) {
|
||||||
|
tokens = lexer.tokenize(input);
|
||||||
|
return parseSemVerExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseSemVerExpression() {
|
||||||
|
Expression expr;
|
||||||
|
if (tokens.positiveLookahead(NOT)) {
|
||||||
|
tokens.consume();
|
||||||
|
tokens.consume(LEFT_PAREN);
|
||||||
|
expr = new Not(parseSemVerExpression());
|
||||||
|
tokens.consume(RIGHT_PAREN);
|
||||||
|
} else if (tokens.positiveLookahead(LEFT_PAREN)) {
|
||||||
|
tokens.consume(LEFT_PAREN);
|
||||||
|
expr = parseSemVerExpression();
|
||||||
|
tokens.consume(RIGHT_PAREN);
|
||||||
|
} else {
|
||||||
|
expr = parseExpression();
|
||||||
|
}
|
||||||
|
return parseBooleanExpression(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseBooleanExpression(Expression expr) {
|
||||||
|
if (tokens.positiveLookahead(AND)) {
|
||||||
|
tokens.consume();
|
||||||
|
expr = new And(expr, parseSemVerExpression());
|
||||||
|
} else if (tokens.positiveLookahead(OR)) {
|
||||||
|
tokens.consume();
|
||||||
|
expr = new Or(expr, parseSemVerExpression());
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseExpression() {
|
||||||
|
if (tokens.positiveLookahead(TILDE)) {
|
||||||
|
return parseTildeExpression();
|
||||||
|
} else if (isVersionExpression()) {
|
||||||
|
return parseVersionExpression();
|
||||||
|
} else if (isRangeExpression()) {
|
||||||
|
return parseRangeExpression();
|
||||||
|
}
|
||||||
|
return parseComparisonExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseComparisonExpression() {
|
||||||
|
Token token = tokens.lookahead();
|
||||||
|
Expression expr;
|
||||||
|
switch (token.type) {
|
||||||
|
case EQUAL:
|
||||||
|
tokens.consume();
|
||||||
|
expr = new Equal(parseVersion());
|
||||||
|
break;
|
||||||
|
case NOT_EQUAL:
|
||||||
|
tokens.consume();
|
||||||
|
expr = new NotEqual(parseVersion());
|
||||||
|
break;
|
||||||
|
case GREATER:
|
||||||
|
tokens.consume();
|
||||||
|
expr = new Greater(parseVersion());
|
||||||
|
break;
|
||||||
|
case GREATER_EQUAL:
|
||||||
|
tokens.consume();
|
||||||
|
expr = new GreaterOrEqual(parseVersion());
|
||||||
|
break;
|
||||||
|
case LESS:
|
||||||
|
tokens.consume();
|
||||||
|
expr = new Less(parseVersion());
|
||||||
|
break;
|
||||||
|
case LESS_EQUAL:
|
||||||
|
tokens.consume();
|
||||||
|
expr = new LessOrEqual(parseVersion());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
expr = new Equal(parseVersion());
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseTildeExpression() {
|
||||||
|
tokens.consume(TILDE);
|
||||||
|
int major = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
|
return new GreaterOrEqual(versionOf(major, 0, 0));
|
||||||
|
}
|
||||||
|
tokens.consume(DOT);
|
||||||
|
int minor = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
|
return new And(
|
||||||
|
new GreaterOrEqual(versionOf(major, minor, 0)),
|
||||||
|
new Less(versionOf(major + 1, 0, 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tokens.consume(DOT);
|
||||||
|
int patch = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
return new And(
|
||||||
|
new GreaterOrEqual(versionOf(major, minor, patch)),
|
||||||
|
new Less(versionOf(major, minor + 1, 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isVersionExpression() {
|
||||||
|
return isVersionFollowedBy(STAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseVersionExpression() {
|
||||||
|
int major = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
tokens.consume(DOT);
|
||||||
|
if (tokens.positiveLookahead(STAR)) {
|
||||||
|
tokens.consume();
|
||||||
|
return new And(
|
||||||
|
new GreaterOrEqual(versionOf(major, 0, 0)),
|
||||||
|
new Less(versionOf(major + 1, 0, 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int minor = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
tokens.consume(DOT);
|
||||||
|
tokens.consume(STAR);
|
||||||
|
return new And(
|
||||||
|
new GreaterOrEqual(versionOf(major, minor, 0)),
|
||||||
|
new Less(versionOf(major, minor + 1, 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRangeExpression() {
|
||||||
|
return isVersionFollowedBy(HYPHEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseRangeExpression() {
|
||||||
|
Expression ge = new GreaterOrEqual(parseVersion());
|
||||||
|
tokens.consume(HYPHEN);
|
||||||
|
Expression le = new LessOrEqual(parseVersion());
|
||||||
|
return new And(ge, le);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Version parseVersion() {
|
||||||
|
int major = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
int minor = 0;
|
||||||
|
if (tokens.positiveLookahead(DOT)) {
|
||||||
|
tokens.consume();
|
||||||
|
minor = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
}
|
||||||
|
int patch = 0;
|
||||||
|
if (tokens.positiveLookahead(DOT)) {
|
||||||
|
tokens.consume();
|
||||||
|
patch = intOf(tokens.consume(NUMERIC).lexeme);
|
||||||
|
}
|
||||||
|
return versionOf(major, minor, patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isVersionFollowedBy(ElementType<Token> type) {
|
||||||
|
EnumSet<Token.Type> expected = EnumSet.of(NUMERIC, DOT);
|
||||||
|
Iterator<Token> it = tokens.iterator();
|
||||||
|
Token lookahead = null;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
lookahead = it.next();
|
||||||
|
if (!expected.contains(lookahead.type)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type.isMatchedBy(lookahead);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Version versionOf(int major, int minor, int patch) {
|
||||||
|
return Version.forIntegers(major, minor, patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int intOf(String value) {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
}
|
||||||
|
}
|
@ -305,6 +305,13 @@ public class VersionTest {
|
|||||||
assertTrue(0 == v1.compareTo(v2));
|
assertTrue(0 == v1.compareTo(v2));
|
||||||
assertTrue(0 > v1.compareWithBuildsTo(v2));
|
assertTrue(0 > v1.compareWithBuildsTo(v2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCheckIfVersionSatisfiesExpression() {
|
||||||
|
Version v = Version.valueOf("2.0.0-beta");
|
||||||
|
assertTrue(v.satisfies("~1.0"));
|
||||||
|
assertFalse(v.satisfies(">=2.0 & <3.0"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EqualsMethodTest {
|
public static class EqualsMethodTest {
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 Zafar Khaja <zafarkhaja@gmail.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.github.zafarkhaja.semver.expr;
|
||||||
|
|
||||||
|
import com.github.zafarkhaja.semver.Version;
|
||||||
|
import com.github.zafarkhaja.semver.util.UnexpectedElementTypeException;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
public class ExpressionParserTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseEqualComparisonExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression eq = parser.parse("=1.0.0");
|
||||||
|
assertTrue(eq.interpret(Version.valueOf("1.0.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseEqualComparisonExpressionIfOnlyVersionGiven() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression eq = parser.parse("1.0.0");
|
||||||
|
assertTrue(eq.interpret(Version.valueOf("1.0.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseNotEqualComparisonExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression ne = parser.parse("!=1.0.0");
|
||||||
|
assertTrue(ne.interpret(Version.valueOf("1.2.3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseGreaterComparisonExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression gt = parser.parse(">1.0.0");
|
||||||
|
assertTrue(gt.interpret(Version.valueOf("1.2.3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseGreaterOrEqualComparisonExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression ge = parser.parse(">=1.0.0");
|
||||||
|
assertTrue(ge.interpret(Version.valueOf("1.0.0")));
|
||||||
|
assertTrue(ge.interpret(Version.valueOf("1.2.3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseLessComparisonExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression lt = parser.parse("<1.2.3");
|
||||||
|
assertTrue(lt.interpret(Version.valueOf("1.0.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseLessOrEqualComparisonExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression le = parser.parse("<=1.2.3");
|
||||||
|
assertTrue(le.interpret(Version.valueOf("1.0.0")));
|
||||||
|
assertTrue(le.interpret(Version.valueOf("1.2.3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseTildeExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression expr1 = parser.parse("~1");
|
||||||
|
assertTrue(expr1.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertTrue(expr1.interpret(Version.valueOf("3.2.1")));
|
||||||
|
Expression expr2 = parser.parse("~1.2");
|
||||||
|
assertTrue(expr2.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertFalse(expr2.interpret(Version.valueOf("2.0.0")));
|
||||||
|
Expression expr3 = parser.parse("~1.2.3");
|
||||||
|
assertTrue(expr3.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertFalse(expr3.interpret(Version.valueOf("1.3.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseShortFormOfVersion() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression expr1 = parser.parse("1");
|
||||||
|
assertTrue(expr1.interpret(Version.valueOf("1.0.0")));
|
||||||
|
Expression expr2 = parser.parse("2.0");
|
||||||
|
assertTrue(expr2.interpret(Version.valueOf("2.0.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseVersionExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression expr1 = parser.parse("1.*");
|
||||||
|
assertTrue(expr1.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertFalse(expr1.interpret(Version.valueOf("3.2.1")));
|
||||||
|
Expression expr2 = parser.parse("1.2.*");
|
||||||
|
assertTrue(expr2.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertFalse(expr2.interpret(Version.valueOf("1.3.2")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseRangeExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression range = parser.parse("1.0.0 - 2.0.0");
|
||||||
|
assertTrue(range.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertFalse(range.interpret(Version.valueOf("3.2.1")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseAndBooleanExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression and = parser.parse(">=1.0.0 & <2.0.0");
|
||||||
|
assertTrue(and.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertFalse(and.interpret(Version.valueOf("3.2.1")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseOrBooleanExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression or = parser.parse("1.* | =2.0.0");
|
||||||
|
assertTrue(or.interpret(Version.valueOf("1.2.3")));
|
||||||
|
assertFalse(or.interpret(Version.valueOf("2.1.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseParenthesizedExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression expr = parser.parse("(1)");
|
||||||
|
assertTrue(expr.interpret(Version.valueOf("1.0.0")));
|
||||||
|
assertFalse(expr.interpret(Version.valueOf("2.0.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseExpressionWithMultipleParentheses() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression expr = parser.parse("((1))");
|
||||||
|
assertTrue(expr.interpret(Version.valueOf("1.0.0")));
|
||||||
|
assertFalse(expr.interpret(Version.valueOf("2.0.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseNotExpression() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression not1 = parser.parse("!(1)");
|
||||||
|
assertTrue(not1.interpret(Version.valueOf("2.0.0")));
|
||||||
|
assertFalse(not1.interpret(Version.valueOf("1.0.0")));
|
||||||
|
Expression not2 = parser.parse("0.* & !(>=1 & <2)");
|
||||||
|
assertTrue(not2.interpret(Version.valueOf("0.5.0")));
|
||||||
|
assertFalse(not2.interpret(Version.valueOf("1.0.1")));
|
||||||
|
Expression not3 = parser.parse("!(>=1 & <2) & >=2");
|
||||||
|
assertTrue(not3.interpret(Version.valueOf("2.0.0")));
|
||||||
|
assertFalse(not3.interpret(Version.valueOf("1.2.3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRespectPrecedenceWhenUsedWithParentheses() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression expr1 = parser.parse("(~1.0 & <2.0) | >2.0");
|
||||||
|
assertTrue(expr1.interpret(Version.valueOf("2.5.0")));
|
||||||
|
Expression expr2 = parser.parse("~1.0 & (<2.0 | >2.0)");
|
||||||
|
assertFalse(expr2.interpret(Version.valueOf("2.5.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldParseComplexExpressions() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
Expression expr = parser.parse(
|
||||||
|
"((>=1.0.1 & <2) | (>=3.0 & <4)) & ((1-1.5) & (~1.5))"
|
||||||
|
);
|
||||||
|
assertTrue(expr.interpret(Version.valueOf("1.5.0")));
|
||||||
|
assertFalse(expr.interpret(Version.valueOf("2.5.0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRaiseErrorIfClosingParenthesisIsMissing() {
|
||||||
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
|
try {
|
||||||
|
parser.parse("((>=1.0.1 & < 2)");
|
||||||
|
} catch (UnexpectedElementTypeException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("Should raise error if closing parenthesis is missing");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user