Java implementation of vers, a mostly universal version range specifier
Scheme | Supported |
---|---|
Alpine | ❌ |
CPAN | ❌ |
Debian | ✅ |
Ruby Gem | ❌ |
Generic | ✅ |
Gentoo | ❌ |
Go | ✅ |
Maven | ✅ |
NPM | ✅ |
Nuget | ❌ |
PyPI | ❌ |
RPM | ✅ |
Note
Support for new schemes can be added, and default implementations overwritten, by extending versatile!
Versions for which the appropriate scheme is not currently supported will fall back to generic
.
<dependency>
<groupId>io.github.nscuro</groupId>
<artifactId>versatile-core</artifactId>
<version>${versatile.version}</version>
</dependency>
Note
versatile requires Java >= 17.
Ranges are constructed using a builder. Builders must be initialized with a versioning scheme.
Constraint
s may be provided in structured, or freeform format. Versions used in constraints must
be valid according to the chosen versioning scheme. When VersBuilder#build
is called, constraints are sorted
by version, and the built Vers
is validated. If the range turns out to be invalid, an VersException
is thrown.
import io.github.nscuro.versatile.Comparator;
import io.github.nscuro.versatile.Vers;
import static io.github.nscuro.versatile.version.KnownVersioningSchemes.SCHEME_GOLANG;
class ConstructVers {
void shouldConstructVers() {
Vers vers = Vers.builder(SCHEME_GOLANG)
.withConstraint(Comparator.GREATER_THAN, "v1.2.3")
.withConstraint(Comparator.LESS_THAN_OR_EQUAL, "v3.2.1")
.withConstraint("!= v2.1.3")
.build();
assert "vers:golang/>v1.2.3|!=v2.1.3|<=v3.2.1".equals(vers.toString());
}
}
vers
ranges may
be parsed
using the Vers#parse
method. If the range turns out to be invalid, a VersException
is thrown.
import io.github.nscuro.versatile.Vers;
import static io.github.nscuro.versatile.version.KnownVersioningSchemes.SCHEME_GOLANG;
class ParseVers {
void shouldParseVers() {
Vers vers = Vers.parse("vers:golang/>v1.2.3|!=v2.1.3|<=v3.2.1");
assert SCHEME_GOLANG.equals(vers.scheme());
assert vers.constraints().size() == 3;
}
}
The vers
specification defines an algorithm
to simplify
constraints in a range. This mechanism is exposed through the Vers#simplify
method.
import io.github.nscuro.versatile.Vers;
class SimplifyVers {
void shouldSimplify() {
Vers vers = Vers.parse("vers:golang/>v0.0.0|>=v0.0.1|v0.0.2|<v0.0.3|v0.0.4|<v0.0.5|>=v0.0.6");
assert "vers:golang/>v0.0.0|<v0.0.5|>=v0.0.6".equals(vers.simplify().toString());
}
}
Note
versatile will never simplify ranges on its own. If simplification is desired,simplify
must be called explicitly.
To check whether a given vers
range contains
a specific version, the Vers#contains
method may be used. The provided version must be valid according
to the range's versioning scheme.
import io.github.nscuro.versatile.Vers;
class VersContains {
Vers vers = Vers.parse("vers:golang/>v1.2.3|!=v2.1.3|<=v3.2.1");
void shouldContainVersion() {
assert vers.contains("v1.2.4");
assert vers.contains("v2.0.2");
assert vers.contains("v3.2.1");
}
void shouldNotContainVersion() {
assert !vers.contains("v1.2.3");
assert !vers.contains("v2.1.3");
assert !vers.contains("v3.2.2");
}
}
Versions can be used directly, outside the context of a vers
range. To acquire a Version
object,
VersionFactory
may be used:
import io.github.nscuro.versatile.VersionFactory;
import io.github.nscuro.versatile.version.GoVersion;
import io.github.nscuro.versatile.version.Version;
import static io.github.nscuro.versatile.version.KnownVersioningSchemes.SCHEME_GOLANG;
class VersContains {
void shouldReturnGoVersion() {
Version version = VersionFactory.getVersion(SCHEME_GOLANG, "v1.2.4");
assert version instanceof GoVersion;
assert "golang".equals(version.scheme());
}
void shouldFallbackToGenericVersion() {
Version version = VersionFactory.getVersion("foobar", "v1.2.4");
assert version instanceof GenericVersion;
assert "foobar".equals(version.scheme());
}
}
As shown above, if versatile doesn't recognize the provided versioning scheme, it will fall back
to GenericVersion
. Support for additional schemes can be added by extending versatile.
versatile ships with support for a few versioning schemes (see Supported Versioning Schemes).
While contributions to add support for more schemes is highly appreciated, it's not always feasible to wait for changes
to be released. It should be possible to leverage versatile's vers
functionality, without being reliant on
how fast support for new schemes is added upstream.
On the other hand, the default implementations may not always align with the desired behavior. Perhaps they are too strict, and a more lax parsing logic is required. In that case, the default will need to be overwritten.
To address these concerns, versatile exposes an SPI for versioning scheme support.
To add support for a new scheme, let's say alpine
, the following steps may be performed:
- Add either
versatile-core
, orversatile-spi
as dependency to your project - Create a class
AlpineVersion
that extendsio.github.nscuro.versatile.version.Version
- Implement the version parsing logic as desired
- Be sure to overwrite
compareTo
,equals
,hashCode
, andtoString
- Be sure to overwrite
- Create a class
AlpineVersionProvider
that implementsio.github.nscuro.versatile.version.VersionProvider
- Implement
#supportsScheme(String scheme)
to returntrue
for thealpine
scheme - Implement
#getVersion(String scheme, String verstionStr)
to return an instance ofAlpineVersion
- Implement
#priority()
to return a value between0
(lowest), andInteger.MAX_VALUE
(highest)- Built-in providers have a priority of
50
(VersionProvider#PRIORITY_BUILTIN
) - By defining a priority higher than
50
, built-in providers can effectively be overwritten
- Built-in providers have a priority of
- Implement
- Create a file
src/main/resources/META-INF/services/io.github.nscuro.versatile.version.VersionProvider
- List the fully qualified package name of all custom
VersionProvider
implementations, one per line- e.g.
com.acme.AlpineVersionProvider
- e.g.
That's it! Now, whenever versatile encounters a vers
range with the scheme alpine
, it will use your AlpineVersion
!