diff --git a/docs/usage.rst b/docs/usage.rst
index 642c82d..98b6525 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -661,6 +661,11 @@ Check `crates.io `_ for updates.
cratesio
The crate name on crates.io, e.g. ``tokio``.
+use_pre_release
+ Whether to accept pre release. Default is false.
+
+This source supports :ref:`list options`.
+
Check Local Pacman Database
~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
diff --git a/nvchecker_source/cratesio.py b/nvchecker_source/cratesio.py
index 7c1f0d1..6aa0aeb 100644
--- a/nvchecker_source/cratesio.py
+++ b/nvchecker_source/cratesio.py
@@ -1,15 +1,40 @@
# MIT licensed
# Copyright (c) 2013-2020 lilydjwg , et al.
+import re
+
+import structlog
+
from nvchecker.api import RichResult
+logger = structlog.get_logger(logger_name=__name__)
+
+
API_URL = 'https://crates.io/api/v1/crates/%s'
+# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
+VERSION_PATTERN = r'^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
+
async def get_version(name, conf, *, cache, **kwargs):
name = conf.get('cratesio') or name
+ use_pre_release = conf.get('use_pre_release', False)
data = await cache.get_json(API_URL % name)
- version = [v['num'] for v in data['versions'] if not v['yanked']][0]
- return RichResult(
- version = version,
- url = f'https://crates.io/crates/{name}/{version}',
- )
+ results = []
+ for v in data['versions']:
+ if v['yanked']:
+ continue
+ version = v['num']
+ match = re.fullmatch(VERSION_PATTERN, version)
+ if match is None:
+ logger.warning('ignoring invalid version', version=version)
+ continue
+ if not use_pre_release and match.group('prerelease'):
+ continue
+ results.append(
+ RichResult(
+ version=version,
+ url=f'https://crates.io/crates/{name}/{version}',
+ )
+ )
+
+ return results
diff --git a/tests/test_cratesio.py b/tests/test_cratesio.py
index bb4bf25..ee397c5 100644
--- a/tests/test_cratesio.py
+++ b/tests/test_cratesio.py
@@ -8,3 +8,23 @@ async def test_cratesio(get_version):
assert await get_version("example", {
"source": "cratesio",
}) == "1.1.0"
+
+async def test_cratesio_list(get_version):
+ assert await get_version("example", {
+ "source": "cratesio",
+ "include_regex": r"^1\.0.*",
+ }) == "1.0.2"
+
+async def test_cratesio_skip_prerelease(get_version):
+ with pytest.raises(RuntimeError, match='include_regex matched no versions'):
+ await get_version("cargo-lock", {
+ "source": "cratesio",
+ "include_regex": r".*-.*",
+ })
+
+async def test_cratesio_use_prerelease(get_version):
+ await get_version("cargo-lock", {
+ "source": "cratesio",
+ "use_pre_release": "true",
+ "include_regex": r".*-.*",
+ })