This package provides a complete Fortran implementation of Semantic Versioning 2.0.0. It aims to be a user-friendly tool for handling versions in your Fortran projects.
It follows the major
.minor
.patch
pattern and allows the inclusion of
prerelease
labels and build
metadata. Versions can be created or parsed from
strings, compared, incremented and printed as strings.
If you are using fpm, you can
simply add this package as a dependency to your fpm.toml
file:
[dependencies]
[dependencies.version-f]
git = "https://github.com/minhqdao/version-f.git"
tag = "v0.4.0"
Then import the version_f
module into your Fortran code:
use version_f, only: version_t, error_t
Run fpm build
to download the dependency.
To build the library using the provided Makefile
, navigate to the project's root directory and execute the following command if you are on a Unix-like system:
make
This will generate the static library libversion-f.a
in the root directory. You can compile it alongside your project or link it using the -L
and -l
flags.
A dynamic library will also be created for use in your projects (suffix .so
on Linux systems and .dylib
on macOS).
If you wish to use a compiler other than gfortran
, simply specify it by running:
make FC=<compiler>
Create versions using one of the following commands:
type(version_t) :: version
type(error_t), allocatable :: error
! The default way using individual arguments
version = version_t(0, 1, 0)
! Parse from string
version = version_t('0.1.0')
! From arguments with error handling
call version%create(0, 1, 0, error=error)
! From string with error handling
call version%parse('0.1.0', error)
! From arguments with prerelease labels and build metadata
call version%create(0, 1, 0, 'alpha', '1', error)
! From string with prerelease labels and build metadata
call version%parse('0.1.0-alpha+1', error)
prerelease
labels can be included and will be appended after the patch
via a -
sign. The identifiers must comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]
and are separated by dots. Numerical identifiers must not start with a 0
digit. A version containing prerelease
data has lower precedence than the equivalent version without. prerelease
information is cleared each time the version is incremented. A prerelease
can be incremented.
type(version_t) :: v1, v2
v1 = version_t(0, 5, 3, 'beta.1')
print *, v1%to_string() ! '0.5.3-beta.1'
v2 = version_t(0, 5, 3)
print *, v2%to_string() ! '0.5.3'
print *, v1 < v2 ! true
print *, v1 == v2 ! false
call v1%increment_patch() ! 0.5.4
call v1%increment_prerelease() ! 0.5.4-1
call v1%increment_prerelease() ! 0.5.4-2
build
metadata can be included and will be appended after the patch
or the prerelease
via a +
sign. The identifiers must comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]
and are separated by dots. Numerical identifiers must not start with a 0
digit. build
data is not used for comparison and it is cleared every time a major
, minor
, patch
or prerelease
version is incremented. A build
value can be incremented.
type(version_t) :: v1, v2
v1 = version_t(0, 5, 3, build='1')
print *, v1%to_string() ! '0.5.3+1'
v2 = version_t(0, 5, 3, build='abc.1-13')
print *, v2%to_string() ! '0.5.3+abc.1-13'
print *, v1 == v2 ! true
call v1%increment_patch() ! 0.5.4
call v1%increment_build() ! 0.5.4+1
v1 = version_t(0, 5, 3, 'alpha.1' '1')
print *, v1%to_string() ! '0.5.3-alpha.1+1'
print *, v1%increment_build() ! '0.5.3-alpha.1+2'
Versions can be compared using the standard Fortran operators. Be aware that a version containing prerelease
labels has lower precedence than the equivalent version without. build
information isn't used for comparison. However, you can use the is_exactly() function to include it.
type(version_t) :: v1, v2, v3, v4
v1 = version_t(0, 1, 0)
v2 = version_t(1, 0, 0)
v3 = version_t(1, 0, 0, 'alpha')
v4 = version_t(1, 0, 0, build='1')
if (v1 < v2) then ! true
if (v1 <= v2) then ! true
if (v1 > v2) then ! false
if (v1 <= v2) then ! false
if (v1 == v2) then ! false
if (v1 /= v2) then ! true
! With prerelease labels
if (v2 == v3) then ! false
if (v2 > v3) then ! true
! With build metadata
if (v2 == v4) then ! true
Use the satisfies
/try_satisfy
procedures to verify whether a version meets a range. A range encompasses one or more comparator sets, with multiple sets separated by || (logical OR). Each comparator set combines one or more comparators using a space-separated arrangement (logical AND). A comparator consists of an operator and a version. The available operators include the following:
=
: Equal to!=
: Not equal to>
: Greater than>=
: Greater than or equal to<
: Less than<=
: Less than or equal to
program main
use version_f
type(version_t) :: version
logical :: is_satisfied
type(error_t), allocatable :: error
version = version_t(0, 1, 0)
print *, version%satisfies('0.1.0') ! true
print *, version%satisfies('=0.1.0') ! true
print *, version%satisfies('!=0.1.0') ! false
print *, version%satisfies('>0.1.0') ! false
print *, version%satisfies('>=0.1.0') ! true
print *, version%satisfies('<0.1.0') ! false
print *, version%satisfies('<=0.1.0') ! true
print *, version%satisfies('>=0.1.0 <0.2.0') ! true
print *, version%satisfies('>0.1.0 <0.2.0') ! false
print *, version%satisfies('>0.1.0 <0.2.0 || 0.1.0') ! true
print *, version%satisfies('0.0.8 || 0.0.9 || >0.1.0 <0.2.0') ! false
call version%try_satisfy('<=0.1.0', is_satisfied, error)
if (allocated(error)) call exit(1)
print *, is_satisfied ! true
end
In strict_mode
(optional parameter in create
, parse
and is_version
), all major
, minor
and patch
numbers must be provided. Implicit zeros are forbidden.
type(version_t) :: version
type(error_t), allocatable :: error
call version%create(1, error=error, strict_mode=.true.)
print *, allocated(error) ! true
call version%parse('1.2', error=error, strict_mode=.true.)
print *, allocated(error) ! true
print *, is_version('0.1.0-alpha.1', strict_mode=.true.) ! true
print *, is_version('0.1.-alpha.1', strict_mode=.true.) ! false
print *, is_version('0.1.-alpha.1') ! true
print *, is_version('0.1-alpha.1', strict_mode=.true.) ! false
print *, is_version('0.1-alpha.1') ! true
Prerelease
and build
data are cleared every time a major, minor or patch number is incremented. prerelease
and build
values are incremented by adding 1
to the their last identifier if it is numeric. If the last identifier isn't a number or no identifiers exist, a new identifier is added with the value of 1
. Existing build
information is cleared each time a prerelease
is incremented.
type(version_t) :: version
version = version_t(0, 5, 3, 'beta.1', '1')
call version%increment_build() ! 0.5.3-beta.1+2
call version%increment_prerelease() ! 0.5.3-beta.2
call version%increment_patch() ! 0.5.4
call version%increment_minor() ! 0.6.0
call version%increment_major() ! 1.0.0
The is_version()
function can be used to conveniently check if the string is
a valid version. Use parse
to receive detailed error messages.
print *, is_version('0.1.0-alpha.1') ! true
print *, is_version('abc') ! false
The is_stable()
function returns true
if the major
version is strictly positive and the version does not have a prerelease
label.
v1 = version_t(0, 9, 10)
print *, v1%is_stable() ! false
v1 = version_t(1, 0, 0)
print *, v1%is_stable() ! true
v1 = version_t(1, 0, 0, 'alpha')
print *, v1%is_stable() ! false
The is_exactly()
function has been added for convencience and isn't part of the original Semantic Versioning 2.0.0 specification. It is true
if both versions are equal including the build
metadata.
v1 = version_t(0, 1, 0, 'a', '1')
v2 = version_t(0, 1, 0, 'a', '1')
print *, v1%is_exactly(v2) ! true
v1 = version_t(0, 1, 0, 'a', '1')
v2 = version_t(0, 1, 0, 'a', '2')
print *, v1%is_exactly(v2) ! false
print *, v1 == v2 ! true
Versions are converted to strings using the to_string()
method.
type(version_t) :: version
version = version_t(0, 5, 3, 'beta.1', '1-100')
print *, version%to_string() ! '0.5.3-beta.1+1-100'
type(version_t) :: version
version = version_t(0) ! 0.0.0
version = version_t(1) ! 1.0.0
version = version_t(3, 2) ! 3.2.0
version = version_t('4.1') ! 4.1.0
version = version_t('.5.') ! 0.5.0
version = version_t('..1') ! 0.0.1
There are also full examples in the example folder. Run them with:
fpm run --example <example_program>
Run tests with:
fpm test
Alternatively, you can use the provided Makefile
if you are on a Unix-like system:
make test
The CI will fail if the code is not formatted correctly. Please configure your
editor to use fprettify and use an
indentation width of 2 or run fprettify -i 2 -r .
before committing.
Feel free to create an issue in case you found a bug, have any questions or want to propose further improvements. Please stick to the existing coding style when you open a pull request.
You can use, redistribute and/or modify it under the terms of the Apache License, Version 2.0.