diff --git a/src/packageurl/contrib/purl2url.py b/src/packageurl/contrib/purl2url.py index 6926150..151cd2b 100644 --- a/src/packageurl/contrib/purl2url.py +++ b/src/packageurl/contrib/purl2url.py @@ -513,6 +513,83 @@ def build_swift_download_url(purl): return f"https://{namespace}/{name}/archive/{version}.zip" +@download_router.route("pkg:luarocks/.*") +def build_luarocks_download_url(purl): + """ + Return a LuaRocks download URL from the `purl` string. + """ + purl_data = PackageURL.from_string(purl) + + qualifiers = purl_data.qualifiers or {} + + repository_url = qualifiers.get("repository_url", "https://luarocks.org") + + name = purl_data.name + version = purl_data.version + + if name and version: + return f"{repository_url}/{name}-{version}.src.rock" + + +@download_router.route("pkg:conda/.*") +def build_conda_download_url(purl): + """ + Resolve a Conda PURL to a real downloadable URL + + Supported qualifiers: + - channel: e.g., main, conda-forge (required for deterministic base) + - subdir: e.g., linux-64, osx-arm64, win-64, noarch + - build: exact build string (optional but recommended) + - type: 'conda' or 'tar.bz2' (preference; fallback to whichever exists) + """ + p = PackageURL.from_string(purl) + if not p.name or not p.version: + return None + + q = p.qualifiers or {} + name = p.name + version = p.version + build = q.get("build") + channel = q.get("channel") or "main" + subdir = q.get("subdir") or "noarch" + req_type = q.get("type") + + def _conda_base_for_channel(channel: str) -> str: + """ + Map a conda channel to its base URL. + - 'main' / 'defaults' -> repo.anaconda.com + - any other channel -> conda.anaconda.org/ + """ + ch = (channel or "").lower() + if ch in ("main", "defaults"): + return "https://repo.anaconda.com/pkgs/main" + return f"https://conda.anaconda.org/{ch}" + + base = _conda_base_for_channel(channel) + + package_identifier = ( + f"{name}-{version}-{build}.{req_type}" if build else f"{name}-{version}.{req_type}" + ) + + download_url = f"{base}/{subdir}/{package_identifier}" + return download_url + + +@download_router.route("pkg:alpm/.*") +def build_alpm_download_url(purl_str): + purl = PackageURL.from_string(purl_str) + name = purl.name + version = purl.version + arch = purl.qualifiers.get("arch", "any") + + if not name or not version: + return None + + first_letter = name[0] + url = f"https://archive.archlinux.org/packages/{first_letter}/{name}/{name}-{version}-{arch}.pkg.tar.zst" + return url + + def get_repo_download_url(purl): """ Return ``download_url`` if present in ``purl`` qualifiers or diff --git a/tests/contrib/test_purl2url.py b/tests/contrib/test_purl2url.py index 52d1ec3..7585670 100644 --- a/tests/contrib/test_purl2url.py +++ b/tests/contrib/test_purl2url.py @@ -105,6 +105,10 @@ def test_purl2url_get_download_url(): "pkg:pub/http@0.13.3": "https://pub.dev/api/archives/http-0.13.3.tar.gz", "pkg:swift/github.com/Alamofire/Alamofire@5.4.3": "https://github.com/Alamofire/Alamofire/archive/5.4.3.zip", "pkg:swift/github.com/RxSwiftCommunity/RxFlow@2.12.4": "https://github.com/RxSwiftCommunity/RxFlow/archive/2.12.4.zip", + "pkg:luarocks/luasocket@3.1.0-1": "https://luarocks.org/luasocket-3.1.0-1.src.rock", + "pkg:luarocks/hisham/luafilesystem@1.8.0-1": "https://luarocks.org/luafilesystem-1.8.0-1.src.rock", + "pkg:conda/absl-py@0.4.1?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2": "https://repo.anaconda.com/pkgs/main/linux-64/absl-py-0.4.1-py36h06a4308_0.tar.bz2", + "pkg:alpm/arch/pacman@6.0.1-1?arch=x86_64": "https://archive.archlinux.org/packages/p/pacman/pacman-6.0.1-1-x86_64.pkg.tar.zst", # From `download_url` qualifier "pkg:github/yarnpkg/yarn@1.3.2?download_url=https://github.com/yarnpkg/yarn/releases/download/v1.3.2/yarn-v1.3.2.tar.gz&version_prefix=v": "https://github.com/yarnpkg/yarn/releases/download/v1.3.2/yarn-v1.3.2.tar.gz", "pkg:generic/lxc-master.tar.gz?download_url=https://salsa.debian.org/lxc-team/lxc/-/archive/master/lxc-master.tar.gz": "https://salsa.debian.org/lxc-team/lxc/-/archive/master/lxc-master.tar.gz",