Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
_scripts.py362 linesDownload Raw Back to setuptools
1from __future__ import annotations2 3import os4import re5import shlex6import shutil7import struct8import subprocess9import sys10import textwrap11from collections.abc import Iterable12from typing import TYPE_CHECKING, TypedDict13 14from ._importlib import metadata, resources15 16if TYPE_CHECKING:17    from typing_extensions import Self18 19from .warnings import SetuptoolsWarning20 21from distutils.command.build_scripts import first_line_re22from distutils.util import get_platform23 24 25class _SplitArgs(TypedDict, total=False):26    comments: bool27    posix: bool28 29 30class CommandSpec(list):31    """32    A command spec for a #! header, specified as a list of arguments akin to33    those passed to Popen.34    """35 36    options: list[str] = []37    split_args = _SplitArgs()38 39    @classmethod40    def best(cls):41        """42        Choose the best CommandSpec class based on environmental conditions.43        """44        return cls45 46    @classmethod47    def _sys_executable(cls):48        _default = os.path.normpath(sys.executable)49        return os.environ.get('__PYVENV_LAUNCHER__', _default)50 51    @classmethod52    def from_param(cls, param: Self | str | Iterable[str] | None) -> Self:53        """54        Construct a CommandSpec from a parameter to build_scripts, which may55        be None.56        """57        if isinstance(param, cls):58            return param59        if isinstance(param, str):60            return cls.from_string(param)61        if isinstance(param, Iterable):62            return cls(param)63        if param is None:64            return cls.from_environment()65        raise TypeError(f"Argument has an unsupported type {type(param)}")66 67    @classmethod68    def from_environment(cls):69        return cls([cls._sys_executable()])70 71    @classmethod72    def from_string(cls, string: str) -> Self:73        """74        Construct a command spec from a simple string representing a command75        line parseable by shlex.split.76        """77        items = shlex.split(string, **cls.split_args)78        return cls(items)79 80    def install_options(self, script_text: str):81        self.options = shlex.split(self._extract_options(script_text))82        cmdline = subprocess.list2cmdline(self)83        if not isascii(cmdline):84            self.options[:0] = ['-x']85 86    @staticmethod87    def _extract_options(orig_script):88        """89        Extract any options from the first line of the script.90        """91        first = (orig_script + '\n').splitlines()[0]92        match = _first_line_re().match(first)93        options = match.group(1) or '' if match else ''94        return options.strip()95 96    def as_header(self):97        return self._render(self + list(self.options))98 99    @staticmethod100    def _strip_quotes(item):101        _QUOTES = '"\''102        for q in _QUOTES:103            if item.startswith(q) and item.endswith(q):104                return item[1:-1]105        return item106 107    @staticmethod108    def _render(items):109        cmdline = subprocess.list2cmdline(110            CommandSpec._strip_quotes(item.strip()) for item in items111        )112        return '#!' + cmdline + '\n'113 114 115class WindowsCommandSpec(CommandSpec):116    split_args = _SplitArgs(posix=False)117 118 119class ScriptWriter:120    """121    Encapsulates behavior around writing entry point scripts for console and122    gui apps.123    """124 125    template = textwrap.dedent(126        r"""127        # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r128        import re129        import sys130 131        # for compatibility with easy_install; see #2198132        __requires__ = %(spec)r133 134        try:135            from importlib.metadata import distribution136        except ImportError:137            try:138                from importlib_metadata import distribution139            except ImportError:140                from pkg_resources import load_entry_point141 142 143        def importlib_load_entry_point(spec, group, name):144            dist_name, _, _ = spec.partition('==')145            matches = (146                entry_point147                for entry_point in distribution(dist_name).entry_points148                if entry_point.group == group and entry_point.name == name149            )150            return next(matches).load()151 152 153        globals().setdefault('load_entry_point', importlib_load_entry_point)154 155 156        if __name__ == '__main__':157            sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])158            sys.exit(load_entry_point(%(spec)r, %(group)r, %(name)r)())159        """160    ).lstrip()161 162    command_spec_class = CommandSpec163 164    @classmethod165    def get_args(cls, dist, header=None):166        """167        Yield write_script() argument tuples for a distribution's168        console_scripts and gui_scripts entry points.169        """170 171        # If distribution is not an importlib.metadata.Distribution, assume172        # it's a pkg_resources.Distribution and transform it.173        if not hasattr(dist, 'entry_points'):174            SetuptoolsWarning.emit("Unsupported distribution encountered.")175            dist = metadata.Distribution.at(dist.egg_info)176 177        if header is None:178            header = cls.get_header()179        spec = f'{dist.name}=={dist.version}'180        for type_ in 'console', 'gui':181            group = f'{type_}_scripts'182            for ep in dist.entry_points.select(group=group):183                name = ep.name184                cls._ensure_safe_name(ep.name)185                script_text = cls.template % locals()186                args = cls._get_script_args(type_, ep.name, header, script_text)187                yield from args188 189    @staticmethod190    def _ensure_safe_name(name):191        """192        Prevent paths in *_scripts entry point names.193        """194        has_path_sep = re.search(r'[\\/]', name)195        if has_path_sep:196            raise ValueError("Path separators not allowed in script names")197 198    @classmethod199    def best(cls):200        """201        Select the best ScriptWriter for this environment.202        """203        if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'):204            return WindowsScriptWriter.best()205        else:206            return cls207 208    @classmethod209    def _get_script_args(cls, type_, name, header, script_text):210        # Simply write the stub with no extension.211        yield (name, header + script_text)212 213    @classmethod214    def get_header(215        cls,216        script_text: str = "",217        executable: str | CommandSpec | Iterable[str] | None = None,218    ) -> str:219        """Create a #! line, getting options (if any) from script_text"""220        cmd = cls.command_spec_class.best().from_param(executable)221        cmd.install_options(script_text)222        return cmd.as_header()223 224 225class WindowsScriptWriter(ScriptWriter):226    command_spec_class = WindowsCommandSpec227 228    @classmethod229    def best(cls):230        """231        Select the best ScriptWriter suitable for Windows232        """233        writer_lookup = dict(234            executable=WindowsExecutableLauncherWriter,235            natural=cls,236        )237        # for compatibility, use the executable launcher by default238        launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')239        return writer_lookup[launcher]240 241    @classmethod242    def _get_script_args(cls, type_, name, header, script_text):243        "For Windows, add a .py extension"244        ext = dict(console='.pya', gui='.pyw')[type_]245        if ext not in os.environ['PATHEXT'].lower().split(';'):246            msg = (247                "{ext} not listed in PATHEXT; scripts will not be "248                "recognized as executables."249            ).format(**locals())250            SetuptoolsWarning.emit(msg)251        old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']252        old.remove(ext)253        header = cls._adjust_header(type_, header)254        blockers = [name + x for x in old]255        yield name + ext, header + script_text, 't', blockers256 257    @classmethod258    def _adjust_header(cls, type_, orig_header):259        """260        Make sure 'pythonw' is used for gui and 'python' is used for261        console (regardless of what sys.executable is).262        """263        pattern = 'pythonw.exe'264        repl = 'python.exe'265        if type_ == 'gui':266            pattern, repl = repl, pattern267        pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE)268        new_header = pattern_ob.sub(string=orig_header, repl=repl)269        return new_header if cls._use_header(new_header) else orig_header270 271    @staticmethod272    def _use_header(new_header):273        """274        Should _adjust_header use the replaced header?275 276        On non-windows systems, always use. On277        Windows systems, only use the replaced header if it resolves278        to an executable on the system.279        """280        clean_header = new_header[2:-1].strip('"')281        return sys.platform != 'win32' or shutil.which(clean_header)282 283 284class WindowsExecutableLauncherWriter(WindowsScriptWriter):285    @classmethod286    def _get_script_args(cls, type_, name, header, script_text):287        """288        For Windows, add a .py extension and an .exe launcher289        """290        if type_ == 'gui':291            launcher_type = 'gui'292            ext = '-script.pyw'293            old = ['.pyw']294        else:295            launcher_type = 'cli'296            ext = '-script.py'297            old = ['.py', '.pyc', '.pyo']298        hdr = cls._adjust_header(type_, header)299        blockers = [name + x for x in old]300        yield (name + ext, hdr + script_text, 't', blockers)301        yield (302            name + '.exe',303            get_win_launcher(launcher_type),304            'b',  # write in binary mode305        )306        if not is_64bit():307            # install a manifest for the launcher to prevent Windows308            # from detecting it as an installer (which it will for309            #  launchers like easy_install.exe). Consider only310            #  adding a manifest for launchers detected as installers.311            #  See Distribute #143 for details.312            m_name = name + '.exe.manifest'313            yield (m_name, load_launcher_manifest(name), 't')314 315 316def get_win_launcher(type):317    """318    Load the Windows launcher (executable) suitable for launching a script.319 320    `type` should be either 'cli' or 'gui'321 322    Returns the executable as a byte string.323    """324    launcher_fn = f'{type}.exe'325    if is_64bit():326        if get_platform() == "win-arm64":327            launcher_fn = launcher_fn.replace(".", "-arm64.")328        else:329            launcher_fn = launcher_fn.replace(".", "-64.")330    else:331        launcher_fn = launcher_fn.replace(".", "-32.")332    return resources.files('setuptools').joinpath(launcher_fn).read_bytes()333 334 335def load_launcher_manifest(name):336    res = resources.files(__name__).joinpath('launcher manifest.xml')337    return res.read_text(encoding='utf-8') % vars()338 339 340def _first_line_re():341    """342    Return a regular expression based on first_line_re suitable for matching343    strings.344    """345    if isinstance(first_line_re.pattern, str):346        return first_line_re347 348    # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern.349    return re.compile(first_line_re.pattern.decode())350 351 352def is_64bit():353    return struct.calcsize("P") == 8354 355 356def isascii(s):357    try:358        s.encode('ascii')359    except UnicodeError:360        return False361    return True362 
codekingpro/portable-devtools · Team Ai