badgerp_get_crates_version 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- badgerp_get_crates_version-0.1.0/.gitignore +15 -0
- badgerp_get_crates_version-0.1.0/PKG-INFO +21 -0
- badgerp_get_crates_version-0.1.0/README.md +12 -0
- badgerp_get_crates_version-0.1.0/plugin.py +62 -0
- badgerp_get_crates_version-0.1.0/pyproject.toml +16 -0
- badgerp_get_crates_version-0.1.0/tests/test_plugin.py +117 -0
- badgerp_get_crates_version-0.1.0/uv.lock +8 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: badgerp_get_crates_version
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Show the latest published version of a crate from crates.io
|
|
5
|
+
Author-email: 𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕 <regime_skylark.0y@icloud.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# get-crates-version
|
|
11
|
+
|
|
12
|
+
A [badger](https://github.com/) plugin that looks up the latest published
|
|
13
|
+
version of a crate from crates.io.
|
|
14
|
+
|
|
15
|
+
By default it resolves the crate name from a `Cargo.toml` in the current
|
|
16
|
+
directory (with basic support for workspaces), then queries the crates.io
|
|
17
|
+
API and returns a `crates.io: <version>` badge entry. You can also pass an
|
|
18
|
+
explicit `crate` name to skip the `Cargo.toml` lookup.
|
|
19
|
+
|
|
20
|
+
This package is not meant to be used as a standalone library — badger loads
|
|
21
|
+
`plugin.py`'s `run()` function directly as its entrypoint.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# get-crates-version
|
|
2
|
+
|
|
3
|
+
A [badger](https://github.com/) plugin that looks up the latest published
|
|
4
|
+
version of a crate from crates.io.
|
|
5
|
+
|
|
6
|
+
By default it resolves the crate name from a `Cargo.toml` in the current
|
|
7
|
+
directory (with basic support for workspaces), then queries the crates.io
|
|
8
|
+
API and returns a `crates.io: <version>` badge entry. You can also pass an
|
|
9
|
+
explicit `crate` name to skip the `Cargo.toml` lookup.
|
|
10
|
+
|
|
11
|
+
This package is not meant to be used as a standalone library — badger loads
|
|
12
|
+
`plugin.py`'s `run()` function directly as its entrypoint.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# This badge does the really simple thing of getting the CURRENTLY PUBLISHED crates version
|
|
2
|
+
# This is subtly different from the version of the crate in question in your repo because it's not FOR SURE
|
|
3
|
+
# on crates.io yet (just is the expectation).
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import tomllib
|
|
7
|
+
import urllib.request
|
|
8
|
+
from badger import entry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _crate_name_from_toml(cargo_toml: str) -> str:
|
|
12
|
+
"""Resolve the crate name from a Cargo.toml, with workspace support.
|
|
13
|
+
|
|
14
|
+
Handles three shapes:
|
|
15
|
+
- ``[package]`` only — a normal single-crate project.
|
|
16
|
+
- ``[package]`` + ``[workspace]`` — a workspace root that is also a
|
|
17
|
+
publishable crate (e.g. ``badger`` itself).
|
|
18
|
+
- ``[workspace]`` only (virtual workspace) — no ``[package]`` at the root.
|
|
19
|
+
Falls back to ``workspace.package.name`` if the workspace defines
|
|
20
|
+
inherited package metadata; otherwise raises with a helpful message.
|
|
21
|
+
"""
|
|
22
|
+
with open(cargo_toml, "rb") as f:
|
|
23
|
+
data = tomllib.load(f)
|
|
24
|
+
|
|
25
|
+
if "package" in data:
|
|
26
|
+
return data["package"]["name"]
|
|
27
|
+
|
|
28
|
+
# Virtual workspace: try workspace-level inherited package metadata.
|
|
29
|
+
workspace = data.get("workspace", {})
|
|
30
|
+
workspace_pkg_name = workspace.get("package", {}).get("name")
|
|
31
|
+
if workspace_pkg_name:
|
|
32
|
+
return workspace_pkg_name
|
|
33
|
+
|
|
34
|
+
members = workspace.get("members", [])
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"{cargo_toml!r} is a virtual workspace root (no [package] section). "
|
|
37
|
+
f"Workspace members: {members}. "
|
|
38
|
+
"Pass crate='<name>' explicitly to get-crates-version, or point "
|
|
39
|
+
"cargo_toml at a specific member's Cargo.toml."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run(crate: str = "", cargo_toml: str = "Cargo.toml") -> entry:
|
|
44
|
+
"""Fetch the newest crates.io release for a crate.
|
|
45
|
+
|
|
46
|
+
When no crate name is supplied, the package name is resolved from the
|
|
47
|
+
given Cargo manifest, including workspace metadata where available.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
crate: crates.io package name; empty uses the manifest's package name.
|
|
51
|
+
cargo_toml: manifest used when `crate` is empty.
|
|
52
|
+
"""
|
|
53
|
+
if not crate:
|
|
54
|
+
crate = _crate_name_from_toml(cargo_toml)
|
|
55
|
+
|
|
56
|
+
req = urllib.request.Request(
|
|
57
|
+
f"https://crates.io/api/v1/crates/{crate}",
|
|
58
|
+
headers={"User-Agent": "badger-badge-generator/0.1"},
|
|
59
|
+
)
|
|
60
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
61
|
+
version = json.loads(resp.read().decode())["crate"]["newest_version"]
|
|
62
|
+
return entry(label="crates.io", status=version)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "badgerp_get_crates_version"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Show the latest published version of a crate from crates.io"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{ name = "𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕", email = "regime_skylark.0y@icloud.com" }]
|
|
8
|
+
dependencies = []
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ["hatchling"]
|
|
13
|
+
build-backend = "hatchling.build"
|
|
14
|
+
|
|
15
|
+
[tool.hatch.build.targets.wheel]
|
|
16
|
+
include = ["plugin.py"]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.network
|
|
7
|
+
def test_label(plugin):
|
|
8
|
+
assert plugin.run(crate="serde").label == "crates.io"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.network
|
|
12
|
+
def test_version_is_semver(plugin):
|
|
13
|
+
parts = plugin.run(crate="serde").status.split(".")
|
|
14
|
+
assert len(parts) >= 2
|
|
15
|
+
assert parts[0].isdigit()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.network
|
|
19
|
+
def test_unknown_crate_raises(plugin):
|
|
20
|
+
with pytest.raises(Exception):
|
|
21
|
+
plugin.run(crate="this-crate-does-not-exist-xyzzy-99999")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.network
|
|
25
|
+
def test_infers_crate_from_cargo_toml(plugin, monkeypatch, project_root):
|
|
26
|
+
monkeypatch.chdir(project_root)
|
|
27
|
+
result = plugin.run()
|
|
28
|
+
assert result.label == "crates.io"
|
|
29
|
+
assert result.status
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def tmp_toml(tmp_path):
|
|
34
|
+
"""Write a Cargo.toml in a temp dir and return its absolute path."""
|
|
35
|
+
def _write(content: str) -> str:
|
|
36
|
+
path = tmp_path / "Cargo.toml"
|
|
37
|
+
path.write_text(textwrap.dedent(content))
|
|
38
|
+
return str(path)
|
|
39
|
+
return _write
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_crate_name_from_package_only_toml(plugin, tmp_toml):
|
|
43
|
+
path = tmp_toml("""\
|
|
44
|
+
[package]
|
|
45
|
+
name = "my-crate"
|
|
46
|
+
version = "0.1.0"
|
|
47
|
+
edition = "2021"
|
|
48
|
+
""")
|
|
49
|
+
assert plugin._crate_name_from_toml(path) == "my-crate"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_crate_name_from_package_plus_workspace_toml(plugin, tmp_toml):
|
|
53
|
+
"""A crate that is also the workspace root (e.g. badger itself)."""
|
|
54
|
+
path = tmp_toml("""\
|
|
55
|
+
[package]
|
|
56
|
+
name = "root-crate"
|
|
57
|
+
version = "0.1.0"
|
|
58
|
+
edition = "2021"
|
|
59
|
+
|
|
60
|
+
[workspace]
|
|
61
|
+
members = ["sub-a", "sub-b"]
|
|
62
|
+
""")
|
|
63
|
+
assert plugin._crate_name_from_toml(path) == "root-crate"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_crate_name_from_virtual_workspace_with_inherited_package(plugin, tmp_toml):
|
|
67
|
+
"""Virtual workspace that declares workspace.package.name."""
|
|
68
|
+
path = tmp_toml("""\
|
|
69
|
+
[workspace]
|
|
70
|
+
members = ["crate-a", "crate-b"]
|
|
71
|
+
|
|
72
|
+
[workspace.package]
|
|
73
|
+
name = "my-workspace"
|
|
74
|
+
version = "1.0.0"
|
|
75
|
+
edition = "2021"
|
|
76
|
+
""")
|
|
77
|
+
assert plugin._crate_name_from_toml(path) == "my-workspace"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_crate_name_from_virtual_workspace_without_package_raises(plugin, tmp_toml):
|
|
81
|
+
path = tmp_toml("""\
|
|
82
|
+
[workspace]
|
|
83
|
+
members = ["crate-a", "crate-b"]
|
|
84
|
+
""")
|
|
85
|
+
with pytest.raises(ValueError, match="virtual workspace root"):
|
|
86
|
+
plugin._crate_name_from_toml(path)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_virtual_workspace_error_mentions_members(plugin, tmp_toml):
|
|
90
|
+
path = tmp_toml("""\
|
|
91
|
+
[workspace]
|
|
92
|
+
members = ["alpha", "beta"]
|
|
93
|
+
""")
|
|
94
|
+
with pytest.raises(ValueError, match="alpha"):
|
|
95
|
+
plugin._crate_name_from_toml(path)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_explicit_crate_kwarg_skips_toml_read(plugin, tmp_toml, monkeypatch):
|
|
99
|
+
"""When crate= is provided the Cargo.toml is never opened."""
|
|
100
|
+
path = tmp_toml("""\
|
|
101
|
+
[workspace]
|
|
102
|
+
members = ["a"]
|
|
103
|
+
""")
|
|
104
|
+
|
|
105
|
+
original_open = open
|
|
106
|
+
|
|
107
|
+
def _guarded(name, *args, **kwargs):
|
|
108
|
+
if str(name) == path:
|
|
109
|
+
raise AssertionError(f"Cargo.toml should not be read when crate= is explicit: {name}")
|
|
110
|
+
return original_open(name, *args, **kwargs)
|
|
111
|
+
|
|
112
|
+
monkeypatch.setattr("builtins.open", _guarded)
|
|
113
|
+
|
|
114
|
+
# We expect a network/URL error, NOT a ValueError about virtual workspaces.
|
|
115
|
+
with pytest.raises(Exception) as exc_info:
|
|
116
|
+
plugin.run(crate="some-crate-name", cargo_toml=path)
|
|
117
|
+
assert "virtual workspace root" not in str(exc_info.value)
|