#!/usr/bin/env python3
#
# sdist.py
"""
Utilities for working with source distributions.
.. versionadded:: 0.9.0
.. autosummary::
~shippinglabel.sdist.NotAnSdistError
~shippinglabel.sdist.ParsedSdistFilename
~shippinglabel.sdist.parse_sdist_filename
"""
#
# Copyright © 2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
#
# Based on wheel-filename
# https://github.com/jwodder/wheel-filename
# Copyright (c) 2020 John Thorvald Wodder II
# MIT Licensed
# stdlib
import os
import re
from typing import NamedTuple
# 3rd party
from domdf_python_tools.paths import PathPlus
from domdf_python_tools.typing import PathLike
from packaging.utils import InvalidSdistFilename
__all__ = ["NotAnSdistError", "ParsedSdistFilename", "parse_sdist_filename"]
[docs]class NotAnSdistError(ValueError):
"""
Raised when something other than a source distribution is passed to :func:`~.parse_sdist_filename`.
"""
filename: str
def __init__(self, filename: PathLike, msg: str = '') -> None:
#: The invalid filename
self.filename = str(filename)
super().__init__(msg)
def __str__(self) -> str:
return super().__str__().format(repr(self.filename))
[docs]class ParsedSdistFilename(NamedTuple):
"""
Represents a parsed sdist filename.
:param project: The name of the project.
"""
#: The name of the project. The case is the same as in the filename.
project: str
#: The version number of the project.
version: str
#: The file extension of the sdist, e.g. ``.tar.gz``.
extension: str
def __str__(self) -> str:
return f'{self.project}-{self.version}{self.extension}'
SDIST_FILENAME_CRGX = re.compile(
r'(?P<project>[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?(-stubs)?)'
r'-(?P<version>[A-Za-z0-9_.!+]+)'
r'(?P<extension>.tar.gz|.tar.bz2|.zip)'
)
[docs]def parse_sdist_filename(filename: PathLike) -> ParsedSdistFilename:
"""
Parse a sdist filename into its components.
:param filename: An sdist path or filename.
:raises:
* :exc:`packaging.utils.InvalidSdistFilename` if the filename is invalid.
.. versionchanged:: 1.0.0 Previously raised :exc:`wheel_filename.InvalidFilenameError`
* :exc:`shippinglabel.sdist.NotAnSdistError` if the file is not an sdist.
:rtype:
.. seealso:: :func:`packaging.utils.parse_sdist_filename`
"""
filename = PathPlus(filename)
if filename.suffix == ".whl":
raise NotAnSdistError(filename, "{} is a wheel.")
basename = os.path.basename(filename)
m = SDIST_FILENAME_CRGX.fullmatch(basename)
if not m:
raise InvalidSdistFilename(f"Invalid sdist filename: {basename!r}")
return ParsedSdistFilename(**m.groupdict())