# MIT licensed # Copyright (c) 2024 Daniel Peukert , et al. import asyncio from io import BytesIO import tarfile from typing import List from nvchecker.api import ( session, VersionResult, Entry, AsyncCache, KeyManager, RichResult ) OPAM_REPO_INDEX_URL = "%s/index.tar.gz" OPAM_VERSION_PATH_PREFIX = "packages/%s/%s." OPAM_VERSION_PATH_SUFFIX = "/opam" OPAM_DEFAULT_REPO = 'https://opam.ocaml.org' OPAM_DEFAULT_REPO_VERSION_URL = "%s/packages/%s/%s.%s" def _decompress_and_list_files(data: bytes) -> List[str]: # Convert the bytes to a file object and get a list of files archive = tarfile.open(mode='r', fileobj=BytesIO(data)) return archive.getnames() async def get_files(url: str) -> List[str]: # Download the file and get its contents res = await session.get(url) data = res.body # Get the file list of the archive loop = asyncio.get_running_loop() return await loop.run_in_executor(None, _decompress_and_list_files, data) async def get_package_versions(files: List[str], pkg: str) -> List[str]: # Prepare the filename prefix based on the package name prefix = OPAM_VERSION_PATH_PREFIX % (pkg , pkg) # Only keep opam files that are relevant to the package we're working with filtered_files = [] for filename in files: if filename.startswith(prefix) and filename.endswith(OPAM_VERSION_PATH_SUFFIX): filtered_files.append(filename[len(prefix):-1*len(OPAM_VERSION_PATH_SUFFIX)]) return filtered_files async def get_version( name: str, conf: Entry, *, cache: AsyncCache, keymanager: KeyManager, **kwargs, ): pkg = conf.get('pkg', name) repo = conf.get('repo', OPAM_DEFAULT_REPO).rstrip('/') # Get the list of files in the repo index (see https://opam.ocaml.org/doc/Manual.html#Repositories for repo structure) files = await cache.get(OPAM_REPO_INDEX_URL % repo, get_files) # type: ignore # Parse the version strings from the file names raw_versions = await get_package_versions(files, pkg) # Convert the version strings into RichResults versions = [] for version in raw_versions: versions.append(RichResult( version = version, # There is no standardised URL scheme, so we only return an URL for the default registry url = OPAM_DEFAULT_REPO_VERSION_URL % (repo, pkg, pkg, version) if repo == OPAM_DEFAULT_REPO else None, )) return versions