Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
metadata.py181 linesDownload Raw Back to wheel
1"""2Tools for converting old- to new-style metadata.3"""4 5from __future__ import annotations6 7import functools8import itertools9import os.path10import re11import textwrap12from email.message import Message13from email.parser import Parser14from typing import Iterator15 16from .vendored.packaging.requirements import Requirement17 18 19def _nonblank(str):20    return str and not str.startswith("#")21 22 23@functools.singledispatch24def yield_lines(iterable):25    r"""26    Yield valid lines of a string or iterable.27    >>> list(yield_lines(''))28    []29    >>> list(yield_lines(['foo', 'bar']))30    ['foo', 'bar']31    >>> list(yield_lines('foo\nbar'))32    ['foo', 'bar']33    >>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))34    ['foo', 'baz #comment']35    >>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))36    ['foo', 'bar', 'baz', 'bing']37    """38    return itertools.chain.from_iterable(map(yield_lines, iterable))39 40 41@yield_lines.register(str)42def _(text):43    return filter(_nonblank, map(str.strip, text.splitlines()))44 45 46def split_sections(s):47    """Split a string or iterable thereof into (section, content) pairs48    Each ``section`` is a stripped version of the section header ("[section]")49    and each ``content`` is a list of stripped lines excluding blank lines and50    comment-only lines.  If there are any such lines before the first section51    header, they're returned in a first ``section`` of ``None``.52    """53    section = None54    content = []55    for line in yield_lines(s):56        if line.startswith("["):57            if line.endswith("]"):58                if section or content:59                    yield section, content60                section = line[1:-1].strip()61                content = []62            else:63                raise ValueError("Invalid section heading", line)64        else:65            content.append(line)66 67    # wrap up last segment68    yield section, content69 70 71def safe_extra(extra):72    """Convert an arbitrary string to a standard 'extra' name73    Any runs of non-alphanumeric characters are replaced with a single '_',74    and the result is always lowercased.75    """76    return re.sub("[^A-Za-z0-9.-]+", "_", extra).lower()77 78 79def safe_name(name):80    """Convert an arbitrary string to a standard distribution name81    Any runs of non-alphanumeric/. characters are replaced with a single '-'.82    """83    return re.sub("[^A-Za-z0-9.]+", "-", name)84 85 86def requires_to_requires_dist(requirement: Requirement) -> str:87    """Return the version specifier for a requirement in PEP 345/566 fashion."""88    if getattr(requirement, "url", None):89        return " @ " + requirement.url90 91    requires_dist = []92    for spec in requirement.specifier:93        requires_dist.append(spec.operator + spec.version)94 95    if requires_dist:96        return " " + ",".join(sorted(requires_dist))97    else:98        return ""99 100 101def convert_requirements(requirements: list[str]) -> Iterator[str]:102    """Yield Requires-Dist: strings for parsed requirements strings."""103    for req in requirements:104        parsed_requirement = Requirement(req)105        spec = requires_to_requires_dist(parsed_requirement)106        extras = ",".join(sorted(safe_extra(e) for e in parsed_requirement.extras))107        if extras:108            extras = f"[{extras}]"109 110        yield safe_name(parsed_requirement.name) + extras + spec111 112 113def generate_requirements(114    extras_require: dict[str, list[str]],115) -> Iterator[tuple[str, str]]:116    """117    Convert requirements from a setup()-style dictionary to118    ('Requires-Dist', 'requirement') and ('Provides-Extra', 'extra') tuples.119 120    extras_require is a dictionary of {extra: [requirements]} as passed to setup(),121    using the empty extra {'': [requirements]} to hold install_requires.122    """123    for extra, depends in extras_require.items():124        condition = ""125        extra = extra or ""126        if ":" in extra:  # setuptools extra:condition syntax127            extra, condition = extra.split(":", 1)128 129        extra = safe_extra(extra)130        if extra:131            yield "Provides-Extra", extra132            if condition:133                condition = "(" + condition + ") and "134            condition += "extra == '%s'" % extra135 136        if condition:137            condition = " ; " + condition138 139        for new_req in convert_requirements(depends):140            yield "Requires-Dist", new_req + condition141 142 143def pkginfo_to_metadata(egg_info_path: str, pkginfo_path: str) -> Message:144    """145    Convert .egg-info directory with PKG-INFO to the Metadata 2.1 format146    """147    with open(pkginfo_path, encoding="utf-8") as headers:148        pkg_info = Parser().parse(headers)149 150    pkg_info.replace_header("Metadata-Version", "2.1")151    # Those will be regenerated from `requires.txt`.152    del pkg_info["Provides-Extra"]153    del pkg_info["Requires-Dist"]154    requires_path = os.path.join(egg_info_path, "requires.txt")155    if os.path.exists(requires_path):156        with open(requires_path, encoding="utf-8") as requires_file:157            requires = requires_file.read()158 159        parsed_requirements = sorted(split_sections(requires), key=lambda x: x[0] or "")160        for extra, reqs in parsed_requirements:161            for key, value in generate_requirements({extra: reqs}):162                if (key, value) not in pkg_info.items():163                    pkg_info[key] = value164 165    description = pkg_info["Description"]166    if description:167        description_lines = pkg_info["Description"].splitlines()168        dedented_description = "\n".join(169            # if the first line of long_description is blank,170            # the first line here will be indented.171            (172                description_lines[0].lstrip(),173                textwrap.dedent("\n".join(description_lines[1:])),174                "\n",175            )176        )177        pkg_info.set_payload(dedented_description)178        del pkg_info["Description"]179 180    return pkg_info181 
codekingpro/portable-devtools · Team Ai