././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5235376 distlib-0.4.3/0000775000175000017500000000000015212736416012257 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781250942.0 distlib-0.4.3/CHANGES.rst0000664000175000017500000007042615212735576014100 0ustar00vinayvinayChange log for ``distlib`` -------------------------- 0.4.4 (future) ~~~~~~~~~~~~~~ Released: Not yet. 0.4.3 ~~~~~ Released: Not yet. - resources - Removed too-restrictive check for escaping resources. 0.4.2 ~~~~~ Released: 2026-06-08 - locators - Fix URL percent-encoding using space-padding instead of zero-padding. Thanks to Kadir Can Ozden for the patch. - Harden decompression against malicious input. Thanks to tonghuaroot for the patch, which was adapted slightly. - manifest - Use os.lstat in findall to correctly detect symlinked directories. Thanks to Kadir Can Ozden for the patch. - metadata - Improve logic to incorporate newer metadata versions. - resources - Ensure that constructed resource paths don't escape the package. Thanks to tonghuaroot for the patch. - util - Fix #255: Update cache_from_source() for Python 3.15. Thanks to Victor Stinner for the patch. - Check during unarchiving that the destination directory isn't escaped via symlinks. Thanks to tonghuaroot for the patch. - Improved performance of normalize_name using dual replace. Thanks to Hugo van Kemenade for the patch. - wheel - Add checks that installed files don't escape the installation directory. Thanks to tonghuaroot for the patch. - Add checks when mounting extensions to ensure path containment. Thanks to tonghuaroot for the patch. 0.4.1 ~~~~~ Released: 2026-06-02 - scripts - Fix path traversal bug in handling entry points which allowed escaping the scripts directory. Thanks to tonghuaroot for the comprehensive report. - tests - Fix #251: Change test function following a reorganization which happened in the Python stdlib. 0.4.0 ~~~~~ Released: 2025-07-17 - markers - Add the ``interpret_parsed`` function. - wheel - Fix #238: Add build tag to wheel metadata if specified. - Fix #243: Update to support free-threading version of Python (3.13t). - Fix #246: Support subdirectories in the dist-info directory. Thanks to Pieter P for the patch. - Fix #248: Fix path normalisation issue caused by the fix for #246. - Move import in script wrapper to "if __name__ == 'main'" clause. - tests - Fix #245: Skip test_package_data if a SKIP_EXT_PACKAGE_DATA environment variable is present. 0.3.9 ~~~~~ Released: 2024-10-09 - scripts - Merge #215: preload script wrappers on Windows to assist with a pip issue (thanks, Paul Moore). - Fix #220: Remove duplicated newline in shebang of windows launcher (thanks. A2uria). - Fix #230: Add handling for cross-compilation environments (thanks, Russell Keith-Magee). - util - Fix #224: Do not use the absolute path to cache wheel extensions (thanks, Stewart Miles). - wheel - Fix #222: Support mounting wheels that use extensions without an EXTENSIONS file (thanks, Stewart Miles). - Fix #225: Add support for wheel compatibility with the limited API (thanks, Stewart Miles). 0.3.8 ~~~~~ Released: 2023-12-12 - markers - Fix #209: use legacy version implementation for Python versions. - tests - Fix #204: use symlinks in venv creation during test. - Fix #208: handle deprecation removals in Python 3.13. 0.3.7 ~~~~~ Released: 2023-07-17 - database - Handle newlines when parsing metadata. - markers - Use version comparison logic for python_full_version. Thanks to Albert Peschar for the patch. - Simplify code with a set comprehension. Thanks to Christian Clauss for the patch. - scripts - Fix shebang computation for source builds of Python. Thanks to Eli Schwartz for the patch. - util - Extract tarfiles more safely by incorporating tarfile filters. Thanks to Petr Viktorin for the patch. - Check for 'has_cert' attribute before using it. Thanks to Lumir Balhar for the patch. - Fix #203: Handle parsing of export entries to allow script names such as "," or ",foo". Thanks to Flavio Amurrio for the report. - versions - Fix #200: Improve conformance to PEP440. Thanks to GitHub user con-f-use for the report. In addition to the above, an SPDX license identifier is now used. 0.3.6 ~~~~~ Released: 2022-08-26 - scripts - Fixed #175: Updated launcher executables to better handle the relationship between launcher and child process in the Job API. 0.3.5 ~~~~~ Released: 2022-07-14 - database - Fixed #170: Corrected implementation of ``get_required_dists()``. - index - Updated coverage pragmas for tests relating to obsolete PyPI APIs. - locators - Changed the default locator configuration. - metadata - Updates in support of PEP 643 / Metadata 2.2. - scripts - Updated launcher executables. Thanks to Michael Bikovitsky for his help with the launcher changes. - Fixed #164: Improved support for reproducible builds by allowing a fixed date/time to be inserted into created .exe files. Thanks to Somber Night for the patch. - util - Fixed #161: Updated test case. - wheel - Updated to write archive path of RECORD to RECORD instead of staging path. Thanks to Pieter Pas for the patch. - Fixed #169: Removed usage of deprecated imp module in favour of importlib. - Fixed #172: Compute ABI correctly for Python < 3.8. In addition to the above, setup.py was replaced by setup.cfg and pyproject.toml. 0.3.4 ~~~~~ Released: 2021-12-08 - database - Fixed #153: Raise warnings in get_distributions() if bad metadata seen, but keep going. - markers - Fixed #154: Determine Python versions correctly for Python >= 3.10. - scripts - Updated launcher executables. Code relating to support for Python 2.6 was also removed. 0.3.3 ~~~~~ Released: 2021-09-22 - compat - Fixed #152: Removed splituser() function which wasn't used and is deprecated. - markers - Fixed #149: Handle version comparisons correctly in environment markers. - scripts - Add ARM-64 launchers and support code to use them. Thanks to Niyas Sait and Adrian Vladu for their contributions. - util - Fixed #148: Handle a single trailing comma following a version. Thanks to Blazej Floch for the report and suggested fix. - version - Fixed #150: Fix incorrect handling of epochs. - wheel - Reverted handling of tags for Python >= 3.10 (use 310 rather than 3_10). This is because PEP 641 was rejected. - tests - Made changes relating to implementing CI using GitHub Actions. 0.3.2 ~~~~~ Released: 2021-05-29 - locators - Fixed #141: removed unused regular expression. - metadata - Fixed #140: allowed "Obsoletes" in more scenarios, to better handle faulty metadata already on PyPI. - resources - Fixed #146: added entry for SourcelessFileLoader to the finder registry. - scripts - Made the generation of scripts more configurable: - the ``variant_separator`` attribute can be set to determine the separator used between a script basename and its X.Y variant. The default value is ``'-'`` and would result in a final script basename like ``'foo-X.Y'``, whereas setting it to ``''`` would result in a final script basename like ``'fooX.Y'``. - You can also subclass and override the ``get_script_filenames()`` method to provide a more customised set of file basenames. - util - Fixed #140: allowed a trailing comma in constraints, to better handle faulty metadata already on PyPI. - Moved get_platform() logic from distutils to here. - Fixed #143: removed normcase() to avoid some problems on Windows. - wheel - Dropped any trailing data when computing the Python tag. - Added support for manylinux tags. - Changed handling of tags for Python >= 3.10 (use 3_10 rather than 310). - Fixed #147: permission bits are now preserved on POSIX when installing from a wheel. - tests - Fixed #139: improved handling of errors related to the test PyPI server. 0.3.1 ~~~~~ Released: 2020-06-27 The repository has been migrated to Git. References to earlier changesets (commits) in issue comments, etc. will be invalid. - scripts - Fixed #132: Added documentation to help with relative interpreter paths. Thanks to Paul Kienzle for the patch. - Fixed #134: Allowed specifying a different target Python version when generating scripts. - Fixed #135: Exposed the ``enquote_executable`` function previously implemented as an internal function. - Addressed #138: Improved metadata support for newer metadata versions. Thanks to James Tocknell for the patch. - wheel - Changed the output of flags in entry point definitions. Thanks to frostming (明希) for the patch. - Stopped writing JSON metadata. Only old-style metadata is written now. 0.3.0 ~~~~~ Released: 2019-10-29 - database - Issue #102 (partial): modules attribute of InstalledDistribution was incorrectly computed as a list of bytes, rather than a list of str. This has now been corrected. - locators - Updated Locator._get_digest to check PyPI JSON responses for a "digests" dictionary before trying "algo_digest" keys. Thanks to Jeffery To for the patch. - scripts - Fixed #123: Improved error message if a resource isn't found. - Fixed #124: Stopped norm-casing the executable written into shebangs, as it doesn't work for some non-ASCII paths. - Fixed #125: Updated launchers with versions that correctly report errors containing non-ASCII characters. The updated launchers now also support relative paths (see http://bit.ly/2JxmOoi for more information). - Changed Python version handling to accommodate versions like e.g. 3.10 (no longer assume a version X.Y where X and Y are single digits). - util - Fixed #127: Allowed hyphens in flags in export specifications. - wheel - Changed Python version handling to accommodate versions like e.g. 3.10 (no longer assume a version X.Y where X and Y are single digits). 0.2.9 ~~~~~ Released: 2019-05-14 - index - Updated default PyPI URL to https://pypi.org/pypi. - locators - Updated default PyPI URL to https://pypi.org/pypi. - metadata - Relaxed metadata format checks to ignore 'Provides'. - scripts - Fixed #33, #34: Simplified script template. - Updated Windows launchers. - util - Fixed #116: Corrected parsing of credentials from URLs. - wheel - Fixed #115: Relaxed check for '..' in wheel archive entries by not checking filename parts, only directory segments. - Skip entries in archive entries ending with '/' (directories) when verifying or installing. - docs - Updated default PyPI URL to https://pypi.org/pypi. - Commented out Disqus comment section. - Changed theme configuration. - Updated some out-of-date argument lists. - tests - Updated default PyPI URL to https://pypi.org/pypi. - Preserved umask on POSIX across a test. 0.2.8 ~~~~~ Released: 2018-10-01 - database - Fixed #108: Updated metadata scan to look for the METADATA file as well as the JSON formats. - locators - Fixed #112: Handled wheel tags and platform-dependent downloads correctly in SimpleScrapingLocator. - metadata - Fixed #107: Updated documentation on testing to include information on setting PYTHONHASHSEED. - scripts - Fixed #111: Avoided unnecessary newlines in script preambles, which caused problems with detecting encoding declarations. Thanks to Wim Glenn for the report and patch. - util - Fixed #109: Removed existing files (which might have been symlinks) before overwriting. 0.2.7 ~~~~~ Released: 2018-04-16 - compat - Fixed #105: cache_from_source is now imported from importlib.util where available. - database - Addressed #102: InstalledDistributions now have a modules attribute which is a list of top-level modules as read from top_level.txt, if that is in the distribution info. - locators - Fixed #103: Thanks to Saulius Žemaitaitis for the patch. - metadata - Added support for PEP 566 / Metadata 1.3. - scripts - Fixed #104: Updated launcher binaries. Thanks to Atsushi Odagiri for the diagnosis and fix. 0.2.6 ~~~~~ Released: 2017-10-28 - compat - Fixed #99: Updated to handle a case where sys.getfilesystemencoding() returns None. - database - Fixed #97: Eliminated a crash in EggInfoDistribution.list_distinfo_files() which was caused by trying to open a non-existent file. - Handled a case where an installed distribution didn't have 'Provides:' metadata. - locators - Fixed #96: SimpleScrapingLocator no longer fails prematurely when scraping links due to invalid versions. - markers - Improved error messages issued when interpreting markers - scripts - Improved the shebangs written into installed scripts when the interpreter path is very long or contains spaces (to cater for a limitation in shebang line parsing on Linux) - Updated launcher binaries. - tests - Numerous test refinements, not detailed further here. 0.2.5 ~~~~~ Released: 2017-05-06 - general - Changed regular expressions to be compatible with 3.6 as regards escape sequences. Thanks to Ville Skyttä for the patch. - closed some resource leaks related to XML-RPC proxies. - Removed Python 2.6 from the support list. - locators - Made downloadability a factor in scoring URLs for preferences. - markers - Replaced the implementation with code which parses requirements in accordance with PEP 508 and evaluates marker expressions according to PEP 508. - util - Changed _csv_open to use utf-8 across all platforms on Python 3.x. Thanks to Alastair McCormack for the patch. - wheel - Changed to look for metadata in metadata.json as well as pydist.json. - version - Updated requirement parsing in version matchers to use the new PEP 508-compliant code. - tests - Numerous test refinements, not detailed further here. 0.2.4 ~~~~~ Released: 2016-09-30 - compat - Updated to not fail on import if SSL is unavailable. - index - Switch from using gpg in preference to gpg2 for signing. This is to avoid gpg2's behaviour of prompting for passwords, which interferes with the tests on some machines. - locators - Changed project name comparisons to follow PEP 503. Thanks to Steven Arcangeli for the patch. - Added errors queue to Locator. - manifest - Changed match logic to work under Python 3.6, due to differences in how fnmatch.translate behaves. - resources - Updated finder registry logic to reflect changes in Python 3.6. - scripts - Fixed regular expression in generated script boilerplate. - util - Updated to not fail on import if SSL is unavailable. - Added normalize_name for project name comparisons using PEP 503. - tests - Updated to skip certain tests if SSL is unavailable. - Numerous other test refinements, not detailed further here. 0.2.3 ~~~~~ Released: 2016-04-30 - util - Changed get_executable to return Unicode rather than bytes. - Fixed #84: Allow + character in output script names. - Relaxed too-stringent test looking for application/json in headers. - wheel - sorted the entries in RECORD before writing to file. - tests - Numerous test refinements, not detailed further here. 0.2.2 ~~~~~ Released: 2016-01-30 - database - Issue #81: Added support for detecting distributions installed by wheel versions >= 0.23 (which use metadata.json rather than pydist.json). Thanks to Te-jé Rodgers for the patch. - locators - Updated default PyPI URL to https://pypi.python.org/pypi - metadata - Updated to use different formatting for description field for V1.1 metadata. - Corrected "classifier" to "classifiers" in the mapping for V1.0 metadata. - scripts - Improved support for Jython when quoting executables in output scripts. - util - Issue #77: Made the internal URL used for extended metadata fetches configurable via a module attribute. - Issue #78: Improved entry point parsing to handle leading spaces in ini-format files. - docs - Numerous documentation updates, not detailed further here. - tests - renamed environment variable SKIP_SLOW to SKIP_ONLINE in tests and applied to some more tests. - Numerous other test refinements, not detailed further here. 0.2.1 ~~~~~ Released: 2015-07-07 - locators - Issue #58: Return a Distribution instance or None from ``locate()``. - Issue #59: Skipped special keys when looking for versions. - Improved behaviour of PyPIJSONLocator to be analogous to that of other locators. - resource - Added resource iterator functionality. - scripts - Issue #71: Updated launchers to decode shebangs using UTF-8. This allows non-ASCII pathnames to be correctly handled. - Ensured that the executable written to shebangs is normcased. - Changed ScriptMaker to work better under Jython. - util - Changed the mode setting method to work better under Jython. - Changed get_executable() to return a normcased value. - wheel - Handled multiple-architecture wheel filenames correctly. - docs - Numerous documentation updates, not detailed further here. - tests - Numerous test refinements, not detailed further here. 0.2.0 ~~~~~ Released: 2014-12-17 - compat - Updated ``match_hostname`` to use the latest Python implementation. - database - Added `download_urls` and `digests` attributes to ``Distribution``. - locators - Issue #48: Fixed the problem of adding a tuple containing a set (unhashable) to a set, by wrapping with frozenset(). - Issue #55: Return multiple download URLs for distributions, if available. - manifest - Issue #57: Remove unhelpful warnings about pattern matches. - metadata - Updated to reflect changes to PEP 426. - resources - Issue #50: The type of the path needs to be preserved on 2.x. - scripts - Updated (including launchers) to support providing arguments to interpreters in shebang lines. - The launcher sources are now included in the repository and the source distribution (they are to be found in the PC directory). - Added frames support in IronPython (patch by Pawel Jasinski). - Issue #51: encode shebang executable using utf-8 rather than fsencode. - util - Removed reference to __PYVENV_LAUNCHER__ when determining executable for scripts (relevant only on macOS). - Updated to support changes to PEP 426. - version - Updated to reflect changes to versioning proposed in PEP 440. - wheel - Updated build() code to respect interpreter arguments in prebuilt scripts. - Updated to support changes to PEP 426 / PEP 440. - docs - Numerous documentation updates, not detailed further here. - tests - Numerous test refinements, not detailed further here. 0.1.9 ~~~~~ Released: 2014-05-19 - index - Added ``keystore`` keyword argument to signing and verification APIs. - scripts - Issue #47: Updated binary launchers to fix double-quoting bug where script executable paths have spaces. - docs - Numerous documentation updates, not detailed further here. - tests - Numerous test refinements, not detailed further here. 0.1.8 ~~~~~ Released: 2014-03-18 - index - Improved thread-safety in SimpleScrapingLocator (issue #45). - Replaced absolute imports with relative ones. - Added ``search`` method to ``PackageIndex``. - locators - Improved thread-safety in ``SimpleScrapingLocator`` (issue #45). - metadata - Fixed bug in add_requirements implementation. - resources - The ``Cache`` class was refactored into ``distlib.util.Cache`` and ``distlib.resources.ResourceCache`` classes. - scripts - Implement quoting for executables with spaces in them. - util - Gained the ``Cache`` class, which is also used in ``distlib.wheel``. - version - Allowed versions with a single numeric component and a local version component. - Adjusted pre-release computation for legacy versions to be the same as the logic in the setuptools documentation. - wheel - Added ``verify``, ``update``, ``is_compatible`` and ``is_mountable`` methods to the ``Wheel`` class. - Converted local version separators from '-' to '_' and back. - If SOABI not available, used Py_DEBUG, Py_UNICODE_SIZE and WITH_PYMALLOC to derive the ABI. - Added "exists" property to Wheel instances. - Factored out RECORD writing and zip building to separate methods. - Provided the ability to determine the location where extensions are extracted, by using the ``distlib.util.Cache`` class. - Avoided using ``pydist.json`` in 1.0 wheels (``bdist_wheel`` writes a non-conforming ``pydist.json``.) - Improved computation of compatible tags on macOS, and made COMPATIBLE_TAGS a set. - _backport/sysconfig - Replaced an absolute import with a relative one. - docs - Numerous documentation updates, not detailed further here. - tests - Numerous test refinements, not detailed further here. 0.1.7 ~~~~~ Released: 2014-01-16 - metadata - Added some more fields to the metadata for the index. - resources - Use native literal string in cache path. - Issue #40: Now does path adjustments differently for files and zips. - scripts - Improved checking for venvs when generating scripts. - util - Issue #39: Fall back to temporary directory for cache if home directory unavailable. - wheel - Use native literal string in cache path. 0.1.6 ~~~~~ Released: 2013-12-31 - scripts - Updated binary launchers because the wrong variant was shipped with the previous release. - version - Added support for local component in PEP 440 versions. - tests - Numerous test refinements, not detailed further here. 0.1.5 ~~~~~ Released: 2013-12-15 - compat - Changed source of import for unescape in Python >= 3.4. - index - Used dummy_threading when threading isn't available. - Used https for default index. - locators - Used dummy_threading when threading isn't available. - scripts - Defaulted to setting script mode bits on POSIX. - Use uncompressed executable launchers, since some anti-virus products raise false positive errors. - util - Used dummy_threading when threading isn't available. - docs - Updated out-of-date links in overview. - tests - Used dummy_threading when threading isn't available. 0.1.4 ~~~~~ Released: 2013-10-31 - scripts - Updated the logic for finding the distlib package using a relative, rather than absolute method. This fixes a problem for pip, where distlib is kept in the pip.vendor.distlib package. - _backport/sysconfig - The analogous change to that made for scripts, described above. 0.1.3 ~~~~~ Released: 2013-10-18 - database - Added support for PEP 426 JSON metadata (pydist.json). - Generalised digests to support e.g. SHA256. - Fixed a bug in parsing legacy metadata from .egg directories. - Removed duplicated code relating to parsing "provides" fields. - index - Changes relating to support for PEP 426 JSON metadata (pydist.json). - locators - Changes relating to support for PEP 426 JSON metadata (pydist.json). - Fixed a bug in scoring download URLs for preference when multiple URLs are available. - The legacy scheme is used for the default locator. - Made changes relating to parsing "provides" fields. - Generalised digests to support e.g. SHA256. - If no release version is found for a requirement, prereleases are now considered even if not explicitly requested. - markers - Added support for markers as specified in PEP 426. - metadata - Added support for PEP 426 JSON metadata (pydist.json). The old metadata class is renamed to LegacyMetadata, and the (new) Metadata class wraps the JSON format (and also the legacy format, through LegacyMetadata). - Removed code which was only used if docutils was installed. This code implemented validation of .rst descriptions, which is not done in distlib. - scripts - Updated the logic for writing executable files to deal as best we can with files which are already in use and hence cannot be deleted on Windows. - Changed the script generation when launchers are used to write a single executable which wraps a script (whether pre-built or generated) and includes a manifest to avoid UAC prompts on Windows. - Changed the interface for script generation options: the ``make`` and ``make_multiple`` methods of ``ScriptMaker`` now take an optional ``options`` dictionary. - util - Added extract_by_key() to copy selected keys from one dict to another. - Added parse_name_and_version() for use in parsing "provides" fields. - Made split_filename more flexible. - version - Added support for PEP 440 version matching. - Removed AdaptiveVersion, AdaptiveMatcher etc. as they don't add sufficient value to justify keeping them in. - wheel - Added wheel_version kwarg to Wheel.build API. - Changed Wheel.install API (after consultation on distutils-sig). - Added support for PEP 426 JSON metadata (pydist.json). - Added lib_only flag to install() method. - docs - Numerous documentation updates, not detailed further here. - tests - Numerous test refinements, not detailed further here. 0.1.2 ~~~~~ Released: 2013-04-30 - compat - Added BaseConfigurator backport for 2.6. - database - Return RECORD path from write_installed_files (or None if dry_run). - Explicitly return None from write_shared_locations if dry run. - metadata - Added missing condition in :meth:`~distlib.metadata.Metadata.todict`. - scripts - Add variants and clobber flag for generation of foo/fooX/foo-X.Y. - Added .exe manifests for Windows. - util - Regularised recording of written files. - Added Configurator. - version - Tidyups, most suggested by Donald Stufft: Made key functions private, removed _Common class, removed checking for huge version numbers, made UnsupportedVersionError a ValueError. - wheel - Replaced absolute import with relative. - Handle None return from write_shared_locations correctly. - Fixed bug in Mounter for extension modules not in sub-packages. - Made dylib-cache Python version-specific. - docs - Numerous documentation updates, not detailed further here. - tests - Numerous test refinements, not detailed further here. - other - Corrected setup.py to ensure that sysconfig.cfg is included. 0.1.1 ~~~~~ Released: 2013-03-22 - database - Updated requirements logic to use extras and environment markers. - Made it easier to subclass Distribution and EggInfoDistribution. - locators - Added method to clear locator caches. - Added the ability to skip pre-releases. - manifest - Fixed bug which caused side-effect when sorting a manifest. - metadata - Updated to handle most 2.0 fields, though PEP 426 is still a draft. - Added the option to skip unset fields when writing. - resources - Made separate subclasses ResourceBase, Resource and ResourceContainer from Resource. Thanks to Thomas Kluyver for the suggestion and patch. - scripts - Fixed bug which prevented writing shebang lines correctly on Windows. - util - Made get_cache_base more useful by parameterising the suffix to use. - Fixed a bug when reading CSV streams from .zip files under 3.x. - version - Added is_prerelease property to versions. - Moved to PEP 426 version formats and sorting. - wheel - Fixed CSV stream reading under 3.x and handled UTF-8 in zip entries correctly. - Added metadata and info properties, and updated the install method to return the installed distribution. - Added mount/unmount functionality. - Removed compatible_tags() function in favour of COMPATIBLE_TAGS attribute. - docs - Numerous documentation updates, not detailed further here. - tests - Numerous test refinements, not detailed further here. 0.1.0 ~~~~~ Released: 2013-03-02 - Initial release. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/CONTRIBUTORS.txt0000644000175000017500000000315513453041443014751 0ustar00vinayvinay======================= Distutils2 Contributors ======================= The Distutils2 project was started by Tarek Ziadé and is currently maintained by Éric Araujo. Many people have contributed to the project. distlib has started off using some of the code from distutil2. If you're making a patch, please add your name below in alphabetical order, and welcome into the Fellowship of the Packaging! Thanks to: - Rajiv Abraham - Ali Afshar - David Barnett - Pior Bastida - Anthony Baxter - Erik Bray - C. Titus Brown - Francisco Martín Brugué - Nicolas Cadou - Godefroid Chapelle - Julien Courteau - Christophe Combelles - Jason R. Coombs - Pierre-Yves David - Ned Deily - Konrad Delong - Josip Djolonga - John Edmonds - André Espaze - Boris Feld - Andrew Francis - Hallvard B Furuseth - Patrice Gauthier - Yannick Gingras - Filip Gruszczyński - Walker Hale IV - Alexandre Hamelin - Kelsey Hightower - Thomas Holmes - Preston Holmes - Christian Hudon - Julien Jehannet - Jeremy Kloth - Thomas Kluyver - Amos Latteier - Mathieu Leduc-Hamel - Pierre Paul Lefebvre - Tshepang Lekhonkhobe - Alain Leufroy - Janusz Lewandowski - Martin von Löwis - Hugo Lopes Tavares - Guillermo López-Anglada - Justin Love - Simon Mathieu - Carl Meyer - Alexis Métaireau - Julien Miotte - Zubin Mithra - Derek McTavish Mounce - Paul Moore - Michael Mulich - Louis Munro - Gaël Pasgrimaud - George Peristerakis - Mathieu Perreault - Guillaume Pratte - Sean Reifschneider - Antoine Reversat - Arc Riley - C. Anthony Risinger - Elson Rodriguez - Luis Rojas - Erik Rose - Brian Rosner - Vinay Sajip - Victor Stinner - Alexandre Vassalotti - Nadeem Vawda ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781250013.0 distlib-0.4.3/LICENSE.txt0000664000175000017500000003430315212733735014106 0ustar00vinayvinayA. HISTORY OF THE SOFTWARE ========================== Python was created in the early 1990s by Guido van Rossum at Stichting Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands as a successor of a language called ABC. Guido remains Python's principal author, although it includes many contributions from others. In 1995, Guido continued his work on Python at the Corporation for National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) in Reston, Virginia where he released several versions of the software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same year, the PythonLabs team moved to Digital Creations (now Zope Corporation, see http://www.zope.com). In 2001, the Python Software Foundation (PSF, see http://www.python.org/psf/) was formed, a non-profit organization created specifically to own Python-related Intellectual Property. Zope Corporation is a sponsoring member of the PSF. All Python releases are Open Source (see http://www.opensource.org for the Open Source Definition). Historically, most, but not all, Python releases have also been GPL-compatible; the table below summarizes the various releases. Release Derived Year Owner GPL- from compatible? (1) 0.9.0 thru 1.2 1991-1995 CWI yes 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 1.6 1.5.2 2000 CNRI no 2.0 1.6 2000 BeOpen.com no 1.6.1 1.6 2001 CNRI yes (2) 2.1 2.0+1.6.1 2001 PSF no 2.0.1 2.0+1.6.1 2001 PSF yes 2.1.1 2.1+2.0.1 2001 PSF yes 2.2 2.1.1 2001 PSF yes 2.1.2 2.1.1 2002 PSF yes 2.1.3 2.1.2 2002 PSF yes 2.2.1 2.2 2002 PSF yes 2.2.2 2.2.1 2002 PSF yes 2.2.3 2.2.2 2003 PSF yes 2.3 2.2.2 2002-2003 PSF yes 2.3.1 2.3 2002-2003 PSF yes 2.3.2 2.3.1 2002-2003 PSF yes 2.3.3 2.3.2 2002-2003 PSF yes 2.3.4 2.3.3 2004 PSF yes 2.3.5 2.3.4 2005 PSF yes 2.4 2.3 2004 PSF yes 2.4.1 2.4 2005 PSF yes 2.4.2 2.4.1 2005 PSF yes 2.4.3 2.4.2 2006 PSF yes 2.4.4 2.4.3 2006 PSF yes 2.5 2.4 2006 PSF yes 2.5.1 2.5 2007 PSF yes 2.5.2 2.5.1 2008 PSF yes 2.5.3 2.5.2 2008 PSF yes 2.6 2.5 2008 PSF yes 2.6.1 2.6 2008 PSF yes 2.6.2 2.6.1 2009 PSF yes 2.6.3 2.6.2 2009 PSF yes 2.6.4 2.6.3 2009 PSF yes 2.6.5 2.6.4 2010 PSF yes 3.0 2.6 2008 PSF yes 3.0.1 3.0 2009 PSF yes 3.1 3.0.1 2009 PSF yes 3.1.1 3.1 2009 PSF yes 3.1.2 3.1 2010 PSF yes 3.2 3.1 2010 PSF yes Footnotes: (1) GPL-compatible doesn't mean that we're distributing Python under the GPL. All Python licenses, unlike the GPL, let you distribute a modified version without making your changes open source. The GPL-compatible licenses make it possible to combine Python with other software that is released under the GPL; the others don't. (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, because its license has a choice of law clause. According to CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 is "not incompatible" with the GPL. Thanks to the many outside volunteers who have worked under Guido's direction to make these releases possible. B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON =============================================================== PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 ------------------------------------------- BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 --------------------------------------- 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6.1 software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6.1 alone or in any derivative version, provided, however, that CNRI's License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 1995-2001 Corporation for National Research Initiatives; All Rights Reserved" are retained in Python 1.6.1 alone or in any derivative version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with Python 1.6.1 may be located on the Internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This Agreement may also be obtained from a proxy server on the Internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6.1 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 1.6.1. 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by the federal intellectual property law of the United States, including without limitation the federal copyright law, and, to the extent such U.S. federal law does not apply, by the law of the Commonwealth of Virginia, excluding Virginia's conflict of law provisions. Notwithstanding the foregoing, with regard to derivative works based on Python 1.6.1 that incorporate non-separable material that was previously distributed under the GNU General Public License (GPL), the law of the Commonwealth of Virginia shall govern this License Agreement only as to issues arising under or with respect to Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6.1, Licensee agrees to be bound by the terms and conditions of this License Agreement. ACCEPT CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 -------------------------------------------------- Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1689762787.0 distlib-0.4.3/MANIFEST.in0000644000175000017500000000042114455735743014022 0ustar00vinayvinayinclude README.rst CHANGES.rst LICENSE.txt CONTRIBUTORS.txt tox.ini include distlib/*.exe recursive-include tests * recursive-include PC * global-exclude *.log __pycache__ *.pyc *.pyo exclude tests/*pypi* prune tests/pypiserver prune tests/packages exclude tests/passwords ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.4995327 distlib-0.4.3/PC/0000775000175000017500000000000015212736415012560 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1572260752.0 distlib-0.4.3/PC/CLISimpleLauncher.vcxproj0000644000175000017500000002710613555545620017450 0ustar00vinayvinay Debug Win32 Debug x64 Release Win32 Release x64 {A0AB4387-243A-48F7-881D-DE138DDE68BA} SimpleLauncher Win32Proj Application MultiByte true Application MultiByte Application MultiByte true Application MultiByte <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Configuration)\ $(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ true $(SolutionDir)dist\ $(Configuration)\ false $(SolutionDir)dist\ $(Platform)\$(Configuration)\ false AllRules.ruleset AllRules.ruleset AllRules.ruleset AllRules.ruleset t64 t32 Link Link Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true EnableFastChecks MultiThreaded Default Level3 EditAndContinue true true $(OutDir)$(ProjectName).map true Console MachineX86 python make_test.py test\test.exe $(OutDir)$(TargetName)$(TargetExt) make_test.py X64 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true EnableFastChecks MultiThreaded Level3 ProgramDatabase true true $(OutDir)$(ProjectName).map true Console MachineX64 python make_test.py test\test.exe $(OutDir)$(TargetName)$(TargetExt) make_test.py MinSpace true Size WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) MultiThreaded true Level3 ProgramDatabase dist\t32.exe true true $(OutDir)$(ProjectName).map true Console true true MachineX86 X64 MinSpace true Size WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) MultiThreaded true Level3 ProgramDatabase dist\t64.exe true true $(OutDir)$(ProjectName).map true Console true true MachineX64 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1572260752.0 distlib-0.4.3/PC/GUISimpleLauncher.vcxproj0000644000175000017500000002535713555545620017473 0ustar00vinayvinay Debug Win32 Debug x64 Release Win32 Release x64 {6295B87B-6312-4D7D-BCE9-A3626FD25417} GUISimpleLauncher Win32Proj Application MultiByte true Application MultiByte Application MultiByte true Application MultiByte <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Configuration)\ $(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ true $(SolutionDir)dist\ $(Configuration)\ false $(SolutionDir)dist\ $(Platform)\$(Configuration)\ false AllRules.ruleset AllRules.ruleset AllRules.ruleset AllRules.ruleset w32 w64 Disabled WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreaded Level3 EditAndContinue true true $(OutDir)$(ProjectName).map true Windows MachineX86 X64 Disabled WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreaded Level3 ProgramDatabase true true $(OutDir)$(ProjectName).map true Windows MachineX64 MinSpace true Size WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) MultiThreaded true Level3 ProgramDatabase dist\w32.exe true true $(OutDir)$(ProjectName).map true Windows true true MachineX86 X64 MinSpace true Size WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) MultiThreaded true Level3 ProgramDatabase dist\w64.exe true true $(OutDir)$(ProjectName).map true Windows true true MachineX64 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/PC/ReadMe.txt0000644000175000017500000000373413453041443014456 0ustar00vinayvinayThis is a simple launcher for Python files, which is functionally equivalent to the launchers in setuptools but not based on setuptools code. There are two versions of the launcher - console and GUI - built from the same source code. The launcher has been written as part of the pythonv branch, and is intended to facilitate location of the correct Python executable for a script in an environment where there may be multiple versions of Python deployed. The launcher is intended to facilitate script execution under Windows where a PEP 397-compatible launcher is not available. The idea is that each Python script has a copy of the launcher (symlinks not being generally available under Windows). For scripts to work with the launcher, they have to have a name ending in -script.py (for a console script) or -script.pyw (for a GUI script). The deployment system (e.g. packaging) will ensure that for foo-script.py, a console launcher opy named foo.exe is placed in the same directory; for bar-script.pyw, a GUI launcher copy named bar.exe is placed in the same directory. Assuming that the relevant directories are on the path, the scripts can be invoked using just "foo" or "bar". The foo.exe or bar.exe executable then runs: it looks for a script with the appropriate suffix ("-script.py" or "-script.pyw") in the same directory, and if found, opens that script to read a shebang line indicating which Python executable to use for the script. That executable, if found, is launched with the script and other arguments passed: foo a b c -> c:\path\to\python.exe c:\other\path\to\foo-script.py a b c bar d e f -> c:\path\to\pythonw.exe c:\other\path\to\bar-script.pyw d e f Note: More recently, the launchers have been updated to find their script in an archive appended to the executable, rather than a separate file. (This variant is enabled when APPENDED_ARCHIVE is #defined). This allows the launchers to be used to e.g. run .pyz archives as native Windows executables. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1572260752.0 distlib-0.4.3/PC/SimpleLauncher.sln0000644000175000017500000000375213555545620016222 0ustar00vinayvinay Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CLISimpleLauncher", "CLISimpleLauncher.vcxproj", "{A0AB4387-243A-48F7-881D-DE138DDE68BA}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GUISimpleLauncher", "GUISimpleLauncher.vcxproj", "{6295B87B-6312-4D7D-BCE9-A3626FD25417}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Debug|Win32.ActiveCfg = Debug|Win32 {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Debug|Win32.Build.0 = Debug|Win32 {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Debug|x64.ActiveCfg = Debug|x64 {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Debug|x64.Build.0 = Debug|x64 {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Release|Win32.ActiveCfg = Release|Win32 {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Release|Win32.Build.0 = Release|Win32 {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Release|x64.ActiveCfg = Release|x64 {A0AB4387-243A-48F7-881D-DE138DDE68BA}.Release|x64.Build.0 = Release|x64 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Debug|Win32.ActiveCfg = Debug|Win32 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Debug|Win32.Build.0 = Debug|Win32 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Debug|x64.ActiveCfg = Debug|x64 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Debug|x64.Build.0 = Debug|x64 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Release|Win32.ActiveCfg = Release|Win32 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Release|Win32.Build.0 = Release|Win32 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Release|x64.ActiveCfg = Release|x64 {6295B87B-6312-4D7D-BCE9-A3626FD25417}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659773708.0 distlib-0.4.3/PC/launcher.c0000664000175000017500000010112214273421414014516 0ustar00vinayvinay/* * Copyright (C) 2011-2022 Vinay Sajip. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista. #define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows. #endif #include #include #include #include #include #pragma comment (lib, "Shlwapi.lib") #define APPENDED_ARCHIVE #define USE_ENVIRONMENT #define SUPPORT_RELATIVE_PATH #define MSGSIZE 1024 #if !defined(APPENDED_ARCHIVE) static wchar_t suffix[] = { #if defined(_CONSOLE) L"-script.py" #else L"-script.pyw" #endif }; #endif #if defined(SUPPORT_RELATIVE_PATH) #define RELATIVE_PREFIX L"\\" #define RELATIVE_PREFIX_LENGTH 15 #endif #if !defined(_CONSOLE) typedef int (__stdcall *MSGBOXWAPIA)(IN HWND hWnd, IN LPCSTR lpText, IN LPCSTR lpCaption, IN UINT uType, IN WORD wLanguageId, IN DWORD dwMilliseconds); typedef int (__stdcall *MSGBOXWAPIW)(IN HWND hWnd, IN LPWSTR lpText, IN LPWSTR lpCaption, IN UINT uType, IN WORD wLanguageId, IN DWORD dwMilliseconds); #define MB_TIMEDOUT 32000 int MessageBoxTimeoutA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType, WORD wLanguageId, DWORD dwMilliseconds) { static MSGBOXWAPIA MsgBoxTOA = NULL; HMODULE hUser = LoadLibraryA("user32.dll"); if (!MsgBoxTOA) { if (hUser) MsgBoxTOA = (MSGBOXWAPIA)GetProcAddress(hUser, "MessageBoxTimeoutA"); else { /* * stuff happened, add code to handle it here * (possibly just call MessageBox()) */ } } if (MsgBoxTOA) return MsgBoxTOA(hWnd, lpText, lpCaption, uType, wLanguageId, dwMilliseconds); if (hUser) FreeLibrary(hUser); return 0; } int MessageBoxTimeoutW(HWND hWnd, LPWSTR lpText, LPWSTR lpCaption, UINT uType, WORD wLanguageId, DWORD dwMilliseconds) { static MSGBOXWAPIW MsgBoxTOW = NULL; HMODULE hUser = LoadLibraryA("user32.dll"); if (!MsgBoxTOW) { if (hUser) MsgBoxTOW = (MSGBOXWAPIW)GetProcAddress(hUser, "MessageBoxTimeoutW"); else { /* * stuff happened, add code to handle it here * (possibly just call MessageBox()) */ } } if (MsgBoxTOW) return MsgBoxTOW(hWnd, lpText, lpCaption, uType, wLanguageId, dwMilliseconds); if (hUser) FreeLibrary(hUser); return 0; } #endif static void wassert(BOOL condition, wchar_t * format, ... ) { if (!condition) { va_list va; wchar_t message[MSGSIZE]; int len; va_start(va, format); len = _vsnwprintf_s(message, MSGSIZE, MSGSIZE - 1, format, va); #if defined(_CONSOLE) fwprintf(stderr, L"Fatal error in launcher: %s\n", message); #else MessageBoxTimeoutW(NULL, message, L"Fatal Error in Launcher", MB_OK | MB_SETFOREGROUND | MB_ICONERROR, 0, 3000); #endif ExitProcess(1); } } static void assert(BOOL condition, char * format, ... ) { if (!condition) { va_list va; char message[MSGSIZE]; int len; va_start(va, format); len = vsnprintf_s(message, MSGSIZE, MSGSIZE - 1, format, va); #if defined(_CONSOLE) fprintf(stderr, "Fatal error in launcher: %s\n", message); #else MessageBoxTimeoutA(NULL, message, "Fatal Error in Launcher", MB_OK | MB_SETFOREGROUND | MB_ICONERROR, 0, 3000); #endif ExitProcess(1); } } static wchar_t script_path[MAX_PATH]; #if defined(APPENDED_ARCHIVE) #define LARGE_BUFSIZE (65 * 1024 * 1024) typedef struct { DWORD sig; DWORD unused_disk_nos; DWORD unused_numrecs; DWORD cdsize; DWORD cdoffset; } ENDCDR; /* We don't want to pick up this variable when scanning the executable. * So we initialise it statically, but fill in the first byte later. */ static char end_cdr_sig [4] = { 0x00, 0x4B, 0x05, 0x06 }; static char * find_pattern(char *buffer, size_t bufsize, char * pattern, size_t patsize) { char * result = NULL; char * p; char * bp = buffer; size_t n; while ((n = bufsize - (bp - buffer) - patsize) >= 0) { p = (char *) memchr(bp, pattern[0], n); if (p == NULL) break; if (memcmp(pattern, p, patsize) == 0) { result = p; /* keep trying - we want the last one */ } bp = p + 1; } return result; } static char * find_shebang(char * buffer, size_t bufsize) { FILE * fp = NULL; errno_t rc; char * result = NULL; char * p; size_t read; long pos; __int64 file_size; __int64 end_cdr_offset = -1; ENDCDR end_cdr; rc = _wfopen_s(&fp, script_path, L"rb"); assert(rc == 0, "Failed to open executable"); fseek(fp, 0, SEEK_END); file_size = ftell(fp); pos = (long) (file_size - bufsize); if (pos < 0) pos = 0; fseek(fp, pos, SEEK_SET); read = fread(buffer, sizeof(char), bufsize, fp); p = find_pattern(buffer, read, end_cdr_sig, sizeof(end_cdr_sig)); if (p != NULL) { end_cdr = *((ENDCDR *) p); end_cdr_offset = pos + (p - buffer); } else { /* * Try a larger buffer. A comment can only be 64K long, so * go for the largest size. */ char * big_buffer = malloc(LARGE_BUFSIZE); int n = (int) LARGE_BUFSIZE; pos = (long) (file_size - n); if (pos < 0) pos = 0; fseek(fp, pos, SEEK_SET); read = fread(big_buffer, sizeof(char), n, fp); p = find_pattern(big_buffer, read, end_cdr_sig, sizeof(end_cdr_sig)); assert(p != NULL, "Unable to find an appended archive."); end_cdr = *((ENDCDR *) p); end_cdr_offset = pos + (p - big_buffer); free(big_buffer); } end_cdr_offset -= end_cdr.cdsize + end_cdr.cdoffset; /* * end_cdr_offset should now be pointing to the start of the archive. * However, the "start of the archive" is a little ill-defined, as * not all means of prepending data to a zipfile handle the central * directory offset the same way (simple file content appends leave * it alone, obviously, but the stdlib zipapp and zipfile modules * reflect the prepended data in the offset). * We consider two possibilities here: * 1. end_cdr_offset points to the start of the shebang (zipapp) * 2. end_cdr_offset points to the end of the shebang (data copy) * We'll assume the shebang line has no # or ! chars except at the * beginning, and fits into bufsize (which should be MAX_PATH). */ /* Check for case 1 - we are at the start of the shebang */ fseek(fp, (long) end_cdr_offset, SEEK_SET); read = fread(buffer, sizeof(char), bufsize, fp); assert(read > 0, "Unable to read from file"); if (memcmp(buffer, "#!", 2) == 0) { result = buffer; } else { /* We are not at the start, so check backward bufsize bytes */ pos = (long) (end_cdr_offset - bufsize); if (pos < 0) pos = 0; fseek(fp, pos, SEEK_SET); read = fread(buffer, sizeof(char), bufsize, fp); assert(read > 0, "Unable to read from file"); p = &buffer[read - 1]; while (p >= buffer) { if (memcmp(p, "#!", 2) == 0) { result = p; break; } --p; } } fclose(fp); return result; } #endif #if defined(USE_ENVIRONMENT) /* * Where to place any executable found on the path. Should be OK to use a * static as there's only one of these per invocation of this executable. */ static wchar_t path_executable[MSGSIZE]; static BOOL find_on_path(wchar_t * name) { wchar_t * pathext; size_t varsize; wchar_t * context = NULL; wchar_t * extension; DWORD len; errno_t rc; BOOL found = FALSE; if (wcschr(name, L'.') != NULL) { /* assume it has an extension. */ if (SearchPathW(NULL, name, NULL, MSGSIZE, path_executable, NULL)) found = TRUE; } else { /* No extension - search using registered extensions. */ rc = _wdupenv_s(&pathext, &varsize, L"PATHEXT"); _wcslwr_s(pathext, varsize); if (rc == 0) { extension = wcstok_s(pathext, L";", &context); while (extension) { len = SearchPathW(NULL, name, extension, MSGSIZE, path_executable, NULL); if (len) { found = TRUE; break; } extension = wcstok_s(NULL, L";", &context); } free(pathext); } } return found; } /* * Find an executable in the environment. For now, we just look in the path, * but potentially we could expand this to look in the registry, etc. */ static wchar_t * find_environment_executable(wchar_t * line) { BOOL found = find_on_path(line); return found ? path_executable : NULL; } #endif static wchar_t * skip_ws(wchar_t *p) { while (*p && iswspace(*p)) ++p; return p; } static wchar_t * skip_me(wchar_t * p) { wchar_t * result; wchar_t terminator; if (*p != L'\"') terminator = L' '; else { terminator = *p++; ++p; } result = wcschr(p, terminator); if (result == NULL) /* perhaps nothing more on the command line */ result = L""; else result = skip_ws(++result); return result; } static char * find_terminator(char *buffer, size_t size) { char c; char * result = NULL; char * end = buffer + size; char * p; for (p = buffer; p < end; p++) { c = *p; if (c == '\r') { result = p; break; } if (c == '\n') { result = p; break; } } return result; } #if defined(DUPLICATE_HANDLES) static BOOL safe_duplicate_handle(HANDLE in, HANDLE * pout) { BOOL ok; HANDLE process = GetCurrentProcess(); #if defined(_CONSOLE) DWORD rc; #endif *pout = NULL; /* * See https://github.com/pypa/pip/issues/10444 - for the GUI launcher, * errors are returned by DuplicateHandle. There may be no good reason * why a GUI process would want to use these handles, but for now we * attempt duplication but ignore errors in the GUI case. */ ok = DuplicateHandle(process, in, process, pout, 0, TRUE, DUPLICATE_SAME_ACCESS); #if defined(_CONSOLE) if (!ok) { rc = GetLastError(); if (rc == ERROR_INVALID_HANDLE) ok = TRUE; } return ok; #else return TRUE; #endif } #endif /* * These items are global so that thet can be accessed * from the Ctrl-C handler or other auxiliary routine. */ static PROCESS_INFORMATION child_process_info; static JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info; static HANDLE job; static BOOL control_key_handler(DWORD type) { /* * See https://github.com/pypa/pip/issues/10444 */ #if defined(OLD_LOGIC) if ((type == CTRL_C_EVENT) || (type == CTRL_BREAK_EVENT)) { return TRUE; } WaitForSingleObject(child_process_info.hProcess, INFINITE); #else switch (type) { case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: /* * Allow the child to outlive the launcher, to carry out any * cleanup for a graceful exit. It will either exit or get * terminated by the session server. */ if (job != NULL) { job_info.BasicLimitInformation.LimitFlags &= ~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; SetInformationJobObject( job, JobObjectExtendedLimitInformation, &job_info, sizeof(job_info)); } } #endif return TRUE; } #if !defined(_CONSOLE) /* * End the launcher's "app starting" cursor state. * * When Explorer launches a Windows (GUI) application, it displays * the "app starting" (the "pointer + hourglass") cursor for a number * of seconds, or until the app does something UI-ish (eg, creating a * window, or fetching a message). As this launcher doesn't do this * directly, that cursor remains even after the child process does these * things. We avoid that by doing the stuff in here. * See http://bugs.python.org/issue17290 and * https://github.com/pypa/pip/issues/10444#issuecomment-973408601 */ static void clear_app_starting_state() { MSG msg; HWND hwnd; PostMessageW(0, 0, 0, 0); GetMessageW(&msg, 0, 0, 0); /* Proxy the child's input idle event. */ WaitForInputIdle(child_process_info.hProcess, INFINITE); /* * Signal the process input idle event by creating a window and pumping * sent messages. The window class isn't important, so just use the * system "STATIC" class. */ hwnd = CreateWindowExW(0, L"STATIC", L"PyLauncher", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); /* Process all sent messages and signal input idle. */ PeekMessageW(&msg, hwnd, 0, 0, 0); DestroyWindow(hwnd); } #endif /* * See https://github.com/pypa/pip/issues/10444#issuecomment-1055392299 */ static BOOL make_handle_inheritable(HANDLE handle) { DWORD file_type = GetFileType(handle); // Ignore an invalid handle, non-file object type, unsupported file type, // or a console file prior to Windows 8. if (file_type == FILE_TYPE_UNKNOWN || (file_type == FILE_TYPE_CHAR && ((ULONG_PTR)handle & 3))) { return TRUE; } return SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); } static void __cdecl silent_invalid_parameter_handler( wchar_t const* expression, wchar_t const* function, wchar_t const* file, unsigned int line, uintptr_t pReserved ) { } /* * Best-effort cleanup of any C file descriptors that were inherited * from the parent process. This cleans up all fds except 0-2. */ static void cleanup_fds(WORD cbReserved2, LPBYTE lpReserved2) { int handle_count = 0; UNALIGNED HANDLE* first_handle = NULL; UNALIGNED HANDLE* current_handle = NULL; _invalid_parameter_handler old_handler = NULL; // The structure is: , , if (cbReserved2 < sizeof(int) || NULL == lpReserved2) { return; } handle_count = *(UNALIGNED int*)lpReserved2; // Verify the buffer is large enough if (cbReserved2 < sizeof(int) + handle_count + sizeof(HANDLE) * handle_count) { return; } first_handle = (UNALIGNED HANDLE *)(lpReserved2 + sizeof(int) + handle_count); old_handler = _set_invalid_parameter_handler(&silent_invalid_parameter_handler); { // Close all fds inherited from the parent, except for the standard I/O fds. // We'll deal with those later. for (current_handle = first_handle + 3; current_handle < first_handle + handle_count; ++current_handle) { // Ignore invalid handles, as that means this fd was not inherited. // -2 is a special value (https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170) // that we check for just in case. if (NULL == *current_handle || INVALID_HANDLE_VALUE == *current_handle || (HANDLE)-2 == *current_handle) { continue; } _close((int)(current_handle - first_handle)); } } _set_invalid_parameter_handler(old_handler); } /* * Returns the HANDLE associated with a FILE* object, or INVALID_HANDLE_VALUE * on error. */ static HANDLE get_stream_handle(FILE * stream) { _invalid_parameter_handler old_handler = NULL; int fd = -1; HANDLE handle = INVALID_HANDLE_VALUE; old_handler = _set_invalid_parameter_handler(&silent_invalid_parameter_handler); { fd = _fileno(stream); if (fd >= 0) { handle = (HANDLE)_get_osfhandle(fd); } else { handle = INVALID_HANDLE_VALUE; } } _set_invalid_parameter_handler(old_handler); return handle; } /* * Closes the Windows standard I/O handles (GetStdHandle) * in a best-effort manner. Ensures that the stderr stream is left untouched. */ static void cleanup_standard_io(void) { HANDLE stdin_handle = INVALID_HANDLE_VALUE; HANDLE stdout_handle = INVALID_HANDLE_VALUE; HANDLE stderr_handle = INVALID_HANDLE_VALUE; HANDLE hStdIn = INVALID_HANDLE_VALUE; HANDLE hStdOut = INVALID_HANDLE_VALUE; HANDLE hStdErr = INVALID_HANDLE_VALUE; // We need to close both the C streams stdin and stdout, // and the Windows standard I/O handles. However, these may be equal, // so care must be taken not to close a handle twice. Moreover, // handles for several C streams may be equal as well. // Fun all around. // Get the handles associated with the standard I/O streams. stdin_handle = get_stream_handle(stdin); stdout_handle = get_stream_handle(stdout); stderr_handle = get_stream_handle(stderr); // If any two underlying handles are equal, drop everything and return. // There's bound to be trouble if we continue with closing the streams. if (((INVALID_HANDLE_VALUE != stdin_handle) && (stdin_handle == stdout_handle || stdin_handle == stderr_handle)) || ((INVALID_HANDLE_VALUE != stdout_handle) && (stdout_handle == stdin_handle || stdout_handle == stderr_handle)) || ((INVALID_HANDLE_VALUE != stderr_handle) && (stderr_handle == stdin_handle || stderr_handle == stdout_handle))) { return; } // Get the Windows I/O handles before we do anything. hStdIn = GetStdHandle(STD_INPUT_HANDLE); hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); hStdErr = GetStdHandle(STD_ERROR_HANDLE); // At this point, we have confirmed that the I/O streams all have different // handles, and we have the Windows standard I/O handles as well. // Proceed with closing the streams. fclose(stdin); fclose(stdout); // Now we need to close the Windows standard I/O handles, as they might // differ from the handles for the C streams. // First, make sure we don't close handles that we have already closed // by closing the streams. if (stdin_handle == hStdIn || stdout_handle == hStdIn) { SetStdHandle(STD_INPUT_HANDLE, NULL); hStdIn = NULL; } if (stdin_handle == hStdOut || stdout_handle == hStdOut) { SetStdHandle(STD_OUTPUT_HANDLE, NULL); hStdOut = NULL; } if (stdin_handle == hStdErr || stdout_handle == hStdErr) { SetStdHandle(STD_ERROR_HANDLE, NULL); hStdErr = NULL; } // Ensure we don't accidentally close the standard error handle. if (stderr_handle == hStdIn) { hStdIn = NULL; } if (stderr_handle == hStdOut) { hStdOut = NULL; } if (stderr_handle == hStdErr) { hStdErr = NULL; } // Close 'em. if (NULL != hStdIn && INVALID_HANDLE_VALUE != hStdIn) { CloseHandle(hStdIn); SetStdHandle(STD_INPUT_HANDLE, NULL); } if (NULL != hStdOut && INVALID_HANDLE_VALUE != hStdOut) { CloseHandle(hStdOut); SetStdHandle(STD_OUTPUT_HANDLE, NULL); } if (NULL != hStdErr && INVALID_HANDLE_VALUE != hStdErr) { CloseHandle(hStdErr); SetStdHandle(STD_ERROR_HANDLE, NULL); } } #define SWITCH_WORKING_DIR #if defined(SWITCH_WORKING_DIR) /* * Switch the working directory to the user's temp directory. */ static void switch_working_directory() { WCHAR tempDir[MAX_PATH + 1]; DWORD len = GetTempPathW(MAX_PATH + 1, tempDir); if (len > 0 && len <= MAX_PATH) { SetCurrentDirectoryW(tempDir); } } #endif /* * Best-effort cleanup of file descriptors and handles after spawning the child * process. * See discussion starting from here: https://github.com/pypa/pip/issues/10444#issuecomment-1055392299 */ static void post_spawn_cleanup(WORD cbReserved2, LPBYTE lpReserved2) { cleanup_fds(cbReserved2, lpReserved2); cleanup_standard_io(); #if defined(SWITCH_WORKING_DIR) switch_working_directory(); #endif } /* * See https://github.com/pypa/pip/issues/10444#issuecomment-971921420 */ #define STARTF_UNDOC_MONITOR 0x400 /* * See https://github.com/pypa/pip/issues/10444#issuecomment-973396812 */ static void run_child(wchar_t * cmdline) { DWORD rc; BOOL ok; STARTUPINFOW si; job = CreateJobObject(NULL, NULL); assert(job != NULL, "Job creation failed"); ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation, &job_info, sizeof(job_info), &rc); assert(ok && (rc == sizeof(job_info)), "Job information querying failed"); job_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &job_info, sizeof(job_info)); assert(ok, "Job information setting failed"); memset(&si, 0, sizeof(si)); GetStartupInfoW(&si); /* * See https://github.com/pypa/pip/issues/10444#issuecomment-973396812 */ if ((si.dwFlags & (STARTF_USEHOTKEY | STARTF_UNDOC_MONITOR)) == 0) { HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); #if defined(DUPLICATE_HANDLES) ok = safe_duplicate_handle(hIn, &si.hStdInput); assert(ok, "stdin duplication failed"); CloseHandle(hIn); ok = safe_duplicate_handle(hOut, &si.hStdOutput); assert(ok, "stdout duplication failed"); CloseHandle(hOut); /* We might need stderr late, so don't close it but mark as non-inheritable */ SetHandleInformation(hErr, HANDLE_FLAG_INHERIT, 0); ok = safe_duplicate_handle(hErr, &si.hStdError); assert(ok, "stderr duplication failed"); #else /* * See https://github.com/pypa/pip/issues/10444#issuecomment-1055392299 */ ok = make_handle_inheritable(hIn); assert(ok, "making stdin inheritable failed"); ok = make_handle_inheritable(hOut); assert(ok, "making stdout inheritable failed"); ok = make_handle_inheritable(hErr); assert(ok, "making stderr inheritable failed"); si.hStdInput = hIn; si.hStdOutput = hOut; si.hStdError = hErr; #endif si.dwFlags |= STARTF_USESTDHANDLES; } ok = CreateProcessW(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &child_process_info); if (!ok) { // Failed to create process. See if we can find out why. DWORD err = GetLastError(); wchar_t emessage[MSGSIZE]; FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), emessage, MSGSIZE, NULL); wassert(ok, L"Unable to create process using '%ls': %ls", cmdline, emessage); } // Assign the process to the job straight away. See https://github.com/pypa/distlib/issues/175 AssignProcessToJobObject(job, child_process_info.hProcess); post_spawn_cleanup(si.cbReserved2, si.lpReserved2); /* * Control handler setting is now done after process creation because the handler needs access * to the child_process_info structure, which is populated by the CreateProcessW call above. */ ok = SetConsoleCtrlHandler((PHANDLER_ROUTINE) control_key_handler, TRUE); assert(ok, "control handler setting failed"); #if !defined(_CONSOLE) clear_app_starting_state(&child_process_info); #endif CloseHandle(child_process_info.hThread); WaitForSingleObjectEx(child_process_info.hProcess, INFINITE, FALSE); ok = GetExitCodeProcess(child_process_info.hProcess, &rc); assert(ok, "Failed to get exit code of process"); ExitProcess(rc); } static wchar_t * find_exe_extension(wchar_t * line) { wchar_t * p; while ((p = StrStrIW(line, L".exe")) != NULL) { wchar_t c = p[4]; if ((c == L'\0') || (c == L'"') || iswspace(c)) break; line = &p[4]; } return p; } static wchar_t * find_executable_and_args(wchar_t * line, wchar_t ** argp) { wchar_t * p = find_exe_extension(line); #if defined(USE_ENVIRONMENT) wchar_t * q; int n; #endif wchar_t * result; #if !defined(USE_ENVIRONMENT) assert(p != NULL, "Expected to find a command ending in '.exe' in shebang line: %ls", line); p += 4; /* skip past the '.exe' */ result = line; #else if (p != NULL) { p += 4; /* skip past the '.exe' */ result = line; } else { n = _wcsnicmp(line, L"/usr/bin/env", 12); assert(n == 0, "Expected to find a command ending in '.exe' in shebang line: %ls", line); p = line + 12; /* past the '/usr/bin/env' */ assert(*p && iswspace(*p), "Expected to find whitespace after '/usr/bin/env': %ls", line); do { ++p; } while (*p && iswspace(*p)); /* Now, p points to what comes after /usr/bin/env and any following whitespace. */ q = p; /* Skip past executable name and NUL-terminate it. */ while (*q && !iswspace(*q)) ++q; if (iswspace(*q)) *q++ = L'\0'; result = find_environment_executable(p); assert(result != NULL, "Unable to find executable in environment: %ls", line); p = q; /* point past name of executable in shebang */ } #endif if (*line == L'"') { assert(*p == L'"', "Expected terminating double-quote for executable in shebang line: %ls", line); *p++ = L'\0'; ++line; ++result; /* See https://bitbucket.org/pypa/distlib/issues/104 */ } /* p points just past the executable. It must either be a NUL or whitespace. */ #if !defined(SUPPORT_RELATIVE_PATH) assert(*p != L'"', "Terminating quote without starting quote for executable in shebang line: %ls", line); #else if (_wcsnicmp(line, RELATIVE_PREFIX, RELATIVE_PREFIX_LENGTH) && (line[RELATIVE_PREFIX_LENGTH] != L'\"')) { assert(*p != L'"', "Terminating quote without starting quote for executable in shebang line: %ls", line); } #endif /* if p is whitespace, make it NUL to truncate 'line', and advance */ if (*p && iswspace(*p)) *p++ = L'\0'; /* Now we can skip the whitespace, having checked that it's there. */ while(*p && iswspace(*p)) ++p; *argp = p; return result; } static int process(int argc, char * argv[]) { wchar_t * cmdline = skip_me(GetCommandLineW()); wchar_t * psp; size_t len = GetModuleFileNameW(NULL, script_path, MAX_PATH); FILE *fp = NULL; char buffer[MAX_PATH]; wchar_t wbuffer[MAX_PATH]; #if defined(SUPPORT_RELATIVE_PATH) wchar_t dbuffer[MAX_PATH]; wchar_t pbuffer[MAX_PATH]; wchar_t * qp; int prefix_offset; #endif char *cp; wchar_t * wcp; wchar_t * cmdp; char * p; wchar_t * wp; int n; #if !defined(APPENDED_ARCHIVE) errno_t rc; #endif if (script_path[0] != L'\"') psp = script_path; else { psp = &script_path[1]; len -= 2; } psp[len] = L'\0'; #if !defined(APPENDED_ARCHIVE) /* Replace the .exe with -script.py(w) */ wp = wcsstr(psp, L".exe"); assert(wp != NULL, "Failed to find \".exe\" in executable name"); len = MAX_PATH - (wp - script_path); assert(len > sizeof(suffix), "Failed to append \"%ls\" suffix", suffix); wcsncpy_s(wp, len, suffix, sizeof(suffix)); #endif #if defined(APPENDED_ARCHIVE) /* Initialise signature dynamically so that it doesn't appear in * a stock executable. */ end_cdr_sig[0] = 0x50; p = find_shebang(buffer, MAX_PATH); assert(p != NULL, "Failed to find shebang"); #else rc = _wfopen_s(&fp, psp, L"rb"); assert(rc == 0, "Failed to open script file '%ls'", psp); fread(buffer, sizeof(char), MAX_PATH, fp); fclose(fp); p = buffer; #endif cp = find_terminator(p, MAX_PATH); assert(cp != NULL, "Expected to find terminator in shebang line"); *cp = '\0'; // Decode as UTF-8 n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, p, (int) (cp - p), wbuffer, MAX_PATH); assert(n != 0, "Expected to decode shebang line using UTF-8"); wbuffer[n] = L'\0'; wcp = wbuffer; while (*wcp && iswspace(*wcp)) ++wcp; assert(*wcp == L'#', "Expected to find \'#\' at start of shebang line"); ++wcp; while (*wcp && iswspace(*wcp)) ++wcp; assert(*wcp == L'!', "Expected to find \'!\' following \'#\' in shebang line"); ++wcp; while (*wcp && iswspace(*wcp)) ++wcp; wp = NULL; wcp = find_executable_and_args(wcp, &wp); assert(wcp != NULL, "Expected to find executable in shebang line"); assert(wp != NULL, "Expected to find arguments (even if empty) in shebang line"); #if defined(SUPPORT_RELATIVE_PATH) /* If the executable starts with the relative prefix, resolve the following path relative to the launcher's directory. */ prefix_offset = RELATIVE_PREFIX_LENGTH; if (!_wcsnicmp(RELATIVE_PREFIX, wcp, prefix_offset)) { wcscpy_s(dbuffer, MAX_PATH, script_path); PathRemoveFileSpecW(dbuffer); if (wcp[prefix_offset] == L'\"') { prefix_offset++; qp = wcschr(&wcp[prefix_offset], L'\"'); assert(qp != NULL, "Expected terminating double-quote for executable in shebang line: %ls", wcp); *qp = L'\0'; } // The following call appears to canonicalize the path, so no need to // worry about doing that PathCombineW(pbuffer, dbuffer, &wcp[prefix_offset]); wcp = pbuffer; } #endif /* 3 spaces + 4 quotes + NUL */ len = wcslen(wcp) + wcslen(wp) + 8 + wcslen(psp) + wcslen(cmdline); cmdp = (wchar_t *) calloc(len, sizeof(wchar_t)); assert(cmdp != NULL, "Expected to be able to allocate command line memory"); _snwprintf_s(cmdp, len, len, L"\"%ls\" %ls \"%ls\" %ls", wcp, wp, psp, cmdline); run_child(cmdp); /* never actually returns */ free(cmdp); return 0; } #if defined(_CONSOLE) int main(int argc, char* argv[]) { return process(argc, argv); } #else int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { return process(__argc, __argv); } #endif ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/PC/launcher.ico0000644000175000017500000004651613453041443015062 0ustar00vinayvinay v(^ h. 00 %  >8 hH( @...GGGWWWiiiuuu ̀ ww ww||wwwwwwwwfwwwww|fwwwwwwffwwwwwwffwwwwwfflwwffffffwfffffflwffffffwffffff|ffffffffR\Ilffl)ffح2L18Ϥ"A< ΣTπU  ؀ ???????( 444MMMSSSzzzp p{p{ffkof_fffk_fff[f}UUUfL4UUU_kJqO_~'[U_Uwwwww{~( @%%%+++000===AAAIIIMMMQQQVVVYYY]]]~dFgggjjjmmmrrruuu}}}u?z=~;gEkCoAr@:IbpƏKУk(,1?5<\CKQsU[cbð/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq_______CCBABADL*(%$""&GABJIIDDJC??AD_-(''%$GG#_DIJIIJIIIDIJDD_+)(''%GG _JMCEJ?ABAECA?BA_,+)('%%"!_IM@AJACDCIDI____.,++>________JMJJJJJJJJJF_..,+)('%$"! #GIMCEJJJI?J__...,+(('%$"! &_JM?AJJJJCJ__...,+)(''%$" _IMJJJJJJJJ_K=...,++(''%$! _JMCDJJJJAJ_K_______L*%$"!_I_?AMNJJEJ_ L'%$"_I_MMMMMMJJ_ _('%%_I_CEMIJJMEM _(''*_I_?BMDJEMDMF _)(-LI_MMMCMMMMM_________< ____I_CEM1;MACDMEJM__I_?A_D1@IJJMJMM_FF_I__6_@/EMMMNMM_FF_I_C80EJDMJCJFF?:AA20?_M_MIEM______MIC4BC/ N______MMMMJIDAI_9/ HJJMMJMMMJ??BAI_A7503 IJMMCBCBA44CCI___BAO;6___C_MJIDIDMI_CI___O; @__C_C?J?AJI_?A_DJJI@ O_CM?;I?JI_________H9MAIJJIJI_CD_____JJI73C;IJIJI_?AMDJJDIMJIE5:JIJI__________MMIA:IJJJJJJJJJJJJJJJJMJ???( 444MMMRRRYYY~eGuuu}}}x>iErCE~7?HVbx/!P7pLcyϏ1Qq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq!%///* !*/"(%%'%/) /!#''////+/+"*(/ / !%(/ ( (*/ (///) */   )*  /"///// /+/// # + !%//  +"*+'#+//+'!%*++$++!+! !!!<(0`  !!$& &"   WܱNj+<<:5pi$!!!4888&SNJFB=950,(%%%5ǎVRNIEѹ|Jq?o@ZUQMID@<73/+&"-Ɍ444"ք̴wq?]YUPLHC?;72.*%!"""4888&dya\XTPKGC>:61-)% OÑ%%%5ʎA{;ys`\WSOJFB>951,($(9|:z;xq?o@mAkBiCgCdDuZ;73.*Ǒ%%%5̎È>89}:{;yq?o@mAjBhCfD~dEuZ?:625Оa789}:{;yr>p?n@lAjBhCfD}dEB>:5^ό555"܄ѳ„7789}:{;xr?p@n@lAjBgCeDFA=9"""4;;;&ŌC89~9|:z;xr?p@mAkAiBgCIELɑQQQJnnn~~~Įq?o@mAkBiCHHH"""vvv9~:{;yq?o@mAkB:::'''___sssttt8Ϧuǣvyp?n@lA888T---6fffnddd%%%"""OOOSSSċDҧuʤv{;yr?p?xM<<r?̷999VVV###)))444ѳ͝b@~9|:z;}Cg˶Ӎ󑑑ZZZlll;;;!!!""")))___&&&2:::ppp{{{___WWWWWW:::!!!"""***www"""4888&,̑999ԖJJJ|||lll:::"""BBBXXX|||$$$6,uuuKKKCCC@@@...www* Ҍ666"KKKaaaLLLWWW@$"""4;;;&nnnfff!!!$$$999444]ttt|ȇɑ&&&5ЎvvvmmmVVV{{{OOOڥaaaCCCpppό555"ބ|||MMMfff"""4;;;&\\\Ǒ%%%5Ύݧܦ͌555"ۄۥ"""4:::&ڤÑ%%%5ˎܤׇ ww?ww??ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwwwwwwwwwwwwwww?ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwww?wwwwwwww( @ !!!0\\\JeeeQ666D555A0$ vH<602\7qڴvvvj&&&D1"gLF@;5?кVQKE?9(aV[UPJD>82-uuun{{{j`ZTOVo@lAb_YSMGB<60+%<aVmt>q?n@bb^XRLFA;5/)$Ywwwn}}}j~Cv=s>p?bba]WQKE@:4.(+{;xbba[VPJD?93-'aV~:{;xr?o@lAiBfD}dE|cE|cEiB<61?9}:z;w=t>q?n@kAiCfD}cE|cE}dFGA;=aVңi89|:yq?n@kBhCeD|cE|cELF@mzzznjʓP8~9{;yp?mAjBgCeD|cEPJ\¯mAjBgC~dEaV}:z;xq?n@lAiCJJJőPyq?n@~YVVV???wwwiiiΡkA|;y>>3@@@y~~~obbb000kkk@@@---U<<<&&&[[[6uuusssWWWIII$$$&&&qqqlllg|||s4bbbhhhƑUUU000QQQ}}}---{bbb(iiiLLLBBBaVggg+++NNN|||nj]]]tttkkkrrrwwwӦaVӦzzznjӦˉ ?????(  C﫫oIਨS=?_QF4aZмoEcaXMB70~Iq?c`VK@58z;u=qv>3Eyn@iB~dE}dFGHYYYuuuġ=xmAhC}dE}eGS444|||gC}cEMMM~>q?kAiGRRR¢Gu=xKʺ~AAAAAAAAAAAAAAAA././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/PC/launcher.rc0000644000175000017500000000012413453041443014675 0ustar00vinayvinay#define IDI_ICON 101 LANGUAGE 0, 0 IDI_ICON ICON ".\\launcher.ico" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5235376 distlib-0.4.3/PKG-INFO0000644000175000017500000001230515212736416013353 0ustar00vinayvinayMetadata-Version: 2.4 Name: distlib Version: 0.4.3 Summary: Distribution utilities Home-page: https://github.com/pypa/distlib Author: Vinay Sajip Author-email: vinay_sajip@red-dove.com License: PSF-2.0 Project-URL: Documentation, https://distlib.readthedocs.io/ Project-URL: Source, https://github.com/pypa/distlib Project-URL: Tracker, https://github.com/pypa/distlib/issues Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development License-File: LICENSE.txt Dynamic: license-file |badge1| |badge2| .. |badge1| image:: https://img.shields.io/github/actions/workflow/status/pypa/distlib/package-tests.yml :alt: GitHub Workflow Status (with event) .. |badge2| image:: https://img.shields.io/codecov/c/github/pypa/distlib :target: https://app.codecov.io/gh/pypa/distlib :alt: GitHub coverage status What is it? ----------- Distlib is a library which implements low-level functions that relate to packaging and distribution of Python software. It is intended to be used as the basis for third-party packaging tools. The documentation is available at https://distlib.readthedocs.io/ Main features ------------- Distlib currently offers the following features: * The package ``distlib.database``, which implements a database of installed distributions, as defined by :pep:`376`, and distribution dependency graph logic. Support is also provided for non-installed distributions (i.e. distributions registered with metadata on an index like PyPI), including the ability to scan for dependencies and building dependency graphs. * The package ``distlib.index``, which implements an interface to perform operations on an index, such as registering a project, uploading a distribution or uploading documentation. Support is included for verifying SSL connections (with domain matching) and signing/verifying packages using GnuPG. * The package ``distlib.metadata``, which implements distribution metadata as defined by :pep:`643`, :pep:`566`, :pep:`345`, :pep:`314` and :pep:`241`. * The package ``distlib.markers``, which implements environment markers as defined by :pep:`508`. * The package ``distlib.manifest``, which implements lists of files used in packaging source distributions. * The package ``distlib.locators``, which allows finding distributions, whether on PyPI (XML-RPC or via the "simple" interface), local directories or some other source. * The package ``distlib.resources``, which allows access to data files stored in Python packages, both in the file system and in .zip files. * The package ``distlib.scripts``, which allows installing of scripts with adjustment of shebang lines and support for native Windows executable launchers. * The package ``distlib.version``, which implements version specifiers as defined by :pep:`440`, but also support for working with "legacy" versions and semantic versions. * The package ``distlib.wheel``, which provides support for building and installing from the Wheel format for binary distributions (see :pep:`427`). * The package ``distlib.util``, which contains miscellaneous functions and classes which are useful in packaging, but which do not fit neatly into one of the other packages in ``distlib``.* The package implements enhanced globbing functionality such as the ability to use ``**`` in patterns to specify recursing into subdirectories. Python version and platform compatibility ----------------------------------------- Distlib is intended to be used on and is tested on Python versions 2.7 and 3.6 or later, pypy-2.7 and pypy3 on Linux, Windows, and macOS. Project status -------------- The project has reached a mature status in its development: there is a comprehensive test suite and it has been exercised on Windows, Ubuntu and macOS. The project is used by well-known projects such as `pip `_ and `caniusepython3 `_. This project was migrated from Mercurial to Git and from BitBucket to GitHub, and although all information of importance has been retained across the migration, some commit references in issues and issue comments may have become invalid. Code of Conduct --------------- Everyone interacting in the distlib project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1689589485.0 distlib-0.4.3/README.rst0000664000175000017500000000754014455213355013754 0ustar00vinayvinay|badge1| |badge2| .. |badge1| image:: https://img.shields.io/github/actions/workflow/status/pypa/distlib/package-tests.yml :alt: GitHub Workflow Status (with event) .. |badge2| image:: https://img.shields.io/codecov/c/github/pypa/distlib :target: https://app.codecov.io/gh/pypa/distlib :alt: GitHub coverage status What is it? ----------- Distlib is a library which implements low-level functions that relate to packaging and distribution of Python software. It is intended to be used as the basis for third-party packaging tools. The documentation is available at https://distlib.readthedocs.io/ Main features ------------- Distlib currently offers the following features: * The package ``distlib.database``, which implements a database of installed distributions, as defined by :pep:`376`, and distribution dependency graph logic. Support is also provided for non-installed distributions (i.e. distributions registered with metadata on an index like PyPI), including the ability to scan for dependencies and building dependency graphs. * The package ``distlib.index``, which implements an interface to perform operations on an index, such as registering a project, uploading a distribution or uploading documentation. Support is included for verifying SSL connections (with domain matching) and signing/verifying packages using GnuPG. * The package ``distlib.metadata``, which implements distribution metadata as defined by :pep:`643`, :pep:`566`, :pep:`345`, :pep:`314` and :pep:`241`. * The package ``distlib.markers``, which implements environment markers as defined by :pep:`508`. * The package ``distlib.manifest``, which implements lists of files used in packaging source distributions. * The package ``distlib.locators``, which allows finding distributions, whether on PyPI (XML-RPC or via the "simple" interface), local directories or some other source. * The package ``distlib.resources``, which allows access to data files stored in Python packages, both in the file system and in .zip files. * The package ``distlib.scripts``, which allows installing of scripts with adjustment of shebang lines and support for native Windows executable launchers. * The package ``distlib.version``, which implements version specifiers as defined by :pep:`440`, but also support for working with "legacy" versions and semantic versions. * The package ``distlib.wheel``, which provides support for building and installing from the Wheel format for binary distributions (see :pep:`427`). * The package ``distlib.util``, which contains miscellaneous functions and classes which are useful in packaging, but which do not fit neatly into one of the other packages in ``distlib``.* The package implements enhanced globbing functionality such as the ability to use ``**`` in patterns to specify recursing into subdirectories. Python version and platform compatibility ----------------------------------------- Distlib is intended to be used on and is tested on Python versions 2.7 and 3.6 or later, pypy-2.7 and pypy3 on Linux, Windows, and macOS. Project status -------------- The project has reached a mature status in its development: there is a comprehensive test suite and it has been exercised on Windows, Ubuntu and macOS. The project is used by well-known projects such as `pip `_ and `caniusepython3 `_. This project was migrated from Mercurial to Git and from BitBucket to GitHub, and although all information of importance has been retained across the migration, some commit references in issues and issue comments may have become invalid. Code of Conduct --------------- Everyone interacting in the distlib project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5035336 distlib-0.4.3/distlib/0000775000175000017500000000000015212736416013711 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781251035.0 distlib-0.4.3/distlib/__init__.py0000644000175000017500000000116115212735733016020 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2024 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # import logging __version__ = '0.4.3' class DistlibException(Exception): pass try: from logging import NullHandler except ImportError: # pragma: no cover class NullHandler(logging.Handler): def handle(self, record): pass def emit(self, record): pass def createLock(self): self.lock = None logger = logging.getLogger(__name__) logger.addHandler(NullHandler()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780933749.0 distlib-0.4.3/distlib/compat.py0000644000175000017500000011723515211562165015552 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2013-2026 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # from __future__ import absolute_import import os import re import shutil import sys try: import ssl except ImportError: # pragma: no cover ssl = None if sys.version_info[0] < 3: # pragma: no cover from StringIO import StringIO string_types = basestring, text_type = unicode from types import FileType as file_type import __builtin__ as builtins import ConfigParser as configparser from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit from urllib import (urlretrieve, quote as _quote, unquote, url2pathname, pathname2url, ContentTooShortError, splittype) def quote(s): if isinstance(s, unicode): s = s.encode('utf-8') return _quote(s) import urllib2 from urllib2 import (Request, urlopen, URLError, HTTPError, HTTPBasicAuthHandler, HTTPPasswordMgr, HTTPHandler, HTTPRedirectHandler, build_opener) if ssl: from urllib2 import HTTPSHandler import httplib import xmlrpclib import Queue as queue from HTMLParser import HTMLParser import htmlentitydefs raw_input = raw_input from itertools import ifilter as filter from itertools import ifilterfalse as filterfalse # Leaving this around for now, in case it needs resurrecting in some way # _userprog = None # def splituser(host): # """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" # global _userprog # if _userprog is None: # import re # _userprog = re.compile('^(.*)@(.*)$') # match = _userprog.match(host) # if match: return match.group(1, 2) # return None, host else: # pragma: no cover from io import StringIO string_types = str, text_type = str from io import TextIOWrapper as file_type import builtins import configparser from urllib.parse import (urlparse, urlunparse, urljoin, quote, unquote, urlsplit, urlunsplit, splittype) from urllib.request import (urlopen, urlretrieve, Request, url2pathname, pathname2url, HTTPBasicAuthHandler, HTTPPasswordMgr, HTTPHandler, HTTPRedirectHandler, build_opener) if ssl: from urllib.request import HTTPSHandler from urllib.error import HTTPError, URLError, ContentTooShortError import http.client as httplib import urllib.request as urllib2 import xmlrpc.client as xmlrpclib import queue from html.parser import HTMLParser import html.entities as htmlentitydefs raw_input = input from itertools import filterfalse filter = filter try: from ssl import match_hostname, CertificateError except ImportError: # pragma: no cover class CertificateError(ValueError): pass def _dnsname_match(dn, hostname, max_wildcards=1): """Matching according to RFC 6125, section 6.4.3 http://tools.ietf.org/html/rfc6125#section-6.4.3 """ pats = [] if not dn: return False parts = dn.split('.') leftmost, remainder = parts[0], parts[1:] wildcards = leftmost.count('*') if wildcards > max_wildcards: # Issue #17980: avoid denials of service by refusing more # than one wildcard per fragment. A survey of established # policy among SSL implementations showed it to be a # reasonable choice. raise CertificateError('Too many wildcards in certificate DNS name: %r' % dn) # speed up common case w/o wildcards if not wildcards: return dn.lower() == hostname.lower() # RFC 6125, section 6.4.3, subitem 1. # The client SHOULD NOT attempt to match a presented identifier in which # the wildcard character comprises a label other than the left-most label. if leftmost == '*': # When '*' is a fragment by itself, it matches a non-empty dotless # fragment. pats.append('[^.]+') elif leftmost.startswith('xn--') or hostname.startswith('xn--'): # RFC 6125, section 6.4.3, subitem 3. # The client SHOULD NOT attempt to match a presented identifier # where the wildcard character is embedded within an A-label or # U-label of an internationalized domain name. pats.append(re.escape(leftmost)) else: # Otherwise, '*' matches any dotless string, e.g. www* pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) # add the remaining fragments, ignore any wildcards for frag in remainder: pats.append(re.escape(frag)) pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) return pat.match(hostname) def match_hostname(cert, hostname): """Verify that *cert* (in decoded format as returned by SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 rules are followed, but IP addresses are not accepted for *hostname*. CertificateError is raised on failure. On success, the function returns nothing. """ if not cert: raise ValueError("empty or no certificate, match_hostname needs a " "SSL socket or SSL context with either " "CERT_OPTIONAL or CERT_REQUIRED") dnsnames = [] san = cert.get('subjectAltName', ()) for key, value in san: if key == 'DNS': if _dnsname_match(value, hostname): return dnsnames.append(value) if not dnsnames: # The subject is only checked when there is no dNSName entry # in subjectAltName for sub in cert.get('subject', ()): for key, value in sub: # XXX according to RFC 2818, the most specific Common Name # must be used. if key == 'commonName': if _dnsname_match(value, hostname): return dnsnames.append(value) if len(dnsnames) > 1: raise CertificateError("hostname %r " "doesn't match either of %s" % (hostname, ', '.join(map(repr, dnsnames)))) elif len(dnsnames) == 1: raise CertificateError("hostname %r " "doesn't match %r" % (hostname, dnsnames[0])) else: raise CertificateError("no appropriate commonName or " "subjectAltName fields were found") try: from types import SimpleNamespace as Container except ImportError: # pragma: no cover class Container(object): """ A generic container for when multiple values need to be returned """ def __init__(self, **kwargs): self.__dict__.update(kwargs) try: from shutil import which except ImportError: # pragma: no cover # Implementation from Python 3.3 def which(cmd, mode=os.F_OK | os.X_OK, path=None): """Given a command, mode, and a PATH string, return the path which conforms to the given mode on the PATH, or None if there is no such file. `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result of os.environ.get("PATH"), or can be overridden with a custom search path. """ # Check that a given file can be accessed with the correct mode. # Additionally check that `file` is not a directory, as on Windows # directories pass the os.access check. def _access_check(fn, mode): return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn)) # If we're given a path with a directory part, look it up directly rather # than referring to PATH directories. This includes checking relative to the # current directory, e.g. ./script if os.path.dirname(cmd): if _access_check(cmd, mode): return cmd return None if path is None: path = os.environ.get("PATH", os.defpath) if not path: return None path = path.split(os.pathsep) if sys.platform == "win32": # The current directory takes precedence on Windows. if os.curdir not in path: path.insert(0, os.curdir) # PATHEXT is necessary to check on Windows. pathext = os.environ.get("PATHEXT", "").split(os.pathsep) # See if the given file matches any of the expected path extensions. # This will allow us to short circuit when given "python.exe". # If it does match, only test that one, otherwise we have to try # others. if any(cmd.lower().endswith(ext.lower()) for ext in pathext): files = [cmd] else: files = [cmd + ext for ext in pathext] else: # On other platforms you don't have things like PATHEXT to tell you # what file suffixes are executable, so just pass on cmd as-is. files = [cmd] seen = set() for dir in path: normdir = os.path.normcase(dir) if normdir not in seen: seen.add(normdir) for thefile in files: name = os.path.join(dir, thefile) if _access_check(name, mode): return name return None # ZipFile is a context manager in 2.7, but not in 2.6 from zipfile import ZipFile as BaseZipFile if hasattr(BaseZipFile, '__enter__'): # pragma: no cover ZipFile = BaseZipFile else: # pragma: no cover from zipfile import ZipExtFile as BaseZipExtFile class ZipExtFile(BaseZipExtFile): def __init__(self, base): self.__dict__.update(base.__dict__) def __enter__(self): return self def __exit__(self, *exc_info): self.close() # return None, so if an exception occurred, it will propagate class ZipFile(BaseZipFile): def __enter__(self): return self def __exit__(self, *exc_info): self.close() # return None, so if an exception occurred, it will propagate def open(self, *args, **kwargs): base = BaseZipFile.open(self, *args, **kwargs) return ZipExtFile(base) try: from platform import python_implementation except ImportError: # pragma: no cover def python_implementation(): """Return a string identifying the Python implementation.""" if 'PyPy' in sys.version: return 'PyPy' if os.name == 'java': return 'Jython' if sys.version.startswith('IronPython'): return 'IronPython' return 'CPython' import sysconfig try: callable = callable except NameError: # pragma: no cover from collections.abc import Callable def callable(obj): return isinstance(obj, Callable) try: fsencode = os.fsencode fsdecode = os.fsdecode except AttributeError: # pragma: no cover # Issue #99: on some systems (e.g. containerised), # sys.getfilesystemencoding() returns None, and we need a real value, # so fall back to utf-8. From the CPython 2.7 docs relating to Unix and # sys.getfilesystemencoding(): the return value is "the user’s preference # according to the result of nl_langinfo(CODESET), or None if the # nl_langinfo(CODESET) failed." _fsencoding = sys.getfilesystemencoding() or 'utf-8' if _fsencoding == 'mbcs': _fserrors = 'strict' else: _fserrors = 'surrogateescape' def fsencode(filename): if isinstance(filename, bytes): return filename elif isinstance(filename, text_type): return filename.encode(_fsencoding, _fserrors) else: raise TypeError("expect bytes or str, not %s" % type(filename).__name__) def fsdecode(filename): if isinstance(filename, text_type): return filename elif isinstance(filename, bytes): return filename.decode(_fsencoding, _fserrors) else: raise TypeError("expect bytes or str, not %s" % type(filename).__name__) try: from tokenize import detect_encoding except ImportError: # pragma: no cover from codecs import BOM_UTF8, lookup cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)") def _get_normal_name(orig_enc): """Imitates get_normal_name in tokenizer.c.""" # Only care about the first 12 characters. enc = orig_enc[:12].lower().replace("_", "-") if enc == "utf-8" or enc.startswith("utf-8-"): return "utf-8" if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \ enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")): return "iso-8859-1" return orig_enc def detect_encoding(readline): """ The detect_encoding() function is used to detect the encoding that should be used to decode a Python source file. It requires one argument, readline, in the same way as the tokenize() generator. It will call readline a maximum of twice, and return the encoding used (as a string) and a list of any lines (left as bytes) it has read in. It detects the encoding from the presence of a utf-8 bom or an encoding cookie as specified in pep-0263. If both a bom and a cookie are present, but disagree, a SyntaxError will be raised. If the encoding cookie is an invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found, 'utf-8-sig' is returned. If no encoding is specified, then the default of 'utf-8' will be returned. """ try: filename = readline.__self__.name except AttributeError: filename = None bom_found = False encoding = None default = 'utf-8' def read_or_stop(): try: return readline() except StopIteration: return b'' def find_cookie(line): try: # Decode as UTF-8. Either the line is an encoding declaration, # in which case it should be pure ASCII, or it must be UTF-8 # per default encoding. line_string = line.decode('utf-8') except UnicodeDecodeError: msg = "invalid or missing encoding declaration" if filename is not None: msg = '{} for {!r}'.format(msg, filename) raise SyntaxError(msg) matches = cookie_re.findall(line_string) if not matches: return None encoding = _get_normal_name(matches[0]) try: codec = lookup(encoding) except LookupError: # This behaviour mimics the Python interpreter if filename is None: msg = "unknown encoding: " + encoding else: msg = "unknown encoding for {!r}: {}".format(filename, encoding) raise SyntaxError(msg) if bom_found: if codec.name != 'utf-8': # This behaviour mimics the Python interpreter if filename is None: msg = 'Encoding problem: utf-8' else: msg = 'Encoding problem for %r: utf-8' % filename raise SyntaxError(msg) encoding += '-sig' return encoding first = read_or_stop() if first.startswith(BOM_UTF8): bom_found = True first = first[3:] default = 'utf-8-sig' if not first: return default, [] encoding = find_cookie(first) if encoding: return encoding, [first] second = read_or_stop() if not second: return default, [first] encoding = find_cookie(second) if encoding: return encoding, [first, second] return default, [first, second] # For converting & <-> & etc. try: from html import escape except ImportError: from cgi import escape if sys.version_info[:2] < (3, 4): unescape = HTMLParser().unescape else: from html import unescape try: from collections import ChainMap except ImportError: # pragma: no cover from collections import MutableMapping try: from reprlib import recursive_repr as _recursive_repr except ImportError: def _recursive_repr(fillvalue='...'): ''' Decorator to make a repr function return fillvalue for a recursive call ''' def decorating_function(user_function): repr_running = set() def wrapper(self): key = id(self), get_ident() if key in repr_running: return fillvalue repr_running.add(key) try: result = user_function(self) finally: repr_running.discard(key) return result # Can't use functools.wraps() here because of bootstrap issues wrapper.__module__ = getattr(user_function, '__module__') wrapper.__doc__ = getattr(user_function, '__doc__') wrapper.__name__ = getattr(user_function, '__name__') wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) return wrapper return decorating_function class ChainMap(MutableMapping): ''' A ChainMap groups multiple dicts (or other mappings) together to create a single, updateable view. The underlying mappings are stored in a list. That list is public and can accessed or updated using the *maps* attribute. There is no other state. Lookups search the underlying mappings successively until a key is found. In contrast, writes, updates, and deletions only operate on the first mapping. ''' def __init__(self, *maps): '''Initialize a ChainMap by setting *maps* to the given mappings. If no mappings are provided, a single empty dictionary is used. ''' self.maps = list(maps) or [{}] # always at least one map def __missing__(self, key): raise KeyError(key) def __getitem__(self, key): for mapping in self.maps: try: return mapping[key] # can't use 'key in mapping' with defaultdict except KeyError: pass return self.__missing__(key) # support subclasses that define __missing__ def get(self, key, default=None): return self[key] if key in self else default def __len__(self): return len(set().union(*self.maps)) # reuses stored hash values if possible def __iter__(self): return iter(set().union(*self.maps)) def __contains__(self, key): return any(key in m for m in self.maps) def __bool__(self): return any(self.maps) @_recursive_repr() def __repr__(self): return '{0.__class__.__name__}({1})'.format(self, ', '.join(map(repr, self.maps))) @classmethod def fromkeys(cls, iterable, *args): 'Create a ChainMap with a single dict created from the iterable.' return cls(dict.fromkeys(iterable, *args)) def copy(self): 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' return self.__class__(self.maps[0].copy(), *self.maps[1:]) __copy__ = copy def new_child(self): # like Django's Context.push() 'New ChainMap with a new dict followed by all previous maps.' return self.__class__({}, *self.maps) @property def parents(self): # like Django's Context.pop() 'New ChainMap from maps[1:].' return self.__class__(*self.maps[1:]) def __setitem__(self, key, value): self.maps[0][key] = value def __delitem__(self, key): try: del self.maps[0][key] except KeyError: raise KeyError('Key not found in the first mapping: {!r}'.format(key)) def popitem(self): 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' try: return self.maps[0].popitem() except KeyError: raise KeyError('No keys found in the first mapping.') def pop(self, key, *args): 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' try: return self.maps[0].pop(key, *args) except KeyError: raise KeyError('Key not found in the first mapping: {!r}'.format(key)) def clear(self): 'Clear maps[0], leaving maps[1:] intact.' self.maps[0].clear() try: from importlib.util import cache_from_source # Python >= 3.4 except ImportError: # pragma: no cover def cache_from_source(path, debug_override=None, optimization=None): assert path.endswith('.py') if debug_override is None: debug_override = __debug__ if debug_override: suffix = 'c' else: suffix = 'o' return path + suffix try: from collections import OrderedDict except ImportError: # pragma: no cover # {{{ http://code.activestate.com/recipes/576693/ (r9) # Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. # Passes Python2.7's test suite and incorporates all the latest updates. try: from thread import get_ident as _get_ident except ImportError: from dummy_thread import get_ident as _get_ident try: from _abcoll import KeysView, ValuesView, ItemsView except ImportError: pass class OrderedDict(dict): 'Dictionary that remembers insertion order' # An inherited dict maps keys to values. # The inherited dict provides __getitem__, __len__, __contains__, and get. # The remaining methods are order-aware. # Big-O running times for all methods are the same as for regular dictionaries. # The internal self.__map dictionary maps keys to links in a doubly linked list. # The circular doubly linked list starts and ends with a sentinel element. # The sentinel element never gets deleted (this simplifies the algorithm). # Each link is stored as a list of length three: [PREV, NEXT, KEY]. def __init__(self, *args, **kwds): '''Initialize an ordered dictionary. Signature is the same as for regular dictionaries, but keyword arguments are not recommended because their insertion order is arbitrary. ''' if len(args) > 1: raise TypeError('expected at most 1 arguments, got %d' % len(args)) try: self.__root except AttributeError: self.__root = root = [] # sentinel node root[:] = [root, root, None] self.__map = {} self.__update(*args, **kwds) def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 'od.__setitem__(i, y) <==> od[i]=y' # Setting a new item creates a new link which goes at the end of the linked # list, and the inherited dictionary is updated with the new key/value pair. if key not in self: root = self.__root last = root[0] last[1] = root[0] = self.__map[key] = [last, root, key] dict_setitem(self, key, value) def __delitem__(self, key, dict_delitem=dict.__delitem__): 'od.__delitem__(y) <==> del od[y]' # Deleting an existing item uses self.__map to find the link which is # then removed by updating the links in the predecessor and successor nodes. dict_delitem(self, key) link_prev, link_next, key = self.__map.pop(key) link_prev[1] = link_next link_next[0] = link_prev def __iter__(self): 'od.__iter__() <==> iter(od)' root = self.__root curr = root[1] while curr is not root: yield curr[2] curr = curr[1] def __reversed__(self): 'od.__reversed__() <==> reversed(od)' root = self.__root curr = root[0] while curr is not root: yield curr[2] curr = curr[0] def clear(self): 'od.clear() -> None. Remove all items from od.' try: for node in self.__map.itervalues(): del node[:] root = self.__root root[:] = [root, root, None] self.__map.clear() except AttributeError: pass dict.clear(self) def popitem(self, last=True): '''od.popitem() -> (k, v), return and remove a (key, value) pair. Pairs are returned in LIFO order if last is true or FIFO order if false. ''' if not self: raise KeyError('dictionary is empty') root = self.__root if last: link = root[0] link_prev = link[0] link_prev[1] = root root[0] = link_prev else: link = root[1] link_next = link[1] root[1] = link_next link_next[0] = root key = link[2] del self.__map[key] value = dict.pop(self, key) return key, value # -- the following methods do not depend on the internal structure -- def keys(self): 'od.keys() -> list of keys in od' return list(self) def values(self): 'od.values() -> list of values in od' return [self[key] for key in self] def items(self): 'od.items() -> list of (key, value) pairs in od' return [(key, self[key]) for key in self] def iterkeys(self): 'od.iterkeys() -> an iterator over the keys in od' return iter(self) def itervalues(self): 'od.itervalues -> an iterator over the values in od' for k in self: yield self[k] def iteritems(self): 'od.iteritems -> an iterator over the (key, value) items in od' for k in self: yield (k, self[k]) def update(*args, **kwds): '''od.update(E, **F) -> None. Update od from dict/iterable E and F. If E is a dict instance, does: for k in E: od[k] = E[k] If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] Or if E is an iterable of items, does: for k, v in E: od[k] = v In either case, this is followed by: for k, v in F.items(): od[k] = v ''' if len(args) > 2: raise TypeError('update() takes at most 2 positional ' 'arguments (%d given)' % (len(args), )) elif not args: raise TypeError('update() takes at least 1 argument (0 given)') self = args[0] # Make progressively weaker assumptions about "other" other = () if len(args) == 2: other = args[1] if isinstance(other, dict): for key in other: self[key] = other[key] elif hasattr(other, 'keys'): for key in other.keys(): self[key] = other[key] else: for key, value in other: self[key] = value for key, value in kwds.items(): self[key] = value __update = update # let subclasses override update without breaking __init__ __marker = object() def pop(self, key, default=__marker): '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised. ''' if key in self: result = self[key] del self[key] return result if default is self.__marker: raise KeyError(key) return default def setdefault(self, key, default=None): 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' if key in self: return self[key] self[key] = default return default def __repr__(self, _repr_running=None): 'od.__repr__() <==> repr(od)' if not _repr_running: _repr_running = {} call_key = id(self), _get_ident() if call_key in _repr_running: return '...' _repr_running[call_key] = 1 try: if not self: return '%s()' % (self.__class__.__name__, ) return '%s(%r)' % (self.__class__.__name__, self.items()) finally: del _repr_running[call_key] def __reduce__(self): 'Return state information for pickling' items = [[k, self[k]] for k in self] inst_dict = vars(self).copy() for k in vars(OrderedDict()): inst_dict.pop(k, None) if inst_dict: return (self.__class__, (items, ), inst_dict) return self.__class__, (items, ) def copy(self): 'od.copy() -> a shallow copy of od' return self.__class__(self) @classmethod def fromkeys(cls, iterable, value=None): '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S and values equal to v (which defaults to None). ''' d = cls() for key in iterable: d[key] = value return d def __eq__(self, other): '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive while comparison to a regular mapping is order-insensitive. ''' if isinstance(other, OrderedDict): return len(self) == len(other) and self.items() == other.items() return dict.__eq__(self, other) def __ne__(self, other): return not self == other # -- the following methods are only used in Python 2.7 -- def viewkeys(self): "od.viewkeys() -> a set-like object providing a view on od's keys" return KeysView(self) def viewvalues(self): "od.viewvalues() -> an object providing a view on od's values" return ValuesView(self) def viewitems(self): "od.viewitems() -> a set-like object providing a view on od's items" return ItemsView(self) try: from logging.config import BaseConfigurator, valid_ident except ImportError: # pragma: no cover IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) def valid_ident(s): m = IDENTIFIER.match(s) if not m: raise ValueError('Not a valid Python identifier: %r' % s) return True # The ConvertingXXX classes are wrappers around standard Python containers, # and they serve to convert any suitable values in the container. The # conversion converts base dicts, lists and tuples to their wrapped # equivalents, whereas strings which match a conversion format are converted # appropriately. # # Each wrapper should have a configurator attribute holding the actual # configurator to use for conversion. class ConvertingDict(dict): """A converting dictionary wrapper.""" def __getitem__(self, key): value = dict.__getitem__(self, key) result = self.configurator.convert(value) # If the converted value is different, save for next time if value is not result: self[key] = result if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result def get(self, key, default=None): value = dict.get(self, key, default) result = self.configurator.convert(value) # If the converted value is different, save for next time if value is not result: self[key] = result if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result def pop(self, key, default=None): value = dict.pop(self, key, default) result = self.configurator.convert(value) if value is not result: if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result class ConvertingList(list): """A converting list wrapper.""" def __getitem__(self, key): value = list.__getitem__(self, key) result = self.configurator.convert(value) # If the converted value is different, save for next time if value is not result: self[key] = result if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result def pop(self, idx=-1): value = list.pop(self, idx) result = self.configurator.convert(value) if value is not result: if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self return result class ConvertingTuple(tuple): """A converting tuple wrapper.""" def __getitem__(self, key): value = tuple.__getitem__(self, key) result = self.configurator.convert(value) if value is not result: if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result class BaseConfigurator(object): """ The configurator base class which defines some useful defaults. """ CONVERT_PATTERN = re.compile(r'^(?P[a-z]+)://(?P.*)$') WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') DIGIT_PATTERN = re.compile(r'^\d+$') value_converters = { 'ext': 'ext_convert', 'cfg': 'cfg_convert', } # We might want to use a different one, e.g. importlib importer = staticmethod(__import__) def __init__(self, config): self.config = ConvertingDict(config) self.config.configurator = self def resolve(self, s): """ Resolve strings to objects using standard import and attribute syntax. """ name = s.split('.') used = name.pop(0) try: found = self.importer(used) for frag in name: used += '.' + frag try: found = getattr(found, frag) except AttributeError: self.importer(used) found = getattr(found, frag) return found except ImportError: e, tb = sys.exc_info()[1:] v = ValueError('Cannot resolve %r: %s' % (s, e)) v.__cause__, v.__traceback__ = e, tb raise v def ext_convert(self, value): """Default converter for the ext:// protocol.""" return self.resolve(value) def cfg_convert(self, value): """Default converter for the cfg:// protocol.""" rest = value m = self.WORD_PATTERN.match(rest) if m is None: raise ValueError("Unable to convert %r" % value) else: rest = rest[m.end():] d = self.config[m.groups()[0]] while rest: m = self.DOT_PATTERN.match(rest) if m: d = d[m.groups()[0]] else: m = self.INDEX_PATTERN.match(rest) if m: idx = m.groups()[0] if not self.DIGIT_PATTERN.match(idx): d = d[idx] else: try: n = int(idx) # try as number first (most likely) d = d[n] except TypeError: d = d[idx] if m: rest = rest[m.end():] else: raise ValueError('Unable to convert ' '%r at %r' % (value, rest)) # rest should be empty return d def convert(self, value): """ Convert values to an appropriate type. dicts, lists and tuples are replaced by their converting alternatives. Strings are checked to see if they have a conversion format and are converted if they do. """ if not isinstance(value, ConvertingDict) and isinstance(value, dict): value = ConvertingDict(value) value.configurator = self elif not isinstance(value, ConvertingList) and isinstance(value, list): value = ConvertingList(value) value.configurator = self elif not isinstance(value, ConvertingTuple) and isinstance(value, tuple): value = ConvertingTuple(value) value.configurator = self elif isinstance(value, string_types): m = self.CONVERT_PATTERN.match(value) if m: d = m.groupdict() prefix = d['prefix'] converter = self.value_converters.get(prefix, None) if converter: suffix = d['suffix'] converter = getattr(self, converter) value = converter(suffix) return value def configure_custom(self, config): """Configure an object with a user-supplied factory.""" c = config.pop('()') if not callable(c): c = self.resolve(c) props = config.pop('.', None) # Check for valid identifiers kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) result = c(**kwargs) if props: for name, value in props.items(): setattr(result, name, value) return result def as_tuple(self, value): """Utility function which converts lists to tuples.""" if isinstance(value, list): value = tuple(value) return value ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710843576.0 distlib-0.4.3/distlib/database.py0000664000175000017500000014373014576263270016044 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2023 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # """PEP 376 implementation.""" from __future__ import unicode_literals import base64 import codecs import contextlib import hashlib import logging import os import posixpath import sys import zipimport from . import DistlibException, resources from .compat import StringIO from .version import get_scheme, UnsupportedVersionError from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME) from .util import (parse_requirement, cached_property, parse_name_and_version, read_exports, write_exports, CSVReader, CSVWriter) __all__ = [ 'Distribution', 'BaseInstalledDistribution', 'InstalledDistribution', 'EggInfoDistribution', 'DistributionPath' ] logger = logging.getLogger(__name__) EXPORTS_FILENAME = 'pydist-exports.json' COMMANDS_FILENAME = 'pydist-commands.json' DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', 'RESOURCES', EXPORTS_FILENAME, 'SHARED') DISTINFO_EXT = '.dist-info' class _Cache(object): """ A simple cache mapping names and .dist-info paths to distributions """ def __init__(self): """ Initialise an instance. There is normally one for each DistributionPath. """ self.name = {} self.path = {} self.generated = False def clear(self): """ Clear the cache, setting it to its initial state. """ self.name.clear() self.path.clear() self.generated = False def add(self, dist): """ Add a distribution to the cache. :param dist: The distribution to add. """ if dist.path not in self.path: self.path[dist.path] = dist self.name.setdefault(dist.key, []).append(dist) class DistributionPath(object): """ Represents a set of distributions installed on a path (typically sys.path). """ def __init__(self, path=None, include_egg=False): """ Create an instance from a path, optionally including legacy (distutils/ setuptools/distribute) distributions. :param path: The path to use, as a list of directories. If not specified, sys.path is used. :param include_egg: If True, this instance will look for and return legacy distributions as well as those based on PEP 376. """ if path is None: path = sys.path self.path = path self._include_dist = True self._include_egg = include_egg self._cache = _Cache() self._cache_egg = _Cache() self._cache_enabled = True self._scheme = get_scheme('default') def _get_cache_enabled(self): return self._cache_enabled def _set_cache_enabled(self, value): self._cache_enabled = value cache_enabled = property(_get_cache_enabled, _set_cache_enabled) def clear_cache(self): """ Clears the internal cache. """ self._cache.clear() self._cache_egg.clear() def _yield_distributions(self): """ Yield .dist-info and/or .egg(-info) distributions. """ # We need to check if we've seen some resources already, because on # some Linux systems (e.g. some Debian/Ubuntu variants) there are # symlinks which alias other files in the environment. seen = set() for path in self.path: finder = resources.finder_for_path(path) if finder is None: continue r = finder.find('') if not r or not r.is_container: continue rset = sorted(r.resources) for entry in rset: r = finder.find(entry) if not r or r.path in seen: continue try: if self._include_dist and entry.endswith(DISTINFO_EXT): possible_filenames = [METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME] for metadata_filename in possible_filenames: metadata_path = posixpath.join(entry, metadata_filename) pydist = finder.find(metadata_path) if pydist: break else: continue with contextlib.closing(pydist.as_stream()) as stream: metadata = Metadata(fileobj=stream, scheme='legacy') logger.debug('Found %s', r.path) seen.add(r.path) yield new_dist_class(r.path, metadata=metadata, env=self) elif self._include_egg and entry.endswith(('.egg-info', '.egg')): logger.debug('Found %s', r.path) seen.add(r.path) yield old_dist_class(r.path, self) except Exception as e: msg = 'Unable to read distribution at %s, perhaps due to bad metadata: %s' logger.warning(msg, r.path, e) import warnings warnings.warn(msg % (r.path, e), stacklevel=2) def _generate_cache(self): """ Scan the path for distributions and populate the cache with those that are found. """ gen_dist = not self._cache.generated gen_egg = self._include_egg and not self._cache_egg.generated if gen_dist or gen_egg: for dist in self._yield_distributions(): if isinstance(dist, InstalledDistribution): self._cache.add(dist) else: self._cache_egg.add(dist) if gen_dist: self._cache.generated = True if gen_egg: self._cache_egg.generated = True @classmethod def distinfo_dirname(cls, name, version): """ The *name* and *version* parameters are converted into their filename-escaped form, i.e. any ``'-'`` characters are replaced with ``'_'`` other than the one in ``'dist-info'`` and the one separating the name from the version number. :parameter name: is converted to a standard distribution name by replacing any runs of non- alphanumeric characters with a single ``'-'``. :type name: string :parameter version: is converted to a standard version string. Spaces become dots, and all other non-alphanumeric characters (except dots) become dashes, with runs of multiple dashes condensed to a single dash. :type version: string :returns: directory name :rtype: string""" name = name.replace('-', '_') return '-'.join([name, version]) + DISTINFO_EXT def get_distributions(self): """ Provides an iterator that looks for distributions and returns :class:`InstalledDistribution` or :class:`EggInfoDistribution` instances for each one of them. :rtype: iterator of :class:`InstalledDistribution` and :class:`EggInfoDistribution` instances """ if not self._cache_enabled: for dist in self._yield_distributions(): yield dist else: self._generate_cache() for dist in self._cache.path.values(): yield dist if self._include_egg: for dist in self._cache_egg.path.values(): yield dist def get_distribution(self, name): """ Looks for a named distribution on the path. This function only returns the first result found, as no more than one value is expected. If nothing is found, ``None`` is returned. :rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution` or ``None`` """ result = None name = name.lower() if not self._cache_enabled: for dist in self._yield_distributions(): if dist.key == name: result = dist break else: self._generate_cache() if name in self._cache.name: result = self._cache.name[name][0] elif self._include_egg and name in self._cache_egg.name: result = self._cache_egg.name[name][0] return result def provides_distribution(self, name, version=None): """ Iterates over all distributions to find which distributions provide *name*. If a *version* is provided, it will be used to filter the results. This function only returns the first result found, since no more than one values are expected. If the directory is not found, returns ``None``. :parameter version: a version specifier that indicates the version required, conforming to the format in ``PEP-345`` :type name: string :type version: string """ matcher = None if version is not None: try: matcher = self._scheme.matcher('%s (%s)' % (name, version)) except ValueError: raise DistlibException('invalid name or version: %r, %r' % (name, version)) for dist in self.get_distributions(): # We hit a problem on Travis where enum34 was installed and doesn't # have a provides attribute ... if not hasattr(dist, 'provides'): logger.debug('No "provides": %s', dist) else: provided = dist.provides for p in provided: p_name, p_ver = parse_name_and_version(p) if matcher is None: if p_name == name: yield dist break else: if p_name == name and matcher.match(p_ver): yield dist break def get_file_path(self, name, relative_path): """ Return the path to a resource file. """ dist = self.get_distribution(name) if dist is None: raise LookupError('no distribution named %r found' % name) return dist.get_resource_path(relative_path) def get_exported_entries(self, category, name=None): """ Return all of the exported entries in a particular category. :param category: The category to search for entries. :param name: If specified, only entries with that name are returned. """ for dist in self.get_distributions(): r = dist.exports if category in r: d = r[category] if name is not None: if name in d: yield d[name] else: for v in d.values(): yield v class Distribution(object): """ A base class for distributions, whether installed or from indexes. Either way, it must have some metadata, so that's all that's needed for construction. """ build_time_dependency = False """ Set to True if it's known to be only a build-time dependency (i.e. not needed after installation). """ requested = False """A boolean that indicates whether the ``REQUESTED`` metadata file is present (in other words, whether the package was installed by user request or it was installed as a dependency).""" def __init__(self, metadata): """ Initialise an instance. :param metadata: The instance of :class:`Metadata` describing this distribution. """ self.metadata = metadata self.name = metadata.name self.key = self.name.lower() # for case-insensitive comparisons self.version = metadata.version self.locator = None self.digest = None self.extras = None # additional features requested self.context = None # environment marker overrides self.download_urls = set() self.digests = {} @property def source_url(self): """ The source archive download URL for this distribution. """ return self.metadata.source_url download_url = source_url # Backward compatibility @property def name_and_version(self): """ A utility property which displays the name and version in parentheses. """ return '%s (%s)' % (self.name, self.version) @property def provides(self): """ A set of distribution names and versions provided by this distribution. :return: A set of "name (version)" strings. """ plist = self.metadata.provides s = '%s (%s)' % (self.name, self.version) if s not in plist: plist.append(s) return plist def _get_requirements(self, req_attr): md = self.metadata reqts = getattr(md, req_attr) logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr, reqts) return set(md.get_requirements(reqts, extras=self.extras, env=self.context)) @property def run_requires(self): return self._get_requirements('run_requires') @property def meta_requires(self): return self._get_requirements('meta_requires') @property def build_requires(self): return self._get_requirements('build_requires') @property def test_requires(self): return self._get_requirements('test_requires') @property def dev_requires(self): return self._get_requirements('dev_requires') def matches_requirement(self, req): """ Say if this instance matches (fulfills) a requirement. :param req: The requirement to match. :rtype req: str :return: True if it matches, else False. """ # Requirement may contain extras - parse to lose those # from what's passed to the matcher r = parse_requirement(req) scheme = get_scheme(self.metadata.scheme) try: matcher = scheme.matcher(r.requirement) except UnsupportedVersionError: # XXX compat-mode if cannot read the version logger.warning('could not read version %r - using name only', req) name = req.split()[0] matcher = scheme.matcher(name) name = matcher.key # case-insensitive result = False for p in self.provides: p_name, p_ver = parse_name_and_version(p) if p_name != name: continue try: result = matcher.match(p_ver) break except UnsupportedVersionError: pass return result def __repr__(self): """ Return a textual representation of this instance, """ if self.source_url: suffix = ' [%s]' % self.source_url else: suffix = '' return '' % (self.name, self.version, suffix) def __eq__(self, other): """ See if this distribution is the same as another. :param other: The distribution to compare with. To be equal to one another. distributions must have the same type, name, version and source_url. :return: True if it is the same, else False. """ if type(other) is not type(self): result = False else: result = (self.name == other.name and self.version == other.version and self.source_url == other.source_url) return result def __hash__(self): """ Compute hash in a way which matches the equality test. """ return hash(self.name) + hash(self.version) + hash(self.source_url) class BaseInstalledDistribution(Distribution): """ This is the base class for installed distributions (whether PEP 376 or legacy). """ hasher = None def __init__(self, metadata, path, env=None): """ Initialise an instance. :param metadata: An instance of :class:`Metadata` which describes the distribution. This will normally have been initialised from a metadata file in the ``path``. :param path: The path of the ``.dist-info`` or ``.egg-info`` directory for the distribution. :param env: This is normally the :class:`DistributionPath` instance where this distribution was found. """ super(BaseInstalledDistribution, self).__init__(metadata) self.path = path self.dist_path = env def get_hash(self, data, hasher=None): """ Get the hash of some data, using a particular hash algorithm, if specified. :param data: The data to be hashed. :type data: bytes :param hasher: The name of a hash implementation, supported by hashlib, or ``None``. Examples of valid values are ``'sha1'``, ``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and ``'sha512'``. If no hasher is specified, the ``hasher`` attribute of the :class:`InstalledDistribution` instance is used. If the hasher is determined to be ``None``, MD5 is used as the hashing algorithm. :returns: The hash of the data. If a hasher was explicitly specified, the returned hash will be prefixed with the specified hasher followed by '='. :rtype: str """ if hasher is None: hasher = self.hasher if hasher is None: hasher = hashlib.md5 prefix = '' else: hasher = getattr(hashlib, hasher) prefix = '%s=' % self.hasher digest = hasher(data).digest() digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii') return '%s%s' % (prefix, digest) class InstalledDistribution(BaseInstalledDistribution): """ Created with the *path* of the ``.dist-info`` directory provided to the constructor. It reads the metadata contained in ``pydist.json`` when it is instantiated., or uses a passed in Metadata instance (useful for when dry-run mode is being used). """ hasher = 'sha256' def __init__(self, path, metadata=None, env=None): self.modules = [] self.finder = finder = resources.finder_for_path(path) if finder is None: raise ValueError('finder unavailable for %s' % path) if env and env._cache_enabled and path in env._cache.path: metadata = env._cache.path[path].metadata elif metadata is None: r = finder.find(METADATA_FILENAME) # Temporary - for Wheel 0.23 support if r is None: r = finder.find(WHEEL_METADATA_FILENAME) # Temporary - for legacy support if r is None: r = finder.find(LEGACY_METADATA_FILENAME) if r is None: raise ValueError('no %s found in %s' % (METADATA_FILENAME, path)) with contextlib.closing(r.as_stream()) as stream: metadata = Metadata(fileobj=stream, scheme='legacy') super(InstalledDistribution, self).__init__(metadata, path, env) if env and env._cache_enabled: env._cache.add(self) r = finder.find('REQUESTED') self.requested = r is not None p = os.path.join(path, 'top_level.txt') if os.path.exists(p): with open(p, 'rb') as f: data = f.read().decode('utf-8') self.modules = data.splitlines() def __repr__(self): return '' % (self.name, self.version, self.path) def __str__(self): return "%s %s" % (self.name, self.version) def _get_records(self): """ Get the list of installed files for the distribution :return: A list of tuples of path, hash and size. Note that hash and size might be ``None`` for some entries. The path is exactly as stored in the file (which is as in PEP 376). """ results = [] r = self.get_distinfo_resource('RECORD') with contextlib.closing(r.as_stream()) as stream: with CSVReader(stream=stream) as record_reader: # Base location is parent dir of .dist-info dir # base_location = os.path.dirname(self.path) # base_location = os.path.abspath(base_location) for row in record_reader: missing = [None for i in range(len(row), 3)] path, checksum, size = row + missing # if not os.path.isabs(path): # path = path.replace('/', os.sep) # path = os.path.join(base_location, path) results.append((path, checksum, size)) return results @cached_property def exports(self): """ Return the information exported by this distribution. :return: A dictionary of exports, mapping an export category to a dict of :class:`ExportEntry` instances describing the individual export entries, and keyed by name. """ result = {} r = self.get_distinfo_resource(EXPORTS_FILENAME) if r: result = self.read_exports() return result def read_exports(self): """ Read exports data from a file in .ini format. :return: A dictionary of exports, mapping an export category to a list of :class:`ExportEntry` instances describing the individual export entries. """ result = {} r = self.get_distinfo_resource(EXPORTS_FILENAME) if r: with contextlib.closing(r.as_stream()) as stream: result = read_exports(stream) return result def write_exports(self, exports): """ Write a dictionary of exports to a file in .ini format. :param exports: A dictionary of exports, mapping an export category to a list of :class:`ExportEntry` instances describing the individual export entries. """ rf = self.get_distinfo_file(EXPORTS_FILENAME) with open(rf, 'w') as f: write_exports(exports, f) def get_resource_path(self, relative_path): """ NOTE: This API may change in the future. Return the absolute path to a resource file with the given relative path. :param relative_path: The path, relative to .dist-info, of the resource of interest. :return: The absolute path where the resource is to be found. """ r = self.get_distinfo_resource('RESOURCES') with contextlib.closing(r.as_stream()) as stream: with CSVReader(stream=stream) as resources_reader: for relative, destination in resources_reader: if relative == relative_path: return destination raise KeyError('no resource file with relative path %r ' 'is installed' % relative_path) def list_installed_files(self): """ Iterates over the ``RECORD`` entries and returns a tuple ``(path, hash, size)`` for each line. :returns: iterator of (path, hash, size) """ for result in self._get_records(): yield result def write_installed_files(self, paths, prefix, dry_run=False): """ Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any existing ``RECORD`` file is silently overwritten. prefix is used to determine when to write absolute paths. """ prefix = os.path.join(prefix, '') base = os.path.dirname(self.path) base_under_prefix = base.startswith(prefix) base = os.path.join(base, '') record_path = self.get_distinfo_file('RECORD') logger.info('creating %s', record_path) if dry_run: return None with CSVWriter(record_path) as writer: for path in paths: if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')): # do not put size and hash, as in PEP-376 hash_value = size = '' else: size = '%d' % os.path.getsize(path) with open(path, 'rb') as fp: hash_value = self.get_hash(fp.read()) if path.startswith(base) or (base_under_prefix and path.startswith(prefix)): path = os.path.relpath(path, base) writer.writerow((path, hash_value, size)) # add the RECORD file itself if record_path.startswith(base): record_path = os.path.relpath(record_path, base) writer.writerow((record_path, '', '')) return record_path def check_installed_files(self): """ Checks that the hashes and sizes of the files in ``RECORD`` are matched by the files themselves. Returns a (possibly empty) list of mismatches. Each entry in the mismatch list will be a tuple consisting of the path, 'exists', 'size' or 'hash' according to what didn't match (existence is checked first, then size, then hash), the expected value and the actual value. """ mismatches = [] base = os.path.dirname(self.path) record_path = self.get_distinfo_file('RECORD') for path, hash_value, size in self.list_installed_files(): if not os.path.isabs(path): path = os.path.join(base, path) if path == record_path: continue if not os.path.exists(path): mismatches.append((path, 'exists', True, False)) elif os.path.isfile(path): actual_size = str(os.path.getsize(path)) if size and actual_size != size: mismatches.append((path, 'size', size, actual_size)) elif hash_value: if '=' in hash_value: hasher = hash_value.split('=', 1)[0] else: hasher = None with open(path, 'rb') as f: actual_hash = self.get_hash(f.read(), hasher) if actual_hash != hash_value: mismatches.append((path, 'hash', hash_value, actual_hash)) return mismatches @cached_property def shared_locations(self): """ A dictionary of shared locations whose keys are in the set 'prefix', 'purelib', 'platlib', 'scripts', 'headers', 'data' and 'namespace'. The corresponding value is the absolute path of that category for this distribution, and takes into account any paths selected by the user at installation time (e.g. via command-line arguments). In the case of the 'namespace' key, this would be a list of absolute paths for the roots of namespace packages in this distribution. The first time this property is accessed, the relevant information is read from the SHARED file in the .dist-info directory. """ result = {} shared_path = os.path.join(self.path, 'SHARED') if os.path.isfile(shared_path): with codecs.open(shared_path, 'r', encoding='utf-8') as f: lines = f.read().splitlines() for line in lines: key, value = line.split('=', 1) if key == 'namespace': result.setdefault(key, []).append(value) else: result[key] = value return result def write_shared_locations(self, paths, dry_run=False): """ Write shared location information to the SHARED file in .dist-info. :param paths: A dictionary as described in the documentation for :meth:`shared_locations`. :param dry_run: If True, the action is logged but no file is actually written. :return: The path of the file written to. """ shared_path = os.path.join(self.path, 'SHARED') logger.info('creating %s', shared_path) if dry_run: return None lines = [] for key in ('prefix', 'lib', 'headers', 'scripts', 'data'): path = paths[key] if os.path.isdir(paths[key]): lines.append('%s=%s' % (key, path)) for ns in paths.get('namespace', ()): lines.append('namespace=%s' % ns) with codecs.open(shared_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) return shared_path def get_distinfo_resource(self, path): if path not in DIST_FILES: raise DistlibException('invalid path for a dist-info file: ' '%r at %r' % (path, self.path)) finder = resources.finder_for_path(self.path) if finder is None: raise DistlibException('Unable to get a finder for %s' % self.path) return finder.find(path) def get_distinfo_file(self, path): """ Returns a path located under the ``.dist-info`` directory. Returns a string representing the path. :parameter path: a ``'/'``-separated path relative to the ``.dist-info`` directory or an absolute path; If *path* is an absolute path and doesn't start with the ``.dist-info`` directory path, a :class:`DistlibException` is raised :type path: str :rtype: str """ # Check if it is an absolute path # XXX use relpath, add tests if path.find(os.sep) >= 0: # it's an absolute path? distinfo_dirname, path = path.split(os.sep)[-2:] if distinfo_dirname != self.path.split(os.sep)[-1]: raise DistlibException('dist-info file %r does not belong to the %r %s ' 'distribution' % (path, self.name, self.version)) # The file must be relative if path not in DIST_FILES: raise DistlibException('invalid path for a dist-info file: ' '%r at %r' % (path, self.path)) return os.path.join(self.path, path) def list_distinfo_files(self): """ Iterates over the ``RECORD`` entries and returns paths for each line if the path is pointing to a file located in the ``.dist-info`` directory or one of its subdirectories. :returns: iterator of paths """ base = os.path.dirname(self.path) for path, checksum, size in self._get_records(): # XXX add separator or use real relpath algo if not os.path.isabs(path): path = os.path.join(base, path) if path.startswith(self.path): yield path def __eq__(self, other): return (isinstance(other, InstalledDistribution) and self.path == other.path) # See http://docs.python.org/reference/datamodel#object.__hash__ __hash__ = object.__hash__ class EggInfoDistribution(BaseInstalledDistribution): """Created with the *path* of the ``.egg-info`` directory or file provided to the constructor. It reads the metadata contained in the file itself, or if the given path happens to be a directory, the metadata is read from the file ``PKG-INFO`` under that directory.""" requested = True # as we have no way of knowing, assume it was shared_locations = {} def __init__(self, path, env=None): def set_name_and_version(s, n, v): s.name = n s.key = n.lower() # for case-insensitive comparisons s.version = v self.path = path self.dist_path = env if env and env._cache_enabled and path in env._cache_egg.path: metadata = env._cache_egg.path[path].metadata set_name_and_version(self, metadata.name, metadata.version) else: metadata = self._get_metadata(path) # Need to be set before caching set_name_and_version(self, metadata.name, metadata.version) if env and env._cache_enabled: env._cache_egg.add(self) super(EggInfoDistribution, self).__init__(metadata, path, env) def _get_metadata(self, path): requires = None def parse_requires_data(data): """Create a list of dependencies from a requires.txt file. *data*: the contents of a setuptools-produced requires.txt file. """ reqs = [] lines = data.splitlines() for line in lines: line = line.strip() # sectioned files have bare newlines (separating sections) if not line: # pragma: no cover continue if line.startswith('['): # pragma: no cover logger.warning('Unexpected line: quitting requirement scan: %r', line) break r = parse_requirement(line) if not r: # pragma: no cover logger.warning('Not recognised as a requirement: %r', line) continue if r.extras: # pragma: no cover logger.warning('extra requirements in requires.txt are ' 'not supported') if not r.constraints: reqs.append(r.name) else: cons = ', '.join('%s%s' % c for c in r.constraints) reqs.append('%s (%s)' % (r.name, cons)) return reqs def parse_requires_path(req_path): """Create a list of dependencies from a requires.txt file. *req_path*: the path to a setuptools-produced requires.txt file. """ reqs = [] try: with codecs.open(req_path, 'r', 'utf-8') as fp: reqs = parse_requires_data(fp.read()) except IOError: pass return reqs tl_path = tl_data = None if path.endswith('.egg'): if os.path.isdir(path): p = os.path.join(path, 'EGG-INFO') meta_path = os.path.join(p, 'PKG-INFO') metadata = Metadata(path=meta_path, scheme='legacy') req_path = os.path.join(p, 'requires.txt') tl_path = os.path.join(p, 'top_level.txt') requires = parse_requires_path(req_path) else: # FIXME handle the case where zipfile is not available zipf = zipimport.zipimporter(path) fileobj = StringIO(zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8')) metadata = Metadata(fileobj=fileobj, scheme='legacy') try: data = zipf.get_data('EGG-INFO/requires.txt') tl_data = zipf.get_data('EGG-INFO/top_level.txt').decode('utf-8') requires = parse_requires_data(data.decode('utf-8')) except IOError: requires = None elif path.endswith('.egg-info'): if os.path.isdir(path): req_path = os.path.join(path, 'requires.txt') requires = parse_requires_path(req_path) path = os.path.join(path, 'PKG-INFO') tl_path = os.path.join(path, 'top_level.txt') metadata = Metadata(path=path, scheme='legacy') else: raise DistlibException('path must end with .egg-info or .egg, ' 'got %r' % path) if requires: metadata.add_requirements(requires) # look for top-level modules in top_level.txt, if present if tl_data is None: if tl_path is not None and os.path.exists(tl_path): with open(tl_path, 'rb') as f: tl_data = f.read().decode('utf-8') if not tl_data: tl_data = [] else: tl_data = tl_data.splitlines() self.modules = tl_data return metadata def __repr__(self): return '' % (self.name, self.version, self.path) def __str__(self): return "%s %s" % (self.name, self.version) def check_installed_files(self): """ Checks that the hashes and sizes of the files in ``RECORD`` are matched by the files themselves. Returns a (possibly empty) list of mismatches. Each entry in the mismatch list will be a tuple consisting of the path, 'exists', 'size' or 'hash' according to what didn't match (existence is checked first, then size, then hash), the expected value and the actual value. """ mismatches = [] record_path = os.path.join(self.path, 'installed-files.txt') if os.path.exists(record_path): for path, _, _ in self.list_installed_files(): if path == record_path: continue if not os.path.exists(path): mismatches.append((path, 'exists', True, False)) return mismatches def list_installed_files(self): """ Iterates over the ``installed-files.txt`` entries and returns a tuple ``(path, hash, size)`` for each line. :returns: a list of (path, hash, size) """ def _md5(path): f = open(path, 'rb') try: content = f.read() finally: f.close() return hashlib.md5(content).hexdigest() def _size(path): return os.stat(path).st_size record_path = os.path.join(self.path, 'installed-files.txt') result = [] if os.path.exists(record_path): with codecs.open(record_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() p = os.path.normpath(os.path.join(self.path, line)) # "./" is present as a marker between installed files # and installation metadata files if not os.path.exists(p): logger.warning('Non-existent file: %s', p) if p.endswith(('.pyc', '.pyo')): continue # otherwise fall through and fail if not os.path.isdir(p): result.append((p, _md5(p), _size(p))) result.append((record_path, None, None)) return result def list_distinfo_files(self, absolute=False): """ Iterates over the ``installed-files.txt`` entries and returns paths for each line if the path is pointing to a file located in the ``.egg-info`` directory or one of its subdirectories. :parameter absolute: If *absolute* is ``True``, each returned path is transformed into a local absolute path. Otherwise the raw value from ``installed-files.txt`` is returned. :type absolute: boolean :returns: iterator of paths """ record_path = os.path.join(self.path, 'installed-files.txt') if os.path.exists(record_path): skip = True with codecs.open(record_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if line == './': skip = False continue if not skip: p = os.path.normpath(os.path.join(self.path, line)) if p.startswith(self.path): if absolute: yield p else: yield line def __eq__(self, other): return (isinstance(other, EggInfoDistribution) and self.path == other.path) # See http://docs.python.org/reference/datamodel#object.__hash__ __hash__ = object.__hash__ new_dist_class = InstalledDistribution old_dist_class = EggInfoDistribution class DependencyGraph(object): """ Represents a dependency graph between distributions. The dependency relationships are stored in an ``adjacency_list`` that maps distributions to a list of ``(other, label)`` tuples where ``other`` is a distribution and the edge is labeled with ``label`` (i.e. the version specifier, if such was provided). Also, for more efficient traversal, for every distribution ``x``, a list of predecessors is kept in ``reverse_list[x]``. An edge from distribution ``a`` to distribution ``b`` means that ``a`` depends on ``b``. If any missing dependencies are found, they are stored in ``missing``, which is a dictionary that maps distributions to a list of requirements that were not provided by any other distributions. """ def __init__(self): self.adjacency_list = {} self.reverse_list = {} self.missing = {} def add_distribution(self, distribution): """Add the *distribution* to the graph. :type distribution: :class:`distutils2.database.InstalledDistribution` or :class:`distutils2.database.EggInfoDistribution` """ self.adjacency_list[distribution] = [] self.reverse_list[distribution] = [] # self.missing[distribution] = [] def add_edge(self, x, y, label=None): """Add an edge from distribution *x* to distribution *y* with the given *label*. :type x: :class:`distutils2.database.InstalledDistribution` or :class:`distutils2.database.EggInfoDistribution` :type y: :class:`distutils2.database.InstalledDistribution` or :class:`distutils2.database.EggInfoDistribution` :type label: ``str`` or ``None`` """ self.adjacency_list[x].append((y, label)) # multiple edges are allowed, so be careful if x not in self.reverse_list[y]: self.reverse_list[y].append(x) def add_missing(self, distribution, requirement): """ Add a missing *requirement* for the given *distribution*. :type distribution: :class:`distutils2.database.InstalledDistribution` or :class:`distutils2.database.EggInfoDistribution` :type requirement: ``str`` """ logger.debug('%s missing %r', distribution, requirement) self.missing.setdefault(distribution, []).append(requirement) def _repr_dist(self, dist): return '%s %s' % (dist.name, dist.version) def repr_node(self, dist, level=1): """Prints only a subgraph""" output = [self._repr_dist(dist)] for other, label in self.adjacency_list[dist]: dist = self._repr_dist(other) if label is not None: dist = '%s [%s]' % (dist, label) output.append(' ' * level + str(dist)) suboutput = self.repr_node(other, level + 1) subs = suboutput.split('\n') output.extend(subs[1:]) return '\n'.join(output) def to_dot(self, f, skip_disconnected=True): """Writes a DOT output for the graph to the provided file *f*. If *skip_disconnected* is set to ``True``, then all distributions that are not dependent on any other distribution are skipped. :type f: has to support ``file``-like operations :type skip_disconnected: ``bool`` """ disconnected = [] f.write("digraph dependencies {\n") for dist, adjs in self.adjacency_list.items(): if len(adjs) == 0 and not skip_disconnected: disconnected.append(dist) for other, label in adjs: if label is not None: f.write('"%s" -> "%s" [label="%s"]\n' % (dist.name, other.name, label)) else: f.write('"%s" -> "%s"\n' % (dist.name, other.name)) if not skip_disconnected and len(disconnected) > 0: f.write('subgraph disconnected {\n') f.write('label = "Disconnected"\n') f.write('bgcolor = red\n') for dist in disconnected: f.write('"%s"' % dist.name) f.write('\n') f.write('}\n') f.write('}\n') def topological_sort(self): """ Perform a topological sort of the graph. :return: A tuple, the first element of which is a topologically sorted list of distributions, and the second element of which is a list of distributions that cannot be sorted because they have circular dependencies and so form a cycle. """ result = [] # Make a shallow copy of the adjacency list alist = {} for k, v in self.adjacency_list.items(): alist[k] = v[:] while True: # See what we can remove in this run to_remove = [] for k, v in list(alist.items())[:]: if not v: to_remove.append(k) del alist[k] if not to_remove: # What's left in alist (if anything) is a cycle. break # Remove from the adjacency list of others for k, v in alist.items(): alist[k] = [(d, r) for d, r in v if d not in to_remove] logger.debug('Moving to result: %s', ['%s (%s)' % (d.name, d.version) for d in to_remove]) result.extend(to_remove) return result, list(alist.keys()) def __repr__(self): """Representation of the graph""" output = [] for dist, adjs in self.adjacency_list.items(): output.append(self.repr_node(dist)) return '\n'.join(output) def make_graph(dists, scheme='default'): """Makes a dependency graph from the given distributions. :parameter dists: a list of distributions :type dists: list of :class:`distutils2.database.InstalledDistribution` and :class:`distutils2.database.EggInfoDistribution` instances :rtype: a :class:`DependencyGraph` instance """ scheme = get_scheme(scheme) graph = DependencyGraph() provided = {} # maps names to lists of (version, dist) tuples # first, build the graph and find out what's provided for dist in dists: graph.add_distribution(dist) for p in dist.provides: name, version = parse_name_and_version(p) logger.debug('Add to provided: %s, %s, %s', name, version, dist) provided.setdefault(name, []).append((version, dist)) # now make the edges for dist in dists: requires = (dist.run_requires | dist.meta_requires | dist.build_requires | dist.dev_requires) for req in requires: try: matcher = scheme.matcher(req) except UnsupportedVersionError: # XXX compat-mode if cannot read the version logger.warning('could not read version %r - using name only', req) name = req.split()[0] matcher = scheme.matcher(name) name = matcher.key # case-insensitive matched = False if name in provided: for version, provider in provided[name]: try: match = matcher.match(version) except UnsupportedVersionError: match = False if match: graph.add_edge(dist, provider, req) matched = True break if not matched: graph.add_missing(dist, req) return graph def get_dependent_dists(dists, dist): """Recursively generate a list of distributions from *dists* that are dependent on *dist*. :param dists: a list of distributions :param dist: a distribution, member of *dists* for which we are interested """ if dist not in dists: raise DistlibException('given distribution %r is not a member ' 'of the list' % dist.name) graph = make_graph(dists) dep = [dist] # dependent distributions todo = graph.reverse_list[dist] # list of nodes we should inspect while todo: d = todo.pop() dep.append(d) for succ in graph.reverse_list[d]: if succ not in dep: todo.append(succ) dep.pop(0) # remove dist from dep, was there to prevent infinite loops return dep def get_required_dists(dists, dist): """Recursively generate a list of distributions from *dists* that are required by *dist*. :param dists: a list of distributions :param dist: a distribution, member of *dists* for which we are interested in finding the dependencies. """ if dist not in dists: raise DistlibException('given distribution %r is not a member ' 'of the list' % dist.name) graph = make_graph(dists) req = set() # required distributions todo = graph.adjacency_list[dist] # list of nodes we should inspect seen = set(t[0] for t in todo) # already added to todo while todo: d = todo.pop()[0] req.add(d) pred_list = graph.adjacency_list[d] for pred in pred_list: d = pred[0] if d not in req and d not in seen: seen.add(d) todo.append(pred) return req def make_dist(name, version, **kwargs): """ A convenience method for making a dist given just a name and version. """ summary = kwargs.pop('summary', 'Placeholder for summary') md = Metadata(**kwargs) md.name = name md.version = version md.summary = summary or 'Placeholder for summary' return Distribution(md) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1702289035.0 distlib-0.4.3/distlib/index.py0000644000175000017500000005047514535557213015406 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2013-2023 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # import hashlib import logging import os import shutil import subprocess import tempfile try: from threading import Thread except ImportError: # pragma: no cover from dummy_threading import Thread from . import DistlibException from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr, urlparse, build_opener, string_types) from .util import zip_dir, ServerProxy logger = logging.getLogger(__name__) DEFAULT_INDEX = 'https://pypi.org/pypi' DEFAULT_REALM = 'pypi' class PackageIndex(object): """ This class represents a package index compatible with PyPI, the Python Package Index. """ boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$' def __init__(self, url=None): """ Initialise an instance. :param url: The URL of the index. If not specified, the URL for PyPI is used. """ self.url = url or DEFAULT_INDEX self.read_configuration() scheme, netloc, path, params, query, frag = urlparse(self.url) if params or query or frag or scheme not in ('http', 'https'): raise DistlibException('invalid repository: %s' % self.url) self.password_handler = None self.ssl_verifier = None self.gpg = None self.gpg_home = None with open(os.devnull, 'w') as sink: # Use gpg by default rather than gpg2, as gpg2 insists on # prompting for passwords for s in ('gpg', 'gpg2'): try: rc = subprocess.check_call([s, '--version'], stdout=sink, stderr=sink) if rc == 0: self.gpg = s break except OSError: pass def _get_pypirc_command(self): """ Get the distutils command for interacting with PyPI configurations. :return: the command. """ from .util import _get_pypirc_command as cmd return cmd() def read_configuration(self): """ Read the PyPI access configuration as supported by distutils. This populates ``username``, ``password``, ``realm`` and ``url`` attributes from the configuration. """ from .util import _load_pypirc cfg = _load_pypirc(self) self.username = cfg.get('username') self.password = cfg.get('password') self.realm = cfg.get('realm', 'pypi') self.url = cfg.get('repository', self.url) def save_configuration(self): """ Save the PyPI access configuration. You must have set ``username`` and ``password`` attributes before calling this method. """ self.check_credentials() from .util import _store_pypirc _store_pypirc(self) def check_credentials(self): """ Check that ``username`` and ``password`` have been set, and raise an exception if not. """ if self.username is None or self.password is None: raise DistlibException('username and password must be set') pm = HTTPPasswordMgr() _, netloc, _, _, _, _ = urlparse(self.url) pm.add_password(self.realm, netloc, self.username, self.password) self.password_handler = HTTPBasicAuthHandler(pm) def register(self, metadata): # pragma: no cover """ Register a distribution on PyPI, using the provided metadata. :param metadata: A :class:`Metadata` instance defining at least a name and version number for the distribution to be registered. :return: The HTTP response received from PyPI upon submission of the request. """ self.check_credentials() metadata.validate() d = metadata.todict() d[':action'] = 'verify' request = self.encode_request(d.items(), []) self.send_request(request) d[':action'] = 'submit' request = self.encode_request(d.items(), []) return self.send_request(request) def _reader(self, name, stream, outbuf): """ Thread runner for reading lines of from a subprocess into a buffer. :param name: The logical name of the stream (used for logging only). :param stream: The stream to read from. This will typically a pipe connected to the output stream of a subprocess. :param outbuf: The list to append the read lines to. """ while True: s = stream.readline() if not s: break s = s.decode('utf-8').rstrip() outbuf.append(s) logger.debug('%s: %s' % (name, s)) stream.close() def get_sign_command(self, filename, signer, sign_password, keystore=None): # pragma: no cover """ Return a suitable command for signing a file. :param filename: The pathname to the file to be signed. :param signer: The identifier of the signer of the file. :param sign_password: The passphrase for the signer's private key used for signing. :param keystore: The path to a directory which contains the keys used in verification. If not specified, the instance's ``gpg_home`` attribute is used instead. :return: The signing command as a list suitable to be passed to :class:`subprocess.Popen`. """ cmd = [self.gpg, '--status-fd', '2', '--no-tty'] if keystore is None: keystore = self.gpg_home if keystore: cmd.extend(['--homedir', keystore]) if sign_password is not None: cmd.extend(['--batch', '--passphrase-fd', '0']) td = tempfile.mkdtemp() sf = os.path.join(td, os.path.basename(filename) + '.asc') cmd.extend(['--detach-sign', '--armor', '--local-user', signer, '--output', sf, filename]) logger.debug('invoking: %s', ' '.join(cmd)) return cmd, sf def run_command(self, cmd, input_data=None): """ Run a command in a child process , passing it any input data specified. :param cmd: The command to run. :param input_data: If specified, this must be a byte string containing data to be sent to the child process. :return: A tuple consisting of the subprocess' exit code, a list of lines read from the subprocess' ``stdout``, and a list of lines read from the subprocess' ``stderr``. """ kwargs = { 'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE, } if input_data is not None: kwargs['stdin'] = subprocess.PIPE stdout = [] stderr = [] p = subprocess.Popen(cmd, **kwargs) # We don't use communicate() here because we may need to # get clever with interacting with the command t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout)) t1.start() t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr)) t2.start() if input_data is not None: p.stdin.write(input_data) p.stdin.close() p.wait() t1.join() t2.join() return p.returncode, stdout, stderr def sign_file(self, filename, signer, sign_password, keystore=None): # pragma: no cover """ Sign a file. :param filename: The pathname to the file to be signed. :param signer: The identifier of the signer of the file. :param sign_password: The passphrase for the signer's private key used for signing. :param keystore: The path to a directory which contains the keys used in signing. If not specified, the instance's ``gpg_home`` attribute is used instead. :return: The absolute pathname of the file where the signature is stored. """ cmd, sig_file = self.get_sign_command(filename, signer, sign_password, keystore) rc, stdout, stderr = self.run_command(cmd, sign_password.encode('utf-8')) if rc != 0: raise DistlibException('sign command failed with error ' 'code %s' % rc) return sig_file def upload_file(self, metadata, filename, signer=None, sign_password=None, filetype='sdist', pyversion='source', keystore=None): """ Upload a release file to the index. :param metadata: A :class:`Metadata` instance defining at least a name and version number for the file to be uploaded. :param filename: The pathname of the file to be uploaded. :param signer: The identifier of the signer of the file. :param sign_password: The passphrase for the signer's private key used for signing. :param filetype: The type of the file being uploaded. This is the distutils command which produced that file, e.g. ``sdist`` or ``bdist_wheel``. :param pyversion: The version of Python which the release relates to. For code compatible with any Python, this would be ``source``, otherwise it would be e.g. ``3.2``. :param keystore: The path to a directory which contains the keys used in signing. If not specified, the instance's ``gpg_home`` attribute is used instead. :return: The HTTP response received from PyPI upon submission of the request. """ self.check_credentials() if not os.path.exists(filename): raise DistlibException('not found: %s' % filename) metadata.validate() d = metadata.todict() sig_file = None if signer: if not self.gpg: logger.warning('no signing program available - not signed') else: sig_file = self.sign_file(filename, signer, sign_password, keystore) with open(filename, 'rb') as f: file_data = f.read() md5_digest = hashlib.md5(file_data).hexdigest() sha256_digest = hashlib.sha256(file_data).hexdigest() d.update({ ':action': 'file_upload', 'protocol_version': '1', 'filetype': filetype, 'pyversion': pyversion, 'md5_digest': md5_digest, 'sha256_digest': sha256_digest, }) files = [('content', os.path.basename(filename), file_data)] if sig_file: with open(sig_file, 'rb') as f: sig_data = f.read() files.append(('gpg_signature', os.path.basename(sig_file), sig_data)) shutil.rmtree(os.path.dirname(sig_file)) request = self.encode_request(d.items(), files) return self.send_request(request) def upload_documentation(self, metadata, doc_dir): # pragma: no cover """ Upload documentation to the index. :param metadata: A :class:`Metadata` instance defining at least a name and version number for the documentation to be uploaded. :param doc_dir: The pathname of the directory which contains the documentation. This should be the directory that contains the ``index.html`` for the documentation. :return: The HTTP response received from PyPI upon submission of the request. """ self.check_credentials() if not os.path.isdir(doc_dir): raise DistlibException('not a directory: %r' % doc_dir) fn = os.path.join(doc_dir, 'index.html') if not os.path.exists(fn): raise DistlibException('not found: %r' % fn) metadata.validate() name, version = metadata.name, metadata.version zip_data = zip_dir(doc_dir).getvalue() fields = [(':action', 'doc_upload'), ('name', name), ('version', version)] files = [('content', name, zip_data)] request = self.encode_request(fields, files) return self.send_request(request) def get_verify_command(self, signature_filename, data_filename, keystore=None): """ Return a suitable command for verifying a file. :param signature_filename: The pathname to the file containing the signature. :param data_filename: The pathname to the file containing the signed data. :param keystore: The path to a directory which contains the keys used in verification. If not specified, the instance's ``gpg_home`` attribute is used instead. :return: The verifying command as a list suitable to be passed to :class:`subprocess.Popen`. """ cmd = [self.gpg, '--status-fd', '2', '--no-tty'] if keystore is None: keystore = self.gpg_home if keystore: cmd.extend(['--homedir', keystore]) cmd.extend(['--verify', signature_filename, data_filename]) logger.debug('invoking: %s', ' '.join(cmd)) return cmd def verify_signature(self, signature_filename, data_filename, keystore=None): """ Verify a signature for a file. :param signature_filename: The pathname to the file containing the signature. :param data_filename: The pathname to the file containing the signed data. :param keystore: The path to a directory which contains the keys used in verification. If not specified, the instance's ``gpg_home`` attribute is used instead. :return: True if the signature was verified, else False. """ if not self.gpg: raise DistlibException('verification unavailable because gpg ' 'unavailable') cmd = self.get_verify_command(signature_filename, data_filename, keystore) rc, stdout, stderr = self.run_command(cmd) if rc not in (0, 1): raise DistlibException('verify command failed with error code %s' % rc) return rc == 0 def download_file(self, url, destfile, digest=None, reporthook=None): """ This is a convenience method for downloading a file from an URL. Normally, this will be a file from the index, though currently no check is made for this (i.e. a file can be downloaded from anywhere). The method is just like the :func:`urlretrieve` function in the standard library, except that it allows digest computation to be done during download and checking that the downloaded data matched any expected value. :param url: The URL of the file to be downloaded (assumed to be available via an HTTP GET request). :param destfile: The pathname where the downloaded file is to be saved. :param digest: If specified, this must be a (hasher, value) tuple, where hasher is the algorithm used (e.g. ``'md5'``) and ``value`` is the expected value. :param reporthook: The same as for :func:`urlretrieve` in the standard library. """ if digest is None: digester = None logger.debug('No digest specified') else: if isinstance(digest, (list, tuple)): hasher, digest = digest else: hasher = 'md5' digester = getattr(hashlib, hasher)() logger.debug('Digest specified: %s' % digest) # The following code is equivalent to urlretrieve. # We need to do it this way so that we can compute the # digest of the file as we go. with open(destfile, 'wb') as dfp: # addinfourl is not a context manager on 2.x # so we have to use try/finally sfp = self.send_request(Request(url)) try: headers = sfp.info() blocksize = 8192 size = -1 read = 0 blocknum = 0 if "content-length" in headers: size = int(headers["Content-Length"]) if reporthook: reporthook(blocknum, blocksize, size) while True: block = sfp.read(blocksize) if not block: break read += len(block) dfp.write(block) if digester: digester.update(block) blocknum += 1 if reporthook: reporthook(blocknum, blocksize, size) finally: sfp.close() # check that we got the whole file, if we can if size >= 0 and read < size: raise DistlibException( 'retrieval incomplete: got only %d out of %d bytes' % (read, size)) # if we have a digest, it must match. if digester: actual = digester.hexdigest() if digest != actual: raise DistlibException('%s digest mismatch for %s: expected ' '%s, got %s' % (hasher, destfile, digest, actual)) logger.debug('Digest verified: %s', digest) def send_request(self, req): """ Send a standard library :class:`Request` to PyPI and return its response. :param req: The request to send. :return: The HTTP response from PyPI (a standard library HTTPResponse). """ handlers = [] if self.password_handler: handlers.append(self.password_handler) if self.ssl_verifier: handlers.append(self.ssl_verifier) opener = build_opener(*handlers) return opener.open(req) def encode_request(self, fields, files): """ Encode fields and files for posting to an HTTP server. :param fields: The fields to send as a list of (fieldname, value) tuples. :param files: The files to send as a list of (fieldname, filename, file_bytes) tuple. """ # Adapted from packaging, which in turn was adapted from # http://code.activestate.com/recipes/146306 parts = [] boundary = self.boundary for k, values in fields: if not isinstance(values, (list, tuple)): values = [values] for v in values: parts.extend(( b'--' + boundary, ('Content-Disposition: form-data; name="%s"' % k).encode('utf-8'), b'', v.encode('utf-8'))) for key, filename, value in files: parts.extend(( b'--' + boundary, ('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)).encode('utf-8'), b'', value)) parts.extend((b'--' + boundary + b'--', b'')) body = b'\r\n'.join(parts) ct = b'multipart/form-data; boundary=' + boundary headers = { 'Content-type': ct, 'Content-length': str(len(body)) } return Request(self.url, body, headers) def search(self, terms, operator=None): # pragma: no cover if isinstance(terms, string_types): terms = {'name': terms} rpc_proxy = ServerProxy(self.url, timeout=3.0) try: return rpc_proxy.search(terms, operator or 'and') finally: rpc_proxy('close')() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780588013.0 distlib-0.4.3/distlib/locators.py0000664000175000017500000014655115210316755016123 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2026 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # import gzip from io import BytesIO import json import logging import os import posixpath import re try: import threading except ImportError: # pragma: no cover import dummy_threading as threading import zlib from . import DistlibException from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, queue, quote, unescape, build_opener, HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError) from .database import Distribution, DistributionPath, make_dist from .metadata import Metadata, MetadataInvalidError from .util import (cached_property, ensure_slash, split_filename, get_project_data, parse_requirement, parse_name_and_version, ServerProxy, normalize_name) from .version import get_scheme, UnsupportedVersionError from .wheel import Wheel, is_compatible logger = logging.getLogger(__name__) HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)') CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I) HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml') DEFAULT_INDEX = 'https://pypi.org/pypi' def get_all_distribution_names(url=None): """ Return all distribution names known by an index. :param url: The URL of the index. :return: A list of all known distribution names. """ if url is None: url = DEFAULT_INDEX client = ServerProxy(url, timeout=3.0) try: return client.list_packages() finally: client('close')() class RedirectHandler(BaseRedirectHandler): """ A class to work around a bug in some Python 3.2.x releases. """ # There's a bug in the base version for some 3.2.x # (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header # returns e.g. /abc, it bails because it says the scheme '' # is bogus, when actually it should use the request's # URL for the scheme. See Python issue #13696. def http_error_302(self, req, fp, code, msg, headers): # Some servers (incorrectly) return multiple Location headers # (so probably same goes for URI). Use first header. newurl = None for key in ('location', 'uri'): if key in headers: newurl = headers[key] break if newurl is None: # pragma: no cover return urlparts = urlparse(newurl) if urlparts.scheme == '': newurl = urljoin(req.get_full_url(), newurl) if hasattr(headers, 'replace_header'): headers.replace_header(key, newurl) else: headers[key] = newurl return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, headers) http_error_301 = http_error_303 = http_error_307 = http_error_302 class Locator(object): """ A base class for locators - things that locate distributions. """ source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz') binary_extensions = ('.egg', '.exe', '.whl') excluded_extensions = ('.pdf', ) # A list of tags indicating which wheels you want to match. The default # value of None matches against the tags compatible with the running # Python. If you want to match other values, set wheel_tags on a locator # instance to a list of tuples (pyver, abi, arch) which you want to match. wheel_tags = None downloadable_extensions = source_extensions + ('.whl', ) def __init__(self, scheme='default'): """ Initialise an instance. :param scheme: Because locators look for most recent versions, they need to know the version scheme to use. This specifies the current PEP-recommended scheme - use ``'legacy'`` if you need to support existing distributions on PyPI. """ self._cache = {} self.scheme = scheme # Because of bugs in some of the handlers on some of the platforms, # we use our own opener rather than just using urlopen. self.opener = build_opener(RedirectHandler()) # If get_project() is called from locate(), the matcher instance # is set from the requirement passed to locate(). See issue #18 for # why this can be useful to know. self.matcher = None self.errors = queue.Queue() def get_errors(self): """ Return any errors which have occurred. """ result = [] while not self.errors.empty(): # pragma: no cover try: e = self.errors.get(False) result.append(e) except self.errors.Empty: continue self.errors.task_done() return result def clear_errors(self): """ Clear any errors which may have been logged. """ # Just get the errors and throw them away self.get_errors() def clear_cache(self): self._cache.clear() def _get_scheme(self): return self._scheme def _set_scheme(self, value): self._scheme = value scheme = property(_get_scheme, _set_scheme) def _get_project(self, name): """ For a given project, get a dictionary mapping available versions to Distribution instances. This should be implemented in subclasses. If called from a locate() request, self.matcher will be set to a matcher for the requirement to satisfy, otherwise it will be None. """ raise NotImplementedError('Please implement in the subclass') def get_distribution_names(self): """ Return all the distribution names known to this locator. """ raise NotImplementedError('Please implement in the subclass') def get_project(self, name): """ For a given project, get a dictionary mapping available versions to Distribution instances. This calls _get_project to do all the work, and just implements a caching layer on top. """ if self._cache is None: # pragma: no cover result = self._get_project(name) elif name in self._cache: result = self._cache[name] else: self.clear_errors() result = self._get_project(name) self._cache[name] = result return result def score_url(self, url): """ Give an url a score which can be used to choose preferred URLs for a given project release. """ t = urlparse(url) basename = posixpath.basename(t.path) compatible = True is_wheel = basename.endswith('.whl') is_downloadable = basename.endswith(self.downloadable_extensions) if is_wheel: compatible = is_compatible(Wheel(basename), self.wheel_tags) return (t.scheme == 'https', 'pypi.org' in t.netloc, is_downloadable, is_wheel, compatible, basename) def prefer_url(self, url1, url2): """ Choose one of two URLs where both are candidates for distribution archives for the same version of a distribution (for example, .tar.gz vs. zip). The current implementation favours https:// URLs over http://, archives from PyPI over those from other locations, wheel compatibility (if a wheel) and then the archive name. """ result = url2 if url1: s1 = self.score_url(url1) s2 = self.score_url(url2) if s1 > s2: result = url1 if result != url2: logger.debug('Not replacing %r with %r', url1, url2) else: logger.debug('Replacing %r with %r', url1, url2) return result def split_filename(self, filename, project_name): """ Attempt to split a filename in project name, version and Python version. """ return split_filename(filename, project_name) def convert_url_to_download_info(self, url, project_name): """ See if a URL is a candidate for a download URL for a project (the URL has typically been scraped from an HTML page). If it is, a dictionary is returned with keys "name", "version", "filename" and "url"; otherwise, None is returned. """ def same_project(name1, name2): return normalize_name(name1) == normalize_name(name2) result = None scheme, netloc, path, params, query, frag = urlparse(url) if frag.lower().startswith('egg='): # pragma: no cover logger.debug('%s: version hint in fragment: %r', project_name, frag) m = HASHER_HASH.match(frag) if m: algo, digest = m.groups() else: algo, digest = None, None origpath = path if path and path[-1] == '/': # pragma: no cover path = path[:-1] if path.endswith('.whl'): try: wheel = Wheel(path) if not is_compatible(wheel, self.wheel_tags): logger.debug('Wheel not compatible: %s', path) else: if project_name is None: include = True else: include = same_project(wheel.name, project_name) if include: result = { 'name': wheel.name, 'version': wheel.version, 'filename': wheel.filename, 'url': urlunparse((scheme, netloc, origpath, params, query, '')), 'python-version': ', '.join(['.'.join(list(v[2:])) for v in wheel.pyver]), } except Exception: # pragma: no cover logger.warning('invalid path for wheel: %s', path) elif not path.endswith(self.downloadable_extensions): # pragma: no cover logger.debug('Not downloadable: %s', path) else: # downloadable extension path = filename = posixpath.basename(path) for ext in self.downloadable_extensions: if path.endswith(ext): path = path[:-len(ext)] t = self.split_filename(path, project_name) if not t: # pragma: no cover logger.debug('No match for project/version: %s', path) else: name, version, pyver = t if not project_name or same_project(project_name, name): result = { 'name': name, 'version': version, 'filename': filename, 'url': urlunparse((scheme, netloc, origpath, params, query, '')), } if pyver: # pragma: no cover result['python-version'] = pyver break if result and algo: result['%s_digest' % algo] = digest return result def _get_digest(self, info): """ Get a digest from a dictionary by looking at a "digests" dictionary or keys of the form 'algo_digest'. Returns a 2-tuple (algo, digest) if found, else None. Currently looks only for SHA256, then MD5. """ result = None if 'digests' in info: digests = info['digests'] for algo in ('sha256', 'md5'): if algo in digests: result = (algo, digests[algo]) break if not result: for algo in ('sha256', 'md5'): key = '%s_digest' % algo if key in info: result = (algo, info[key]) break return result def _update_version_data(self, result, info): """ Update a result dictionary (the final result from _get_project) with a dictionary for a specific version, which typically holds information gleaned from a filename or URL for an archive for the distribution. """ name = info.pop('name') version = info.pop('version') if version in result: dist = result[version] md = dist.metadata else: dist = make_dist(name, version, scheme=self.scheme) md = dist.metadata dist.digest = digest = self._get_digest(info) url = info['url'] result['digests'][url] = digest if md.source_url != info['url']: md.source_url = self.prefer_url(md.source_url, url) result['urls'].setdefault(version, set()).add(url) dist.locator = self result[version] = dist def locate(self, requirement, prereleases=False): """ Find the most recent distribution which matches the given requirement. :param requirement: A requirement of the form 'foo (1.0)' or perhaps 'foo (>= 1.0, < 2.0, != 1.3)' :param prereleases: If ``True``, allow pre-release versions to be located. Otherwise, pre-release versions are not returned. :return: A :class:`Distribution` instance, or ``None`` if no such distribution could be located. """ result = None r = parse_requirement(requirement) if r is None: # pragma: no cover raise DistlibException('Not a valid requirement: %r' % requirement) scheme = get_scheme(self.scheme) self.matcher = matcher = scheme.matcher(r.requirement) logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__) versions = self.get_project(r.name) if len(versions) > 2: # urls and digests keys are present # sometimes, versions are invalid slist = [] vcls = matcher.version_class for k in versions: if k in ('urls', 'digests'): continue try: if not matcher.match(k): pass # logger.debug('%s did not match %r', matcher, k) else: if prereleases or not vcls(k).is_prerelease: slist.append(k) except Exception: # pragma: no cover logger.warning('error matching %s with %r', matcher, k) pass # slist.append(k) if len(slist) > 1: slist = sorted(slist, key=scheme.key) if slist: logger.debug('sorted list: %s', slist) version = slist[-1] result = versions[version] if result: if r.extras: result.extras = r.extras result.download_urls = versions.get('urls', {}).get(version, set()) d = {} sd = versions.get('digests', {}) for url in result.download_urls: if url in sd: # pragma: no cover d[url] = sd[url] result.digests = d self.matcher = None return result class PyPIRPCLocator(Locator): """ This locator uses XML-RPC to locate distributions. It therefore cannot be used with simple mirrors (that only mirror file content). """ def __init__(self, url, **kwargs): """ Initialise an instance. :param url: The URL to use for XML-RPC. :param kwargs: Passed to the superclass constructor. """ super(PyPIRPCLocator, self).__init__(**kwargs) self.base_url = url self.client = ServerProxy(url, timeout=3.0) def get_distribution_names(self): """ Return all the distribution names known to this locator. """ return set(self.client.list_packages()) def _get_project(self, name): result = {'urls': {}, 'digests': {}} versions = self.client.package_releases(name, True) for v in versions: urls = self.client.release_urls(name, v) data = self.client.release_data(name, v) metadata = Metadata(scheme=self.scheme) metadata.name = data['name'] metadata.version = data['version'] metadata.license = data.get('license') metadata.keywords = data.get('keywords', []) metadata.summary = data.get('summary') dist = Distribution(metadata) if urls: info = urls[0] metadata.source_url = info['url'] dist.digest = self._get_digest(info) dist.locator = self result[v] = dist for info in urls: url = info['url'] digest = self._get_digest(info) result['urls'].setdefault(v, set()).add(url) result['digests'][url] = digest return result class PyPIJSONLocator(Locator): """ This locator uses PyPI's JSON interface. It's very limited in functionality and probably not worth using. """ def __init__(self, url, **kwargs): super(PyPIJSONLocator, self).__init__(**kwargs) self.base_url = ensure_slash(url) def get_distribution_names(self): """ Return all the distribution names known to this locator. """ raise NotImplementedError('Not available from this locator') def _get_project(self, name): result = {'urls': {}, 'digests': {}} url = urljoin(self.base_url, '%s/json' % quote(name)) try: resp = self.opener.open(url) data = resp.read().decode() # for now d = json.loads(data) md = Metadata(scheme=self.scheme) data = d['info'] md.name = data['name'] md.version = data['version'] md.license = data.get('license') md.keywords = data.get('keywords', []) md.summary = data.get('summary') dist = Distribution(md) dist.locator = self # urls = d['urls'] result[md.version] = dist for info in d['urls']: url = info['url'] dist.download_urls.add(url) dist.digests[url] = self._get_digest(info) result['urls'].setdefault(md.version, set()).add(url) result['digests'][url] = self._get_digest(info) # Now get other releases for version, infos in d['releases'].items(): if version == md.version: continue # already done omd = Metadata(scheme=self.scheme) omd.name = md.name omd.version = version odist = Distribution(omd) odist.locator = self result[version] = odist for info in infos: url = info['url'] odist.download_urls.add(url) odist.digests[url] = self._get_digest(info) result['urls'].setdefault(version, set()).add(url) result['digests'][url] = self._get_digest(info) # for info in urls: # md.source_url = info['url'] # dist.digest = self._get_digest(info) # dist.locator = self # for info in urls: # url = info['url'] # result['urls'].setdefault(md.version, set()).add(url) # result['digests'][url] = self._get_digest(info) except Exception as e: self.errors.put(text_type(e)) logger.exception('JSON fetch failed: %s', e) return result class Page(object): """ This class represents a scraped HTML page. """ # The following slightly hairy-looking regex just looks for the contents of # an anchor link, which has an attribute "href" either immediately preceded # or immediately followed by a "rel" attribute. The attribute values can be # declared with double quotes, single quotes or no quotes - which leads to # the length of the expression. _href = re.compile( """ (rel\\s*=\\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\\s\n]*))\\s+)? href\\s*=\\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\\s\n]*)) (\\s+rel\\s*=\\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\\s\n]*)))? """, re.I | re.S | re.X) _base = re.compile(r"""]+)""", re.I | re.S) def __init__(self, data, url): """ Initialise an instance with the Unicode page contents and the URL they came from. """ self.data = data self.base_url = self.url = url m = self._base.search(self.data) if m: self.base_url = m.group(1) _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I) @cached_property def links(self): """ Return the URLs of all the links on a page together with information about their "rel" attribute, for determining which ones to treat as downloads and which ones to queue for further scraping. """ def clean(url): "Tidy up an URL." scheme, netloc, path, params, query, frag = urlparse(url) return urlunparse((scheme, netloc, quote(path), params, query, frag)) result = set() for match in self._href.finditer(self.data): d = match.groupdict('') rel = (d['rel1'] or d['rel2'] or d['rel3'] or d['rel4'] or d['rel5'] or d['rel6']) url = d['url1'] or d['url2'] or d['url3'] url = urljoin(self.base_url, url) url = unescape(url) url = self._clean_re.sub(lambda m: '%%%02x' % ord(m.group(0)), url) result.add((url, rel)) # We sort the result, hoping to bring the most recent versions # to the front result = sorted(result, key=lambda t: t[0], reverse=True) return result class SimpleScrapingLocator(Locator): """ A locator which scrapes HTML pages to locate downloads for a distribution. This runs multiple threads to do the I/O; performance is at least as good as pip's PackageFinder, which works in an analogous fashion. """ # Upper bound, in bytes, on the size a Content-Encoded index response # may inflate to. A compressed response can expand by a very large ratio # (a few hundred KB inflating to hundreds of MB), so decompression is # capped to avoid unbounded memory use from a malicious or compromised # index. Index pages are HTML listings, so this limit is generous. decode_size_limit = 100 * 1024 * 1024 def _decompress(self, data, decompressor): # Decompress and abort if the inflated output would exceed # decode_size_limit. zlib's decompress(max_length) bounds the output # of a single call; ask for one byte past the limit to detect # overflow, then ensure nothing remains unconsumed. limit = self.decode_size_limit produced = decompressor.decompress(data, limit + 1) if len(produced) > limit or decompressor.unconsumed_tail: raise DistlibException('Decompressed index response exceeds ' '%d bytes' % limit) produced += decompressor.flush() if len(produced) > limit: raise DistlibException('Decompressed index response exceeds ' '%d bytes' % limit) return produced def __init__(self, url, timeout=None, num_workers=10, **kwargs): """ Initialise an instance. :param url: The root URL to use for scraping. :param timeout: The timeout, in seconds, to be applied to requests. This defaults to ``None`` (no timeout specified). :param num_workers: The number of worker threads you want to do I/O, This defaults to 10. :param kwargs: Passed to the superclass. """ super(SimpleScrapingLocator, self).__init__(**kwargs) self.base_url = ensure_slash(url) self.timeout = timeout self._page_cache = {} self._seen = set() self._to_fetch = queue.Queue() self._bad_hosts = set() self.skip_externals = False self.num_workers = num_workers self._lock = threading.RLock() # See issue #45: we need to be resilient when the locator is used # in a thread, e.g. with concurrent.futures. We can't use self._lock # as it is for coordinating our internal threads - the ones created # in _prepare_threads. self._gplock = threading.RLock() self.platform_check = False # See issue #112 # These are used to deal with various Content-Encoding schemes. Each # decoder bounds its inflated output to decode_size_limit (see # _decompress) to avoid unbounded memory use from a hostile index. self.decoders = { 'deflate': lambda b: self._decompress(b, zlib.decompressobj()), # wbits 16 + MAX_WBITS selects gzip framing in zlib. 'gzip': lambda b: self._decompress(b, zlib.decompressobj(16 + zlib.MAX_WBITS)), 'none': lambda b: b, } def _prepare_threads(self): """ Threads are created only when get_project is called, and terminate before it returns. They are there primarily to parallelise I/O (i.e. fetching web pages). """ self._threads = [] for i in range(self.num_workers): t = threading.Thread(target=self._fetch) t.daemon = True t.start() self._threads.append(t) def _wait_threads(self): """ Tell all the threads to terminate (by sending a sentinel value) and wait for them to do so. """ # Note that you need two loops, since you can't say which # thread will get each sentinel for t in self._threads: self._to_fetch.put(None) # sentinel for t in self._threads: t.join() self._threads = [] def _get_project(self, name): result = {'urls': {}, 'digests': {}} with self._gplock: self.result = result self.project_name = name url = urljoin(self.base_url, '%s/' % quote(name)) self._seen.clear() self._page_cache.clear() self._prepare_threads() try: logger.debug('Queueing %s', url) self._to_fetch.put(url) self._to_fetch.join() finally: self._wait_threads() del self.result return result platform_dependent = re.compile(r'\b(linux_(i\d86|x86_64|arm\w+)|' r'win(32|_amd64)|macosx_?\d+)\b', re.I) def _is_platform_dependent(self, url): """ Does an URL refer to a platform-specific download? """ return self.platform_dependent.search(url) def _process_download(self, url): """ See if an URL is a suitable download for a project. If it is, register information in the result dictionary (for _get_project) about the specific version it's for. Note that the return value isn't actually used other than as a boolean value. """ if self.platform_check and self._is_platform_dependent(url): info = None else: info = self.convert_url_to_download_info(url, self.project_name) logger.debug('process_download: %s -> %s', url, info) if info: with self._lock: # needed because self.result is shared self._update_version_data(self.result, info) return info def _should_queue(self, link, referrer, rel): """ Determine whether a link URL from a referring page and with a particular "rel" attribute should be queued for scraping. """ scheme, netloc, path, _, _, _ = urlparse(link) if path.endswith(self.source_extensions + self.binary_extensions + self.excluded_extensions): result = False elif self.skip_externals and not link.startswith(self.base_url): result = False elif not referrer.startswith(self.base_url): result = False elif rel not in ('homepage', 'download'): result = False elif scheme not in ('http', 'https', 'ftp'): result = False elif self._is_platform_dependent(link): result = False else: host = netloc.split(':', 1)[0] if host.lower() == 'localhost': result = False else: result = True logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, referrer, result) return result def _fetch(self): """ Get a URL to fetch from the work queue, get the HTML page, examine its links for download candidates and candidates for further scraping. This is a handy method to run in a thread. """ while True: url = self._to_fetch.get() try: if url: page = self.get_page(url) if page is None: # e.g. after an error continue for link, rel in page.links: if link not in self._seen: try: self._seen.add(link) if (not self._process_download(link) and self._should_queue(link, url, rel)): logger.debug('Queueing %s from %s', link, url) self._to_fetch.put(link) except MetadataInvalidError: # e.g. invalid versions pass except Exception as e: # pragma: no cover self.errors.put(text_type(e)) finally: # always do this, to avoid hangs :-) self._to_fetch.task_done() if not url: # logger.debug('Sentinel seen, quitting.') break def get_page(self, url): """ Get the HTML for an URL, possibly from an in-memory cache. XXX TODO Note: this cache is never actually cleared. It's assumed that the data won't get stale over the lifetime of a locator instance (not necessarily true for the default_locator). """ # http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api scheme, netloc, path, _, _, _ = urlparse(url) if scheme == 'file' and os.path.isdir(url2pathname(path)): url = urljoin(ensure_slash(url), 'index.html') if url in self._page_cache: result = self._page_cache[url] logger.debug('Returning %s from cache: %s', url, result) else: host = netloc.split(':', 1)[0] result = None if host in self._bad_hosts: logger.debug('Skipping %s due to bad host %s', url, host) else: req = Request(url, headers={'Accept-encoding': 'identity'}) try: logger.debug('Fetching %s', url) resp = self.opener.open(req, timeout=self.timeout) logger.debug('Fetched %s', url) headers = resp.info() content_type = headers.get('Content-Type', '') if HTML_CONTENT_TYPE.match(content_type): final_url = resp.geturl() data = resp.read() encoding = headers.get('Content-Encoding') if encoding: decoder = self.decoders[encoding] # fail if not found data = decoder(data) encoding = 'utf-8' m = CHARSET.search(content_type) if m: encoding = m.group(1) try: data = data.decode(encoding) except UnicodeError: # pragma: no cover data = data.decode('latin-1') # fallback result = Page(data, final_url) self._page_cache[final_url] = result except HTTPError as e: if e.code != 404: logger.exception('Fetch failed: %s: %s', url, e) except URLError as e: # pragma: no cover logger.exception('Fetch failed: %s: %s', url, e) with self._lock: self._bad_hosts.add(host) except Exception as e: # pragma: no cover logger.exception('Fetch failed: %s: %s', url, e) finally: self._page_cache[url] = result # even if None (failure) return result _distname_re = re.compile(']*>([^<]+)<') def get_distribution_names(self): """ Return all the distribution names known to this locator. """ result = set() page = self.get_page(self.base_url) if not page: raise DistlibException('Unable to get %s' % self.base_url) for match in self._distname_re.finditer(page.data): result.add(match.group(1)) return result class DirectoryLocator(Locator): """ This class locates distributions in a directory tree. """ def __init__(self, path, **kwargs): """ Initialise an instance. :param path: The root of the directory tree to search. :param kwargs: Passed to the superclass constructor, except for: * recursive - if True (the default), subdirectories are recursed into. If False, only the top-level directory is searched, """ self.recursive = kwargs.pop('recursive', True) super(DirectoryLocator, self).__init__(**kwargs) path = os.path.abspath(path) if not os.path.isdir(path): # pragma: no cover raise DistlibException('Not a directory: %r' % path) self.base_dir = path def should_include(self, filename, parent): """ Should a filename be considered as a candidate for a distribution archive? As well as the filename, the directory which contains it is provided, though not used by the current implementation. """ return filename.endswith(self.downloadable_extensions) def _get_project(self, name): result = {'urls': {}, 'digests': {}} for root, dirs, files in os.walk(self.base_dir): for fn in files: if self.should_include(fn, root): fn = os.path.join(root, fn) url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', '')) info = self.convert_url_to_download_info(url, name) if info: self._update_version_data(result, info) if not self.recursive: break return result def get_distribution_names(self): """ Return all the distribution names known to this locator. """ result = set() for root, dirs, files in os.walk(self.base_dir): for fn in files: if self.should_include(fn, root): fn = os.path.join(root, fn) url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', '')) info = self.convert_url_to_download_info(url, None) if info: result.add(info['name']) if not self.recursive: break return result class JSONLocator(Locator): """ This locator uses special extended metadata (not available on PyPI) and is the basis of performant dependency resolution in distlib. Other locators require archive downloads before dependencies can be determined! As you might imagine, that can be slow. """ def get_distribution_names(self): """ Return all the distribution names known to this locator. """ raise NotImplementedError('Not available from this locator') def _get_project(self, name): result = {'urls': {}, 'digests': {}} data = get_project_data(name) if data: for info in data.get('files', []): if info['ptype'] != 'sdist' or info['pyversion'] != 'source': continue # We don't store summary in project metadata as it makes # the data bigger for no benefit during dependency # resolution dist = make_dist(data['name'], info['version'], summary=data.get('summary', 'Placeholder for summary'), scheme=self.scheme) md = dist.metadata md.source_url = info['url'] # TODO SHA256 digest if 'digest' in info and info['digest']: dist.digest = ('md5', info['digest']) md.dependencies = info.get('requirements', {}) dist.exports = info.get('exports', {}) result[dist.version] = dist result['urls'].setdefault(dist.version, set()).add(info['url']) return result class DistPathLocator(Locator): """ This locator finds installed distributions in a path. It can be useful for adding to an :class:`AggregatingLocator`. """ def __init__(self, distpath, **kwargs): """ Initialise an instance. :param distpath: A :class:`DistributionPath` instance to search. """ super(DistPathLocator, self).__init__(**kwargs) assert isinstance(distpath, DistributionPath) self.distpath = distpath def _get_project(self, name): dist = self.distpath.get_distribution(name) if dist is None: result = {'urls': {}, 'digests': {}} else: result = { dist.version: dist, 'urls': { dist.version: set([dist.source_url]) }, 'digests': { dist.version: set([None]) } } return result class AggregatingLocator(Locator): """ This class allows you to chain and/or merge a list of locators. """ def __init__(self, *locators, **kwargs): """ Initialise an instance. :param locators: The list of locators to search. :param kwargs: Passed to the superclass constructor, except for: * merge - if False (the default), the first successful search from any of the locators is returned. If True, the results from all locators are merged (this can be slow). """ self.merge = kwargs.pop('merge', False) self.locators = locators super(AggregatingLocator, self).__init__(**kwargs) def clear_cache(self): super(AggregatingLocator, self).clear_cache() for locator in self.locators: locator.clear_cache() def _set_scheme(self, value): self._scheme = value for locator in self.locators: locator.scheme = value scheme = property(Locator.scheme.fget, _set_scheme) def _get_project(self, name): result = {} for locator in self.locators: d = locator.get_project(name) if d: if self.merge: files = result.get('urls', {}) digests = result.get('digests', {}) # next line could overwrite result['urls'], result['digests'] result.update(d) df = result.get('urls') if files and df: for k, v in files.items(): if k in df: df[k] |= v else: df[k] = v dd = result.get('digests') if digests and dd: dd.update(digests) else: # See issue #18. If any dists are found and we're looking # for specific constraints, we only return something if # a match is found. For example, if a DirectoryLocator # returns just foo (1.0) while we're looking for # foo (>= 2.0), we'll pretend there was nothing there so # that subsequent locators can be queried. Otherwise we # would just return foo (1.0) which would then lead to a # failure to find foo (>= 2.0), because other locators # weren't searched. Note that this only matters when # merge=False. if self.matcher is None: found = True else: found = False for k in d: if self.matcher.match(k): found = True break if found: result = d break return result def get_distribution_names(self): """ Return all the distribution names known to this locator. """ result = set() for locator in self.locators: try: result |= locator.get_distribution_names() except NotImplementedError: pass return result # We use a legacy scheme simply because most of the dists on PyPI use legacy # versions which don't conform to PEP 440. default_locator = AggregatingLocator( # JSONLocator(), # don't use as PEP 426 is withdrawn SimpleScrapingLocator('https://pypi.org/simple/', timeout=3.0), scheme='legacy') locate = default_locator.locate class DependencyFinder(object): """ Locate dependencies for distributions. """ def __init__(self, locator=None): """ Initialise an instance, using the specified locator to locate distributions. """ self.locator = locator or default_locator self.scheme = get_scheme(self.locator.scheme) def add_distribution(self, dist): """ Add a distribution to the finder. This will update internal information about who provides what. :param dist: The distribution to add. """ logger.debug('adding distribution %s', dist) name = dist.key self.dists_by_name[name] = dist self.dists[(name, dist.version)] = dist for p in dist.provides: name, version = parse_name_and_version(p) logger.debug('Add to provided: %s, %s, %s', name, version, dist) self.provided.setdefault(name, set()).add((version, dist)) def remove_distribution(self, dist): """ Remove a distribution from the finder. This will update internal information about who provides what. :param dist: The distribution to remove. """ logger.debug('removing distribution %s', dist) name = dist.key del self.dists_by_name[name] del self.dists[(name, dist.version)] for p in dist.provides: name, version = parse_name_and_version(p) logger.debug('Remove from provided: %s, %s, %s', name, version, dist) s = self.provided[name] s.remove((version, dist)) if not s: del self.provided[name] def get_matcher(self, reqt): """ Get a version matcher for a requirement. :param reqt: The requirement :type reqt: str :return: A version matcher (an instance of :class:`distlib.version.Matcher`). """ try: matcher = self.scheme.matcher(reqt) except UnsupportedVersionError: # pragma: no cover # XXX compat-mode if cannot read the version name = reqt.split()[0] matcher = self.scheme.matcher(name) return matcher def find_providers(self, reqt): """ Find the distributions which can fulfill a requirement. :param reqt: The requirement. :type reqt: str :return: A set of distribution which can fulfill the requirement. """ matcher = self.get_matcher(reqt) name = matcher.key # case-insensitive result = set() provided = self.provided if name in provided: for version, provider in provided[name]: try: match = matcher.match(version) except UnsupportedVersionError: match = False if match: result.add(provider) break return result def try_to_replace(self, provider, other, problems): """ Attempt to replace one provider with another. This is typically used when resolving dependencies from multiple sources, e.g. A requires (B >= 1.0) while C requires (B >= 1.1). For successful replacement, ``provider`` must meet all the requirements which ``other`` fulfills. :param provider: The provider we are trying to replace with. :param other: The provider we're trying to replace. :param problems: If False is returned, this will contain what problems prevented replacement. This is currently a tuple of the literal string 'cantreplace', ``provider``, ``other`` and the set of requirements that ``provider`` couldn't fulfill. :return: True if we can replace ``other`` with ``provider``, else False. """ rlist = self.reqts[other] unmatched = set() for s in rlist: matcher = self.get_matcher(s) if not matcher.match(provider.version): unmatched.add(s) if unmatched: # can't replace other with provider problems.add(('cantreplace', provider, other, frozenset(unmatched))) result = False else: # can replace other with provider self.remove_distribution(other) del self.reqts[other] for s in rlist: self.reqts.setdefault(provider, set()).add(s) self.add_distribution(provider) result = True return result def find(self, requirement, meta_extras=None, prereleases=False): """ Find a distribution and all distributions it depends on. :param requirement: The requirement specifying the distribution to find, or a Distribution instance. :param meta_extras: A list of meta extras such as :test:, :build: and so on. :param prereleases: If ``True``, allow pre-release versions to be returned - otherwise, don't return prereleases unless they're all that's available. Return a set of :class:`Distribution` instances and a set of problems. The distributions returned should be such that they have the :attr:`required` attribute set to ``True`` if they were from the ``requirement`` passed to ``find()``, and they have the :attr:`build_time_dependency` attribute set to ``True`` unless they are post-installation dependencies of the ``requirement``. The problems should be a tuple consisting of the string ``'unsatisfied'`` and the requirement which couldn't be satisfied by any distribution known to the locator. """ self.provided = {} self.dists = {} self.dists_by_name = {} self.reqts = {} meta_extras = set(meta_extras or []) if ':*:' in meta_extras: meta_extras.remove(':*:') # :meta: and :run: are implicitly included meta_extras |= set([':test:', ':build:', ':dev:']) if isinstance(requirement, Distribution): dist = odist = requirement logger.debug('passed %s as requirement', odist) else: dist = odist = self.locator.locate(requirement, prereleases=prereleases) if dist is None: raise DistlibException('Unable to locate %r' % requirement) logger.debug('located %s', odist) dist.requested = True problems = set() todo = set([dist]) install_dists = set([odist]) while todo: dist = todo.pop() name = dist.key # case-insensitive if name not in self.dists_by_name: self.add_distribution(dist) else: # import pdb; pdb.set_trace() other = self.dists_by_name[name] if other != dist: self.try_to_replace(dist, other, problems) ireqts = dist.run_requires | dist.meta_requires sreqts = dist.build_requires ereqts = set() if meta_extras and dist in install_dists: for key in ('test', 'build', 'dev'): e = ':%s:' % key if e in meta_extras: ereqts |= getattr(dist, '%s_requires' % key) all_reqts = ireqts | sreqts | ereqts for r in all_reqts: providers = self.find_providers(r) if not providers: logger.debug('No providers found for %r', r) provider = self.locator.locate(r, prereleases=prereleases) # If no provider is found and we didn't consider # prereleases, consider them now. if provider is None and not prereleases: provider = self.locator.locate(r, prereleases=True) if provider is None: logger.debug('Cannot satisfy %r', r) problems.add(('unsatisfied', r)) else: n, v = provider.key, provider.version if (n, v) not in self.dists: todo.add(provider) providers.add(provider) if r in ireqts and dist in install_dists: install_dists.add(provider) logger.debug('Adding %s to install_dists', provider.name_and_version) for p in providers: name = p.key if name not in self.dists_by_name: self.reqts.setdefault(p, set()).add(r) else: other = self.dists_by_name[name] if other != p: # see if other can be replaced by p self.try_to_replace(p, other, problems) dists = set(self.dists.values()) for dist in dists: dist.build_time_dependency = dist not in install_dists if dist.build_time_dependency: logger.debug('%s is a build-time dependency only.', dist.name_and_version) logger.debug('find done for %s', odist) return dists, problems ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780570534.0 distlib-0.4.3/distlib/manifest.py0000664000175000017500000003251315210254646016073 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2026 Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # """ Class representing the list of files in a distribution. Equivalent to distutils.filelist, but fixes some problems. """ import fnmatch import logging import os import re import sys from . import DistlibException from .compat import fsdecode from .util import convert_path __all__ = ['Manifest'] logger = logging.getLogger(__name__) # a \ followed by some spaces + EOL _COLLAPSE_PATTERN = re.compile('\\\\w*\n', re.M) _COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S) # # Due to the different results returned by fnmatch.translate, we need # to do slightly different processing for Python 2.7 and 3.2 ... this needed # to be brought in for Python 3.6 onwards. # _PYTHON_VERSION = sys.version_info[:2] class Manifest(object): """ A list of files built by exploring the filesystem and filtered by applying various patterns to what we find there. """ def __init__(self, base=None): """ Initialise an instance. :param base: The base directory to explore under. """ self.base = os.path.abspath(os.path.normpath(base or os.getcwd())) self.prefix = self.base + os.sep self.allfiles = None self.files = set() # # Public API # def findall(self): """Find all files under the base and set ``allfiles`` to the absolute pathnames of files found. """ from stat import S_ISREG, S_ISDIR, S_ISLNK self.allfiles = allfiles = [] root = self.base stack = [root] pop = stack.pop push = stack.append while stack: root = pop() names = os.listdir(root) for name in names: fullname = os.path.join(root, name) # Avoid excess stat calls -- just one will do, thank you! stat = os.lstat(fullname) mode = stat.st_mode if S_ISREG(mode): allfiles.append(fsdecode(fullname)) elif S_ISDIR(mode) and not S_ISLNK(mode): push(fullname) def add(self, item): """ Add a file to the manifest. :param item: The pathname to add. This can be relative to the base. """ if not item.startswith(self.prefix): item = os.path.join(self.base, item) self.files.add(os.path.normpath(item)) def add_many(self, items): """ Add a list of files to the manifest. :param items: The pathnames to add. These can be relative to the base. """ for item in items: self.add(item) def sorted(self, wantdirs=False): """ Return sorted files in directory order """ def add_dir(dirs, d): dirs.add(d) logger.debug('add_dir added %s', d) if d != self.base: parent, _ = os.path.split(d) assert parent not in ('', '/') add_dir(dirs, parent) result = set(self.files) # make a copy! if wantdirs: dirs = set() for f in result: add_dir(dirs, os.path.dirname(f)) result |= dirs return [os.path.join(*path_tuple) for path_tuple in sorted(os.path.split(path) for path in result)] def clear(self): """Clear all collected files.""" self.files = set() self.allfiles = [] def process_directive(self, directive): """ Process a directive which either adds some files from ``allfiles`` to ``files``, or removes some files from ``files``. :param directive: The directive to process. This should be in a format compatible with distutils ``MANIFEST.in`` files: http://docs.python.org/distutils/sourcedist.html#commands """ # Parse the line: split it up, make sure the right number of words # is there, and return the relevant words. 'action' is always # defined: it's the first word of the line. Which of the other # three are defined depends on the action; it'll be either # patterns, (dir and patterns), or (dirpattern). action, patterns, thedir, dirpattern = self._parse_directive(directive) # OK, now we know that the action is valid and we have the # right number of words on the line for that action -- so we # can proceed with minimal error-checking. if action == 'include': for pattern in patterns: if not self._include_pattern(pattern, anchor=True): logger.warning('no files found matching %r', pattern) elif action == 'exclude': for pattern in patterns: self._exclude_pattern(pattern, anchor=True) elif action == 'global-include': for pattern in patterns: if not self._include_pattern(pattern, anchor=False): logger.warning('no files found matching %r ' 'anywhere in distribution', pattern) elif action == 'global-exclude': for pattern in patterns: self._exclude_pattern(pattern, anchor=False) elif action == 'recursive-include': for pattern in patterns: if not self._include_pattern(pattern, prefix=thedir): logger.warning('no files found matching %r ' 'under directory %r', pattern, thedir) elif action == 'recursive-exclude': for pattern in patterns: self._exclude_pattern(pattern, prefix=thedir) elif action == 'graft': if not self._include_pattern(None, prefix=dirpattern): logger.warning('no directories found matching %r', dirpattern) elif action == 'prune': if not self._exclude_pattern(None, prefix=dirpattern): logger.warning('no previously-included directories found ' 'matching %r', dirpattern) else: # pragma: no cover # This should never happen, as it should be caught in # _parse_template_line raise DistlibException('invalid action %r' % action) # # Private API # def _parse_directive(self, directive): """ Validate a directive. :param directive: The directive to validate. :return: A tuple of action, patterns, thedir, dir_patterns """ words = directive.split() if len(words) == 1 and words[0] not in ('include', 'exclude', 'global-include', 'global-exclude', 'recursive-include', 'recursive-exclude', 'graft', 'prune'): # no action given, let's use the default 'include' words.insert(0, 'include') action = words[0] patterns = thedir = dir_pattern = None if action in ('include', 'exclude', 'global-include', 'global-exclude'): if len(words) < 2: raise DistlibException('%r expects ...' % action) patterns = [convert_path(word) for word in words[1:]] elif action in ('recursive-include', 'recursive-exclude'): if len(words) < 3: raise DistlibException('%r expects ...' % action) thedir = convert_path(words[1]) patterns = [convert_path(word) for word in words[2:]] elif action in ('graft', 'prune'): if len(words) != 2: raise DistlibException('%r expects a single ' % action) dir_pattern = convert_path(words[1]) else: raise DistlibException('unknown action %r' % action) return action, patterns, thedir, dir_pattern def _include_pattern(self, pattern, anchor=True, prefix=None, is_regex=False): """Select strings (presumably filenames) from 'self.files' that match 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not quite the same as implemented by the 'fnmatch' module: '*' and '?' match non-special characters, where "special" is platform-dependent: slash on Unix; colon, slash, and backslash on DOS/Windows; and colon on Mac OS. If 'anchor' is true (the default), then the pattern match is more stringent: "*.py" will match "foo.py" but not "foo/bar.py". If 'anchor' is false, both of these will match. If 'prefix' is supplied, then only filenames starting with 'prefix' (itself a pattern) and ending with 'pattern', with anything in between them, will match. 'anchor' is ignored in this case. If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and 'pattern' is assumed to be either a string containing a regex or a regex object -- no translation is done, the regex is just compiled and used as-is. Selected strings will be added to self.files. Return True if files are found. """ # XXX docstring lying about what the special chars are? found = False pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex) # delayed loading of allfiles list if self.allfiles is None: self.findall() for name in self.allfiles: if pattern_re.search(name): self.files.add(name) found = True return found def _exclude_pattern(self, pattern, anchor=True, prefix=None, is_regex=False): """Remove strings (presumably filenames) from 'files' that match 'pattern'. Other parameters are the same as for 'include_pattern()', above. The list 'self.files' is modified in place. Return True if files are found. This API is public to allow e.g. exclusion of SCM subdirs, e.g. when packaging source distributions """ found = False pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex) for f in list(self.files): if pattern_re.search(f): self.files.remove(f) found = True return found def _translate_pattern(self, pattern, anchor=True, prefix=None, is_regex=False): """Translate a shell-like wildcard pattern to a compiled regular expression. Return the compiled regex. If 'is_regex' true, then 'pattern' is directly compiled to a regex (if it's a string) or just returned as-is (assumes it's a regex object). """ if is_regex: if isinstance(pattern, str): return re.compile(pattern) else: return pattern if _PYTHON_VERSION > (3, 2): # ditch start and end characters start, _, end = self._glob_to_re('_').partition('_') if pattern: pattern_re = self._glob_to_re(pattern) if _PYTHON_VERSION > (3, 2): assert pattern_re.startswith(start) and pattern_re.endswith(end) else: pattern_re = '' base = re.escape(os.path.join(self.base, '')) if prefix is not None: # ditch end of pattern character if _PYTHON_VERSION <= (3, 2): empty_pattern = self._glob_to_re('') prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)] else: prefix_re = self._glob_to_re(prefix) assert prefix_re.startswith(start) and prefix_re.endswith(end) prefix_re = prefix_re[len(start):len(prefix_re) - len(end)] sep = os.sep if os.sep == '\\': sep = r'\\' if _PYTHON_VERSION <= (3, 2): pattern_re = '^' + base + sep.join((prefix_re, '.*' + pattern_re)) else: pattern_re = pattern_re[len(start):len(pattern_re) - len(end)] pattern_re = r'%s%s%s%s.*%s%s' % (start, base, prefix_re, sep, pattern_re, end) else: # no prefix -- respect anchor flag if anchor: if _PYTHON_VERSION <= (3, 2): pattern_re = '^' + base + pattern_re else: pattern_re = r'%s%s%s' % (start, base, pattern_re[len(start):]) return re.compile(pattern_re) def _glob_to_re(self, pattern): """Translate a shell-like glob pattern to a regular expression. Return a string containing the regex. Differs from 'fnmatch.translate()' in that '*' does not match "special characters" (which are platform-specific). """ pattern_re = fnmatch.translate(pattern) # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, # and by extension they shouldn't match such "special characters" under # any OS. So change all non-escaped dots in the RE to match any # character except the special characters (currently: just os.sep). sep = os.sep if os.sep == '\\': # we're using a regex to manipulate a regex, so we need # to escape the backslash twice sep = r'\\\\' escaped = r'\1[^%s]' % sep pattern_re = re.sub(r'((? y, '!=': lambda x, y: x != y, '<': lambda x, y: x < y, '<=': lambda x, y: x == y or x < y, '>': lambda x, y: x > y, '>=': lambda x, y: x == y or x > y, 'and': lambda x, y: x and y, 'or': lambda x, y: x or y, 'in': lambda x, y: x in y, 'not in': lambda x, y: x not in y, } def evaluate(self, expr, context): """ Evaluate a marker expression returned by the :func:`parse_requirement` function in the specified context. """ if isinstance(expr, string_types): if expr[0] in '\'"': result = expr[1:-1] else: if expr not in context: raise SyntaxError('unknown variable: %s' % expr) result = context[expr] else: assert isinstance(expr, dict) op = expr['op'] if op not in self.operations: raise NotImplementedError('op not implemented: %s' % op) elhs = expr['lhs'] erhs = expr['rhs'] if _is_literal(expr['lhs']) and _is_literal(expr['rhs']): raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs)) lhs = self.evaluate(elhs, context) rhs = self.evaluate(erhs, context) if ((_is_version_marker(elhs) or _is_version_marker(erhs)) and op in ('<', '<=', '>', '>=', '===', '==', '!=', '~=')): lhs = LV(lhs) rhs = LV(rhs) elif _is_version_marker(elhs) and op in ('in', 'not in'): lhs = LV(lhs) rhs = _get_versions(rhs) result = self.operations[op](lhs, rhs) return result _DIGITS = re.compile(r'\d+\.\d+') def default_context(): def format_full_version(info): version = '%s.%s.%s' % (info.major, info.minor, info.micro) kind = info.releaselevel if kind != 'final': version += kind[0] + str(info.serial) return version if hasattr(sys, 'implementation'): implementation_version = format_full_version(sys.implementation.version) implementation_name = sys.implementation.name else: implementation_version = '0' implementation_name = '' ppv = platform.python_version() m = _DIGITS.match(ppv) pv = m.group(0) result = { 'implementation_name': implementation_name, 'implementation_version': implementation_version, 'os_name': os.name, 'platform_machine': platform.machine(), 'platform_python_implementation': platform.python_implementation(), 'platform_release': platform.release(), 'platform_system': platform.system(), 'platform_version': platform.version(), 'platform_in_venv': str(in_venv()), 'python_full_version': ppv, 'python_version': pv, 'sys_platform': sys.platform, } return result DEFAULT_CONTEXT = default_context() del default_context evaluator = Evaluator() def interpret_parsed(expr, execution_context=None): context = dict(DEFAULT_CONTEXT) if execution_context: context.update(execution_context) return evaluator.evaluate(expr, context) def interpret(marker, execution_context=None): """ Interpret a marker and return a result depending on environment. :param marker: The marker to interpret. :type marker: str :param execution_context: The context used for name lookup. :type execution_context: mapping """ try: expr, rest = parse_marker(marker) except Exception as e: raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e)) if rest and rest[0] != '#': raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest)) return interpret_parsed(expr, execution_context) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780732793.0 distlib-0.4.3/distlib/metadata.py0000664000175000017500000011517515210751571016052 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2026 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # """Implementation of the Metadata for Python packages PEPs. Supports metadata formats 1.0, 1.1, 1.2, 1.3/2.1, 2.2, 2,3, 2.4, 2.5. """ from __future__ import unicode_literals import codecs from email import message_from_file import json import logging import re from . import DistlibException, __version__ from .compat import StringIO, string_types, text_type from .markers import interpret from .util import extract_by_key, get_extras from .version import get_scheme, PEP440_VERSION_RE logger = logging.getLogger(__name__) class MetadataMissingError(DistlibException): """A required metadata is missing""" class MetadataConflictError(DistlibException): """Attempt to read or write metadata fields that are conflictual.""" class MetadataUnrecognizedVersionError(DistlibException): """Unknown metadata version number.""" class MetadataInvalidError(DistlibException): """A metadata value is invalid""" # public API of this module __all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION'] # Encoding used for the PKG-INFO files PKG_INFO_ENCODING = 'utf-8' # preferred version. Hopefully will be changed # to 1.2 once PEP 345 is supported everywhere PKG_INFO_PREFERRED_VERSION = '1.1' _LINE_PREFIX_1_2 = re.compile('\n \\|') _LINE_PREFIX_PRE_1_2 = re.compile('\n ') _241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Summary', 'Description', 'Keywords', 'Home-page', 'Author', 'Author-email', 'License') _314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description', 'Keywords', 'Home-page', 'Author', 'Author-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes', 'Provides', 'Requires') _314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', 'Download-URL') _345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description', 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Requires-External') _345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Obsoletes-Dist', 'Requires-External', 'Maintainer', 'Maintainer-email', 'Project-URL') _426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description', 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Requires-External', 'Private-Version', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension', 'Provides-Extra') _426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension') # See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in # the metadata. Include them in the tuple literal below to allow them # (for now). # Ditto for Obsoletes - see issue #140. _566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires', 'Provides', 'Obsoletes') _566_MARKERS = ('Description-Content-Type', ) _643_MARKERS = ('Dynamic',) _643_FIELDS = _566_FIELDS + _643_MARKERS _639_MARKERS = ('License-Expression', 'License-File') _639_FIELDS = _643_FIELDS + _639_MARKERS _794_MARKERS = ('Import-Name', 'Import-Namespace') _794_FIELDS = _639_FIELDS + _794_MARKERS _ALL_FIELDS = set() _ALL_FIELDS.update(_241_FIELDS) _ALL_FIELDS.update(_314_FIELDS) _ALL_FIELDS.update(_345_FIELDS) _ALL_FIELDS.update(_426_FIELDS) _ALL_FIELDS.update(_566_FIELDS) _ALL_FIELDS.update(_643_FIELDS) _ALL_FIELDS.update(_639_FIELDS) _ALL_FIELDS.update(_794_FIELDS) EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''') def _version2fieldlist(version): if version == '1.0': return _241_FIELDS elif version == '1.1': return _314_FIELDS elif version == '1.2': return _345_FIELDS elif version in ('1.3', '2.1'): # avoid adding field names if already there return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS) elif version == '2.0': raise ValueError('Metadata 2.0 is withdrawn and not supported') # return _426_FIELDS elif version in ('2.2', '2.3'): return _643_FIELDS elif version == '2.4': return _639_FIELDS elif version == '2.5': return _794_FIELDS raise MetadataUnrecognizedVersionError(version) def _best_version(fields): """Detect the best version depending on the fields used.""" def _has_marker(keys, markers): return any(marker in keys for marker in markers) keys = [key for key, value in fields.items() if value not in ([], 'UNKNOWN', None)] possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.1', '2.2', '2.4', '2.5'] # 2.0 removed # first let's try to see if a field is not part of one of the version for key in keys: if key not in _241_FIELDS and '1.0' in possible_versions: possible_versions.remove('1.0') logger.debug('Removed 1.0 due to %s', key) if key not in _314_FIELDS and '1.1' in possible_versions: possible_versions.remove('1.1') logger.debug('Removed 1.1 due to %s', key) if key not in _345_FIELDS and '1.2' in possible_versions: possible_versions.remove('1.2') logger.debug('Removed 1.2 due to %s', key) if key not in _566_FIELDS and '1.3' in possible_versions: possible_versions.remove('1.3') logger.debug('Removed 1.3 due to %s', key) if key not in _566_FIELDS and '2.1' in possible_versions: if key != 'Description': # In 2.1, description allowed after headers possible_versions.remove('2.1') logger.debug('Removed 2.1 due to %s', key) if key not in _643_FIELDS and '2.2' in possible_versions: possible_versions.remove('2.2') logger.debug('Removed 2.2 due to %s', key) if key not in _639_FIELDS and '2.4' in possible_versions: possible_versions.remove('2.4') logger.debug('Removed 2.4 due to %s', key) if key not in _794_FIELDS and '2.5' in possible_versions: possible_versions.remove('2.5') logger.debug('Removed 2.5 due to %s', key) # possible_version contains qualified versions if len(possible_versions) == 1: return possible_versions[0] # found ! elif len(possible_versions) == 0: logger.debug('Out of options - unknown metadata set: %s', fields) raise MetadataConflictError('Unknown metadata set') # let's see if one unique marker is found is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS) is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS) is_2_1 = '2.1' in possible_versions and _has_marker(keys, _566_MARKERS) # is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS) is_2_2 = '2.2' in possible_versions and _has_marker(keys, _643_MARKERS) is_2_4 = '2.4' in possible_versions and _has_marker(keys, _639_MARKERS) is_2_5 = '2.5' in possible_versions and _has_marker(keys, _794_MARKERS) if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_2) + int(is_2_4) + int(is_2_5) > 1: raise MetadataConflictError('You used incompatible 1.1/1.2/2.1/2.2/2.4/2.5 fields') # we have the choice, 1.0, or 1.2, 2.1, 2.2, 2.4 or 2.5 # - 1.0 has a broken Summary field but works with all tools # - 1.1 is to avoid # - 1.2 fixes Summary but has little adoption # - 2.1 adds more features # - 2.5 is the latest if not (is_1_1 or is_1_2 or is_2_1 or is_2_2 or is_2_4 or is_2_5): # we couldn't find any specific marker if PKG_INFO_PREFERRED_VERSION in possible_versions: return PKG_INFO_PREFERRED_VERSION if is_1_1: return '1.1' if is_1_2: return '1.2' if is_2_1: return '2.1' if is_2_2: return '2.2' if is_2_4: return '2.4' return '2.5' # This follows the rules about transforming keys as described in # https://www.python.org/dev/peps/pep-0566/#id17 _ATTR2FIELD = {name.lower().replace("-", "_"): name for name in _ALL_FIELDS} _FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()} _PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist') _VERSIONS_FIELDS = ('Requires-Python', ) _VERSION_FIELDS = ('Version', ) _LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', 'Requires', 'Provides', 'Obsoletes-Dist', 'Provides-Dist', 'Requires-Dist', 'Requires-External', 'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist', 'Provides-Extra', 'Extension', 'License-File', 'Import-Name', 'Import-Namespace') _LISTTUPLEFIELDS = ('Project-URL', ) _ELEMENTSFIELD = ('Keywords', ) _UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description') _MISSING = object() _FILESAFE = re.compile('[^A-Za-z0-9.]+') def _get_name_and_version(name, version, for_filename=False): """Return the distribution name with version. If for_filename is true, return a filename-escaped form.""" if for_filename: # For both name and version any runs of non-alphanumeric or '.' # characters are replaced with a single '-'. Additionally any # spaces in the version string become '.' name = _FILESAFE.sub('-', name) version = _FILESAFE.sub('-', version.replace(' ', '.')) return '%s-%s' % (name, version) class LegacyMetadata(object): """The legacy metadata of a release. Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can instantiate the class with one of these arguments (or none): - *path*, the path to a metadata file - *fileobj* give a file-like object with metadata as content - *mapping* is a dict-like object - *scheme* is a version scheme name """ # TODO document the mapping API and UNKNOWN default key def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'): if [path, fileobj, mapping].count(None) < 2: raise TypeError('path, fileobj and mapping are exclusive') self._fields = {} self.requires_files = [] self._dependencies = None self.scheme = scheme if path is not None: self.read(path) elif fileobj is not None: self.read_file(fileobj) elif mapping is not None: self.update(mapping) self.set_metadata_version() def set_metadata_version(self): self._fields['Metadata-Version'] = _best_version(self._fields) def _write_field(self, fileobj, name, value): fileobj.write('%s: %s\n' % (name, value)) def __getitem__(self, name): return self.get(name) def __setitem__(self, name, value): return self.set(name, value) def __delitem__(self, name): field_name = self._convert_name(name) try: del self._fields[field_name] except KeyError: raise KeyError(name) def __contains__(self, name): return (name in self._fields or self._convert_name(name) in self._fields) def _convert_name(self, name): if name in _ALL_FIELDS: return name name = name.replace('-', '_').lower() return _ATTR2FIELD.get(name, name) def _default_value(self, name): if name in _LISTFIELDS or name in _ELEMENTSFIELD: return [] return 'UNKNOWN' def _remove_line_prefix(self, value): if self.metadata_version in ('1.0', '1.1'): return _LINE_PREFIX_PRE_1_2.sub('\n', value) else: return _LINE_PREFIX_1_2.sub('\n', value) def __getattr__(self, name): if name in _ATTR2FIELD: return self[name] raise AttributeError(name) # # Public API # def get_fullname(self, filesafe=False): """ Return the distribution name with version. If filesafe is true, return a filename-escaped form. """ return _get_name_and_version(self['Name'], self['Version'], filesafe) def is_field(self, name): """return True if name is a valid metadata key""" name = self._convert_name(name) return name in _ALL_FIELDS def is_multi_field(self, name): name = self._convert_name(name) return name in _LISTFIELDS def read(self, filepath): """Read the metadata values from a file path.""" fp = codecs.open(filepath, 'r', encoding='utf-8') try: self.read_file(fp) finally: fp.close() def read_file(self, fileob): """Read the metadata values from a file object.""" msg = message_from_file(fileob) self._fields['Metadata-Version'] = msg['metadata-version'] # When reading, get all the fields we can for field in _ALL_FIELDS: if field not in msg: continue if field in _LISTFIELDS: # we can have multiple lines values = msg.get_all(field) if field in _LISTTUPLEFIELDS and values is not None: values = [tuple(value.split(',')) for value in values] self.set(field, values) else: # single line value = msg[field] if value is not None and value != 'UNKNOWN': self.set(field, value) # PEP 566 specifies that the body be used for the description, if # available body = msg.get_payload() self["Description"] = body if body else self["Description"] # logger.debug('Attempting to set metadata for %s', self) # self.set_metadata_version() def write(self, filepath, skip_unknown=False): """Write the metadata fields to filepath.""" fp = codecs.open(filepath, 'w', encoding='utf-8') try: self.write_file(fp, skip_unknown) finally: fp.close() def write_file(self, fileobject, skip_unknown=False): """Write the PKG-INFO format data to a file object.""" self.set_metadata_version() for field in _version2fieldlist(self['Metadata-Version']): values = self.get(field) if skip_unknown and values in ('UNKNOWN', [], ['UNKNOWN']): continue if field in _ELEMENTSFIELD: self._write_field(fileobject, field, ','.join(values)) continue if field not in _LISTFIELDS: if field == 'Description': if self.metadata_version in ('1.0', '1.1'): values = values.replace('\n', '\n ') else: values = values.replace('\n', '\n |') values = [values] if field in _LISTTUPLEFIELDS: values = [','.join(value) for value in values] for value in values: self._write_field(fileobject, field, value) def update(self, other=None, **kwargs): """Set metadata values from the given iterable `other` and kwargs. Behavior is like `dict.update`: If `other` has a ``keys`` method, they are looped over and ``self[key]`` is assigned ``other[key]``. Else, ``other`` is an iterable of ``(key, value)`` iterables. Keys that don't match a metadata field or that have an empty value are dropped. """ def _set(key, value): if key in _ATTR2FIELD and value: self.set(self._convert_name(key), value) if not other: # other is None or empty container pass elif hasattr(other, 'keys'): for k in other.keys(): _set(k, other[k]) else: for k, v in other: _set(k, v) if kwargs: for k, v in kwargs.items(): _set(k, v) def set(self, name, value): """Control then set a metadata field.""" name = self._convert_name(name) if ((name in _ELEMENTSFIELD or name == 'Platform') and not isinstance(value, (list, tuple))): if isinstance(value, string_types): value = [v.strip() for v in value.split(',')] else: value = [] elif (name in _LISTFIELDS and not isinstance(value, (list, tuple))): if isinstance(value, string_types): value = [value] else: value = [] if logger.isEnabledFor(logging.WARNING): project_name = self['Name'] scheme = get_scheme(self.scheme) if name in _PREDICATE_FIELDS and value is not None: for v in value: # check that the values are valid if not scheme.is_valid_matcher(v.split(';')[0]): logger.warning("'%s': '%s' is not valid (field '%s')", project_name, v, name) # FIXME this rejects UNKNOWN, is that right? elif name in _VERSIONS_FIELDS and value is not None: if not scheme.is_valid_constraint_list(value): logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name) elif name in _VERSION_FIELDS and value is not None: if not scheme.is_valid_version(value): logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name) if name in _UNICODEFIELDS: if name == 'Description': value = self._remove_line_prefix(value) self._fields[name] = value def get(self, name, default=_MISSING): """Get a metadata field.""" name = self._convert_name(name) if name not in self._fields: if default is _MISSING: default = self._default_value(name) return default if name in _UNICODEFIELDS: value = self._fields[name] return value elif name in _LISTFIELDS: value = self._fields[name] if value is None: return [] res = [] for val in value: if name not in _LISTTUPLEFIELDS: res.append(val) else: # That's for Project-URL res.append((val[0], val[1])) return res elif name in _ELEMENTSFIELD: value = self._fields[name] if isinstance(value, string_types): return value.split(',') return self._fields[name] def check(self, strict=False): """Check if the metadata is compliant. If strict is True then raise if no Name or Version are provided""" self.set_metadata_version() # XXX should check the versions (if the file was loaded) missing, warnings = [], [] for attr in ('Name', 'Version'): # required by PEP 345 if attr not in self: missing.append(attr) if strict and missing != []: msg = 'missing required metadata: %s' % ', '.join(missing) raise MetadataMissingError(msg) for attr in ('Home-page', 'Author'): if attr not in self: missing.append(attr) # checking metadata 1.2 (XXX needs to check 1.1, 1.0) if self['Metadata-Version'] != '1.2': return missing, warnings scheme = get_scheme(self.scheme) def are_valid_constraints(value): for v in value: if not scheme.is_valid_matcher(v.split(';')[0]): return False return True for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints), (_VERSIONS_FIELDS, scheme.is_valid_constraint_list), (_VERSION_FIELDS, scheme.is_valid_version)): for field in fields: value = self.get(field, None) if value is not None and not controller(value): warnings.append("Wrong value for '%s': %s" % (field, value)) return missing, warnings def todict(self, skip_missing=False): """Return fields as a dict. Field names will be converted to use the underscore-lowercase style instead of hyphen-mixed case (i.e. home_page instead of Home-page). This is as per https://www.python.org/dev/peps/pep-0566/#id17. """ self.set_metadata_version() fields = _version2fieldlist(self['Metadata-Version']) data = {} for field_name in fields: if not skip_missing or field_name in self._fields: key = _FIELD2ATTR[field_name] if key != 'project_url': data[key] = self[field_name] else: data[key] = [','.join(u) for u in self[field_name]] return data def add_requirements(self, requirements): if self['Metadata-Version'] == '1.1': # we can't have 1.1 metadata *and* Setuptools requires for field in ('Obsoletes', 'Requires', 'Provides'): if field in self: del self[field] self['Requires-Dist'] += requirements # Mapping API # TODO could add iter* variants def keys(self): return list(_version2fieldlist(self['Metadata-Version'])) def __iter__(self): for key in self.keys(): yield key def values(self): return [self[key] for key in self.keys()] def items(self): return [(key, self[key]) for key in self.keys()] def __repr__(self): return '<%s %s %s>' % (self.__class__.__name__, self.name, self.version) METADATA_FILENAME = 'pydist.json' WHEEL_METADATA_FILENAME = 'metadata.json' LEGACY_METADATA_FILENAME = 'METADATA' class Metadata(object): """ The metadata of a release. This implementation uses 2.1 metadata where possible. If not possible, it wraps a LegacyMetadata instance which handles the key-value metadata format. """ METADATA_VERSION_MATCHER = re.compile(r'^\d+(\.\d+)*$') NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I) FIELDNAME_MATCHER = re.compile('^[A-Z]([0-9A-Z-]*[0-9A-Z])?$', re.I) VERSION_MATCHER = PEP440_VERSION_RE SUMMARY_MATCHER = re.compile('.{1,2047}') METADATA_VERSION = '2.0' GENERATOR = 'distlib (%s)' % __version__ MANDATORY_KEYS = { 'name': (), 'version': (), 'summary': ('legacy', ), } INDEX_KEYS = ('name version license summary description author ' 'author_email keywords platform home_page classifiers ' 'download_url') DEPENDENCY_KEYS = ('extras run_requires test_requires build_requires ' 'dev_requires provides meta_requires obsoleted_by ' 'supports_environments') SYNTAX_VALIDATORS = { 'metadata_version': (METADATA_VERSION_MATCHER, ()), 'name': (NAME_MATCHER, ('legacy', )), 'version': (VERSION_MATCHER, ('legacy', )), 'summary': (SUMMARY_MATCHER, ('legacy', )), 'dynamic': (FIELDNAME_MATCHER, ('legacy', )), } __slots__ = ('_legacy', '_data', 'scheme') def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'): if [path, fileobj, mapping].count(None) < 2: raise TypeError('path, fileobj and mapping are exclusive') self._legacy = None self._data = None self.scheme = scheme # import pdb; pdb.set_trace() if mapping is not None: try: self._validate_mapping(mapping, scheme) self._data = mapping except MetadataUnrecognizedVersionError: self._legacy = LegacyMetadata(mapping=mapping, scheme=scheme) self.validate() else: data = None if path: with open(path, 'rb') as f: data = f.read() elif fileobj: data = fileobj.read() if data is None: # Initialised with no args - to be added self._data = { 'metadata_version': self.METADATA_VERSION, 'generator': self.GENERATOR, } else: if not isinstance(data, text_type): data = data.decode('utf-8') try: self._data = json.loads(data) self._validate_mapping(self._data, scheme) except ValueError: # Note: MetadataUnrecognizedVersionError does not # inherit from ValueError (it's a DistlibException, # which should not inherit from ValueError). # The ValueError comes from the json.load - if that # succeeds and we get a validation error, we want # that to propagate self._legacy = LegacyMetadata(fileobj=StringIO(data), scheme=scheme) self.validate() common_keys = set(('name', 'version', 'license', 'keywords', 'summary')) none_list = (None, list) none_dict = (None, dict) mapped_keys = { 'run_requires': ('Requires-Dist', list), 'build_requires': ('Setup-Requires-Dist', list), 'dev_requires': none_list, 'test_requires': none_list, 'meta_requires': none_list, 'extras': ('Provides-Extra', list), 'modules': none_list, 'namespaces': none_list, 'exports': none_dict, 'commands': none_dict, 'classifiers': ('Classifier', list), 'source_url': ('Download-URL', None), 'metadata_version': ('Metadata-Version', None), } del none_list, none_dict def __getattribute__(self, key): common = object.__getattribute__(self, 'common_keys') mapped = object.__getattribute__(self, 'mapped_keys') if key in mapped: lk, maker = mapped[key] if self._legacy: if lk is None: result = None if maker is None else maker() else: result = self._legacy.get(lk) else: value = None if maker is None else maker() if key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'): result = self._data.get(key, value) else: # special cases for PEP 459 sentinel = object() result = sentinel d = self._data.get('extensions') if d: if key == 'commands': result = d.get('python.commands', value) elif key == 'classifiers': d = d.get('python.details') if d: result = d.get(key, value) else: d = d.get('python.exports') if not d: d = self._data.get('python.exports') if d: result = d.get(key, value) if result is sentinel: result = value elif key not in common: result = object.__getattribute__(self, key) elif self._legacy: result = self._legacy.get(key) else: result = self._data.get(key) return result def _validate_value(self, key, value, scheme=None): if key in self.SYNTAX_VALIDATORS: pattern, exclusions = self.SYNTAX_VALIDATORS[key] if (scheme or self.scheme) not in exclusions: m = pattern.match(value) if not m: raise MetadataInvalidError("'%s' is an invalid value for " "the '%s' property" % (value, key)) def __setattr__(self, key, value): self._validate_value(key, value) common = object.__getattribute__(self, 'common_keys') mapped = object.__getattribute__(self, 'mapped_keys') if key in mapped: lk, _ = mapped[key] if self._legacy: if lk is None: raise NotImplementedError self._legacy[lk] = value elif key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'): self._data[key] = value else: # special cases for PEP 459 d = self._data.setdefault('extensions', {}) if key == 'commands': d['python.commands'] = value elif key == 'classifiers': d = d.setdefault('python.details', {}) d[key] = value else: d = d.setdefault('python.exports', {}) d[key] = value elif key not in common: object.__setattr__(self, key, value) else: if key == 'keywords': if isinstance(value, string_types): value = value.strip() if value: value = value.split() else: value = [] if self._legacy: self._legacy[key] = value else: self._data[key] = value @property def name_and_version(self): return _get_name_and_version(self.name, self.version, True) @property def provides(self): if self._legacy: result = self._legacy['Provides-Dist'] else: result = self._data.setdefault('provides', []) s = '%s (%s)' % (self.name, self.version) if s not in result: result.append(s) return result @provides.setter def provides(self, value): if self._legacy: self._legacy['Provides-Dist'] = value else: self._data['provides'] = value def get_requirements(self, reqts, extras=None, env=None): """ Base method to get dependencies, given a set of extras to satisfy and an optional environment context. :param reqts: A list of sometimes-wanted dependencies, perhaps dependent on extras and environment. :param extras: A list of optional components being requested. :param env: An optional environment for marker evaluation. """ if self._legacy: result = reqts else: result = [] extras = get_extras(extras or [], self.extras) for d in reqts: if 'extra' not in d and 'environment' not in d: # unconditional include = True else: if 'extra' not in d: # Not extra-dependent - only environment-dependent include = True else: include = d.get('extra') in extras if include: # Not excluded because of extras, check environment marker = d.get('environment') if marker: include = interpret(marker, env) if include: result.extend(d['requires']) for key in ('build', 'dev', 'test'): e = ':%s:' % key if e in extras: extras.remove(e) # A recursive call, but it should terminate since 'test' # has been removed from the extras reqts = self._data.get('%s_requires' % key, []) result.extend(self.get_requirements(reqts, extras=extras, env=env)) return result @property def dictionary(self): if self._legacy: return self._from_legacy() return self._data @property def dependencies(self): if self._legacy: raise NotImplementedError else: return extract_by_key(self._data, self.DEPENDENCY_KEYS) @dependencies.setter def dependencies(self, value): if self._legacy: raise NotImplementedError else: self._data.update(value) def _validate_mapping(self, mapping, scheme): if mapping.get('metadata_version') != self.METADATA_VERSION: raise MetadataUnrecognizedVersionError() missing = [] for key, exclusions in self.MANDATORY_KEYS.items(): if key not in mapping: if scheme not in exclusions: missing.append(key) if missing: msg = 'Missing metadata items: %s' % ', '.join(missing) raise MetadataMissingError(msg) for k, v in mapping.items(): self._validate_value(k, v, scheme) def validate(self): if self._legacy: missing, warnings = self._legacy.check(True) if missing or warnings: logger.warning('Metadata: missing: %s, warnings: %s', missing, warnings) else: self._validate_mapping(self._data, self.scheme) def todict(self): if self._legacy: return self._legacy.todict(True) else: result = extract_by_key(self._data, self.INDEX_KEYS) return result def _from_legacy(self): assert self._legacy and not self._data result = { 'metadata_version': self.METADATA_VERSION, 'generator': self.GENERATOR, } lmd = self._legacy.todict(True) # skip missing ones for k in ('name', 'version', 'license', 'summary', 'description', 'classifier'): if k in lmd: if k == 'classifier': nk = 'classifiers' else: nk = k result[nk] = lmd[k] kw = lmd.get('Keywords', []) if kw == ['']: kw = [] result['keywords'] = kw keys = (('requires_dist', 'run_requires'), ('setup_requires_dist', 'build_requires')) for ok, nk in keys: if ok in lmd and lmd[ok]: result[nk] = [{'requires': lmd[ok]}] result['provides'] = self.provides # author = {} # maintainer = {} return result LEGACY_MAPPING = { 'name': 'Name', 'version': 'Version', ('extensions', 'python.details', 'license'): 'License', 'summary': 'Summary', 'description': 'Description', ('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page', ('extensions', 'python.project', 'contacts', 0, 'name'): 'Author', ('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email', 'source_url': 'Download-URL', ('extensions', 'python.details', 'classifiers'): 'Classifier', } def _to_legacy(self): def process_entries(entries): reqts = set() for e in entries: extra = e.get('extra') env = e.get('environment') rlist = e['requires'] for r in rlist: if not env and not extra: reqts.add(r) else: marker = '' if extra: marker = 'extra == "%s"' % extra if env: if marker: marker = '(%s) and %s' % (env, marker) else: marker = env reqts.add(';'.join((r, marker))) return reqts assert self._data and not self._legacy result = LegacyMetadata() nmd = self._data # import pdb; pdb.set_trace() for nk, ok in self.LEGACY_MAPPING.items(): if not isinstance(nk, tuple): if nk in nmd: result[ok] = nmd[nk] else: d = nmd found = True for k in nk: try: d = d[k] except (KeyError, IndexError): found = False break if found: result[ok] = d r1 = process_entries(self.run_requires + self.meta_requires) r2 = process_entries(self.build_requires + self.dev_requires) if self.extras: result['Provides-Extra'] = sorted(self.extras) result['Requires-Dist'] = sorted(r1) result['Setup-Requires-Dist'] = sorted(r2) # TODO: any other fields wanted return result def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True): if [path, fileobj].count(None) != 1: raise ValueError('Exactly one of path and fileobj is needed') self.validate() if legacy: if self._legacy: legacy_md = self._legacy else: legacy_md = self._to_legacy() if path: legacy_md.write(path, skip_unknown=skip_unknown) else: legacy_md.write_file(fileobj, skip_unknown=skip_unknown) else: if self._legacy: d = self._from_legacy() else: d = self._data if fileobj: json.dump(d, fileobj, ensure_ascii=True, indent=2, sort_keys=True) else: with codecs.open(path, 'w', 'utf-8') as f: json.dump(d, f, ensure_ascii=True, indent=2, sort_keys=True) def add_requirements(self, requirements): if self._legacy: self._legacy.add_requirements(requirements) else: run_requires = self._data.setdefault('run_requires', []) always = None for entry in run_requires: if 'environment' not in entry and 'extra' not in entry: always = entry break if always is None: always = {'requires': requirements} run_requires.insert(0, always) else: rset = set(always['requires']) | set(requirements) always['requires'] = sorted(rset) def __repr__(self): name = self.name or '(no name)' version = self.version or 'no version' return '<%s %s %s (%s)>' % (self.__class__.__name__, self.metadata_version, name, version) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781005788.0 distlib-0.4.3/distlib/resources.py0000664000175000017500000002636415211776734016316 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2013-2026 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # from __future__ import unicode_literals import bisect import io import logging import os import pkgutil import sys import types import zipimport from . import DistlibException from .util import cached_property, get_cache_base, Cache logger = logging.getLogger(__name__) cache = None # created when needed class ResourceCache(Cache): def __init__(self, base=None): if base is None: # Use native string to avoid issues on 2.x: see Python #20140. base = os.path.join(get_cache_base(), str('resource-cache')) super(ResourceCache, self).__init__(base) def is_stale(self, resource, path): """ Is the cache stale for the given resource? :param resource: The :class:`Resource` being cached. :param path: The path of the resource in the cache. :return: True if the cache is stale. """ # Cache invalidation is a hard problem :-) return True def get(self, resource): """ Get a resource into the cache, :param resource: A :class:`Resource` instance. :return: The pathname of the resource in the cache. """ prefix, path = resource.finder.get_cache_info(resource) if prefix is None: result = path else: result = os.path.join(self.base, self.prefix_to_dir(prefix), path) dirname = os.path.dirname(result) if not os.path.isdir(dirname): os.makedirs(dirname) if not os.path.exists(result): stale = True else: stale = self.is_stale(resource, path) if stale: # write the bytes of the resource to the cache location with open(result, 'wb') as f: f.write(resource.bytes) return result class ResourceBase(object): def __init__(self, finder, name): self.finder = finder self.name = name class Resource(ResourceBase): """ A class representing an in-package resource, such as a data file. This is not normally instantiated by user code, but rather by a :class:`ResourceFinder` which manages the resource. """ is_container = False # Backwards compatibility def as_stream(self): """ Get the resource as a stream. This is not a property to make it obvious that it returns a new stream each time. """ return self.finder.get_stream(self) @cached_property def file_path(self): global cache if cache is None: cache = ResourceCache() return cache.get(self) @cached_property def bytes(self): return self.finder.get_bytes(self) @cached_property def size(self): return self.finder.get_size(self) class ResourceContainer(ResourceBase): is_container = True # Backwards compatibility @cached_property def resources(self): return self.finder.get_resources(self) class ResourceFinder(object): """ Resource finder for file system resources. """ if sys.platform.startswith('java'): skipped_extensions = ('.pyc', '.pyo', '.class') else: skipped_extensions = ('.pyc', '.pyo') def __init__(self, module): self.module = module self.loader = getattr(module, '__loader__', None) self.base = os.path.dirname(getattr(module, '__file__', '')) def _adjust_path(self, path): return os.path.realpath(path) def _is_in_base(self, path): base = self._adjust_path(self.base) if path == base: return True if not base.endswith(os.sep): base = base + os.sep return path.startswith(base) def _make_path(self, resource_name): # Issue #50: need to preserve type of path on Python 2.x # like os.path._get_sep if isinstance(resource_name, bytes): # should only happen on 2.x sep = b'/' else: sep = '/' parts = resource_name.split(sep) parts.insert(0, self.base) result = os.path.join(*parts) result = self._adjust_path(result) # See issue 263: code below commented out as it proved to be # too aggressively cautious, causing problems in valid use case scenarios. # Confine the resolved resource to the package base so a resource # name containing '..' cannot read files outside the package. # if not self._is_in_base(result): # raise DistlibException('Resource name escapes package: ' # '%r' % resource_name) return result def _find(self, path): return os.path.exists(path) def get_cache_info(self, resource): return None, resource.path def find(self, resource_name): path = self._make_path(resource_name) if not self._find(path): result = None else: if self._is_directory(path): result = ResourceContainer(self, resource_name) else: result = Resource(self, resource_name) result.path = path return result def get_stream(self, resource): return open(resource.path, 'rb') def get_bytes(self, resource): with open(resource.path, 'rb') as f: return f.read() def get_size(self, resource): return os.path.getsize(resource.path) def get_resources(self, resource): def allowed(f): return (f != '__pycache__' and not f.endswith(self.skipped_extensions)) return set([f for f in os.listdir(resource.path) if allowed(f)]) def is_container(self, resource): return self._is_directory(resource.path) _is_directory = staticmethod(os.path.isdir) def iterator(self, resource_name): resource = self.find(resource_name) if resource is not None: todo = [resource] while todo: resource = todo.pop(0) yield resource if resource.is_container: rname = resource.name for name in resource.resources: if not rname: new_name = name else: new_name = '/'.join([rname, name]) child = self.find(new_name) if child.is_container: todo.append(child) else: yield child class ZipResourceFinder(ResourceFinder): """ Resource finder for resources in .zip files. """ def __init__(self, module): super(ZipResourceFinder, self).__init__(module) archive = self.loader.archive self.prefix_len = 1 + len(archive) # PyPy doesn't have a _files attr on zipimporter, and you can't set one if hasattr(self.loader, '_files'): self._files = self.loader._files else: self._files = zipimport._zip_directory_cache[archive] self.index = sorted(self._files) def _adjust_path(self, path): return path def _find(self, path): path = path[self.prefix_len:] if path in self._files: result = True else: if path and path[-1] != os.sep: path = path + os.sep i = bisect.bisect(self.index, path) try: result = self.index[i].startswith(path) except IndexError: result = False if not result: logger.debug('_find failed: %r %r', path, self.loader.prefix) else: logger.debug('_find worked: %r %r', path, self.loader.prefix) return result def get_cache_info(self, resource): prefix = self.loader.archive path = resource.path[1 + len(prefix):] return prefix, path def get_bytes(self, resource): return self.loader.get_data(resource.path) def get_stream(self, resource): return io.BytesIO(self.get_bytes(resource)) def get_size(self, resource): path = resource.path[self.prefix_len:] return self._files[path][3] def get_resources(self, resource): path = resource.path[self.prefix_len:] if path and path[-1] != os.sep: path += os.sep plen = len(path) result = set() i = bisect.bisect(self.index, path) while i < len(self.index): if not self.index[i].startswith(path): break s = self.index[i][plen:] result.add(s.split(os.sep, 1)[0]) # only immediate children i += 1 return result def _is_directory(self, path): path = path[self.prefix_len:] if path and path[-1] != os.sep: path += os.sep i = bisect.bisect(self.index, path) try: result = self.index[i].startswith(path) except IndexError: result = False return result _finder_registry = {type(None): ResourceFinder, zipimport.zipimporter: ZipResourceFinder} try: # In Python 3.6, _frozen_importlib -> _frozen_importlib_external try: import _frozen_importlib_external as _fi except ImportError: import _frozen_importlib as _fi _finder_registry[_fi.SourceFileLoader] = ResourceFinder _finder_registry[_fi.FileFinder] = ResourceFinder # See issue #146 _finder_registry[_fi.SourcelessFileLoader] = ResourceFinder del _fi except (ImportError, AttributeError): pass def register_finder(loader, finder_maker): _finder_registry[type(loader)] = finder_maker _finder_cache = {} def finder(package): """ Return a resource finder for a package. :param package: The name of the package. :return: A :class:`ResourceFinder` instance for the package. """ if package in _finder_cache: result = _finder_cache[package] else: if package not in sys.modules: __import__(package) module = sys.modules[package] path = getattr(module, '__path__', None) if path is None: raise DistlibException('You cannot get a finder for a module, ' 'only for a package') loader = getattr(module, '__loader__', None) finder_maker = _finder_registry.get(type(loader)) if finder_maker is None: raise DistlibException('Unable to locate finder for %r' % package) result = finder_maker(module) _finder_cache[package] = result return result _dummy_module = types.ModuleType(str('__dummy__')) def finder_for_path(path): """ Return a resource finder for a path, which should represent a container. :param path: The path. :return: A :class:`ResourceFinder` instance for the path. """ result = None # calls any path hooks, gets importer into cache pkgutil.get_importer(path) loader = sys.path_importer_cache.get(path) finder = _finder_registry.get(type(loader)) if finder: module = _dummy_module module.__file__ = os.path.join(path, '') module.__loader__ = loader result = finder(module) return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780397339.0 distlib-0.4.3/distlib/scripts.py0000664000175000017500000004462315207532433015760 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2013-2026 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # from io import BytesIO import logging import os import re import struct import sys import time from zipfile import ZipInfo from . import DistlibException from .compat import sysconfig, detect_encoding, ZipFile from .resources import finder from .util import (FileOperator, get_export_entry, convert_path, get_executable, get_platform, in_venv, is_in_directory) logger = logging.getLogger(__name__) _DEFAULT_MANIFEST = ''' '''.strip() # check if Python is called on the first line with this expression FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$') SCRIPT_TEMPLATE = r'''# -*- coding: utf-8 -*- import re import sys if __name__ == '__main__': from %(module)s import %(import_name)s sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(%(func)s()) ''' # Pre-fetch the contents of all executable wrapper stubs. # This is to address https://github.com/pypa/pip/issues/12666. # When updating pip, we rename the old pip in place before installing the # new version. If we try to fetch a wrapper *after* that rename, the finder # machinery will be confused as the package is no longer available at the # location where it was imported from. So we load everything into memory in # advance. if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'): # Issue 31: don't hardcode an absolute package name, but # determine it relative to the current package DISTLIB_PACKAGE = __name__.rsplit('.', 1)[0] WRAPPERS = { r.name: r.bytes for r in finder(DISTLIB_PACKAGE).iterator("") if r.name.endswith(".exe") } def enquote_executable(executable): if ' ' in executable: # make sure we quote only the executable in case of env # for example /usr/bin/env "/dir with spaces/bin/jython" # instead of "/usr/bin/env /dir with spaces/bin/jython" # otherwise whole if executable.startswith('/usr/bin/env '): env, _executable = executable.split(' ', 1) if ' ' in _executable and not _executable.startswith('"'): executable = '%s "%s"' % (env, _executable) else: if not executable.startswith('"'): executable = '"%s"' % executable return executable # Keep the old name around (for now), as there is at least one project using it! _enquote_executable = enquote_executable class ScriptMaker(object): """ A class to copy or create scripts from source scripts or callable specifications. """ script_template = SCRIPT_TEMPLATE executable = None # for shebangs def __init__(self, source_dir, target_dir, add_launchers=True, dry_run=False, fileop=None): self.source_dir = source_dir self.target_dir = target_dir self.add_launchers = add_launchers self.force = False self.clobber = False # It only makes sense to set mode bits on POSIX. self.set_mode = (os.name == 'posix') or (os.name == 'java' and os._name == 'posix') self.variants = set(('', 'X.Y')) self._fileop = fileop or FileOperator(dry_run) self._is_nt = os.name == 'nt' or (os.name == 'java' and os._name == 'nt') self.version_info = sys.version_info def _get_alternate_executable(self, executable, options): if options.get('gui', False) and self._is_nt: # pragma: no cover dn, fn = os.path.split(executable) fn = fn.replace('python', 'pythonw') executable = os.path.join(dn, fn) return executable if sys.platform.startswith('java'): # pragma: no cover def _is_shell(self, executable): """ Determine if the specified executable is a script (contains a #! line) """ try: with open(executable) as fp: return fp.read(2) == '#!' except (OSError, IOError): logger.warning('Failed to open %s', executable) return False def _fix_jython_executable(self, executable): if self._is_shell(executable): # Workaround for Jython is not needed on Linux systems. import java if java.lang.System.getProperty('os.name') == 'Linux': return executable elif executable.lower().endswith('jython.exe'): # Use wrapper exe for Jython on Windows return executable return '/usr/bin/env %s' % executable def _build_shebang(self, executable, post_interp): """ Build a shebang line. In the simple case (on Windows, or a shebang line which is not too long or contains spaces) use a simple formulation for the shebang. Otherwise, use /bin/sh as the executable, with a contrived shebang which allows the script to run either under Python or sh, using suitable quoting. Thanks to Harald Nordgren for his input. See also: http://www.in-ulm.de/~mascheck/various/shebang/#length https://hg.mozilla.org/mozilla-central/file/tip/mach """ if os.name != 'posix': simple_shebang = True elif getattr(sys, "cross_compiling", False): # In a cross-compiling environment, the shebang will likely be a # script; this *must* be invoked with the "safe" version of the # shebang, or else using os.exec() to run the entry script will # fail, raising "OSError 8 [Errno 8] Exec format error". simple_shebang = False else: # Add 3 for '#!' prefix and newline suffix. shebang_length = len(executable) + len(post_interp) + 3 if sys.platform == 'darwin': max_shebang_length = 512 else: max_shebang_length = 127 simple_shebang = ((b' ' not in executable) and (shebang_length <= max_shebang_length)) if simple_shebang: result = b'#!' + executable + post_interp + b'\n' else: result = b'#!/bin/sh\n' result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n' result += b"' '''\n" return result def _get_shebang(self, encoding, post_interp=b'', options=None): enquote = True if self.executable: executable = self.executable enquote = False # assume this will be taken care of elif not sysconfig.is_python_build(): executable = get_executable() elif in_venv(): # pragma: no cover executable = os.path.join(sysconfig.get_path('scripts'), 'python%s' % sysconfig.get_config_var('EXE')) else: # pragma: no cover if os.name == 'nt': # for Python builds from source on Windows, no Python executables with # a version suffix are created, so we use python.exe executable = os.path.join(sysconfig.get_config_var('BINDIR'), 'python%s' % (sysconfig.get_config_var('EXE'))) else: executable = os.path.join( sysconfig.get_config_var('BINDIR'), 'python%s%s' % (sysconfig.get_config_var('VERSION'), sysconfig.get_config_var('EXE'))) if options: executable = self._get_alternate_executable(executable, options) if sys.platform.startswith('java'): # pragma: no cover executable = self._fix_jython_executable(executable) # Normalise case for Windows - COMMENTED OUT # executable = os.path.normcase(executable) # N.B. The normalising operation above has been commented out: See # issue #124. Although paths in Windows are generally case-insensitive, # they aren't always. For example, a path containing a ẞ (which is a # LATIN CAPITAL LETTER SHARP S - U+1E9E) is normcased to ß (which is a # LATIN SMALL LETTER SHARP S' - U+00DF). The two are not considered by # Windows as equivalent in path names. # If the user didn't specify an executable, it may be necessary to # cater for executable paths with spaces (not uncommon on Windows) if enquote: executable = enquote_executable(executable) # Issue #51: don't use fsencode, since we later try to # check that the shebang is decodable using utf-8. executable = executable.encode('utf-8') # in case of IronPython, play safe and enable frames support if (sys.platform == 'cli' and '-X:Frames' not in post_interp and '-X:FullFrames' not in post_interp): # pragma: no cover post_interp += b' -X:Frames' shebang = self._build_shebang(executable, post_interp) # Python parser starts to read a script using UTF-8 until # it gets a #coding:xxx cookie. The shebang has to be the # first line of a file, the #coding:xxx cookie cannot be # written before. So the shebang has to be decodable from # UTF-8. try: shebang.decode('utf-8') except UnicodeDecodeError: # pragma: no cover raise ValueError('The shebang (%r) is not decodable from utf-8' % shebang) # If the script is encoded to a custom encoding (use a # #coding:xxx cookie), the shebang has to be decodable from # the script encoding too. if encoding != 'utf-8': try: shebang.decode(encoding) except UnicodeDecodeError: # pragma: no cover raise ValueError('The shebang (%r) is not decodable ' 'from the script encoding (%r)' % (shebang, encoding)) return shebang def _get_script_text(self, entry): return self.script_template % dict( module=entry.prefix, import_name=entry.suffix.split('.')[0], func=entry.suffix) manifest = _DEFAULT_MANIFEST def get_manifest(self, exename): base = os.path.basename(exename) return self.manifest % base def _write_script(self, names, shebang, script_bytes, filenames, ext): use_launcher = self.add_launchers and self._is_nt if not use_launcher: script_bytes = shebang + script_bytes else: # pragma: no cover if ext == 'py': launcher = self._get_launcher('t') else: launcher = self._get_launcher('w') stream = BytesIO() with ZipFile(stream, 'w') as zf: source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') if source_date_epoch: date_time = time.gmtime(int(source_date_epoch))[:6] zinfo = ZipInfo(filename='__main__.py', date_time=date_time) zf.writestr(zinfo, script_bytes) else: zf.writestr('__main__.py', script_bytes) zip_data = stream.getvalue() script_bytes = launcher + shebang + zip_data for name in names: outname = os.path.abspath(os.path.join(self.target_dir, name)) if not is_in_directory(outname, self.target_dir): raise DistlibException('Attempt to escape script directory') if use_launcher: # pragma: no cover n, e = os.path.splitext(outname) if e.startswith('.py'): outname = n outname = '%s.exe' % outname try: self._fileop.write_binary_file(outname, script_bytes) except Exception: # Failed writing an executable - it might be in use. logger.warning('Failed to write executable - trying to ' 'use .deleteme logic') dfname = '%s.deleteme' % outname if os.path.exists(dfname): os.remove(dfname) # Not allowed to fail here os.rename(outname, dfname) # nor here self._fileop.write_binary_file(outname, script_bytes) logger.debug('Able to replace executable using ' '.deleteme logic') try: os.remove(dfname) except Exception: pass # still in use - ignore error else: if self._is_nt and not outname.endswith('.' + ext): # pragma: no cover outname = '%s.%s' % (outname, ext) if os.path.exists(outname) and not self.clobber: logger.warning('Skipping existing file %s', outname) continue self._fileop.write_binary_file(outname, script_bytes) if self.set_mode: self._fileop.set_executable_mode([outname]) filenames.append(outname) variant_separator = '-' def get_script_filenames(self, name): result = set() if '' in self.variants: result.add(name) if 'X' in self.variants: result.add('%s%s' % (name, self.version_info[0])) if 'X.Y' in self.variants: result.add('%s%s%s.%s' % (name, self.variant_separator, self.version_info[0], self.version_info[1])) return result def _make_script(self, entry, filenames, options=None): post_interp = b'' if options: args = options.get('interpreter_args', []) if args: args = ' %s' % ' '.join(args) post_interp = args.encode('utf-8') shebang = self._get_shebang('utf-8', post_interp, options=options) script = self._get_script_text(entry).encode('utf-8') scriptnames = self.get_script_filenames(entry.name) if options and options.get('gui', False): ext = 'pyw' else: ext = 'py' self._write_script(scriptnames, shebang, script, filenames, ext) def _copy_script(self, script, filenames): adjust = False script = os.path.join(self.source_dir, convert_path(script)) outname = os.path.join(self.target_dir, os.path.basename(script)) if not self.force and not self._fileop.newer(script, outname): logger.debug('not copying %s (up-to-date)', script) return # Always open the file, but ignore failures in dry-run mode -- # that way, we'll get accurate feedback if we can read the # script. try: f = open(script, 'rb') except IOError: # pragma: no cover if not self.dry_run: raise f = None else: first_line = f.readline() if not first_line: # pragma: no cover logger.warning('%s is an empty file (skipping)', script) return match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n')) if match: adjust = True post_interp = match.group(1) or b'' if not adjust: if f: f.close() self._fileop.copy_file(script, outname) if self.set_mode: self._fileop.set_executable_mode([outname]) filenames.append(outname) else: logger.info('copying and adjusting %s -> %s', script, self.target_dir) if not self._fileop.dry_run: encoding, lines = detect_encoding(f.readline) f.seek(0) shebang = self._get_shebang(encoding, post_interp) if b'pythonw' in first_line: # pragma: no cover ext = 'pyw' else: ext = 'py' n = os.path.basename(outname) self._write_script([n], shebang, f.read(), filenames, ext) if f: f.close() @property def dry_run(self): return self._fileop.dry_run @dry_run.setter def dry_run(self, value): self._fileop.dry_run = value if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'): # pragma: no cover # Executable launcher support. # Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/ def _get_launcher(self, kind): if struct.calcsize('P') == 8: # 64-bit bits = '64' else: bits = '32' platform_suffix = '-arm' if get_platform() == 'win-arm64' else '' name = '%s%s%s.exe' % (kind, bits, platform_suffix) if name not in WRAPPERS: msg = ('Unable to find resource %s in package %s' % (name, DISTLIB_PACKAGE)) raise ValueError(msg) return WRAPPERS[name] # Public API follows def make(self, specification, options=None): """ Make a script. :param specification: The specification, which is either a valid export entry specification (to make a script from a callable) or a filename (to make a script by copying from a source location). :param options: A dictionary of options controlling script generation. :return: A list of all absolute pathnames written to. """ filenames = [] entry = get_export_entry(specification) if entry is None: self._copy_script(specification, filenames) else: self._make_script(entry, filenames, options=options) return filenames def make_multiple(self, specifications, options=None): """ Take a list of specifications and make scripts from them, :param specifications: A list of specifications. :return: A list of all absolute pathnames written to, """ filenames = [] for specification in specifications: filenames.extend(self.make(specification, options)) return filenames ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659773740.0 distlib-0.4.3/distlib/t32.exe0000600000175000017500000027700014273421454015017 0ustar00vinayvinayMZ@ !L!This program cannot be run in DOS mode. $qv%%%[^;%%Ӎ%%Ӎ=%%Ӎ %%h0%%%%Ӎ %%Ӎ9%%Ӎ>%%Rich%PEL b  ;@2@l<`S @\.text `.rdatab,.@@.datad7  @.rsrcS`T@@.reloc(n@BU"A3ʼnE}uEEPu hhP$Ph< A9@P j@M3< U"A3ʼnE}uEEPu hhP^Phx A@P j@M3 UMW3FP.APQQ t.A ;uHE+D0ϋ_]U,VWh AE3h;AP}}8 3;h AQjWuw$uE$UE;}}WuuJ$uhjSS9 ;t+EjY}E~V+EEYE;}}Wuu#uVju>u3;h AuPUE+EjEYu}E7 ,MMj)Muu}#uWjS3; AVPf A$f;u]TEy3jPu,#uWjS3;VP$Df Af;tH;sEu%EY_^U Vj.u3u YYtVh=AhVuV@ttFqWh AEPEP#uui;uIEP AWu Vh=AhPuV@uEPWV ;u3Fu Y_^U}t }v)}w#h;At%;Ajphp;Aj P @3@]UuL@tuEujju@]3@]UQf}rotkUW8k;rWSVh @t<^ YE};s++ttt P/%Y;]ruY^[_UVWh @yuYYx PYWUY_^]UVW P Pu @P Et; ;t;;t;;5@@SjjjE։E( P*# P#5H@YY;t9]u3Sj֋E;t9Eu jjփeE;t9Eu jjփeE;u3;Eue;Eue=X@tt Sjj39]t}t uSj9]t}t uSj[_^Ud "A3ʼnEESVW3WW<@3;h Ah;AQYYPjpp;AVj 5h;A,@tpu3@3h, AP ;A0YYjpVj 5h;A @hL APjDWPcP4@5@@jjjS$l APKVh AP4h AP h=APWWWjWWW@;u=D@WQRQPWh @Ph AW15=A5h;A@YPh8@H=w PT@jh@P@h, AP0YY5=AX@Wj5=A@P5=A$@hL APYY@UQSVW=L@p ASu׋t>FEft"tjPYYu Su׋uӅt ]]j h| ASa3ɅSh AQg{ftjPBYYt3@3Sh AP8 ftjPYYuf?tjPYYuf9ujPYYt3fW`Sh AjX=APf;"u&3f>"ShH AP3f jh ASk tf{"t3f>"Sh APa ft&jP?YYt3fjP*YYt fuE 0_^[U0"A3ʼnESVW@f8"tj j"YQP7YYu Dž A$pjPYYt fu牵W;AVj(@f=;A"t Dž;A3ɍf B.AP3hAPNY3Y;s t tA;r3hAPYYWP+VSjh0@33;hDAP3fuYYf9tjPYYtf9u3f>#hpAPYYj_jP}YYt f;u3f>!hAPhYYjPMYYt f;u荅PVS3hAP%39hAPj[SWh A$uvh;AhP4 PP@f"u+j[G j"P 3WhH AP3f_PPPT@ǍPffu+Hffu+Hffu+ȋPffu+jt1VD3h<APWhpAVVS2 0Sy; "Au@j hPA#3}3];;uP#3{3u ;;t3f9>;t!E;u #ɉ}f9;u #jEPh"A$% PuVSEE EO#u^YËUVuu"j^0 $huu  t3t"^]ËUEfU f;tfuf9t3]j hpA"3u3;;u'"| K39} ;t߉uV5Y}V%؍EPWu Vr&EVS&E EU"udYËU}t-uj5$2Al@uV!D@PF!Y^]ËUQeVEPu u1 u9EtU!t L!M^ËUEVF uc=FHlHhN; h+At )AHpu;F;((AtF )AHpu3FF@puHpF  @F^]U3SW9E]u  } tVuM0ExuA+;Ar Zw Ar Zw MtDft?f;t8EPP?EPP?Mt ftf;t+}^tMap_[ËU=T2AVuy39Euu cM t+Ar Zw Ar Zw Mt ftf;t+juu u^]ËUS]woVW=$2AuCjGBhW?YYt3@Pj5$2Ap@u&j ^98At SDYu00_^SCY 3[]ø Aá`WAVj^u;}ƣ`WAjPTFYYDGAujV5`WA;FYYDGAujX^3ҹ ADGA  "A|j^3ҹ AW FAt;tu1 Bp A|_3^H=2AtF5DGAYËUVu A;r"`"Aw+QJN Y Vt@^]ËUE}PIE H Y]ËE Pt@]ËUE A;r=`"Aw` +PHY]à Px@]ËUME }` QHY]à Px@]̋D$ StRT$3ۊ\$ t 2trt2urWߋ_t 2t@u[Ãr 3˿~3σtJ2t#2t2t 2t_B[ÍB_[ÍB_[ÍB_[ËUEt8uPaY]ËUQQ"A3ʼnESV3W;uj^0O,uVJYY;Er3fՋE @;u)f9tAr Zw ff9u3SSjVWPJȃM;u|*q9Ms3f[j"];~Cj3Xr7D =w J;tPY;t M؅u  놋E QSjVWpdItSuVl j*YSYƍe_^[M3)ËUuMUMEPu n}YYtMapËUju u ]ËUEPjuuu uP ]ËUMS] VW}M]t}tug3_^[Ëut 39Ev!t SjQR t39Ew}F }tFEEF tDFt=;r;}W6uuQ)~>}+߃)}};]r\}t3;v uu+ ;w;Ew[PuVYPMP t{tdE+)E$VXIYtR}t"MEFKME&E} tu jugQ "N +3uN j hA3u9ut/9ut*9uu-} tu Vu Q I3uaYuuuuu uEEEuYËUuuu juZ]j hA3ۉ]3};;u 39] ;t܉}WY]G @uoWYttȃ FAx+AA$u)tt FAx+A@$t"wM9]u#WEPSu WPEWVE EeutYËUQf9Eu3øf9EsE -AAEPjEPj|@u!EEM #ËUEuf]Ë@]ËUE.A]ËU("A3ʼnES]WtS[YjLjPN0 ffffffEMDž0IM M@j@P@uu tSZYM_3[ËUV5.A@u@.A^]ËVjVj V@P@^ËU5.A@t]uuuu u3PPPPPËUQEu~3Vu t MuuU3Wft.>u ftf;t fuf>t fuf9Mt tat0rt#wtA3a 3ۃM M3AWf;y@St  tRHtCt- t!9EE @@EE ljE}utE nTtZtEHt0 tuE G}u;eE1}u% UEut3 ؃f}j _f9>tjVh@g f9>tf>=uuf9>tjh@VV u Ajh@V7 u "jh@V uf> t3f9>thuE SuPft3 E.AMH M x8xxH_^[jhA33}j-Y]3u;5`WADGA9t[@ uHuAFwFP,YDGA4VvYYDGA@ t PVYYF둋}cj8p(Y DGA;tNhDGA P@DGAu4YDGA Pt@DGA<}_ ;tg ___OE Ë}j+YËUE3;͐"AtA-rHwj X]Ë͔"A]DjY;#]Au#AÃ.u#Aà ËUVMQY0^]hA@d5D$l$l$+SVW"A1E3PeuEEEEdËMd Y__^[]Q̋US] Vs35"AWEE{t N3 8N F3 8E@fMUS[ EMt_I[LDEEtEx@GE؃u΀}t$t N3 8N V3 :E_^[]EɋM9csmu)=8GAt h8GAdtUjR8GAM UE 9X th"AWӋE MH t N3 8N V3 :rEH*9S Oh"AWASVWT$D$L$URPQQhC@d5"A3ĉD$d%D$0XL$,3p t;T$4t;v.4v\ H {uhCReCded_^[ËL$At3D$H3Uhp pp> ]D$T$UL$)qqq( ]UVWS33333[_^]Ëjd33333USVWjRhD@Q~_^[]Ul$RQt$ ]UVuVPdYYt| ;u3@;u`3@.AF uNSW<2A?u S#YuFjFXFF ?~>^^N 3_@[3^]ËU}t'Vu F tV %f f&fY^]ËU@ @txtPudYYf;u]]ËUx"A3ʼnEESVu 3W}u 9u*L8t `p ;t3҉f; j[ AfXw(A3HAjZ; $YQ@3  tJt6t%+t    f*u,j X k ɍDЉ= 1 f*u&  k ɍDЉItWhtFltwf>lu 6uf~4ud3uf~2u@d7i.o%uxX QDž|d/St~At+tY+t+ Dž@Dž0 0u u u -AQPbYYtFF9|X+++3F tBPƅPP2ayf6t:Ht3t+Dž-AP `Ypegitqnt(oDžtaU7_c t ffDž@Dž Wufgu]DžQ9~~7]VnYt Dž5@GPPSP5.AЋtuPS5$.AYYfguuPS5 .AYY;-uCSDž$si+Dž'Džlj0XfQfGM t@tGGG@t3҉@t|s؃ځu3} Dž9~ u! t-RPWS^0؋9~N뽍+Ft_tƀ80tT0=u -ADž Kf8tu+@t+tj-tj+tj YfDž++ u$j OYtPS8YYt/u&j0OYt⃽uk~aPWPK[~$(Y.%7YY|3t*j OYtރtYft/kct `pM_^3[NÐH@F@G@cG@G@G@H@ I@jhj@3Ʌ$2AËUMtj3X;E ss 3]M VuF3wVj5$2Ap@u2=8AtV_YuҋEt 3 Mt ^]-t"t t Ht3øøøøËVWh3FWP'3ȋ~~~  ~$A F+ο@Ou@Nu_^ËU"A3ʼnESWPv@3@;rƅ t0;w+@P j R& Cujv vPWPjj]3SvWPWPWv S}\DSvWPWPhv SX\$3EtLtL @;rRDž3)ЍZ w LQ w L QA;rƋM_3[jj hA( )AGptltwhuj Y@j Yewhu;5((At6tV@u$AtVY((AGh5((AuV@E뎋uj YËUS3SM(2Au(2A@8]tEMapE,(AD;FG;v}>uЋuE}urlj{CijC C4(AZf1f0JuL@;v~0C@IuCC Ss3ȋ {95(2ATM_^3[ajhAM}_huqE;CWh :Y؅Fwh#SuYYEuvh@uFh=$AtPY^hS=@Fp )Aj YeC82AC<2AC @2A3E}fLCf E,2A@3E=} L &A@3E=}('A@5((A@u((A=$AtPY((ASE0j Y%u $AtSY]eEÃ=,GAujVY,GA3ËUSV5@W}W֋tP֋tP֋tP֋tP֍_PE{$)At tPփ{t CtPփMu֋P_^[]ËUW}SV5@W֋tP֋tP֋tP֋tP֍_PE{$)At tPփ{t CtPփMu֋P^[_]ËUSVu3W;to=8.Ath;t^9uZ;t9uPZYY;t9uP?ZYYYY;tD9u@-P+P+Pu=()At9uPEVLYY~PE$)At;t 9uP'Y9_tG;t 9uPYMuVY_^[]ËUW} t;Et4V0;t(W8jYtV>Yu*AtVsY^3_]j h8A )AFpt"~ltpluj $Yj Ye5h+AlVYYYEEj Yuj@@V5p+A@u5H2A@V5p+A@^ál+AtP5P2A@Ѓ l+Ap+AtP@ p+AjhXAhL@@uF\Af3G~~pƆCƆKCFh$Aj Yevh@E>j uY}E Fluh+AFlvlYEa3Guj ^Yj UYËVWD@5l+AЋuNhjl YYt:V5l+A5L2A@ЅtjVYY@N VY3W@_^ËVujY^jhAduF$tPGYF,tP9YF4tP+YF7֋5(GA֋5$GA9]u9Et]܉]ЉE؋}ԋ]E@}@sEtЃEE@}@sEtЃEE }u)2Aj Yu}tjYËUjju ]ËUjju ]jjj jjjz ËUu2Yh̋U3M; @t @r3]Ë@]ËU"A3ʼnESVuWV3Y;lj\Yj\Yu ="A6h@h2AW h2AVSf4A(@uh@SV t 3PPPPPV0@YWL@t3%>uN@ uNhF P@t,F N@Ch5FA@3_[^ÃUVW3uYu'98AvV@;8Avuʋ_^]ËUVW3ju u} u'98AvV@;8AvuË_^]ËUVW3u ubXYYu,9E t'98AvV@;8Avu_^]ËUVW3uu uX u,9Et'98AvV@;8Avu_^]jhAt3ۉ]jY]j_};=`WA}TDGA9tE@ tPVYtE|(DGA P@DGA4 YDGAGE E3j6YËUSVuF Ȁ3ۀu@t9FW>+~,WPVYP_ ;uF yF N _Ff^[]ËUVuu V5Y/V|YtF @tV臿P_YY3^]jhA(3}}jBY}3u;5`WADGA98t^@ tVPVYY3BUDGAH t/9UuPJYtE9}utP/Yu E܉}F3uDGA4VYYE}EtEjYjYËVW38A<+Au+A8h0@t F$|3@_^Ã$+A3S@V+AW>t~t WWݲ&Y,A|ܾ+A_t ~uPӃ,A|^[ËUE4Ÿ+Ax@]j hA3G}39$2Auj\hlYYu4+A9tmjY;u 3Pj XY]9u+hW@uW Y ] >WYE E&j )YËUEV4Ÿ+A>uP#YujWY6t@^]ËU SW3j3Y}]9]u8荾}Vu ;t;ufkE;w}uEuEBuuPuUE;t5;|"MxEEPSYYtE39]\>^_[ËU}uֽ]S]VuWuu9u u3t} uD4uuu;v*8CSVh@uG 8"uY8PWVh@D>u}u8"u%yu" _^[]ËUujuuu u]ËUE~ PuYYuuPuu u@]ËU39E vMf9t @;E r]QL$+ȃ YgQL$+ȃ YgUQVu VtE F Yu N /@t "S3ۨt^NF F F ^] u,ٱ ;t ͱ@;u u 8YuVhYF WF>HN+IN;~WPu X EM F yM tt FAx+A@ tjSSQeg#ƒt%FM3GWEPu oX E9}t N E%_[^ËUVuuںF @t F F u VgYFvvV赸YPg FF uQV苸Yt0VYt$WVrV< FAbYY_x+A@$'} ~0EM PE>u?*u˰?P} Ճ?uE^ËU"A3ʼnES] Vu3W}u¤u+Et `p7 F @u^VGYx+Attȃ FAA$uttȃ FA@$q3;g C39y B@Dž W @9} DžjugucDžW9~~=]VYt Dž5@GPPSP5.AЋtuPS5$.AYYguuPS5 .AYY;-uCSDž*snHHXDž'Dž2Qƅ0Dž t@tGGG@t3҉@t|sځu39} Dž9~ u!u t-RPWS)0؋9~N뽍E+Ftbt΀90tW0@?If90tu+(u -AI8t@u+@t5t ƅ-t ƅ+ tƅ Dž++ u% OFtP2YYt.u%˰0O7Ftヽtu~qPjEPPcVu69t.EPYYu#P]YY|2t) O^Et߃tYtrt `pM_^3[Ð>~@=|@m|@|@}@"}@h}@~@%EAËUVuV蚩YuA MWuju P@uD@3t P1Y FAD0 _^]jhPA#]u Ź x;FAr蹹 螹 ҋ< FAD0tS"YeD0tuu S ED L ME E謹Ë]SjYËUQC @VEt {uE C'} ~5EM P2E>Yu?*uj?Y} Ѓ?uE^ËUx"A3ʼnES]Vu3Wu} ?u+0腣t `p 3;tf; jY9x BfXw A3k 0 Aj^;O $@3 ƒ tHt4+t$+t   f*u+f T k ʍDЉ9 - f*u%  k ʍDЉƒItQht@ltwf?lu 6uf4um3uf2uOdFi=o4u+x"XRDž賹Yƒd1StAt+tZ+t+ Dž@Dž0 0u u [u -AQP* YYtFF9|X++3F tBPƅPPfyf6t:Ht3t+Dž-APTYpegitnnt$otbV[x t ffDž@Dž ދCSufguWDžK;~ȁ~7]VYt Dž5@CPPWP5.AЋtuPW5$.AYYfguuPW5 .AYY?-uGWDž$s{+Dž'Dž|j0XfQfWW t@tCC@Ct3҉@t|s؃ځu3} Dž9~ u! t-RPWS$0؋9~N뽍+Ft^tƀ80tS0=Yxt"jWYYtAVWP脠 uI4~f>u5.A%.A# GA3Y[_^5p2A%p2A3PPPPP袔̋V@3;u3^f9tf9uf9uS+ƍXWSYu V@_[^SVW UVuF ttvcf 3YFF^]ËUE8csmu*xu$@= t=!t="t=@u"3]h@@3ËUV2N\UW9t ;r;s9t3tPu3u `3@M S^`N`Hj$Y~\d9 |~d=u Fd~=u Fdn=u Fd^=u FdN=u Fd>=u Fd.=u Fd=u Fd=uFdvdjY~d`QY^`[_^]Ã=,GAuV5.AW3u<=tGVzYtujGW YY=h2Atˋ5.AS3VI>=YXt"jSYYt?VSPN uG>u5.A%.A' GA3Y[_^5h2A%h2A3PPPPP裑̋UQMS3VU 9Et ]EE>"u39E"FE<tBU PFEYt} t M E FU Mt2}u t utBe>< t< uFN>}t EE3C3FA>\t>"u&u}t F8"u 339EEtIt\BuU tU}u< tK< tGt=Pt#8DYt M E FM E  DYtFU FVtBU ME^[t ËU S3VW9,GAu蘼h:AVS;A @WW6x Ej[+PD=P6j% ;݃ FAD$2M0 FAD$M ʀ}u!Etȃ FAD M#;u~EtxuX@juEjPuE%Pu $@;u4D@Pȃ FAD 6脊Y6 FAEUSSSSS蛆jhpAɛ3}3u;;uhj^0辆Y39};t9}tE%@tʉ}uuu uEP\EEE;t芛3u9}t(9}t FAD 6 YËUjuuuuu !]ËU}u3]ËU MMtft f;u +]̋UMMZf9t3]ËA<8PEu3ҹ f9H‹]̋UEH<ASVq3WDt} H ;r X;r B(;r3_^[]̋UjhAhA@dPSVW"A1E3PEdeEh@*tTE-@Ph@Pt:@$ЃEMd Y_^[]ËE3ҁ9‹ËeE3Md Y_^[]USVWUjjh(@u>]_^[]ËL$At2D$H3TuUhP(RP$R]D$T$SVWD$UPjh0@d5"A3PD$dD$(Xp t:|$,t;t$,v-4v L$ H |uhDID_뷋L$d _^[3d y0@uQ R 9QuSQ-A SQ-AL$ KCk UQPXY]Y[ËUEuΗ 3]Åx;FAr賗 ދȃ FAD@]ËU"A3ʼnESVu F @W6VYx+At.VڀYt"V΀V< FA辀YYÊ@$$<V蝀Yt.V葀Yt"V腀V< FAuYYÊ@$$<VTYt.VHYt"V<V< FA,YY@t]uEjPEPX1t]39}~0NxL=AD=VPJYYtG;}|fE Fx Ef EVP7YYM_^3[rËVW3.A@.A(r_^á"A39$;A̋L$t$tNu$$~Ѓ3ƒtAt2t$tt͍AL$+ÍAL$+ÍAL$+ÍAL$+ËUSVu 3;t9]t8uE;t3f3^[uMsE9XuE;tf8]tE`p3@ˍEPPYYt}E~%9M| 39]RuQVj p0@EuM;r 8^t8]fMapZg*8]tE`p;39]PuEjVj p0@:뺋Ujuu u]ËUu MrEMA%}tMapËUjuYY]VD$ u(L$D$ 3؋D$d$ȋd$Gȋ\$T$ D$ ud$ȋD$r;T$ wr;D$v N+D$T$3+D$T$ ؃ʋӋًȋ^U"A3ʼnEUS3VW;~EI8t@;u+H;}@E]9]$u E@E$50@39](SSuuPu$֋};u3R~Cj3Xr7D?=wZ;tP5sY;t E]9]tWuuuju$օ5@SSWuuu ։E;Mt)E ;9EPuWuuu };~Bj3Xr6D?;w;thPxrY;t 3;t?uWuuuu օt"SS9] uSSu uuWSu$@EW uYuuEYe_^[M3mËUuMou(Eu$u uuuuu P$}tMapËUQQ"A3ʼnES3VW]9]u E@E50@39] SSuuPu֋;u3~<w4D?=w^;tP9qY;t ؅t?PjS WSuujuօtuPSu |@ESsEYe_^[M3lËUuMnu$Euuuuu P}tMapËUVucvmvmv mvmvmvm6mv mv$mv(mv,mv0mv4mvmv8mvl3l(llllkkkkkkkkkk@ kkukjk_k Tk$Ik(>k,3k0(k4k8k<k@jDjHj@LjPjTjXj\j`j^]ËUVutY;8.AtP~jYF;<.AtPljYF;@.AtPZjYF0;h.AtPHjYv4;5l.AtV6jY^]ËUVuF ;D.AtPjYF;H.AtPiYF;L.AtPiYF;P.AtPiYF;T.AtPiYF ;X.AtPiYF$;\.AtPiYF8;p.AtPiYF<;t.AtPiYF@;x.AtPniYFD;|.AtP\iYFH;.AtPJiYvL;5.AtV8iY^]UV3PPPPPPPPU I t $uI t $s ^ËUUVWt} u臊j^0u3Eu+ @tOuuQj"Y3_^]̋T$L$u<:u. t&:au% t:Au t:au uҋ3Ðt:u ttf:u t:au tUWVu M};v;r=EAtWV;^_uy,ur)$@Ǻr $@$@$@@@@d@#ъFGFGr$@I#ъFGr$@#ъr$@I@Ը@̸@ĸ@@@@@DDDDDDDDDDDDDD$@@@@(@E^_ÐE^_ÐFGE^_ÍIFGFGE^_Ðt1|9u$r $@$<@IǺr +$@$@@Ĺ@@F#шGr$@IF#шGFGr$@F#шGFGFGV$@I@@H@P@X@`@h@p@@DDDDDDDDD D DDDD$@@@@Ⱥ@E^_ÐFGE^_ÍIFGFGE^_ÐFGFGFGE^_UV3PPPPPPPPU I t $u t $sF ^jhA4襢@xte3@ËeE)Mh0@@0;AËUE4;A8;A<;A@;A]ËUE L AV9Ptk u ;rk M^;s9Pt3]5<;A@j hA舅3}}؋] KtjY+t"+t+tY+uCY}؅uT4;A4;AUw\]YpQÃt2t!HtЄ%p빾<;A<;A8;A8;A @;A@;AEP@E3}9Euj49EtP߰Y3Et tuO`MԉG`u>OdMGdu, @ AM܋ D A @ A9M}Mk W\DEEuwdSUY]}؃}tjpYSUYt tuEԉG`uEЉGd37ËUEH;A]ËUEL;A]ËUQSV5@W5(GA5$GA؉]֋;+GruS'؍GY;sH;s;rPuOYYuC;r>Pu9YYt/P4@(GAu=@׉Vף$GAE3_^[ËVjj 襪YYV@(GA$GAujX^Ã&3^j hAeuYEE EעËUuYH]̋U$"A3ʼnEESEE VWE7e=P;AEu}h A(@؅=@h ASׅ5@Ph ASP;APh AST;APhh ASX;AP֣`;AthP ASP֣\;A\;AM5@;tG9 `;At?P5`;A֋؅t,t(ׅtMQj MQjPӅtEu M 3T;A;Et)Pօt"ЉEtX;A;EtPօtuЉE5P;Aօtuuuu3M_^3[a]ËUVuWt} uȀj^0l_^]ËMu3f݋f:tOut+f ftOu3ufvj"Y몋UUS]VWuu9U u3_^[]Åt} u;j^0k݅u3fЋMu3fԋƒu+fft'Ou"+ fftOtKuu3fy3uM jPfDJXdfj"YjUMx~ u.A]á.A .A]qj]ËU}u u `Y]Vu u u]Y3MW0uFVuj5$2A,@u^98At@V3YtvV#Y~ 3_^]~D@P~Y~D@Py~YʋUM S3;vj3X;Es~ 3AMVW9]t u"YVuYYt;s+VjS _^[]ËU"A3ʼnEE VuW34809}u3;u~8}JiS FAL8$$?tu'Mu} }hD8 tjjjVwVYD@l39H P44@3;`;t 8?P0@4 3,9E#@?g $3 ǃx8tP4UM`8jEPKPYt:4+M3@;jDSPQ C@jSDP- n3PPjMQjDQP C@@=j,PVEP$4@ @089,j,PjEP$E 4@,08<t<u!33Ƀ @D<t<uRDYf;DI8t)j XPDYf;D80E9@8T4D83ɋD8?D49M3+4H;Ms&CA u 0 @F@FrՋH+j(PVHP$4@C(8;;+4;El%?49MH@+4jH^;MsCΉ u0j [f @@fƁ@rH+j(PVHP$4@i(8;a+4;EGK4,9Mu,@+4jH^;Ms;,,΃ uj [f@@fƁ@r3VVhU QH++PPVh@;j(P+P5P$4@t (; D@D;\,+48;E ?Q(Qu448@t(D8 D@D8ulDt-j^9Duw w0?DwY1$D@t48u3$www 8+0[M_3^SjhAw]uCw (w x;FArw w Vbҋ< FAD0tSfYeD0tuu Sn Ev v ME EwË]SfYjh0Av]u\v x;FAr=v aڋ< FADtSeYeDt1SDeYP8@u D@Ee}tuMu ME E6vË]SeYA @tyt$Ix  QP{YYu ËU"A3ʼnES] Vu3W}uStu+tC`t `pa F @u^VE^Yx+Attȃ FAA$uttȃ FA@$q3;g C9 B@Dž GW @} DžjugucDžW9~~=]VYt Dž5@GPPSP5.AЋtuPS5$.AYYguuPS5 .AYY;-uCS*stHH[Dž'Dž5Qƅ0Dž t@tGGG@t3҉@t|s؃ځu3} Dž9~ u!u t-RPWS0؋9~N뽍E+Ftct΀90tX0@@If8tu+(u -AI8t@u+@t5t ƅ-t ƅ+ tƅ Dž++ u% OztPYYt.u%˰0Otヽtu~qPjEPPIu69t.EPqYYu#PCYY|2t) ODt߃tHYt3dt t `pM_^3[EÍIZ@W@@@1@=@@@QL$+#ȋ%;r Y$-UQQE VuEEWVE,XY;uh NjJuMQuP@E;uD@t PhYϋ FAD0 EU_^jhPAh]܉]Euhh Mh Ëx;FAr@h %h zSы< FAL1tPWYeD0tuuu uE܉Ug g ]܉]E E܋U&huWYËU.AhYMAt I AI AAAAa]fQSuƒt7$ffAfA fA0fA@fAPfA`fApHuЅt7tIfIHut3tIJutAHu[XËۃ+3RӃtAJutIKuZUj <@EA3ËUSVu 3W};u;v E;t3{E;tv;fj^0QVuMDE9XfEf;v6;t;v WSV豞 e*e8]tMap_^[;t&;w ej"^0Q8]tE`pyE;t8]HN++ˉN~WPu D EN F =M tt FAx+A@ tSjjQ#ƒt-F]fjEPu ]f] E9}t N %_[^jYWƃуtefofoNfoV fo^0ffOfW f_0fof@fonPfov`fo~pfg@foPfw`fpJutItfofvJut$t vIuȃt FGIuX^_]ú++Q‹ȃt FGIut vHuY tjY.Atjh@j}E jq~̋U}u[F]uj5$2A\@]ËUQ=.Au.AujMQjMQPD@tfEËUS39]u3AVWupjV9 ;tuVWQ u SSSSSE3_^[]UWVSM tMu} AZ I& t' t#:r:w:r:w:u u3:t rً[^_3PPjPjh@h, A$@.Aá.At tPX@̋D$L$ ȋL$ u D$S؋D$d$؋D$[%@$2L^v,<P`pF4$ $0H`r0BXdr~,BT`r  :J`zR"@X@S@@_@@:#@ bM.AP/AccsUTF-8UTF-16LEUNICODEHH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSunHH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSunKERNEL32.DLLFlsFreeFlsSetValueFlsGetValueFlsAlloc  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~CorExitProcessmscoree.dllruntime error TLOSS error SING error DOMAIN error R6033 - Attempt to use MSIL code from this assembly during native code initialization This indicates a bug in your application. It is most likely the result of calling an MSIL-compiled (/clr) function from a native constructor or from DllMain. R6032 - not enough space for locale information R6031 - Attempt to initialize the CRT more than once. This indicates a bug in your application. R6030 - CRT not initialized R6028 - unable to initialize heap R6027 - not enough space for lowio initialization R6026 - not enough space for stdio initialization R6025 - pure virtual function call R6024 - not enough space for _onexit/atexit table R6019 - unable to open console device R6018 - unexpected heap error R6017 - unexpected multithread lock error R6016 - not enough space for thread data R6010 - abort() has been called R6009 - not enough space for environment R6008 - not enough space for arguments R6002 - floating point support not loaded `@@ @ h@@@h@@@P@@p@ @@@ @!@x@y@zh@`@@@Microsoft Visual C++ Runtime Library ...<program name unknown>Runtime Error! Program: (null)(null)EEE50P( 8PX700WP `h````xpxxxx ((((( H h(((( H H  !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~  GetProcessWindowStationGetUserObjectInformationWGetLastActivePopupGetActiveWindowMessageBoxWUSER32.DLLEEE00P('8PW700PP (`h`hhhxppwppCONOUT$Fatal error in launcher: %s Fatal error in launcher: %s rbFailed to open executableUnable to find an appended archive.Unable to read from file#!PATHEXT;Job creation failedJob information querying failedJob information setting failedmaking stdin inheritable failedmaking stdout inheritable failedmaking stderr inheritable failedUnable to create process using '%ls': %lscontrol handler setting failedFailed to get exit code of process.exe/usr/bin/envExpected to find a command ending in '.exe' in shebang line: %lsExpected to find whitespace after '/usr/bin/env': %lsUnable to find executable in environment: %lsExpected terminating double-quote for executable in shebang line: %ls<launcher_dir>\Terminating quote without starting quote for executable in shebang line: %lsFailed to find shebangExpected to find terminator in shebang lineExpected to decode shebang line using UTF-8Expected to find '#' at start of shebang lineExpected to find '!' following '#' in shebang lineExpected to find executable in shebang lineExpected to find arguments (even if empty) in shebang lineExpected to be able to allocate command line memory"%ls" %ls "%ls" %lsH"A0ARSDS#YDkE@WC:\Users\Vinay\Projects\simple_launcher\dist\t32.pdbAC0g@a@()@Q*@d/@o1@43@p2@?6@7@8@T:@;@;@@@T@X@*\@U]@d]@_@#_@c@j@ l@k@m@Ty@ @,@ۧ@@P@T@=@@@@@ L$2L^v,<P`pF4$ $0H`r0BXdr~,BT`r  :J`zRExitProcessGetCommandLineWSearchPathWqSetInformationJobObjectCreateProcessWpSetHandleInformationWaitForSingleObjectExAssignProcessToJobObject^FormatMessageWGetExitCodeProcessGetModuleFileNameWQueryInformationJobObjectgMultiByteToWideCharcGetStartupInfoWGetTempPathWCreateJobObjectAdGetStdHandleGetLastErrorSetStdHandleGetFileType-SetConsoleCtrlHandlerMSetCurrentDirectoryWRCloseHandleKERNEL32.dllPathRemoveFileSpecWEStrStrIW:PathCombineWSHLWAPI.dllHeapFreeHeapAllocEnterCriticalSection9LeaveCriticalSectioniGetStringTypeWUnhandledExceptionFilterSetUnhandledExceptionFilterIsDebuggerPresentEncodePointerDecodePointerTerminateProcessGetCurrentProcessInitializeCriticalSectionAndSpinCountGetCommandLineAHeapSetInformationRtlUnwindHeapCreaterGetCPInfoInterlockedIncrementInterlockedDecrementhGetACP7GetOEMCP IsValidCodePageTlsAllocTlsGetValueTlsSetValueTlsFreeGetModuleHandleWsSetLastErrorGetCurrentThreadIdEGetProcAddress%WriteFileoSetHandleCountDeleteCriticalSectionSleep-LCMapStringWReadFilefSetFilePointeraFreeEnvironmentStringsWGetEnvironmentStringsWGetModuleFileNameAWideCharToMultiByteQueryPerformanceCounterGetTickCountGetCurrentProcessIdyGetSystemTimeAsFileTimeCreateFileW?LoadLibraryWHeapReAllocGetConsoleCPGetConsoleModeWFlushFileBuffersIsProcessorFeaturePresentdCompareStringWWSetEnvironmentVariableWSSetEndOfFileJGetProcessHeapHeapSize$WriteConsoleW`GA`GAN@D        ! 5A CPR S WY l m pr   )     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$A`y!@~ڣ @ڣ AϢ[@~QQ^ _j21~CH@D@@@<@8@4@0@(@ @@ @@@@@@@@@@@@@@@@@@@@@@@@t@l@`@T@P@L@@@,@ @ @@@@@@@@@@@@@l@d@\@T@L@D@<@4@,@$@@@ @@@@@D@@@@@@p@\@T@L@8@@@$)A$)A$)A$)A$)A8.AA0AA()A*AA @A0AAA 4@4@4@4@4@4@4@4@4@4@..0.A(;A(;A(;A(;A(;A(;A(;A(;A(;A4.A,;A,;A,;A,;A,;A,;A,;A8.A.K0x 8Pehf 0 @Pb8e(`fohpt%h(hZ( @...GGGWWWiiiuuu ̀ ww ww||wwwwwwwwfwwwww|fwwwwwwffwwwwwwffwwwwwfflwwffffffwfffffflwffffffwffffff|ffffffffR\Ilffl)ffح2L18Ϥ"A< ΣTπU  ؀ ???????( 444MMMSSSzzzp p{p{ffkof_fffk_fff[f}UUUfL4UUU_kJqO_~'[U_Uwwwww{~( @%%%+++000===AAAIIIMMMQQQVVVYYY]]]~dFgggjjjmmmrrruuu}}}u?z=~;gEkCoAr@:IbpƏKУk(,1?5<\CKQsU[cbð/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq_______CCBABADL*(%$""&GABJIIDDJC??AD_-(''%$GG#_DIJIIJIIIDIJDD_+)(''%GG _JMCEJ?ABAECA?BA_,+)('%%"!_IM@AJACDCIDI____.,++>________JMJJJJJJJJJF_..,+)('%$"! #GIMCEJJJI?J__...,+(('%$"! &_JM?AJJJJCJ__...,+)(''%$" _IMJJJJJJJJ_K=...,++(''%$! _JMCDJJJJAJ_K_______L*%$"!_I_?AMNJJEJ_ L'%$"_I_MMMMMMJJ_ _('%%_I_CEMIJJMEM _(''*_I_?BMDJEMDMF _)(-LI_MMMCMMMMM_________< ____I_CEM1;MACDMEJM__I_?A_D1@IJJMJMM_FF_I__6_@/EMMMNMM_FF_I_C80EJDMJCJFF?:AA20?_M_MIEM______MIC4BC/ N______MMMMJIDAI_9/ HJJMMJMMMJ??BAI_A7503 IJMMCBCBA44CCI___BAO;6___C_MJIDIDMI_CI___O; @__C_C?J?AJI_?A_DJJI@ O_CM?;I?JI_________H9MAIJJIJI_CD_____JJI73C;IJIJI_?AMDJJDIMJIE5:JIJI__________MMIA:IJJJJJJJJJJJJJJJJMJ???( 444MMMRRRYYY~eGuuu}}}x>iErCE~7?HVbx/!P7pLcyϏ1Qq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq!%///* !*/"(%%'%/) /!#''////+/+"*(/ / !%(/ ( (*/ (///) */   )*  /"///// /+/// # + !%//  +"*+'#+//+'!%*++$++!+! !!!<(0`  !!$& &"   WܱNj+<<:5pi$!!!4888&SNJFB=950,(%%%5ǎVRNIEѹ|Jq?o@ZUQMID@<73/+&"-Ɍ444"ք̴wq?]YUPLHC?;72.*%!"""4888&dya\XTPKGC>:61-)% OÑ%%%5ʎA{;ys`\WSOJFB>951,($(9|:z;xq?o@mAkBiCgCdDuZ;73.*Ǒ%%%5̎È>89}:{;yq?o@mAjBhCfD~dEuZ?:625Оa789}:{;yr>p?n@lAjBhCfD}dEB>:5^ό555"܄ѳ„7789}:{;xr?p@n@lAjBgCeDFA=9"""4;;;&ŌC89~9|:z;xr?p@mAkAiBgCIELɑQQQJnnn~~~Įq?o@mAkBiCHHH"""vvv9~:{;yq?o@mAkB:::'''___sssttt8Ϧuǣvyp?n@lA888T---6fffnddd%%%"""OOOSSSċDҧuʤv{;yr?p?xM<<r?̷999VVV###)))444ѳ͝b@~9|:z;}Cg˶Ӎ󑑑ZZZlll;;;!!!""")))___&&&2:::ppp{{{___WWWWWW:::!!!"""***www"""4888&,̑999ԖJJJ|||lll:::"""BBBXXX|||$$$6,uuuKKKCCC@@@...www* Ҍ666"KKKaaaLLLWWW@$"""4;;;&nnnfff!!!$$$999444]ttt|ȇɑ&&&5ЎvvvmmmVVV{{{OOOڥaaaCCCpppό555"ބ|||MMMfff"""4;;;&\\\Ǒ%%%5Ύݧܦ͌555"ۄۥ"""4:::&ڤÑ%%%5ˎܤׇ ww?ww??ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwwwwwwwwwwwwwww?ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwww?wwwwwwww( @ !!!0\\\JeeeQ666D555A0$ vH<602\7qڴvvvj&&&D1"gLF@;5?кVQKE?9(aV[UPJD>82-uuun{{{j`ZTOVo@lAb_YSMGB<60+%<aVmt>q?n@bb^XRLFA;5/)$Ywwwn}}}j~Cv=s>p?bba]WQKE@:4.(+{;xbba[VPJD?93-'aV~:{;xr?o@lAiBfD}dE|cE|cEiB<61?9}:z;w=t>q?n@kAiCfD}cE|cE}dFGA;=aVңi89|:yq?n@kBhCeD|cE|cELF@mzzznjʓP8~9{;yp?mAjBgCeD|cEPJ\¯mAjBgC~dEaV}:z;xq?n@lAiCJJJőPyq?n@~YVVV???wwwiiiΡkA|;y>>3@@@y~~~obbb000kkk@@@---U<<<&&&[[[6uuusssWWWIII$$$&&&qqqlllg|||s4bbbhhhƑUUU000QQQ}}}---{bbb(iiiLLLBBBaVggg+++NNN|||nj]]]tttkkkrrrwwwӦaVӦzzznjӦˉ ?????(  C﫫oIਨS=?_QF4aZмoEcaXMB70~Iq?c`VK@58z;u=qv>3Eyn@iB~dE}dFGHYYYuuuġ=xmAhC}dE}eGS444|||gC}cEMMM~>q?kAiGRRR¢Gu=xKʺ~AAAAAAAAAAAAAAAA ( h00 %   h4VS_VERSION_INFO?fStringFileInfoB080904b0JCompanyNameSimple Launcher User^FileDescriptionSimple Launcher Executable2 FileVersion1.1.0.140InternalNamet32.exej#LegalCopyrightCopyright (C) Simple Launcher User8OriginalFilenamet32.exe@ProductNameSimple Launcher6 ProductVersion1.1.0.14DVarFileInfo$Translation  PAPADDINGXXPAD 0A0Z0t000001)1A12h2y22 33)3S3_3o3333333/44=5q55!6<6H6M6c6l6r666666667,7B7r77777777 88%8+808>8D8M8S8`8f8k888888(999999X:f:::::::;I;m;};;;;>}??? 00,070X120262E22222222222 3/3A3O3d3n3333342458R99999::::;;;<<'<, ?~????000-0M00000*1W1b11111152Z2g22223445P6X6i6u66689S9]9u9999f::::::::::/;4;>;x;};;;;< <<<<$<+<2<9<@>?/?????@T0(0R0c0p0w00000111D1q1112222N3u33445566690;z<<<=>P Y1]1a1e1i1m1q1u111111{222?4P444444445 5$5.5A5e5555T6q66)7H77777 88808I8e8n8t8}8888889R999=:;.;;;; <:}>>>>>3?9?O?T?\?b?i?o?v?|?????????????????`0 0000$0)000?0D0J0S0s0y00P1V1_1f11122#2(2:2D2I2e2o2222222223'3M3T3n3u33B4U4g44444445;5H5]5555%6L6Y6_66666$7+7@7777778F8X8888888%9.9:9s9|999999::D:Q:V:d:?;b;m;;;&<-<7>> >,>U>]>l 0001'191L1^111444555?5V5{55K6X7]7o777788899(9i999992:{:,;2;8;H;S;<<>W>>?:0]004455)5O5a5s5555555566'6777788 8898_8}88888888888888b9m99999999: :$:(:,:0:4:8:<::::::3;j;p;u;;;;;;;;;&<++>4>C>f>k>p>>>???&?+?>$>>&?;?j??40011r111233S555555666667Hh1l1p1t1x11111>>>>>>>>? ???$?,?4?>> >>>>> >$>8><>@>D>H>L>P>T>X>\>h>l>p>t>x>|>>>>././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659773740.0 distlib-0.4.3/distlib/t64-arm.exe0000644000175000017500000054500014273421454015607 0ustar00vinayvinayMZ@ !L!This program cannot be run in DOS mode. $z)))薜()薚(o)薛()()()()薞()))I()I`))))I()Rich)PEdb" (84@ `H\<T D JTJ8.text, `.rdata@@.data8%p R@.pdata ^@@.rsrcTVl@@.relocD@B0@AT_{011"@1@GI>c2 __S[cks{CsP@uXAw`ByhC{pDa(@@?{EsDkCcB[ASƨ_S[cks{CsP@uXAw`ByhC{pDa(@@?{EsDkCcB[ASƨ_ T!_ "Tb6 ` b6 ` b6"@9b9_BTBcT! BT? @aT @`T L$ L!@!`` Ld LB"Tb06 @" A$B&C!!@!``b dfcBbTb06 L` Lb(6 L`Lb 6 pL`pLb6 p `p b6 ` b6 ` b6"@9b9_!!_ T6!c ` ` 6!c ` ` b6"_8b8_BTBT!BT? @aT @T! @L!$ @L!!c` Lcd LBT!&C$Bc" A @!!fdb `BbT06!c @L` L(6!c @L`L 6!@c@ p@L`pL6! c p@ `p 6!c ` ` 6!c ` ` b6"_8b8_{S[cks!@SP@UXAW`BYhC[pD](@?@BsFkEcD[CSB{A_@xb 4@7 @`T,@_?Tt@Lqn"&"4@| ՠtLqn"&5@`N!Npn&BR@_!@ˢ$@x4!T$@x5ѠA_`7? #T @T,@_?Tc@ˠt@Lqn"&4!˥? CT#CtLqn"&4caT!@T! ˥ˠtLqn"&b4$@xB4!TA_c@ˢ$@x"4!TclT_@8 4 @@T,@_? Tp@L1n"&4@|pL1n"&5@a N!N0n&B R_֥Ѡ_!@ˢ@8B4!T ?@#T @T,@_?lTc@ˠp@L1n"&4!˥?@CT#DpL1n"&B4caT! @T!@˥ˠpL1n"&4@84!T_c@ˢ@84!TclT_C11@c1_1@1@c0TC_ {C{A_Q @s/3CT_t1@?@?T_ NNNN_Tf ib8 * $! )&&&&&&&&&&&&&&&&@q @! @! _@q @! @ _@q @! A9_@q @! _@q @! _@q @ _@q A9_@q _H@ qL@qL_R_TIE)@LTB@_H @TAB@qLIF     )@!LTB@B_+T@LBT__@lTChhb8c`(($TJ ) @ $Z_@(@Tc  `$Z_D@(D@Tc `$Z_@8(@8`$Z_@(@Tc  `$Z_D@(D@Tc `$Z_$@x($@xTc`$Z_@8(@8`$Z_@(@Tc  `$Z_D@(D@Tc `$Z_$@x($@xc`$Z__@(@Tc  `$Z_D@(D@c `$Z_@(@c  `$Z_@(@Tc  `$Z_@8(@8`$Z_@(@Tc  `$Z_$@x($@xc`$Z_@(@Tc  `$Z_$@x($@xTc`$Z_@8(@8`$Z__+TB(($!T_ !T(($aT_ aTBjTB_@mTB @(@TB lT TB chhb8c`J ) @ $Z_c  `$Z_ΐ|ؚtl\JH_S {@{ @SŨ_S {@- q{ @S¨_S[{@ q{@[ASŨ_S {@h{ @SŨ_S {@q{ @S¨_{ 5 @R1 uM@ R? {Ĩ_{5@R M@ R?{Ĩ_S[c{89* h(@ q"ѓ{cB[ASè_S[{ )!)C; q @BRRn @ R|@ @Q?q3*d @ !#@$@c @h%h~@ j3$Ҩ# R K?q @4*RG @!j#!yh&@cj@h%~@ j3/"D)R @( sB(*( @ !K#С^Ё2B`5iQ @?q!R @ !3#FhBҁ24s"T @ {è@[ASè_S[{RR6Р@2R?44R$AC&!@* @!S5 @ac @@2R?5ca `4R @!q2{è@[ASè_{qTQqT(B`(H@R!R y @I? R{_{U@?ր4qaT@T@"R!R? R{A__S{  7g  {S_S[c{R  R @R TS TTS TTdT@ T@ ?@@?@`?R 5 R 2 XTA@ ?ҿWTA@@?ҿVTA@`? (T=@?A@ ?ֈ HT=@?A@@?h HT=@?A@`?{cB[ASè_{S[C <S_q$(@T+@t}@ _ T(+ F vtbbT҈@hT`CDs"T5 \)@ R?Q qTE@?C[BSA{è_S[C"{CI@?(1V( @VDR!R?֠4#@ RAq@TRh@RVD!R 2 @i?}}}}}}1@?g@rT@ ?@@?@`?!g@G2g[Q@R$R?ր55@?1@*RCRR?CRL!@@VD?;@@y8(@!R?!U=@@?9@@R?Y@@?CM@#@?S[{YAB?s`@y4q@TR 5ZAB`"?s"8qb@y4R `4 RR! .@x4R* @5h@y4R* `5h.@xh5`@yR `4ys @yqTh@yqys `4>@yqTh@yq `@y4R `4ys h@y4R* `4h.@xh5{@[ASè_S[c{@?@yqATqJR RAS @y4R*w `4.@xh5()%@ R?ֈ@y |@q`T )  Rz)x*H(9!xj BT 8?5qT?)qT CTѿ!"e-@K9 RcR R?"*qUh~!Tci)xc3@y4R** `4h.@xh5h@y !#qAR* `4h.@xh5h@y !a$q4R* `4h.@xh5C A%# @&5 UA?֨>@yqTARy QA ?yh4(-x5y-h4H-x5yLh4(-x5y+h4H-x5H A*AKt"/''""c C@R`>C_{R{_C@`>C_{ RV $*i% RS4*Z*!54!i %SH4E#5R{A_R{R{_{y&%{_S { RS( 4RC9dS4BNq` Th5(RB!A 3#`4RBa ! #HRB5RC9*qh@SH4s@aA?AR`?eh@Sh4`@$"$@z$@h*ZS4U5#R R* *OS4C@9H5#*{¨ @S¨_R?R=*#*#{{_{`4,!9 @H_hK ȩ5`T!9H_hK ȩ5;R{_ R{*Hy9q)R)I99Sh5R&S5R R{_S{4z9*h5qTu4s5(A9%5(9%4R (A9))9 )*) * (R:9 R{S_֠R{IR (@y kT,=@*, RH@ kATH1@y-qT ),()@y a) @y,) _ @TI @A)TH @  A)TJ jR H%@h6R RRR{¨_{S45(!9ȿ;{A_{(y9S)SH45*%*B R{A_{)()GaT$ A9%q`{A_{Z{_S{4@sXAT c ?@) (? @ |@) ? @ |@) ?#@C@ @H  @?X(( {èS_ 2-+3-+R_ R_R_(A:( (:_{V@ @  {_(@q_(_(_`>_{A ?֠@yIR k!T<@ R (I@? kaTH1@y-qTH@9qTH@h4 RR{_'(! S{@)h@ kTh@q!Ti"@H(K q T?kTR{S_ֲ@$csm @S[{a< 5<!!BTt@bA?ր?#T{[AS¨_S[{< 5<!!BTt@bA?ր?#T{[AS¨_(Eq_@y-<S54|= A  `N `N+>N7)>N S%Ț*`TIh 86<`N0:n>N1 `N(>N  H!Q qS%ɚ( @T  J(>N )(}@Ӊ H AIh5F N |=  `NsnR `N1 `NN>N/>N7 SI>NJ%Ț(>N  ( `T  ki _ k+T-T(}@ $(!Q qSJ%ɚHI?TT K}@ Ap<`NsnNQ:n(>NHR `NH>Nh H>N )(}@i H+Aj@y_ k`_S[ck#{f@@ ˨@h6 y@52A9HJ@?qZ@ R jT@kBTaжR4@_C(T @_C("T@4@q T*@"C( c`7qmT@) kThA` o_ 4sAaA?!R`?@"R @ C(@@"@@!C(h?} @@kTRB@@˟ kT *R4 *@_C(#T @_C(T@r`T R4R4R4R+@B(T @B("T@@ kT @ @ kTk kT * kT @@4B(@z!T *@J @ R"C)S@ * @ k *T R{è#@kCcB[ASŨ_csm{Sh5RSh5 R{_{Sh5 R{_S{C9@ kT@qT @  K qT@ @4@)@@ 6 @4@4@ @aA?`?{èS_ csm  {{_{ {_;@T_{`{_R#S[{@1aT8 ?*@ ? T@  ?`5 @ ȳ ?4(hz@  ?J * ?{[AS¨_{a( ?1T;  ?֠4(hz RR{A_{`@1T ?h R{A_{=(a RR ?4 HiO R IiR{_S[{jO4=sQ`R ?jO Qj35 R{@[ASè_S{ 3%RA{¨S_U{@T%R*N*S{q%R"Lh@*H6"R*%gh@;rTh@(9~@j@), F @yk ҩ!h@"9Oi@;?rT_q!Tyk!*@96yk!*96a@gh@; SH50%Rh@;6 _qATA{S_S[ck#{*~@);F @h{z +! (Ix9q6h@h5L }@h@*i@h9U 7V>ԩ֚>BR*p%aT`@*h@;Sh4 S?q*HH#R*X%aT!lTh@;S4h@;!Sh5@o"h{z @)! *@9 6 S?q*HHV> ֚(֚  {¨#@kCcB[ASŨ_{S[ck  $s/<h@*h5Ii"@ R*)*I@)3F@hzuWA"!@ %hzu"I@? AT@@ R# ?4R*$`@B( T#,A(# _ BTH95qT_ "TH9)qTJS8J)kJT#H@kDcC[BSA{Ũ_H *S_q( T *Aӟ %@x)qkaT` 郈 8J)q_ aT_ֲT(S[c{ K(s @tTuWRRR=* RR\8x"828B8R8*I *9?qcT!TR({cB[ASè_S[{@t T?ֵ"aT6({[AS¨_S[c{* @qcT#(R} R{¨cB[ASè_RRR(ҿ 87E k+TzsH z3RRg(*E s  |@(*F)@yj ! i!(S[c{*7(EkbTh~@)6F@zu "*@_TqT34qT qaT`@   ?zuR"4l#(R_#{cB[ASè_ |@(*F)@yj ! iA(S[c{R'R8qTs6!3*HE IufS*~@ F +yj(Rh9R'*{¨cB[ASè_@T@9H6! ?ֈ@9(6A ?ֵ@"  ɚ ~@ F  @ Hyli! (R(9Hyli! -sS[{*S7(EkTh~@)5F@zt "I@96H@ TqT34qT qaT`@   ?֨zt R"*"(R"{@[ASè_{1T""(R 7(EkT |@(*F)@yj +! i@9i6`@""(RV{_S[ck{hOh5@R qiR(h}@&&GhRh`Ҳ&&Gh 7tR 9RR`(GF@j(ziI!+@h  HTy"sbQ5R{kCcB[ASĨ_ |@ ! _S[{,*')tRG`jh%*Gijh @?s"Q5G&{[AS¨_! A S {*T"R{¨ @S¨_h@y4@y5"RC* @"R*3+s @* @{!RR R`!@R{A_S{!Rh@;5 S4e)*)d`6`@`&)*{S_S{ 3!Rc{¨S_h@;1 Sh4)q*r*{ <SR?k3<S`T?qTEA yix  C'y"R#yK R*4#@y R{¨A__S[c{*B,@AaA?*`?{cB[ASè_ @ hG*)3-ʚS*R @ ШGjlR K ,Ț( ʨ-ʚ_S{ @ *@*{¨S_S{ @*@*{¨S_{S[cs!R?T5s @x9c9SW kCyS9OS#*V6Ts5 @T73bTWs69 @@Tj(8SB(%@9S4@HC yI*.cC[BSA{Ĩ_t @!T38{S[c RV@T5 @x9c9SW kC9yS9OS#*V6Ts5 @T73bTWs6y @@Tz(xSB$@9S4@HC yI*cC[BSA{Ĩ_t @!T3xS {TF RRB3hTBhT Rf+uRB$3R҇$*{ @S¨_S {T RRB3~hTBhT R<+uRBb$3R]$*{ @S¨__qT_ qDLz T_4q T(S QR? j RR__q@T_ qTH0QqiT(<S ~ QR? jT RR_ 0B `@,BA h $ R 8@q @zTQ8B5>*I *( qTh +$@ 9 $@) $ K P $@H$_ 0B ` ,BB( h $ R 8@q @zTQ8B5>*I *( =SqTh *K=S $@h(y$@ $ A P$@ $_ 0B `@,BA h $R O|@8@q @TQ8O>+Ϛi ( LqT ,$@ 9 $@) $ K P $@H$_ 0B ` ,BB(O|@ h $R 8@q @TQ8O>+Ϛi (=S L=SqT *L=S $@(y$@ $ A P$@ $_S[c{*q7RMT@ @;(1 S4@ @@*5/1T@  ?1Tsk+T{cB[ASè_S[c{*q7RmTR@ @;(1 S4@ @@>S.?# kT@  ?1Tsk+T{@cB[ASĨ_S[{*q7RmT*5 @1TskT{@[ASè_S[{*q7RmT>S9 @1TskT{@[ASè_{b9($@hA5 )@i(a"`H@h D@i *`@aB*i@(C72*R(jb9{A_{SCh@@.h@S@jAh@,@i @K@@*@#KCys9?CG*CB"c@9C4@HC yI*.*C@SA{è_{SCh@@x.h@S@jAh@,@i @K@@*@#KC9ys9?CG*CBX"c@9C4@HC yI*.*C[@SA{è_(@ }@9*@L94* *? kT8*h5 4h94RS Q? j`Th8h5i*9_qT*8_qT(_ k,h885_-@ 9@Sh8?qT@ 8Sihx7Sh8?qAT 9?q }@m @*9j98 *8*5_S[{ @ u@BR@C ( @` @qT@ @?T RRh@h5U4u{¨[AS¨_S[{ @p u@BR@C ( @`b @qT@ @?T RRh@h5U4u{¨[AS¨_S{`6B  SH4h@hrD ir? qTR.d j*@i 7l9 S?iqT}@ +}ӊmij Rh.@ *}Kitk.!q`TqT (I8( /Sh5R{S_~)h~)9R950qTa]*j@ @* K!kl_l66h2@ Kj6 2i2(R:q`Taj@ @* K!kh_ h:qh:@)i:ASh4h@ 9i9h@)5 ihrD ir? qT`*@S{h6Bhh@Rm}hrD ir? qTR.b j*@i 7l9 S?iqT}@ +}ӊhijR  h.@) *}Kitk.!q TqhTi (I8( RS4a9b` :~)h~)9R94[/qTa)j@ @* K!kl_l66h2@ Kj6 2i2(R:q`Taj@ @* K!kh_ qh:)i:kS4h@ 9i9h@i5 ih.@qGzThrD ir? qT`*@{S_֩S{h6Bhh@,RshrD ir? q TR.\ j*@i 7k@yRh =S?iqTh }ӉLii Rh.@ *}Litl.!q@TqTI (I8( 6S4:~)h~)9R94/qTau)j@ @* K!kl_l66h2@ Kj6 2i2(R:q`Taj@ @* K!kh_ qh:)i:QSH4h@ @yiyh@)5 ihrD ir? qT`*@{S_ֲS{h6Bhh@RWzhrD ir? qTR._ j*@iJ 7k@yRh =S?iqTh }ӉLii Rh.@ ) *}Litl.!q TqhT (I8( *RbjR9 *` :~)h~)9R94d/qTa)j@ @* K!kl_l66h2@ Kj6 2i2(R:q`Taj@ @* K!kh_ qh:)i:83S4h@ @yiyh@5 ih.@qGzThrD ir? qT`*@{S_֬9qTqTq`TqTqT0@ 2 0@ 2 0@ 20@ 20@ 2 0 R_@yqTqTq`TqTqT0@ 2 0@ 2 0@ 20@ 20@ 2 0 R_{R;Sh5Rh6B`9 @;(1 S4h6B @a6B+1Th*@ i*h* R{A_{(R`@yhR9h6B @;(1 S4h6B @a6Bz*R! kTh*@ i*h* R{A_{`9R91Th@ @*@K`xhR4h6B @;(1 S4h6B @a6B*1Th*@ i*h*h@ 8i9h5R4R R{A_{a9R9?1Th@ @*@KaxhR4b`h@ 8i9h5`RR R{A_{ 9?qT@ 7R ,S`?9qaT@h 7 R ,DRRT<@(5?qTT?%qT?1q@T?QqT?qT @(9qT( )R=IR;R<9 R6 @*9_qT(9qaTJR_q!T(9qTjR( < $HaQ S?qTiX(%Ț6)RR?qT?q T?qT?q!TRRR  @(9qT( RiR < R{_  { 9?qT@ 7R ,%S`?9qaT@h 7 R ,RRT<@(5?qTT?%qT?1q@T?QqT?qT @(9qT( )R=IR;R<9 R6 @*9_qT(9qaTJR_q!T(9qTjR( < $HaQ S?qTiX(%Ț6)RR?qT?q T?qT?q!TRRR  @(9qT( RiR < R{_  { @y?qT@ 7R ,Sa?9qaT@ 7 R ,dRRU<@(5?qTT?%qT?1q@T?QqT?qT @(@yqT(  )R>IR<R<: R7 @*@y_qT(@yqaTJR_q!T(@yqTjR( < %RH =S?qTHX%ɚ6)RR?qT?q T?qT?q!TRRR  @(@yqT(  RiR < R{_ { @y?qT@ 7R ,Sa?9qaT@ 7 R ,RRU<@(5?qTT?%qT?1q@T?QqT?qT @(@yqT(  )R>IR<R<: R7 @*@y_qT(@yqaTJR_q!T(@yqTjR( < %RH =S?qTHX%ɚ6)RR?qT?q T?qT?q!TRRR  @(@yqT(  RiR < R{_ S {h9 Q_qT(Y ( R$M!fh2@ 2i2ARR i2@(Sh4(2h2R"RRR RhRi")SH 4hA9 5l2@Cy  9S4Sh4Rl6hRS4RC9+m9RS aQ? jTSh4.RRS QR* N55CRhi)8aqTqRAT RGhi)8k i6@ *hR@r)K4KTc*R`h6B @;(1 S4h6B @h*@  i*dBc*C`,i2@( S4( S5c*R`Rh*@(7h2@ S4c*R` RR{¨ @S¨_S {h9 Q_qT (Y ( R$o!h2@ 2i2ARR-i2@(Sh4(2h2R"RRR RhRi")<S 4hA9H 5l2@Cy  9S4Sh4Rl6hRS4RC9+m9RS aQ? jTSh4.RRS QR* N55CRhi)8aqTqRAT RGhi)8k i6@ *hR@r)K4KTc*R`dBc*C`i2@( S4( S5c*R`rR=h*@(7h2@ S4c*R`e RR{¨ @S¨_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{SFh@y Q_qTi(Y ( R9$!rh2@ 2i2ARRi2@(Sh4(2h2R"RRR RhRi")iS 4hA9 5k2@  yhS4hSh4Rk6hRhS4Ry*l@yRaQ jThSh4-R RQR  M55R(y*xaqTqRAT R (y*xJ i6@ *hR@r)K4KTc*R`h6B @;(1 S4h6B @h*@  i*dBc*`i2@( S4( S5c*R`gRh*@(7h2@ S4c*R`Z RR@SA{è_{S[hh@y Q_qhT (Y ( R[$!h2@ 2i2ARR i2@(Sh4(2h2R"RRR RhRi")S 4hA9( 5k2@  yhS4hSh4Rk6hRhS4Ry*l@yRaQ jThSh4-R RQR  M55R(y*xaqTqRAT R (y*xJ i6@ *hR@r)K5KTc*R`tdBc*xi2@( S4( S5c*RR h*@(7h2@ S4c*R RR[BSA{è_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxS {j@ @* K!ku_@b>@a9`@*t&@yS4(RhR9)}S H%h&I%(85H%)R9iR R{ @S¨_S {j@ @* K!ku_@b>@a@y`@t&@yS4(RhR9)}S H%h&I%(85H%)R9iR R{ @S¨_S{Ch2@4Rj:@ 2i2J6hA9R Q? jRR*j: *5h9qT RqaTt:*RHu}@`btS(5h2Bh@h.BA uQi:ib(B(h&j@ @* K!kp_a2Ba@h.BAӡ@abcb h.B#H"Ag"@ e9f:@#h2@ S4h:@5a@`&@hA9R Q? jTh2@ S5a@`&@j&@K9qTh2@Jj& 2i2K9h%Q S?qTX(%Țh79qTh2@kRk9 yi2K9 k4(85( hR RC{¨S_ !S{Ch2@4Rj:@ 2i2J6h@yR Q? jRR*j: *5h@yqT RqaTt:*RHu}@`bS(5h2Bh@h.BA uQi:ib(B(h&j@ @* K!kp_a2Ba@h.BAӡ@abcb h.B#H"Ag"@ e 9f:@C#h2@ S4h:@5a@`&@Ah@yR Q? jTh2@ S5a@`&@j&@K9qTh2@Jj& 2i2K9h%Q S?qTX(%Țh79qTh2@kRky yi2K9 k4(85( hR RC{¨S_ !{b>@a9`@j@S4 @* K!kc_xa2B@abh.BAd@`B 4(Rh9ib(B, @* K!kh89(RhRib(B R(h&{A_S {5RuR9j@ @* K!kb>@a@y`@t_xibS5(BC9Cc@G9 h@ 6u9(B(yib(B RuR(h&{¨ @S¨_S[c{j>@*XS_-q(TI(I8( t@6 Sh@4 !i)85}@7 !i5_83tj@@V S 4 @* K!khx}@% @* K!ku_xtj@@ S @* K!ku_tj@@ S 4 @* K!ku @* K!ku_҉@(S4)2j:@6(Rh:(yA}@`b@ yR9"**T*)@ S4hR@4h&@ 9?q Th&@ R i&*9hR@ iR RRR{cB[ASè_ֱS[c{j>@*XS_-q(Ti(I8( t@6 Sh@4 !i)85}@7 !i5_83tj@@V S 4 @* K!khx}@% @* K!ku_xtj@@ S @* K!ku_tj@@ S 4 @* K!ku @* K!ku_҉@(S4)2j:@9Rj6y:(yA}@`bU@ yyR9"**T#*@ S4hR@4h&@ @y?q Th&@ R i&*yhR@ iR RVRR{@cB[ASĨ_ְS{j@ @* K!kt_{"4j>@_-qTi(I8( j* h*@hRyyh99(Rh9 R%RR{S_S {@ @* K! G) s_9@15& S4($&3$(R~@R9 (%&3%~@ *R R{ @S¨_S {@ @* K! G) s_@y@15&S4($&3$(R~@R9*(%&3% RqTh94 S@ @*@KkxrhkTR R{ @S¨_{@;1 S5Bq |@ TjFyji@ ,!  95q TjFyji@ +!  h@96R9R R{_ @I@ THa@94H@ IH@a@9H@ I @H @ I @H@9 @ Rh@ i_ @I@ THa@94H@ IH@a@9H@ I @H @ I @H@y @ Rh@ i_{SChRA9(4hR@qTu&@R&@xd@# 5@4h6B @;(1 S4h6B @h*@  i*dBc#`2hR@kATh*h6BbR@a&@ @;(1 S4h6B @h*@  i*dBc` RCe@SA{è_{STChRA94hR@qMTu&@R&@xd@#5@b4dBc#`hR@kT h*bR@dBa&@c` RC3@SA{è_S[c{hRA95hR@q-Tu&@RRc@#yCh@ *q-Th6B#@y @;(1 S4h6B @a6Ba # kTh*@ i*w*hR@6kTw*h6BbR@a&@ @;(1 S4h6B @h*@  i*dBc` R{¨cB[ASè_S[{hRA95hR@qMTu&@Rc@#yCh@ *qmT#@yb`hR@6kT h*bR@dBa&@c`8 R{¨[AS¨_S[{*4@*@ T(a@94h@  H @~@1@H@ I@H @ I @Ha@95Thh@ i{@[ASè_S[{*4@*@ T(a@94h@  H @~@1Ӳ@H@ I@H @ I @Ha@95Thh@ i{@[ASè_S[ck{3@*38u@T@8 @;(1 S4@ @)@1T @` @qT@ @;(1 S4@ @@R1T@ !Th@h5U4u{kCcB[ASĨ_S[ck#{@*~@v@7?TR@ '@x @;(1 S4@ @I@9R! kT@`q@qT@ @;(1 S4@ @@R$# kT@ ?Th@h5V4v{#@kCcB[ASŨ_{DRBc+C#C {Ȩ_{&RBc+C#C{Ȩ_S[ck{7R9sTiT@1T@qT"@h81AT!T@qT 691THRRy{kCcB[ASĨ_S[ck{7R9sTiT@1T@qT"@h1xAT!T@qT 6y1TyHRvR'{kCcB[ASĨ_S { *SaR{¨ @S¨_֟ qT#*K*"*S{_q Th@; R jTh@;rTh@qT`@; |@*F)@yj +! i9)7h955"RҨ@h@ (ˋ )ӟ kThӟkT j"@  ?lTh@( THhi@ R(KhR{S_S {h@*;5 S5 R8jRI_)yI5;*Sh5qTR{h@hi@;( S4jRI_)uI5; h@;)R qTh@;!Sh5@Rh"`@;*F@TR{ @S¨_!|@O{$@T RdRbyJHiix(%xh4!сy HRR@RR{_@@@@y4*@y * 4 * *kT-@x *i5i4,@x5@yH4)@y *)4 @y * * kT-@x*h55,@xh5$x @@_{k RA`H@{_S{ @h@@ |@*F)@ yj)! *@96:*G (R@*{¨S_{1T- 5 (R7EkT |@*F)@yj )! *@9J6S)Ccs  (R{è_S {*IaTR@qaTH!C97 qATHB96 R9@R6T*2 ?5 ?**~@ F @(yk I! ?94* R{ @S¨_S[{`R^ R{R"S)4(yh4) ( A6A RR`5SvR`RZ*{[AS¨_RIS {3yh4(-x5(i@A(y h4H-x5H ˿"T`@xuxqT(`4i@h@  { @S¨_R#R{a R@;{_{S[cK RRlcTy@@@yJ4RH =S?eqhTHy.@x*5RUR Ri*5& HR# @G~@Ty HR@R= A? T<LT|Dӊs/sR4RhsB R @@*@ R.41* HRTRh_R kaT`B8*EcC[BSA{Ĩ_S{C?co@94 @HC yI{èS_C߈CbR%@x+$@xHQeqIhQLieqhK5l4BB_{Ah5 RKA{_S[{ R4=VsR9C@h@(&&@xqBT @ ylx6h@il8Sc@<S&@xqBT @ ylx6h@ il8 Sc@ <S K5t4sѓ@94 @HC yI{è@[ASè_S[ck#{- R{è#@kCcB[ASŨ_ֳ ؚTTR3 ؚTh@; R j`Ti"@ R  Rh@; R j Tw@4 77BT*_C7 Ta@~@ӂ  aRv h@B7j@ZC7 KHA7ih~@B)T{@ W4I5>   K_C7CTh@hf@* 4@7 |@Z @@ 1T@Z(@9i"@  TR4 HRzjRI_)2I5jRI_)2I5;@ ؚlS[{ uTRy R*{¨@[ASè_<,9S[{*-sj@K_  T(AkT? T @ T Ra(AaT[w@v@"q TI+A?)A? T@I v@ kT@T  kT  kTI  kT  k*!T(R4R%RR"hRtRHRTRRR kT kTI kT  k*TRR RRRRHRTRhaA?*R?vaA?*?wR{@[ASè_֑@__ @h@*)*-ʚ___S{ @h@*)3-ʚsRaA?`?{S_ @ RIi K,ɚ h_S[c{((v"R8qTqUR@  8h9s8*q! 4@  9h9s545q`T&qT8R9(4q`T%qT94v"(@ R )9+R R8JqqTqTJ749qaTq RJ}S*4JQsw9s@  *59 45qIzTK4`99s!! 4@  9h9s@  s9s@  V(@ ){@cB[ASĨ_{bTB> š?T)|  )T !& O {A_S[{*u 4QqTRRSB RB(3@sh9H5Bc,A"R R0cq!T@tQHYC [*4 @   @ H@ h@)iY  R*{è[AS¨_S[{a`@h@HN@4t@@RRRn |@vA @*RR` @4R2#Ҹ @`@Ұ {[AS¨_S{@hRP s /`RҒ  *{S_S{@hR s Z`Rs q *{S_S[{9 4q)h4H85H8H5 - 94i4H85H?qT! u`5"? h@8 @4 2 / {@[ASè_RyS[{@y Ҩ4qk h4(-x5( A* @y5` 3@y4= =Sh4(-x5( A7_qTA u`5" h@ @  {@[ASè_R'S {@h@HRRR|@u! b@*RRs@4R!"ҩ h@R { @S¨_S{@@`TSh@ @ {S_S{@@`TSh@y @u {S_{a`@@h@h`4`5`@{A_zS{a`"@Sh@J @F @Sh@> @: {S_S{@a`@@h@h~`4V`5`@{S_tS[{( *Cӿvд@bA?ր?sT{@[ASè_S[{`Tu@bA?֠?ր5TR{[AS¨_S{ @ `@9 {¨S_S {"E95)R ;Ո@ @5 @K(@ `TI3-˚aA?R`?A?qT@ @5 a !  @ @5(R"9 @)R 9{ @S¨_k_csm{*)b5A ?֠@yIR k!T<@ R (I@? kaTH1@y-qTH@9qTH@h4*3cC9SC#HRIR)sGy@4{ĨA_*{* qT1@ @*!SJ5 ?ֈ! * ?*a * ?S{( (a *CR ?4) @ ?aA?*`? @A ?{¨S__"RARR"RRR}RARz @@ RIk K T ,˚* ʊ_ցRRh{RFR J;R{_{@Qur!T0@qT!(;R{R,R{_|@?@q- F@yl !*@9+9OT? @q T(@@Qyr@T?AqTH2(9(Ryl%H9 H2(9HRH(9H2(9yl%_9o5Rk5RqR(R _{ ? ? R{_a__S[{ @ C9'"`Tɂ h@ T ["`s"@ {è[AS¨_)R ;_{SCcCRR){¨_{*ZCJR)RrI1 Ts4q`T qTR y2H  *{A_A;_{qTRy H_@ 5;*{_!_S{ @? *@` *{¨S_S{ @- y*@N *{¨S_S[c{@ @- @@* ʩ@V-˚* ʩ @N-˚* M-˚ TC @(  U#T!Tu!Ҽ @  *C 胊kT @@ RJ KR @*-ʚK  @ RI K@.ɚ  !I@ R+ @J K -ʚ@+ H@ R  @HK@-Ț+ H@ {cB[ASè_S[ck#{@ @k?L@i@* i@U-Κ* ʩ? T-ΚHT "џT@`T  ʙhcA3-Κ?`?@L@+@h@  h@M-Κ  K-Κ`XT    T=L@@ @,@H@ @*@L R{#@kCcB[ASŨ_A{ScCIRHR){è_`  @@ T@ R_{ScC'HRIR){Ĩ_ ( R_{A R{_{ R{_{@d!! R{A_{RS{_S{jDH_QH 5;(5`DBTBh R{S_{`D`D`@`@ R{A_{A) S{_{S4GH RA) S{_{  @aA?`?"{$@T RRb9JHi8(8h4!с9HR@RR{_{,!`R;!)AH6`>`R57 66R?kT R_RS[c{h@*wSR|4 QqTC6y@R@-49m @S ix69 lq2q `TqT,49l Ru4BqaTB S?%qhTQ ~ S?eqhT^Q S?eqQITh4HR9RkS aQ? jTqRlѵl49 kTx*l@lt9lRqU5> KB S?%qhTQ ~ S?eqhT^Q S?eqQIT1@TkT kT k KziTR" Rh@7* 9lh49kT=7@9y4 @HC yIi@ih@(R1**HS4'HRw7@964 @HC yIi@ih@(R4 @HC yIi@ih@(W6K@94 @HC yIi@ih@(*{è@cB[ASĨ_S[c{@*uSRo4 QqTCV@R3@y( @%@x*R;@52qhQR jT@%@xzrRTqTqbThQp R k Tq TqbThQgq TqbThQa%q# T%qbTh%Q['qc T'qbTh'QU)q T)qbTh)QO+q T+qbTh+QI-q# T-qbTh-QC1qcT1qbTh1Q=3qT3qbTh3Q75qT5qbTh5Q1B9q#Tj9qbThB9Q+B;qcTj;qbThB;Q% KqTqbThQp R k Tq TqbThQgq TqbThQa%q# T%qbTh%Q['qc T'qbTh'QU)q T)qbTh)QO+q T+qbTh+QI-q# T-qbTh-QC1qcT1qbTh1Q=3qT3qbTh3Q75qT5qbTh5Q1B9q#Tj9qbThB9Q+B;qcTj;qbThB;Q%{ƨ[AS¨_{SC9Cc#R) R)sGC@9@q@ {Ĩ_S{i@;( q!T?rTh.@ Kkq-Ta@*k TjRI_)2I5; h@; S4jRI_)yI5;R{S_{R`5h@;- S4`5R{A_ R{h@;5 S4h@;S4`@mjR (I_) I5;~{A_{RC@h  R{¨A_|P;_S[{G)ᏹa( ѶT@Sh@;5 S(5kRh_ 2i 5;7h*7 _h  _RR_jRI_)2I5;x{[AS¨_S {C @y A ,@x?qT5R?q`T?qT?q!T3`RR*3!RJR RRRR)RDRREҨ@y** 4MqTTq TqTqTq@T9qTIqa T5s2s2=rTs22'R4f5&R37j3305s2/Rq$QqTqTqTqTq TqTr`Ts2rTs2 5Jy5J2.RqraTs2s`6 Rs2)R?q  5 q@y.@xqT5@yH4R@ @{¨ @S¨_ֈ b 5(@y(-@xqTq!T4 @y.@xqTP5h2IAG5h2  >`5h2jixqT H-@xqT5c9S {*&#@9h5@0R*C5HO@ IjRI_) *I5;@~h{è @S¨_ha {CH@  @@{¨_S{ @@ @*E@I_)I5;`@9{¨S_S{ @h@@`J@^`J@DT T@H5J@{¨S_S{ @@ @@!@@@`@{¨S_S{ @@ @ E@_Q 5;5A@T`@{¨S_{cR#c#RR)'+R'R (/h A%@C @AK@(E@iRIyy@ y@pC{Ũ_{O{A_{RRRR) @hA%?T 7@8@4@-@1@1@.@5@+@%@(@)@%@=@"@A@@AScCssC:{Ĩ_S{J@DJ@T T@H5Js{S_S {h ?*B1 T}TB`4y ҬB҇5B~>h * ?{ @S¨_nS{B1TG@TB[4y wBRҜM5BI ҏ{S_?S {h ?*B1 TTB&`4y BBg5BZh * ?{ @S¨_{ 1aTRR R{_{`B1Th R{A_S {hT(h!E R ?`h!`44ER?R{ @S¨_{A #RC{¨_{A #RCҞ{¨_S { 95 yKC@*R( @ kaT' q (@s@9hy R@9H4 @HC yI-@9cw4@# @qTkKT @!RQ @5( @(T94 @@#R @!R? 5?HRR{è @S¨_ҡS {#y @TSR7sh{@TRR+@)R @ kT#@ycS`qmT@@#@yqT@TRHR@R@94@HC yI{Ĩ @S¨_tu9S(Rh*S#RCR 4@5S`Rh ?qT@TRPHRt@Rc<S{Di@?TI C?jaT`{A_{Di@?TI C?jaT `{A_S[ck#{CK@9qS*.(TxHR)@R@HtaT@*R* `49 R+  R*iSKA 9 9Rh9@sRIS6y_)LRT{9@@8(Rh9ws t5R@}@ @(9(9@ @IRҟqmT@ +@h%š =S+j=S_qiTH =SHQj9=s!DӔQ6b7@S4kRl9S Q? jaT{8Tq@T i9h8h8qT*RsB49RqHkSk Ai9@l Ht ?*?hRh9_9 T }H ɚ  9 aT_ T H ɚ  i9k D J TIH ɚ  i9kH i99R@94@HC yIC{è#@kCcB[ASŨ_S[{*  `@_C1CS*@[@*Cqqן  H `49 +@RC***{¨@[ASè_S[ck{*qhŸ %)wS*STlHR@R{èkCcB[ASĨ_C64@q*qmTH9 h4(85(  @@RqaT9q Th9@h9(}@ @*9j8@ 33 Hi"12% 5` w4Rh9@ 9?q T@ Qm6 Kz9qkT}( `h 9+}l}K R h 9)qkT}( `h9+}l}K  h9 Kh9 h9? qT9qTb@94 @HC yIRRҦ QgfffS[c{C*S[@Rw4HQkT@q**H_i388@qTRh9s@q,T5@ 9(R?q@TR4h4t h9h4(85(tmx9t(qT9h4(85(^@}@ @*99@6Kq VzԢt4h9h4(85(`4G~@R@94 @HC yIR{ècB[ASè_S[c{@*C?}C*S*@c@*C*q@3Q v`49@ Q?1$UzT kTh8587@$RC*\ 7@&RC***{¨@cB[ASĨ_{)S(*T9R{_4R98@TcшS R-ySNQo/ ,yk9 h4H85H 臟  + /yk`5RRһS_{@W< *@K%ךs=S5"q7*5@h @*%ךL=S!qiT RT(!ך * _@TqTK @*%ךKS)L` qT34@qTs4@HR{_AS¨_S[{C*RpRwT+qTQ qRHT&RG7@tT@aT R   aTRs(*K`2S*]SD 2[@{qlTQyrTqTq+@ * *  :qTqT+@ **  +@} *#@**q'@   ˁo`49+@R*_ +@ * *  C{¨[AS¨_{S[C@<S;1 S 51@T1T*h~@ Fhyl @ I!  (9 S?q T1@T1T*h~@ Fhyl @ I!  (96*#<5@RqT#8!1T@sk TR@ Q6*<S@y@* *CA@[BSA{Ĩ_(@ Q)I6(@9)@S**_֨@)D @? _{@R_`T R[TI`5R R{A_S{Sh4 RFTA'@R?Ta'HO Ij@;R_jTjRKPRI_) *I5;Ո@ҭ@ RhrhIRhii"h R@R{S_{S4h@;% S4jR PI_) I5;"~{A_{A5HR A{@_T{_S {sR*-R#U{@TC@@f**!R5RQ@94 @HC yI{è @S¨_AH5S {CgsR#{@TR@@c@**"Rc_5RQ@94 @HC yI{è @S¨_S[c{C*q**Ta~@{k***C{cB[ASè_{<SR#y kaTR%c#@yqTh& @ ykx6@@ik8S@ @%RK#RC R `5#@y'@y@94@HC yI{Ĩ_S{3gR{S_h@;5 S(4h@;1 S5h@;SjR4I_)2I5;I_)2I5;h@; R jaTh@hb"@a@:`qTq RR+jRI_) *I5;h@;rT1`T1T*~@) F @)yk H% ( @9IR  qTjRI_)2I5;h"@qaTh@;S4h@;!Sh5Rh"h@j@ QiH@@9hbT_S[{*4h@94` @39~N95h@hh@94` @'9@R` qqh9i 5h @yR!R*|@5H ?֊@%i@ iTh@94` @9R` qqh9i5d @ *!R*|@I(hR{[AS¨_S {*4h@94` @9~R@y5h@hh@94` @9 ҕR` qqh9i5h @9RR*n|@5H ?%F@'i@ iTh@94` @9jR` qqh9i@5d @ *R*F|@I(hR{@ @S¨_S[ck#{RRq@HSRRCC9G9K9@i*t5Ȏ@hWB(C( C탉 ҭj@H9 h4(85(  T" WB T˂@H9h4(85(I!@5@wk4"TR *C@T*{Ĩ#@kCcB[ASŨ_R>S[c{9h4(85(4 TR{@cB[ASĨ_!қD5> 5i@?T`@Ҁ҅`Үi@ (iC  TҬ @ҝRҘ* `" i i@5h@ !iRR{S[c# X 9Q S?qhTi&Ț7TT 9?qATTy(Q S?qTh&Țh6)R R?qKc9e@)R @ k!Tc@94@HC yI"RIc@954@HC yI"R4@HC yIRC@RqRH R ?T1*^&@)7C39%@)R @ k!T@94@HC yI"R @954@HC yI"R4@HC yIR3+@q9qT9h4qaT94*35A9h4H  ?֠5"@  +C T @ aARA9h4H  ?cB9h4C@* #@cC[BSA{Ũ_ 4S {*h@9H49~8@y5h@hh@9H495HR@R9+h @9RR*C |@)H ?@h@?Td @*R*-|@I(hR{ @S¨_{Sa H! T|@Ӣ RC ?5H ?R)SS##9[@)R @ k!Tc@94@HC yI"R?c@954@HC yI"R4@HC yIRC{#@ .SA{¨_S[{ @H@ @*E@Aa'@ " R_R" R2RV@ @*E@Le@%h% Th}TB@ @*@H_QH 5;(5@ @ @A@T@ @@*@hE@H@*@KE@i_)i5;@%{¨[AS¨_qTq`TqThA q h h h _{*C?  1T(R( I(! 1!T(R( I( ?*1T(R( @ @@94 @HC yI*{èA_S{b" RR AhaB) R ˟His8)Qh85fhe  Rhii8JQ(85{S_{ i`@(Rk THA C ? 4R(8qT R[@99[4*@9qTI(8 kiT(-@85e@R  R!Rҕg@ RaA R Rg@ RaA R@Rof,R R%@x6iB,Kig8(a@92(a9 6iB,Kif8(a@92(a9 RKi/8JQ5  R RhQeqTh}QjB(nSIa@9(2Ha9 hQeq(Th}QjB(Ia@9(2iHa9.SRh e9kQ5 @{¨_S[{*6SS*%@*E@ @ kaTREҟ@ERE@**1T`R*{Ĩ[AS¨_V5N@ E@H_QH 5;5@E@(A@T(R@E@I*C_jT'RR)ScCqV4@ @IS {CI?jTJ@hF@RF@@Th_Qh 5;5A`Tc@Fi_)i5;ՠR{ @S¨_ S{h9h5'(Ah )Ah&@cB!R](R(9 R{S_{B'{_{S[1Cv*4! R U}@Ө~ iv?k@ TJ_q#TRk@ TH Ȁ>S ?ր4(RkaTh:y pHA * ?֠4ub" RR&@t qT@9(4(@94+@9kHT Kj Jj8kQ2J*8Jk5(-@8(5)R)Ji8?q2J)8cT`@)R` Ri J59wb" RRA! ҎRB2 j@9j4h@9(4_kTMqBTJm8Jig8(*J-8h@9_kTj-@85Q5(Rt)* `l2R*kQJyhx%xk5 RC@[BSA{Ĩ_S {*C**@jS* e@9?jaT4@@ jx*  Rj5R R@94 @HC yI{è @S¨_*҃RRRkTRK%qTTR(%h7qaTRӚRkT(՛RkiTh֛RkTRk`T(RkAT!Ha RKqR釟kHTRK%qTTRH%7qӚRkT(՛RkITh֛Rk)TRkT(Rk`T!xR?q?qi4GHa S[{H ?U@y4 =h4(-x5( A*k h@y5h 7A*RRR|@**RR`4H  ?{@[ASè_S {H ?t#@y4 =h4(-x5( A*k h@y5h 5AzsҜH  ?{ @S¨_S[ck#{**RRH V`Tt@9@Tst4@ t@@ATdt4 /`Xt@T@H %N@t@ @5@ i8?q$@z`T@Cu@(zu0z5zu(@((@h.Җz54ˡ ?#T?T }t49h4(854ˀ !uR `5 *_q_85HR RR*{#@kCcB[ASŨ_R"S[ck{*eRR V`TT@@y(@TT@4G T@(@ATT4h hґ@(T@4 ^@҇T@A@5@ yyx?q$@z`T@C˕@Hzuix5zu(@((@hgXT z54ˡ ?T?TSD }TW4yh4(-x5(A AuR 5(H q?x  ? 5HR @  @RR *{¨kCcB[ASĨ_RUS[{{[AS¨_h@ H@) Ti@(9 h4H85U ˠ!Ұj3js b@j5i@R|S[{{[AS¨_h@ H@) ti@(y h4H-x5H AAuj3Ҟjs b@5i@ҔRA{ 1@*@K @7Cm@Rq@T R{¨_H J RRq(H _S{A%`@@@Ta`@@@T\`@@@TW`@@@TR`@@@TM`"@"@@TH`&@&@@TC`6@6@@T>`:@:@@T9`>@>@@T4`B@B@@T/`F@F@@T*`J@J@@T%{S_S{A%`@@@T`@@@T` @ @@T `.@.@@T`2@2@@T{S_S {(@ҟ @sT{ @S¨_{```BA``@`@`@``b`B`A`B `RA`VA`ZA`^A{A_bRII @y 4(@y_kT!) @y(@y K_{S[ck+****t5H@ @q)R(R!R**%*v5R;~@ A? T<LT|Dӕs/sR?RhsBR**!R*`4Ha ** ?*Rh_R kaT`BEHc@94J@HC yI*_K+@kDcC[BSA{ƨ_ @I_)I5; p@I_)I5; x@I_)I5; t@I_)I5; @I_)I5;Ո ! RH_ TL@_)5;H^L__)5;JkQk5@US[{i~@iA%?Thr@@h5`z@@5`~@r`v@@5`~@`r@`~@h@@5h@h@h@`@`@2!vtR_T@@5@^_@H5"Q5{@[ASè_֠H2 T pH_H 5;*_{sH2Th^A;Ո5{A_֠H2 T pH_QH 5;*_ @I_)QI5; p@I_)QI5; x@I_)QI5; t@I_)QI5; @I_)QI5;Ո ! RH_ TL@_)Q5;H^L__)Q5;JkQk5@_S{9CI?j`TJ@SRWDB Rv{S_S{@aTt@5 `T{S_S[{U ՚TR]ҵ~jnUTR{[AS¨_{H ?ֈ {_ֈ  R_S[{@TWv@bA??S4sBT`TTs"h_u@bA?R?sBh"TR R{@[ASè_S[{TVu_bA?R?sBT R{[AS¨_ֈ_S{SHaA?`?`4 RR{S_{R @+HE*S-˚R{A_S{ @z @+HE*T-˚`@{¨S_qTq T<q`TTqTXq`T(! (a((_{SCcChRiR){¨_ֈ(_S[c{*R3RC9"q,T`T qTqTq T$.q@T>qTVQqT*!a5{è@@cB[ASĨ_֩@H E*(@kT)A? aT uR&7!RC9s4`R@4(@ V-˚`T.qT"R%6@"qT@R"qTH E@  KiEH _T_JA(@s4`RaTR"qaTHaA?a@R?HaA?*?֟.qhT"R%6"qTs4`R`Rg{1T(R7EkT |@*F)@yj )! *@9@(RR{_S[c{Uh7RtR }H 6}~@|iw- 4`6Qk-T |"(ih7qT }|JH ihR{cB[ASè_S{ @h@@ |@*F)@ yj)! *@9 6H ?`4R H ?*(R@>*{¨S_{1T(R7EkT |@*F)@yj )! *@9J6S)Ccsw(R({è_{S[ck+ͯ*~@:F@H{y xC"3" (@/hC"I(! ?#C@RR @'T~@)F+R{9_ k8R T+yh R h-"*94qKT@q TH{y"*@9 8. K6Tch"c *,i8kQH85 Rq-Tch8I J_k)8kT H{yQ  &k_9M5cHRq)R8@:`TQi@98(kTq*RIRX@#C&Ts6*I{y & l@9qT& j9B 9hR 9h991j9HS x6@v TB1T"1 T#@ҥR*Rs* 4H/@ s* ? 43@@jK@T k T+qTR3yH s"Rc ? 4@4@@TF'@@RT H{y Ji8 )&j9}@_ T ! RmT H{y"+ Ji8j9}@_ TH{y " *9J{y* h@92h9H ? @CB94?@HC yI@*+@kDcC[BSA{ƨ_{S[/(ҵs/ |@ *F)@yj RtB")! 7@TccRc1bTi9s?)qT 9! 8 TK H C*c ?@4@)@ ?kCTTH ? @@/(s/`@[BSA{Ĩ_{S[N/(hs/ |@ *F)@yj RtB")! 6@%cRтc1bTi@ys ?)qT y! %x Tc  AH CzSc ?@4@)@ ?kCTcTH ? @@/(s/[BSA{è_{S[ck+O(s/ |@*F)@yj vB")!  9@"T(cR*cBTi@ys ?)qaTy %x T AҥR#cR R*4R4#A4ȢKC?@4@ kTiKT( ?@ @O(s/+@kDcC[BSA{ƨ_S[c{** 1T+3(R,7Ek"Th~@F8@ {w)#*@96*y{w)#*@97(R *****(R{¨@@cB[ASĨ_{S[ck+Y**43R~@;F@h{z "T9 S?qHTU7H@9(6BR*T*4h{z"*9*64H@ @h{z"*9*4h{z"( @ ?4T4qBz TyB5 RRT&@x*" k!T* *q!TR<S5qT)T( ? @C2**h{z"I964qT q!T** ****8@@ ( * ?5( ?3(@) @HI @@h5@4qTR(RERd)bh{z"*@906h9iq TBR5UKR+@kDcC[BSA{ƨ_bR@8+@8HQeqIhQLieqhK5l4BB_{A5Rǻ A{@_T{_S {RT{@TsRCe@ @@8 ij8@8ij8 K5i4s@94 @HC yI{è @S¨_S {SRR{¨ @S¨_ue4trAT****C*@44i*F)@ yj*! I@9(H9`@GT4u*S[ck{*~@*F9)@yj *)! *96uj!TC5@ jATs2i ?@q:RXRT(A@Qyr`T(@QyrT(AQyrT9 3*(`Rh  qT99 r T9 07@ R RR kT R kT R k! T @ 4 qTq Tq! T3R4bRC*q`Az1T@qT qTkaT9*BR*R*T/@7~S=R kT&RߟR k!TRA*`T9R*`TS49RqT qTߟRSRsR4RC4bK*1T k TR{¨kCcB[ASĨ_Rҝ@S[{*9r**R TqT q T 6 r`TRRR h?qT?yr`T?qT?qT? qTIRRiR?qT?q T?q Tdh@ R)Ri BqTqTqTqTqTP  R khRHR(RR Rh)86h92h9x7HjTC5@!@qTh9a2h9@6IJ(** j87(RhT06h@j@ 2ii@H2h*2j`6h@ 2ih6h@ 2i(6h@ 2 6h@ 2i{¨@[ASè_R @S{*i~@*F yj)@)!  R+@9jaTK86BRT)@ qT/@"R#yC*v#@yq ZzT*O1TR*@TR{¨S_S[ck#{****?@H%@cJ @1h%j T@1!TR(RG@+RF)Ks* C@[#*E*(A  ?kE) D)T7@ R( kT6(A 6y E*#* ?aT *Fyj)@*! I@9(+iH9(?֊@(  ?`5( ?*| *F)@yj*! I@9(H9)( ?5RqTc92 qTc92c9@ Ҹ2*Fc9yj)@)!  89KFykI@)! ?936@*4@R*gc@C9H%@J @C*h%j @*u4*}@ F @(yk C9K! i9KFykI@,! @9(ASJ*S R h J9 jTs6 *Fyj)@+! i@9(2h9 R kT6(  ?(A z D)E*# ?aT( ? *Fyj)@+! i@9(h9@  *Fyj)@)! R{Ȩ#@kCcB[ASŨ_***%R{*C[hqT@@ sx R@94 @HC yI{èA_{Cg T@R kR)Siy{¨A_S[ck#{@6yҚRh9h5"h9h5Bh 9q"C: T`@kTIT A@QR)J*yR U3.xy UA ҟy~HR{¨#@kCcB[ASŨ_uҖh9h5"h9h5Bh 9q @TAT{_ .a59?`raT9?PrT* R?<rTR(KqTJ R?( rATkTj R ( a2i 8!|Slh*9@__'HR{_S {**5h9qෟ& @5h9qTTiU4(9(8qT5h_8 RqTh9qTq RaT qTh9qTq R@TR{ @S¨_S[{RRDa_q9H ?)TSRR*3R@v9u9h4R8BQ_q T97@**SH4(969(8qT(9h9qT@  jH9 h4(85( aPR{[AS¨_S[C@H4'@4 QQ 5*@_qT@C[AS¨_5@J5> ( q藟 C[AS¨_ ҟ1T }@  xiK N>h ΚQ }@ 1Th` )q(RKRh C[AS¨_ kT K k *Th}@ H}@.xi xikTJQ_ kkQTBT 4Q .xjQ 5xj(R RZJK S?qTRK4&!.*"qT Q +xjh%*ҥQ% 7}@Ӧ kT|@ xiRQI xiQ  xj4@%֚! q(!ԚOT Q  xjh%  *@C> Ú@Ƚ }@өT` %@@?T~@ӯ}@j}@ _Ti ? J )T R'4}@ *xiH-* `  xl_ kH Kk%x,k#TA$IT R4Ҩ }@ H xl+xhk(A+  x,O`ӃT*}@QQe6@ *_kTH}@ x)@J_kCT 4kQi xij5 k5C[AS¨_C[AS¨_{S[ck+,*c @7R}qaT9c 9@RRRR( it)@`T+@6h?Th@aT(R   aTRhs(*Kh2qT qT q`TqTh!?53Rhhh1@5Rh@g t)R_)@9 @3(RLRJ)T dl`* )ZR x(Kyrq)RHR+KqcT3XRR, 4 R33YjYj kA TJ_ qATQQ }S'RK)(!ƚQ3hQ(Yh*RR%* ZHIK K S_qZ ?q T k藟  6 qHTQQkT K Q kT3NYhR? kT3YiR  %(!J*3jY,Qk`TK@O@R43?Y( kTO@KRKRQQ }S'RK)(!ƚQ3hQ(Yh*RR%* ZHIK K S_qZ ?q T k藟  6 qHTQQkT K Q kT3NYhR? kT3YiR  %(!J*3jY,Qk`TK@O@R43?Y( kTO@KRKq Ts%XRR[ c 4 Rs%3YjYj kTJ_ qATZ(RIKR K S_qZ ? q'V qTRKQ?1T(Q? kT3JYi R kT3hYhRHy3hY))Q?1`TK@O@KȆRKt~S~~Rh)R)!*IY4}~s_0hTˣ6q Z*RHIKR K S_qZ ?q'V qTRKQ?1T(Q? kT3JYi R kT3hYhRH}3hY))Q?1`TK@O@KR9RpBBHU!7~`YS4IR?q Q)=3 Rt @9u@9~ӈ Ui@yH~ @ uC?qTC5RqTw4 R RsYk} A)Y+kkI`!T4BqTsIY(BBR RqT *@"}~_0sTC9RHR;BB34q T 4 R RsYk} A)Y+kkI`!T?k'?ksc1?ksb1?k$1qRR4fXn5kT?Y.1R*Re4qTkTY- LXg+Xmk} A/=*A+  )X-7@ET/4q TkTY-HYm A/IY-/`E/5q TkT*}~_0sTҢ9RHRʳBB)R4 @I9K5@HS uS* Z  K4h!iQ Yi 4_qT4 R R sYk} A)Y+kkI`!T)4BqTsIY(B"4 R R3M҈Yk} A)Y+kkI`!T)!4K@qT3IY(K@KKH`Sw4IRq Q)=3 Rt @9u@9~ӈ Ii@yH~ @ iC?qTC5RKq`TV4 R R3Yk} A)Y+kkI`!Ti4K@qT3IY(K@KO@RKRqT *K3@"}~_03T79R ~HR/gI)S4q T 4 R R3Yk} A)Y+kkI`!T?k'?k3c1?k3b1?k$1qRR4fXn5kTY.2R*R4qTkTY- LXg+Xmk| A+, *A/  )X-7@ETO4q@ TkTY-iYm}@ A)jY-O`E5q TkTK~~_03Tǡ9RHRK@O@(RH4 @IK5S uS* X K K4h!iQ Yi4_qT4 R R 3Yk} A)Y+kkI`!TI4K@q"T3IY(K@KK@KK93c#<@(qT)RBi9s4 R RsNҨYk} A)Y+k kI`!T)4BqTsIY(B9sz5Qh9s@@7_k  #@ ?A*H}@)145TK@ N4 R R3 XYk} A)Y+kkI`!T)4K@qT3IY(K@KK93Ac#K@ R*Kq9h} k `ӊ}SHuS L k KjH}STqhJ-8Q1T9%)(T@99q@9h4c*,+@kDcC[BSA{ƨ_Rʚ;S {sR@'RرR{ @S¨_ֿST͠RRTSR*{ {_S {C9h@;r Th.@R Khhi"@q*QjMTa@**i@kC9(9"qIT |@*F)@yj +! h  h@9/6BRQTjRI_)2I5; R"RCq{¨ @S¨_S {#yQh@;r Th.@R Kh hi"@q* QjMTa@*b*i@k#@y(y"qIT |@*F)@yj +! h  h@9/6BR TjRI_)2I5; RBRC<q{¨ @S¨_{@; Sh4 R"@;rT$@ @T@;բT( #RC ?ր4(a c ?֠4#A?R{¨A_S{*h@;rT=(RjRI_)2I5;Gh@;1 SH4.HRjRI_)2I5h@;6jRSh4h@hI_)yI5;jRI_)2I5;jRI_)yI5;h@; R jTHSh5S5jRI_)2I5I_)2I5{S_S{*~h@;rT(RjRI_)2I5;RGh@;1 SH4HRjRI_)2I5h@;6KjRSh4h@hI_)yI5;jRI_)2I5;jRI_)yI5;h@; R jTSh5P>SS5jRI_)2I5I_)2I5>{S_S[c{C*q**T~@C*qTa~@=*44***kh} y* HRC{cB[ASè_{S[ck+C*Aq***Ta~@*1TqT~@*1kTw5@ @S45k@TqTq,T(A A#* ? 4qMTH @ qTH;@9J;H4K@9 4@9H@9?k*!Kz TH-@85`RqMTH @ qTH;@9J;H4K@9 4)@9H@9?k*!KzTH-@85 R|R*!R**X4 A? RT<LT|Dds/CtRBT **!R*@4R*!R**64~ A? T<LT|D8s/CsRRhsBҳ**!R*4@@****R3h_R kT`BR_R kaTB*@RR_C+@kDcC[BSA{ƨ_S[c{C****۱****c@94 @HC yI{è@cB[ASĨ_{jHO! It`ҝh@jRI_)2I5;R I_)2I5;hrhHRh"h@h{A_{S[ck+Rɮ+@kDcC[BSA{ƨ_vY?T(ci# 7 ֚*_!HTTTbA? ?qt”TTH9kщi8i*8I8kT@HA}bAt? ?qT_Tjh8I9H9i8LbA? ?qT_TLH9kщi8i*8I8kbA? ?qTTH9kщi8i*8I8kTBTbA? ?qT (TbA? ?qT;˿)ThcA? ?qTTTlH9kщi8i*8I8k7 @TIT(aA? ?4 ; ThcA? ?4 @ W@ T_Twk{#7T9Txk{#_bT,7kѵ"W{# +@x@#{AR c9"9RíR{_LTi8h84)щ i8h84)iJJJ9iT8 R9HR@R{JR }@?BT?i*8qKT)@9i4-R+C( iil8!H *)@8hi,8)5 @9i4+R( )Cj!(il8_jT @8)5-{_ֈS{ReSTC@ @h5` kkџ Th@9 e@9I7h @j @@94 @HC yI{èS_{S[ck+CC*Eq***Ta~@ kw5@ @Hs@*R)RRqA**e*x5R A? RT<LT|D՝s/CtRB **!R*> 4R***U 49P66 4kTD@***{*u59~ A? T<LT|Dӕs/CsR?RhsB***S4*R*5R*5h_R kaT`BFR_R kaTB>*_CCJ+@kDcC[BSA{ƨ_D@*h_R kT`B*S[c{C****2@****#@94@HC yIC{è@cB[ASĨ_{Sq{_S[{ SC9R;9@)R @ k!T@94 @HC yI"R@954 @HC yI"R4 @HC yIR 5ڮ@)R @ k!T#A94@HC yI"R#A954@HC yI"R4@HC yIRCb`5 3@K@ ?A93@*A93@Ru4B9h4K@*{˨[AS¨_{%R֫ h E (A R ?{_S[{<$dHT!vE R ?ր!(4 4ER?֠R{[AS¨_{#y4S!RC`4#@yR{¨_S[c{"R*K*BRF T TMT!RR*>@ R*"*1TRs *mT@1T@qTR@!*RR* @T* ? 5}R ( ?*ks@R**{cB[ASè_{țChA)j -?.?(G?g [H @y5@987OqQh qaTLR h qaTlRhqTRR)R K(! Q+  * L@9 SK@ S? qAzT kT 8qaT  * T? T)KKL y SI yRhKqiTADq"TR R(R) (yhkTOq__HRCc{_{*Ax   jTRR9`RR0`-R{A_{``R{A_{`@`@IC @5@h@ kT@Rh@ k@T R{¨A_{ C5 @C@h(25R R{¨A_f )L T@`T %RR#RH/&RaR;@TERRCR X X Tn`t \8pp \ \ \+qf }`r;g;{y*sz | \8 yOT \RX yBXu+qR }yK]Yg^`g^;"Tz \gR1:p.fRn2\ 2lȵm}\l ~\b(-Lgt+KiQ}@8b HAu rymH :uP\VQU\q\st\KT7 yBUymFW\t wfY*u_VX }yc]Rg^`K^_ V]4x?Q#Ib?ƺ??^?AG;BIb?WUUUUU?B.?_D; R J}S rTPq T`qTpq `R R@RRr R_qRqR**qKRI*q**} SK * j *K*`*_(D;*r R_qRqOR**qJ*}SK * j *@ *_rTq TqT q  D; }@(X+Sjq Iq q *q @Iq * qI)* H D_`? r(D; X S KqjqHK qiq *I)D_`(94Th85_{s`DTH (A RdRbRR ?`{A_hD T _S[{wD ** ?*5 ?qTD T ?H (A RdRbRR ? * ?**{@[ASè_|S H2rKh2rjH2rKh2rjH 2rK rTPq T`qTpqTk2k2k2rh2`_*RjRTSH2rKh2rjH2rKh2r`L86 2_(D;}@(D{, *D; R J*,**  N*k T mSH2rKh2rjH2rKh2rjH2 rKrTq TqT qTk 2k 2k 2h2rRj @*DՋ{_S {*6@6 Ҥsz6H6@Ҟsz 6P6Ҙsz6X6u 6ґsz 6`6Ҍszq{ @S¨_{S[c#'yX (@`****bpg*5 qT@iR;(3#C**)RS(44@mm@AcS`5*&@*"@5@p'@#@cC[BSA{Ũ_Mg_RS[c{  @** @? 6 @H@ 2I6 @H@ 2I6 @TH@ 2I6 @H@ 2I6 @H@ 2Ih@ @ HӨ @)*iJ*, J h@ @ I @)*iJ*, J h@ @ JӨ @ )*iJ*, J h@ @ K @)*iJ*, J h@ @ LӨ @)*h3 6 @H @ 2I 6 @H @ 2I 6 @H @ 2I 6 @H @ 2I 6 @H @ 2I h@  r`T_Pq`T_`qT_pq!T @H@ 2 *RJR @(@H3( @H@ uI @@ J*+-h J @H!@ 2I! @h!@V4 mi! @@P @ha@ 2ia @Ha@ mIa @@QQ)R( 3h! @@0 @Ha@ 2Ia @)Rha@( 3ha @@1)Z C"RR* ? @H @ 6h@ wiH @6h@ viH @6h@ uiH @6h@ tiH @6h@ siH@ r T?qT? qT? qTh@ j )h@(jhIh@ hi4PQ@Q)@{¨cB[ASè_ {qTQqTHR(R{_(D;}@ *D_D; R J!*-)|@|@  i D_(D; }@K+D_(D;}@_<@ R (I)@yL @y( a4 I@?@)TH@  ?@)Tk kJT_S{4`5R aˀ`R$@h* R{S_@yIR kaT<@ R (I@? kTH1@y R-q@TR_{h@ @+u(kj}@I*@!ڔ R{_!594  |j7p= S %Ț N2 NI>N*`TIH>N )I87(}@` Hw }@H S %Ț 1 )>N*TH }@Ӏ IilA< N0:n>N1 N(>N }S(>N +j}SA*AV4 N|=   N4n1 Nr N,>NO>N 7 S)>NJ%ȚH>N-  (!Q qSJ%ɚOL ҆RkL ki _ kcTl>Nk>NJ86ki  K H <4n Nq NR N+>N/>NM>NL>N) (!ʚ * KډR) K HQ(!Ț * `TI  K(}@ H_954  |j7p= S %Ț N2 NI>N*`TIH>N )I87(}@` Hr }@H S %Ț 1 )>N*TH }@Ӏ IdlA< N0:n>N1 N(>N }S(>N +j}SA*AQh5N  | 73 Np= SJ%Ț NR N3nH>N1 N)>N.>N  , `TH>N *l _ k+T-T(}@` , }@( SI%Ț0  >N0   10.1 (>N  _ TTH }@ 3 NkAp< N3nNQ:n(>NHR NH>Nh H>N )(}@i H+Aj9_!k`__ BT@8!k`TBт_t3 @3.1 (>N ?qT(}@ _ @1gJ _A cT6 N_TpE@ir = mAn6n0=+A76nJT6n6nNN1N2:nH>N(_T_A#TpN_A"T_!Tp@3.1 (>N ?qT(}@` 53;nh>N(:n>Nh N  N :n>N N  N>Nh >N )(}@ 1 N(>Nh (>N )(}@i A J!k!h@8!k`TJъh__{(@@k {_a{@ @? k{_a{ ȨC@94( @c)ȱc*H@ kaTc( @?qTc(ȩ!@ kTc( !@ kTc(i!@ kTKc()3@Dl ձS?S ߈{A_a csm !"{ @J {_a{R& {_a{@ @x ՠ @5 {_a{ @@, {_a{ @@ {_a{`R {_a{ @@ {_a{@@ {_a{@@ {_a{@ġ {_a{R {_a{@@ {_a{R {_a{R {_a{R {_a{C@94`R {_a{@H4@@4i*Fh)@ yj*! I@9(H9`@y {_a{@ @R r? k{_aH_Z_r_______ `"`2`H`Z`j`````````e~epedeTe\avaaaaaabb"b.b><<!==!=[]operator->*++---+&->*/%<<=>>=,()~^|&&||*=+=-=/=%=>>=<<=&=|=^=`vftable'`vbtable'`vcall'`typeof'`local static guard'`string'`vbase destructor'`vector deleting destructor'`default constructor closure'`scalar deleting destructor'`vector constructor iterator'`vector destructor iterator'`vector vbase constructor iterator'`virtual displacement map'`eh vector constructor iterator'`eh vector destructor iterator'`eh vector vbase constructor iterator'`copy constructor closure'`udt returning'`EH`RTTI`local vftable'`local vftable constructor closure' new[] delete[]`omni callsig'`placement delete closure'`placement delete[] closure'`managed vector constructor iterator'`managed vector destructor iterator'`eh vector copy constructor iterator'`eh vector vbase copy constructor iterator'`dynamic initializer for '`dynamic atexit destructor for '`vector copy constructor iterator'`vector vbase copy constructor iterator'`managed vector copy constructor iterator'`local static thread guard'operator "" operator co_awaitoperator<=> Type Descriptor' Base Class Descriptor at ( Base Class Array' Class Hierarchy Descriptor' Complete Object Locator'`anonymous namespace'(null)(null)   mscoree.dllCorExitProcess@@@@X@p8@p8@ @@P@P@@@@@@H@p8@x6@p8@@@p8@(@@p8@        ! 5A CPR S WY l m pr  )   Y* `@@@ @p@@ @`@@@ @`@@@`@@@@@8@api-ms-win-core-datetime-l1-1-1api-ms-win-core-fibers-l1-1-1api-ms-win-core-file-l1-2-2api-ms-win-core-localization-l1-2-1api-ms-win-core-localization-obsolete-l1-2-0api-ms-win-core-processthreads-l1-1-2api-ms-win-core-string-l1-1-0api-ms-win-core-synch-l1-2-0api-ms-win-core-sysinfo-l1-2-1api-ms-win-core-winrt-l1-1-0api-ms-win-core-xstate-l2-1-0api-ms-win-rtcore-ntuser-window-l1-1-0api-ms-win-security-systemfunctions-l1-1-0ext-ms-win-ntuser-dialogbox-l1-1-0ext-ms-win-ntuser-windowstation-l1-1-0advapi32kernel32ntdllapi-ms-win-appmodel-runtime-l1-1-2user32api-ms-ext-ms-AreFileApisANSICompareStringExFlsAllocFlsFreeFlsGetValueFlsSetValueInitializeCriticalSectionExLCMapStringExLocaleNameToLCIDAppPolicyGetProcessTerminationMethodccsUTF-8UTF-16LEUNICODE ((((( H   !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~ ((((( H ( 0@@@@D@D@H@H@L@L@P@H@`@L@p@H@@L@INFinfNANnanNAN(SNAN)nan(snan)NAN(IND)nan(ind)e+000`@d@h@l@p@t@x@|@@@@@@@@@@@@@@@@@@@@@@@@$@,@4@@@P@X@h@t@x@@@@@@@@@@@@@@ @8@P@`@x@@@@@@@@@@@@@@@@@ @0@@@P@h@x@@@@@@@@SunMonTueWedThuFriSatSundayMondayTuesdayWednesdayThursdayFridaySaturdayJanFebMarAprMayJunJulAugSepOctNovDecJanuaryFebruaryMarchAprilJuneJulyAugustSeptemberOctoberNovemberDecemberAMPMMM/dd/yydddd, MMMM dd, yyyyHH:mm:ssSunMonTueWedThuFriSatSundayMondayTuesdayWednesdayThursdayFridaySaturdayJanFebMarAprMayJunJulAugSepOctNovDecJanuaryFebruaryMarchAprilJuneJulyAugustSeptemberOctoberNovemberDecemberAMPMMM/dd/yydddd, MMMM dd, yyyyHH:mm:ssen-USja-JPzh-CNko-KRzh-TWuk@@@@@@@@ @ @ @ @ @ @(@0@8@@@H@P@X@`@h@p@x@@@@@@ @!@"\@#@$@%@&@'@)@*@+@,@-@/@6@7@8 @9(@>0@?8@@@@AH@CP@DX@F`@Gh@Ip@Jx@K@N@O@P@V@W@Z@e@dE@@@@P@@@@ @ @ 0@ @@ P@`@p@@ @@@@@@@@@@@@ @0@@@ P@!`@"p@#@$@%@&@'@)@*@+@,@-@/(@28@4H@5X@6h@7x@8@9@:@;@>@?@@@A@C@D @E0@F@@GP@I`@Jp@K@L@N@O@P@R@V@W@Z@e@k @l0@@@P@0@`@ p@ @ @@@@@@@,@; @>0@C@@kX@ h@ x@ @ @ @ @ @; @k @@@ @ 0@ @@ P@`@;p@@@@ @ @ @@;@@ @ (@ 8@H@;`@p@ @ @@;@ @ @ @; @$@ $@ $(@;$8@(H@ (X@ (h@,x@ ,@ ,@0@ 0@ 0@4@ 4@ 4@8@ 8@<(@ <8@@H@ @X@ Dh@ Hx@ L@ P@|@|@arbgcazh-CHScsdadeelenesfifrhehuisitjakonlnoplptroruhrsksqsvthtruridbesletlvltfavihyazeumkafkafohimskkkyswuzttpagutateknmrsamnglkoksyrdivar-SAbg-BGca-EScs-CZda-DKde-DEel-GRfi-FIfr-FRhe-ILhu-HUis-ISit-ITnl-NLnb-NOpl-PLpt-BRro-ROru-RUhr-HRsk-SKsq-ALsv-SEth-THtr-TRur-PKid-IDuk-UAbe-BYsl-SIet-EElv-LVlt-LTfa-IRvi-VNhy-AMaz-AZ-Latneu-ESmk-MKtn-ZAxh-ZAzu-ZAaf-ZAka-GEfo-FOhi-INmt-MTse-NOms-MYkk-KZky-KGsw-KEuz-UZ-Latntt-RUbn-INpa-INgu-INta-INte-INkn-INml-INmr-INsa-INmn-MNcy-GBgl-ESkok-INsyr-SYdiv-MVquz-BOns-ZAmi-NZar-IQde-CHen-GBes-MXfr-BEit-CHnl-BEnn-NOpt-PTsr-SP-Latnsv-FIaz-AZ-Cyrlse-SEms-BNuz-UZ-Cyrlquz-ECar-EGzh-HKde-ATen-AUes-ESfr-CAsr-SP-Cyrlse-FIquz-PEar-LYzh-SGde-LUen-CAes-GTfr-CHhr-BAsmj-NOar-DZzh-MOde-LIen-NZes-CRfr-LUbs-BA-Latnsmj-SEar-MAen-IEes-PAfr-MCsr-BA-Latnsma-NOar-TNen-ZAes-DOsr-BA-Cyrlsma-SEar-OMen-JMes-VEsms-FIar-YEen-CBes-COsmn-FIar-SYen-BZes-PEar-JOen-TTes-ARar-LBen-ZWes-ECar-KWen-PHes-CLar-AEes-UYar-BHes-PYar-QAes-BOes-SVes-HNes-NIes-PRzh-CHTsrdE@B@,,@q@,@ ,@0,@@,@P,@`,@p,@,@,@,@,@,@,@C,@,@-@@)-@(-@k@!@-@c@P-@D`-@}p-@@-@E@-@G-@@-@H@-@-@-@I-@.@@A.@@(.@J@8.@H.@X.@h.@x.@.@.@.@.@.@.@K.@.@@ /@/@(/@8/@H/@X/@h/@x/@/@/@/@/@/@/@/@/@0@0@(0@@#80@e@*H0@l@&X0@h@ h0@L @.x0@s@ 0@0@0@0@M0@0@@>0@h@70@@ 1@N(@/1@tx@(1@81@Z @ H1@O@(X1@j@h1@a(@x1@P0@1@1@Q8@1@R@-1@r8@11@x@:1@@@@?1@1@S@@22@y@%2@g@$(2@f82@@+H2@mX2@@=h2@@;x2@0@02@2@w2@u2@UH@2@2@T2@P@2@`@63@~X@3@V`@(3@W83@H3@X3@h3@h@x3@Xp@3@Y@<3@3@3@v3@@3@[@"3@d3@4@4@(4@84@H4@@X4@\@h4@4@4@4@@4@4@]H@34@z@@4@p@85@x@95@@(5@^85@n@H5@_X@5X5@|\@ h5@b@x5@`P@45@5@{@'5@i5@o5@5@5@6@6@(6@86@FH6@paf-zaar-aear-bhar-dzar-egar-iqar-joar-kwar-lbar-lyar-maar-omar-qaar-saar-syar-tnar-yeaz-az-cyrlaz-az-latnbe-bybg-bgbn-inbs-ba-latnca-escs-czcy-gbda-dkde-atde-chde-dede-lide-ludiv-mvel-gren-auen-bzen-caen-cben-gben-ieen-jmen-nzen-phen-tten-usen-zaen-zwes-ares-boes-cles-coes-cres-does-eces-eses-gtes-hnes-mxes-nies-paes-pees-pres-pyes-sves-uyes-veet-eeeu-esfa-irfi-fifo-fofr-befr-cafr-chfr-frfr-lufr-mcgl-esgu-inhe-ilhi-inhr-bahr-hrhu-huhy-amid-idis-isit-chit-itja-jpka-gekk-kzkn-inkok-inko-krky-kglt-ltlv-lvmi-nzmk-mkml-inmn-mnmr-inms-bnms-mymt-mtnb-nonl-benl-nlnn-nons-zapa-inpl-plpt-brpt-ptquz-boquz-ecquz-pero-roru-rusa-inse-fise-nose-sesk-sksl-sisma-nosma-sesmj-nosmj-sesmn-fisms-fisq-alsr-ba-cyrlsr-ba-latnsr-sp-cyrlsr-sp-latnsv-fisv-sesw-kesyr-syta-inte-inth-thtn-zatr-trtt-ruuk-uaur-pkuz-uz-cyrluz-uz-latnvi-vnxh-zazh-chszh-chtzh-cnzh-hkzh-mozh-sgzh-twzu-za Tc-^k@tFМ, a\)cd4҇f;lDِe,BbE"&'O@V$gmsmrd'c%{pk>_nj f29.EZ%qVJ.C|!@Ί Ą' |Ô%I@T̿aYܫ\ DgR)`*! VG6K]_܀ @َЀk#cd8L2WBJa"=UD~ $s%rс@b;zO]3AOmm!3VV%(w;I-G 8NhU]i<$qE}A'JnWb쪉"f37>,ެdNj5jVg@;*xh2kůid&_U JW {,Ji)Ǫv6 UړǚK%v t:H孎cY˗i&>r䴆["93uzKG-wn@  _l%Bɝ s|-Ciu+-,W@zbjUUYԾX1EL9MLy;-"m^8{yrvxyN\lo};obwQ4Y+XW߯_w[R/=OB R E]B.4o?nz(wKgg;ɭVl H[=J6RMq! EJjت|Lu<@rd 6x)Q9%0+L ;<(wXC=sF|bt!ۮ.P9B4Ҁy7P,=87MsgmQĢR:#שsDp:RRN/M׫ Ob{!@fu)/wdq=v/}fL3. iLs&`@< q!-7ڊ1BALlȸ|Rabڇ3ah𔽚j-6zƞ) ?IϦw#[/r5D¨N2Lɭ3v2!L.2>p6\BF8҇i>o@@w,=q/ cQrFZ**F΍$'#+GK ŎQ1VÎX/4Bycg6fvPbag ;s?.❲ac*&pa%¹u !,`j;҉s}`+i7$fnIoۍut^6n16B(Ȏy$dAՙ,C瀢.=k=yICyJ"pפldNnEtTWtøBncW[5laQۺNPqc+/ޝ"^̯p?m- }oi^,dH94X<H'W&|.ڋu;-Hm~$P  %-5 > H R ] i u -C Y p        %  d'@Bʚ;01#INF1#QNAN1#SNAN1#IND??Xt?0 ?A??m?'?~?R?+M?0??ZR???'??$?\g?A)??@L?a???Ȇ?9??3?G?#??@D?M??@2?j?@Bt?@?X?R%?@>r?r? ?T??@_?01?^y?? ?(N??`??a?@?? -*?k?@@??B.?y|6>gsh>ƈ[^>/T,X>0S:zb>|si\a>酲j%M>zz[>ܥY#4o>wHn>G2k>)l>YHȆ>0AvVL>3/c>v ka>zzk>RUWk>q9a>de>B!Ѳm>dNpm>6/EiU>[ >$'<>F(o>BĮ^>&qN]k>YWm>bj ;>L!\>G[%_f>PHIZ>w_FGm>{]>`hj>>duo>3>ҾC`>\_>h_=N>qCF>WN@l>^b @>L-n>4b>\ngd>Z=@pn>6ނBg>?xb>8rZ>Oc>m>Bĩ*Vc>LHT>z\P>[0>'>m>[a>"߹oI>bAe>USY>kX>,HR>log10CONOUT$Fatal error in launcher: %s Fatal error in launcher: %s rbFailed to open executableUnable to find an appended archive.Unable to read from file#!PATHEXT;Job creation failedJob information querying failedJob information setting failedmaking stdin inheritable failedmaking stdout inheritable failedmaking stderr inheritable failedUnable to create process using '%ls': %lscontrol handler setting failedFailed to get exit code of process.exe/usr/bin/envExpected to find a command ending in '.exe' in shebang line: %lsExpected to find whitespace after '/usr/bin/env': %lsUnable to find executable in environment: %lsExpected terminating double-quote for executable in shebang line: %ls<launcher_dir>\Terminating quote without starting quote for executable in shebang line: %lsFailed to find shebangExpected to find terminator in shebang lineExpected to decode shebang line using UTF-8Expected to find '#' at start of shebang lineExpected to find '!' following '#' in shebang lineExpected to find executable in shebang lineExpected to find arguments (even if empty) in shebang lineExpected to be able to allocate command line memory"%ls" %ls "%ls" %lsbZL8b \L\8b pLp88p@@~@RSDS?嚌kFNTsC:\Users\Vinay\Projects\simple_launcher\ARM64\Release\t64-arm.pdbGCTL.text.text$mn.text$x.idata$5.00cfg.CRT$XCA.CRT$XCAA.CRT$XCZ.CRT$XIA.CRT$XIAA.CRT$XIAC.CRT$XIC.CRT$XIZ.CRT$XPA .CRT$XPX0.CRT$XPXA8.CRT$XPZ@.CRT$XTAH.CRT$XTZPx.rdataL.rdata$zzzdbgO.rtc$IAA O.rtc$IZZ(O.rtc$TAA0O.rtc$TZZ@O .xdataH\(.idata$2p\.idata$3\.idata$4H_V.idata$6p .dataz(.bss .pdataP.rsrc$01PQ.rsrc$02`"Ђ*Ђ**Ȃ*Ȃ*p@p90ȄpBȂ&@tȂ&@@ -@(@hP\@Ђ$=233 4043 @4p=5686 @@@'@"@"!@@".P(@"=|@@d@B@9@@0@ JɆȂ,JɆȂ,@0@ JɆȂ,JɆȂ,@@9H)@8!@ ɊȆB ɊȆBb0ɈȄpP @"=8DdDl@VP$@Ȃ&=$MMMXN.P @Ȃ&=TKKKK/P @Ђ$=@RXR @ P @"=SSl+@@Ȃ&@;@"J0Єp;0ЄpPЄp<0ЄpbPRȄpcPSȄpp"=UU @p"=UU20ЄpPȄpP@Ђ$=l!p"=D>P4@Ȃ$=$ħ((0Ȅp *P@Ȃ&=l@@ɆȂ*J@?@Ȃ&R@G@Ȃ&5@0@CP>@Ђ$=lhp"=@HH!pȂ$=p"=Hp"=H = FpцȂ(= HNpцȂ(=@p=5pȂ$=x1p"=H;pȂ$=4p=\@@Ђ$;@6@Ђ$.@*@"p"=(HHp"=Hp"=PHp"=Hs@E@Ђ$/7*52@ @@@ɆȂ(=`$o0Ȅp@ @"h`Ђ$@@ɆȂ*d@@цȂ(PшȄpB0pJ`$0Ȅp2P-@Ђ$=5T6\@+@Ȃ$0pVpȂ$=|/0H@@ɆȂ*@@ɆȂ(9@@Ȃ$:@@Ȃ$o0 ɈȄpP@"=@OTO8p=dRRXP%@цȂ(=T\UxUVxp"=RRH-p"=XWWFpцȂ(=ab0 ɈȄp40 ɈȄpN0ȄpL0Ȅp_0 ɈȄp6P @Ђ$=Pgxg@@@Ȃ&@@ɆȂ(j@F@ɆȂ**@@Ђ$gP V ɈȄp@=Ȃ$0 ɈȄpP  ɈȄp8@@+P'pP ɈȄpr0pNP D шȄpp"=ػ @@ @@@BB c\a(_PaH_Z_r_______ `"`2`H`Z`j`````````e~epedeTe\avaaaaaabb"b.bM[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq_______CCBABADL*(%$""&GABJIIDDJC??AD_-(''%$GG#_DIJIIJIIIDIJDD_+)(''%GG _JMCEJ?ABAECA?BA_,+)('%%"!_IM@AJACDCIDI____.,++>________JMJJJJJJJJJF_..,+)('%$"! #GIMCEJJJI?J__...,+(('%$"! &_JM?AJJJJCJ__...,+)(''%$" _IMJJJJJJJJ_K=...,++(''%$! _JMCDJJJJAJ_K_______L*%$"!_I_?AMNJJEJ_ L'%$"_I_MMMMMMJJ_ _('%%_I_CEMIJJMEM _(''*_I_?BMDJEMDMF _)(-LI_MMMCMMMMM_________< ____I_CEM1;MACDMEJM__I_?A_D1@IJJMJMM_FF_I__6_@/EMMMNMM_FF_I_C80EJDMJCJFF?:AA20?_M_MIEM______MIC4BC/ N______MMMMJIDAI_9/ HJJMMJMMMJ??BAI_A7503 IJMMCBCBA44CCI___BAO;6___C_MJIDIDMI_CI___O; @__C_C?J?AJI_?A_DJJI@ O_CM?;I?JI_________H9MAIJJIJI_CD_____JJI73C;IJIJI_?AMDJJDIMJIE5:JIJI__________MMIA:IJJJJJJJJJJJJJJJJMJ???( 444MMMRRRYYY~eGuuu}}}x>iErCE~7?HVbx/!P7pLcyϏ1Qq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq!%///* !*/"(%%'%/) /!#''////+/+"*(/ / !%(/ ( (*/ (///) */   )*  /"///// /+/// # + !%//  +"*+'#+//+'!%*++$++!+! !!!<(0`  !!$& &"   WܱNj+<<:5pi$!!!4888&SNJFB=950,(%%%5ǎVRNIEѹ|Jq?o@ZUQMID@<73/+&"-Ɍ444"ք̴wq?]YUPLHC?;72.*%!"""4888&dya\XTPKGC>:61-)% OÑ%%%5ʎA{;ys`\WSOJFB>951,($(9|:z;xq?o@mAkBiCgCdDuZ;73.*Ǒ%%%5̎È>89}:{;yq?o@mAjBhCfD~dEuZ?:625Оa789}:{;yr>p?n@lAjBhCfD}dEB>:5^ό555"܄ѳ„7789}:{;xr?p@n@lAjBgCeDFA=9"""4;;;&ŌC89~9|:z;xr?p@mAkAiBgCIELɑQQQJnnn~~~Įq?o@mAkBiCHHH"""vvv9~:{;yq?o@mAkB:::'''___sssttt8Ϧuǣvyp?n@lA888T---6fffnddd%%%"""OOOSSSċDҧuʤv{;yr?p?xM<<r?̷999VVV###)))444ѳ͝b@~9|:z;}Cg˶Ӎ󑑑ZZZlll;;;!!!""")))___&&&2:::ppp{{{___WWWWWW:::!!!"""***www"""4888&,̑999ԖJJJ|||lll:::"""BBBXXX|||$$$6,uuuKKKCCC@@@...www* Ҍ666"KKKaaaLLLWWW@$"""4;;;&nnnfff!!!$$$999444]ttt|ȇɑ&&&5ЎvvvmmmVVV{{{OOOڥaaaCCCpppό555"ބ|||MMMfff"""4;;;&\\\Ǒ%%%5Ύݧܦ͌555"ۄۥ"""4:::&ڤÑ%%%5ˎܤׇ ww?ww??ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwwwwwwwwwwwwwww?ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwww?wwwwwwww( @ !!!0\\\JeeeQ666D555A0$ vH<602\7qڴvvvj&&&D1"gLF@;5?кVQKE?9(aV[UPJD>82-uuun{{{j`ZTOVo@lAb_YSMGB<60+%<aVmt>q?n@bb^XRLFA;5/)$Ywwwn}}}j~Cv=s>p?bba]WQKE@:4.(+{;xbba[VPJD?93-'aV~:{;xr?o@lAiBfD}dE|cE|cEiB<61?9}:z;w=t>q?n@kAiCfD}cE|cE}dFGA;=aVңi89|:yq?n@kBhCeD|cE|cELF@mzzznjʓP8~9{;yp?mAjBgCeD|cEPJ\¯mAjBgC~dEaV}:z;xq?n@lAiCJJJőPyq?n@~YVVV???wwwiiiΡkA|;y>>3@@@y~~~obbb000kkk@@@---U<<<&&&[[[6uuusssWWWIII$$$&&&qqqlllg|||s4bbbhhhƑUUU000QQQ}}}---{bbb(iiiLLLBBBaVggg+++NNN|||nj]]]tttkkkrrrwwwӦaVӦzzznjӦˉ ?????(  C﫫oIਨS=?_QF4aZмoEcaXMB70~Iq?c`VK@58z;u=qv>3Eyn@iB~dE}dFGHYYYuuuġ=xmAhC}dE}eGS444|||gC}cEMMM~>q?kAiGRRR¢Gu=xKʺ~AAAAAAAAAAAAAAAA ( h00 %   h4VS_VERSION_INFO?fStringFileInfoB080904b0JCompanyNameSimple Launcher User^FileDescriptionSimple Launcher Executable2 FileVersion1.1.0.140InternalNamew32.exej#LegalCopyrightCopyright (C) Simple Launcher User8OriginalFilenamew32.exe@ProductNameSimple Launcher6 ProductVersion1.1.0.14DVarFileInfo$Translation  Т (0PX`pУ 0@P`pФ 0@P`pХ 0@P`pЦ 0@P`pЧ 0@P`pШ 0@P`pЩ 0@P`dP`pxȪЪت(08@HȬЬج (08@HPXȫЫث (08ȬЬج (08@HPX`hpxȭЭح (08@HPX`hpxȮЮخ (08@HPXxȣأ(8HXhxȤؤ(8HXhxȥإ(8HXhxȦئ(8HXhxȧا(8HXhxȨب(8HXhxȩة(8HXhxȪت(8HXhxȫث(8HXhxȬج(8HXhxȭح(8HXhxȮخ(8HXhxȯد(8HXhxȠؠ(8HXhxЭ 0@P`pЮ 0@P`pЯ  0@P`pР 0@P`pС 0@P`pТ 0@P`pУ 0@P`pФ 0@P`pХ 0@P`pЦ 0@P`pЧ 0@P`pШ 0@P`pЩ 0@P`pЪ 0@P`pЫ@ت𪰫pH(HhPX`hpxȩЩة././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659773740.0 distlib-0.4.3/distlib/t64.exe0000600000175000017500000032300014273421454015014 0ustar00vinayvinayMZ@ !L!This program cannot be run in DOS mode. $`v%%%tՖ%%%ƛ%%%%%%%%%%%%%%%Rich%PEd b"  |B@@.<S@ l0.text! `.rdataD8:@@.dataDA@.@.pdata@  B@@.rsrcSTN@@.relocT@BumLISMCMK HHH3H3H$0LʺICDBHL$0HD$ *HLD$0HH`H ̅umLISMCMK HHH33H3H$0LʺICDBHL$0HD$ jLD$0HH`H SH\$Ht$WH BHH3LLBILHtBHA;IDII+LD9Ht$8HH\$0H _H\$Ht$H|$ UATAUAVAWHH@LL' H0SHM8E3Le8pA̅H HM8ED$3D*HM8!HM8LcAAHE3*LM8E|$AIDIHHHtHMED$H%HcI+HHM8AHAHE3)LM8DIHHHxAHN HHHMHA覱HcHH+H]EHM8E3EH+1)LM8LII3AH HAEf; uMfHM8AHE3(LM8LIIAH HH1N\+M;r fA9t M+M;sMHM8+L\$@II[0Is@I{HIA_A^A]A\]H\$UVWH03HW.H|$` H-SHt-AE3H3H|$(Hl$ 4}L HT$hHL$X/)HT$hHL$XuRHL$X$ALH3H|$(Hl$ u3LD$`H HuHL$X H\$PHH#H0_^]H(t v1w,H fOHt 5O LrOA YH(@SH Ht#uuHDH [H%&H [H\$WH HH HHx ) HHHHH\$0H _HHXHhHpHx ATAUAVH HHHH0uHHH`dHHtH;GH;>HtH;/H;&HtH;H;zLlL^HnH(aHH0(E3I;tI;u3ҹ6MI;tI;u3ҹMH;tH;u3ҹII;MDI;MDH;IDMtItI3ҹMtItI3ҹHtHtH3ҹH\$@Hl$HHt$PH|$XH A^A]A\HHXHpHx UATAUHH Hw,H3H L333HHHLH vLL\$P LLDˋL\$ t 9\$PuO3HxH ,L RL0L;LDˋ(Hiv3HL$`DBhfHL$`OEu}SHEH7HHxH%HbH/HLHAmHuH}H]HbKE3E3HD$HHD$`IHD$@Hd$8Hd$0d$(3D$ AuKHd$0HAD3DL$(HL$ *LHM3HJH Jf}rtHUHtkHc:EHLH;rZH HtH^L$LI;s1HH+H;tH;tH;t HH&HHI;rIHUй=w HMH HKpH JH IE3 H IHT$PH+0L$PH\$Hl$Ht$WATAVH HHH%E3AHHtDKft+f"t%AuHHKHHuHt IHHA Hc AHLxHwftALuAHLIHft AuHfD9&t Au HfD9#u AtfD#HHAHHLHf?"u)f;"AHLfD#HHHHAHet f"tf;"AHLm ft%AEtfD#A0t HfuH]H\$@Hl$HHHt$PH A^A\_HHXHpHxUATAUAVAWH8HH?'H3Ho"f98tOHH3DfHu L-X#LhA}tIAEfuHGA3Jf9=Gt L5G L5GHHpfA4Vx6PHHH HHHH;s9 t9 t HH;rHHH@7HD$P+LAԹDD$(HD$ ΅HHcft|PH|$Pf9t$PtAnt Hf97uf?#HdA?t Hfuf?!H2A t HfuHT$@HHt$@HHLL|$@MHH DIu|LEH``H`SfA9T$u*IL$ Z[HnHMHaf7HcH`HMDL3IIIɍPfIHLAIfIHHLIfIHHLIfHI\HHHH@ƋLl$8Lt$0L LHHL|$(Ld$ 9 HQH({ffH; #uHfuHq"H\$Ht$WATAUH0Hd$ EHHE3AHu*3AHtAfD9*t4(HHD$PHu )fD9.u")H HL$ <*3LEHH%HHZHH\$XHt$`H0A]A\_@SH ILHHuC)$AHIHHt3)H [ f;tHfuf;uH3HHPHHL@LH SVWH H3Hu(sK3HtHt$PH)LE3HT$HH&+H׋F*HEH _^[Ht7SH LH T73u/(HR'H [@SH d$@LD$@5HHu9D$@t'Ht 'L$@HH [@SH HAHuCHCHHHHKH;+t'uhAHH&H9CtHC 'u q8HCHCuCHH [HHXHhHpHx ATH@E3IHHAMHu& HtHL$ IL\$ E9cu?H+>fAr fZwf fAr fZwf HHt@ft;f;t4HT$ DHT$  DHHHt ftf;t+D8d$8t HL$0H\$PHl$XHt$`H|$hH@A\H(3LL9>5uwMtzHu% H(HtL+C fAr fZwf AfAr fZwf IIt ftf;t+ȋH(E3UH(H\$Ht$WH HHw|HHEH e4Hu JGCH @4L3HHu,9:tH!Jt $ $ HI$ 3H\$0Ht$8H _Ha@SH [u;LHcȺq[(MHEKHu$PHˉT[ MH(KHuv3HHH0HHt HJE3HEHILHIHIHkXLIt ItMuIH0Iu3H [H(gP=X3tMH JH(a@SH HH HH;r>HH;w2HH*H+HHHH?LRkH [HK0H [H%@SH Hڃ}QkH [HJ0H [H%HH;r5H=H;w)qH+H*HHHH?LPH0H%d} rjPHJ0H%GMt 8tHIuIHH#@UAUAVH@Hl$0H]0Hu8H}@LeHHH3HEE3MHHHuQ" o_TH;rfD3IEHtTALúDt$(Lt$ SLcu?!*!fAr fZwf fHfD93u3II;sfD3!"]~g3HBHHrXKL$Hw1HAH;w HHSH+H|$0Ht)HHtHIHu1! /IEALËHDd$(H|$ RtLHH *HO9u}HMH3gH]0Hu8H}@LeHHeA^A]]H\$WH@HHHL$ ILD$ HH|$8t HL$0H\$PH@_E3LMK H8IC(ICIcH8H\$ UVWATAUAVAWH LLHL$hHMMHHT$pMtMtMu'3H\$xH A_A^A]A\_^]H$Ht3HIL;v)HtL3ISHHt3HIL;wIIG HtDO$ADL$`HXG At^LcwEtP I;DBEL;LHHL$h]D)wALt$hH+HHL$pDL$`LH+Lt$hEI;rhEt 3I;v AEA HDID+ DI;EGAH;woH ELt$hI֋;\tkH+vHStXHL$pHt'ADO$HIHDL$`Lt$hHL$pHHt L3IoR&"5O H+3HI(OIHHXHpHxL` AUH0IMHLMt`Mt[H\$`Hu"Ht L3Qc/HH\$ LMHINHHWH3H\$@Ht$HH|$PLd$XH0A]H8LL$ MLH@H8HHPHHL@LH SVWATH(H33Hu3HtLd$`!G@H*t*t%HcHHL+AHkXIH !H !HLAB8u%ttHcHHHkXI A8tR u*H@ME3HT$XH\H׋HH(A\_^[fL$SH f;u3Ef;sH$H&LL$@HT$0DO3ɅtL$@#H [H(HuCAH(H $H\$Ht$UWATH$HHH3HAكtUfd$pHL$t3AVOL\$pHEHML\$HHD$PLHT$@IE3Ht7Hd$8HT$@HL$`HL$0HL$XLHL$(HMMHL$ 3rHHHHHt$p|$tHE!3ɋHL$Huu tpeHH3L$I[(Is0IA\_]H\$WH HH t#HHH[#HH\$0H _H(AAH`HH(H%H\$Hl$Ht$WH0HH "AIH[DLHHHt!LT$`LT$ H\$@Hl$HHt$PH0_HD$`HD$ ^H8Hd$ E3E333wH8H(E3MLMu3HtHu5IHu-IfE9tEfD;tHDfEufD9t HfuH+IfE9tEfD;tHDfEufD9u HfD9ufDHH;I IDH(HHXHhHpHx ATAUAVH HHu2Z{Lc}c3ADBcxҋSu +s;L_<+kt=u'tt u 3ZHDH 3 ( H\$0Hl$8H _H(u  Mx1; 09s)HcH @9HƒHHkXHDtH  IHH(HHXHpHxL` AVH HcLIL58HkXK4|3 u4O B|3 uHL3#D3 AtK HLH\$0Ht$8H|$@Ld$HH A^HcH N8HƒHHkXHHLH%@H\$Ht$H|$ATAUAVH@HDE3^ CAuB3H7|$$@LcJHH\$0JH H;C{ u< A{ u!HKu DhDl$(C @EudHK_Ct HKWHHEu?CH3L.7K+H.袋.HLIIH?LDDd$ HXHH6D;XJ:HD$0HtsHcL6IЃ6 I H H;s@H0@ ` HXHD$0|$ HcHHHkXID[DD|$ m?AH\$`Ht$hH|$pH@A^A]A\H\$Ht$H|$UATAUHHP3MLHHMDC(3IH]EHu^MtHtEBHuHuI?v EC$ELMHLE@HMHAՋHt\xIMxHEЈHEHHEHU3}Ct!HEMxHU3`Ct9]fB\fÍCL\$PI[ Is(I{0IA]A\]H\$Hl$Ht$WATAUH0E3IIHHMMuHuH3HHI;vP"LFH O]HD$xLHD$(HD$pHHD$ Rup8"H ]LD HD$xLHD$(HD$pHHD$ fDl_uHu8"u4D *y)fD/uo" bH\$PHl$XHt$`H0A]A\_H8HD$`HD$(Hd$ H8H\$Hl$Ht$WH AAH٨uj3AAu vH9D[Ey AD[AtAtA rC$HDNj֋QZ@ōEH\$0Hl$8Ht$@H _H\$Ht$HL$WH A؋H3Hu7-t tu`DËHHH\$8Ht$@H _H\$Ht$WH =V4H7HHu3H9tujH$hy huWHHtKHtFHgHH Ht3gH;vH ft' tuEEЃ@@@E|@uvpEubEA cTtOtILt$PnjE3҅EZ Etf77D$pA@A AT$HDt MI9A rIEtLt$PA@tMFEFA@tMcFEFLt$PA@t MyIADu A rEy AA;AOt$tIHH#ʉL$HυMt3IIcHLB09~ƈHԋt$@H|$D+HDE0t8HALjufD;uAAA;AAO|$DA;~']HccHEHpH؋D|$DDIH ILt$PAHcHE߭HMHL$0MDωL$(HMLHD|$ AtEuH DHUHйgfD;uuH yHUHп-@8;uAHH^jt$@E3DAt+fD$`At fD$`x|$H |$H Dt$\Ll$xE+D+A uLL$@MABHuLL$@HL$`MŋHt$ AtAuLL$@0MAB39D$XufE~aHAHELMHL$dLc HagE3Lc~%HT$xL$dLD$@+BIE3҅Ll$x)Ll$xt$@LL$@MAHHt$ \E3ҋt$@x"AtLL$@ MABt$@E3Lt$PA HEHEH|$DE3LUA +AŃItPhtCl;tXwA fA9XuIA AE AfA86ufAx4uIAfA83ufAx2uIAdfA9ifA9ofA9tuHfA9gA fA9ZfA9PDT$lHT$xLD$@AD$X@t$@E3fA*u)A>IXLt$P|$D|$D Aō|HЉ|$DADT$DfA*u,AIXLt$PD$\A؉D$\D$\ AōDHЉD$\AA;t>#t2+;t#-;t0;XuZATAIACA6HtDl18AEAADm`tu ШtL`ШtA;AB؋LHuc xMH3DBjL }LKnL\0@KnIA D0HL0 A:AKnAAIVE`DD0 EtbKnL09A:tQtM KnAHE`DD09Au.KnL0:A:tt KnHE`ADD0:KnLMDH 1H|$ ~HcU܅|H;qL|DKnD05At A> uL0d0IcIMIHEL;A AE<A:t HIHEHL;sIE8 uI~LKnLMHUXH 1AIH|$ ~u |uf9}taL{KnD0Ht}X tD#KnEXD1 ;I;u }X u +MHAHZh}X L{tL{D#HL;m KnD0@uL0 AEHDE+}`EHuHI;rHB8pQt BpQub*u;uHcH]KnD0Ht;HÈL0 |KnHÈD19uKnHÈD1:HcH+MHAHc9gELmPA+DMƉD$(3ҹLl$ zDuz;Ë]H=zJn@E|0HҺ t fA9uL0d0IcIMIHE`L;eA AEf.fA;tfHI HE`HL;sIEf9u ILKnLMHUH 1AIH|$ ~{uy9}LTyKnD0Ht; f9Ut@fD#EKnD1 EKnD19KnT0:LI;u f9Uuf7MHHDBe Lxf9Ut LxfD#HL;m`"KnD0@uL0 AEfHDE+]LmPM;tIzAD܋Yxu Amu뵋,38x 1HXA_A^A]A\_^[]H\$Ht$L$WATAUAVAWH ALHcuJ3ۉ! 3ۅ;=nLLIL=sAMkXKBL tiAuW1KBD tDIՋ 蟭c H\$XHt$`H A_A^A]A\_H\$Ht$WH IIHMu3VHuɧ#~4AMLHIŃ?u;*uLHձ?у;uD#H\$@Hl$HHt$PH A]A\_H\$UVWATAUAVAWH$0HHƸH3H3HHL$pHT$hHMIMD$dDD$XD$DD$LD$\D$TEE3HEHu,踥E3D8]t HEAC@L mtH#H(A;t(t#LcL DtIAHMkXMn LL !tA@8u)A;ttHcHƒHHkXInB8t,HE3D8]t HEAE3LD$hMtE ADT$@DT$HALUEH]AILD$hAD$I,E3҅A tf77D$\pA@A T$LDuA s MI.IA tA@tMFEFA@tMcFEFA@t MyIADu A rEy AA;AOt$dIHH#ʉL$LυMt3IIcHLB09~ƈHԋt$@H|$D+HÉD$HEt ;0 HD$H0uAguCD$D9A;AO|$D~&]HcHEHtH؋D$DIH IAHcHEApHMDHL$0L$xLƉL$(L$DHӉL$ HMAt39D$DuH oHUHAguuH woHUHЀ;-uAHH,t$@E3҉D$HD9T$\ZA@t1AsD$P- AtD$P+|$LAtD$P |$LDd$XHt$pD+d$HD+A uLL$@LAԱ HELL$@HL$PLƋHD$ /AtAuLL$@LAԱ0|$H39D$Ttg~cHDHHMAH]E3҅u/Ut(HELD$pLL$@HHD$ E3҅u(AD|$@"HELL$@LƋHHD$ E3ҋt$@x!AtLD$pLL$@AԱ t$@E3HEHtH E3LU|$DLD$hT$`AL lE EGD8Ut HMHH3賌H$ HA_A^A]A\_^]AIt8Aht)Alt AwuA A8lu IA AA yAA<6uAx4uIAX<3uAx2uIA?t$@E3A*u A>I|$DAD|$D Ač|HЉ|$DoADT$DbA*uAID$XIAD$X AčDHЉD$X)A tAA#t1A+t"A-tA0AAAAADT$xDT$\DT$XDT$LEAD|$DDT$T衛@8u%1H\$Ht$WH HcA辟Hu裴 RDE3H;luj3t 諴HHHHHHkXdH\$0Ht$8H _H\$L$VWATAUAVH ADHcu,  xs;=WskHLIL5`HkXKLtEDKDtDAԋ螳 賳 貟蝳 u .H\$XH A^A]A\_^@SH B@It HzuA"f;u H [̅~LH\$Hl$Ht$WH IILH?tH\$0Hl$8Ht$@H _H\$UVWATAUAVAWH$ HHH3H3HHL$xHUHMIMLL$PD$pDD$XD$HD$LD$lD$D艊TE3HEHu,CE3D8]t HE\ LEMtE(AEELUT$@fE! H]NYD^!ILE AfA+f;wH 6ALAHcH IcHHD ADL$hA AEJM E'P AŹd;5ACQEGSX{ZtOac"Dl$D-D9T$l]A@/Af|$\|$LIILt$PHt9HXHt0-A sDoԙDl$D+DD8EDT$DHcH+$E3L_A0uE 9t$HIDILt$PESHEHDHD8HU%#E3҅tHAHD;|A0uE AIAfD$`Dl$DLt$PEt4D$dHEDT$eLc LMHT$dHM7!E3҅y Dl$lfEH]E}EfEgA@H]Ћ\AD|$He8g;~ǹi; no;ptasu;x;AHHAHDǶDl$DH fD9tHuH+HDAAD$pEi Ey|fQfDl$\AQfD$^kAEyQD LI>ILt$PE3҅D$@EZ EtfD$lA@A A0T$LDt MI9A rIEtLt$PA@tMFEFA@tMcFEFLt$PA@t MyIADu A rEy A;ODt$pIHH#ʉL$LυMt 3IIcHLB09~AƈHLt$PH|$H+HDDt D8+HAD+ufD;u?A;AO|$HA;~']HcHEHhH؋D|$HDIH ۴ILt$PAHcHE9cHMHL$0MDωL$(HMLHD|$ AtEuH bHUHйgfD;uuH qbHUHп-@8;uAHHE3D-Dl$DAt+fD$\At fD$\x|$L |$L Dt$XHt$xE+D+A uLL$@LABHELL$@HL$\LƋHD$ UAtAuLL$@0LAEufE~aHAHELMHL$`Lc HE3Lc~%HT$xL$`LD$@IE3҅Ht$x-Ht$xT$@#HELL$@LAHHD$ 譬E3ҋT$@x_AtYLL$@ LAWLt$PE3A T$@HEHSH|$HE3LUT$@A 5Lt$PA AŃItPhtCl;tXwA fA9XuIA AE AfA86ufAx4uIAfA83ufAx2uIAdfA9ifA9{ofA9luHfA9_A fA9RfA9HDT$hHT$xLD$@AD$DE3fA*u(A>IXLt$P|$Ht$H Aō|HЉ|$HADT$HfA*u,AIXLt$PD$XA؉D$XD$X AōDHЉD$XAA;t>#t2+;t#-;t0;XuWAQAFA@A9A3DUDT$lDT$XDT$LEt$HDT$D|$HDL$hLEXE(fEEtAu=D8Ut HMHH3U}H$0HA_A^A]A\_^]葧JE3D8]t HEH\$Hl$Ht$WHP3IHHMu3Hu7HtIwHL$0I7L\$0AKuGH+fAr fZwf  fAr fZwf HHt ftf;t+GDLǺt$(H\$ Lu'葦@8l$HTHD$@C@8l$Ht HL$@H\$`Hl$hHt$pHP_H(E3LD9 ouxHu'H(HtIwL+AfAr fZwf  fAr fZwf HIt ftf;t+H(H(DHHfuH+HHH\$Hl$WH0Hd$@HԴHHtud$(Hd$ DL33V[HctxHϺHD$@HtaLD33ɉ|$(HD$ [t:HL$@3KxHHHu3H\$HHl$PH0_HL$@Ht HL$@|HHXHhHpHx ATH0H(E3AHuf=tHH\CfuGHc HHγHtHҭfD9#tSHf;=pt.HcHHHtxLHHuQHHcHCfD9#uHyH{L%jL'e3H\$@Hl$HHt$PH|$XH0A\E3E333Ld$ ЉH O{L%H\$Hl$Ht$WH >[3HHtLf9tHf9uHf9u+ǃHcHiHHtLHHHHZHH\$0Hl$8Ht$@H _@SH AHt"AtHIzc3HHCCH [H(H8csmu+xu%@ = t=!t="t=@u'3H(H(H +Y3H(H\$Hl$Ht$WH H袽E3HHHH99tHHH;rHH;s99tIHRLAMEIu LIA@4Iu&HHy0HHLLH|9uǃ9uǃ9u ǃz9u ǃf9u ǃR9u ǃ>9u ǃ*9u ǃ9ǺD‰AЉ LIIAH3H\$0Hl$8Ht$@H _H\$Hl$Ht$WH0=)u>H3Hu<=tHFH\uGHcHHHtH;tPH;=pt.HcHHHtsLHHv"uKHHcH؀;uHPHwH%@H'B3H\$@Hl$HHt$PH0_Hd$ E3E333貅H 1wH%ٮHHXHhHpHx ATAUAVH Ll$`MIAeLHAHtLI3;"u3@"HË9AEHtH3HËItAEHtHH@tu@ t@ uHt GH3;; t; uH;MtI<$IA3H;\t;"u6utHC8"uH 33҅Ht\HAEutOu< tG< tCt7HHttHÈHAEH tHAEAEHYHtHAEMtI$$AH\$@Hl$HHt$PH|$XH A^A]A\H\$Ht$ WH0=.uCH=A3H8UH!H=ڬHt;uHHD$HLL$@E33HHD$ Hct$@HH;s\HcL$HHsQHH;rHH%HHt8LHD$HLL$@HHHD$ gD\$@H=A3DH\$PHt$XH0_HHXHhHpHx ATH@UTE3HHHfD9 tHfD9#uHfD9#uLd$8H+Ld$0HL3DK3Dd$(Ld$ THctQHKHHtALd$8Ld$0DKL33ɉl$(HD$ Su HsIHSH HS3H\$PHl$XHt$`H|$hH@A\H\$WH HtH=tHHtHH;rH\$0H _H\$WH H_tH=XtHHtHH;rH\$0H _H\$WH HHd$0H2-+H;t HHvHL$0SH\$0RDI3\RDI3RHL$8DI3RL\$8L3HL#H3-+L;LDLILH\$@H _HMZf9t3HcHEA IcEHTE+;)DE;D}AHc DeXHAHHkXIƀd8Dd8HcHƒHHkXI Ƌƀd8D8@8}u @tHc HHHkXIƀL A#;AHMIEDEHMPH|$0D$(ALMD$ AJHu5H轒LcIAHMkXIBd |rHcHʃHHkXI H -E3E333H|$ AxH\$WH@d$03H|$pHux3Htك|$xtAtDL$(DD$ DLHHL$0؉D$4|$0t,t!HcHHHHkXH€d}rjH\$PH@_H8ADL$`ELD$(HL$ DAIH8Mu3ftf;u HHIu +H(u ސ Bx.; 4s&HcHDHHHkXHD@蟐 Xw3H(H\$Hl$ VWATH@H߉H3HD$0B@HHtH-L%ɴt5Htt(HtHHcHtDAMkXMLAC8$< HZtt5HMtt(H@tHHcH1tDAMkXMLAC8$<Htt1Hst$HsHHcHsHkXI,EHT$$HL$ DA*53ۅt 9\$ ~>Hl$$OxHEHHHMHȃtH;\$ |@HcOHOx&Hf1HcGHGxHf0H H1<HL$0H3DdH\$pHl$xH@A\_^H\$WH H H AEHHHuH\$0H _H 3HH9 dHHXHpHxL` UHHPE3IHHHtMtD8"u%HtfD!3H\$`Ht$hH|$pLd$xHP]HMIfL]E9cu#HtfD8et HEHUHMD A~0A;|+IAHLǺ D$(H\$ _CHMuHc H;r&D8gt D8e6HM&*D8et HEAAHAQLljD$(HEH\$ HB E3@SH@HL$ dHD$ DH@BY%|$8t HL$0H@[@SH@HL$ 3dHD$ DH@BY%|$8t HL$0H@[ffHHHtfHt_uI~IHMHLHI3I#tHPtQtGHt9t/Ht!tt uHDHDHDHDHDHDHDHD@UATAUAVAWHPHl$@H]@HuHH}PHH3HE]`3MEU~*DIA@88t HEuAA+;ÍX|DexEuHD`DMẢ|$(H|$ @Lcu3I~^3HBIHrOKL-Hw*HAH;wIHڼH+H|$@HtdHHt HHtDMƺADl$(H|$ ;@LDu!t$(H!t$ AELAAHc"AEt7Mp ;HEhL$(ELAAHD$ lA~g3HBHHrXHL6I;w5HAH;w HH޻H+H\$@HcHHtH3HtnELAAΉt$(H\$ @3ɅtVH [HSH HHIH; tVHK H; tVHK(H; tUHK0H; tUHK8H; tUHK@H; tUHKHH; tUHKhH; tUHKpH; tUHKxH; چtsUHH; ͆t^UHH; tIUHH; t4UH [@SH Ht HtMuDc}dH [LM+ACItHuHu*}"3ffH+LtB :uVHtWHuIJ ffwHJ H;uI~LHHI3ItHH3ffft't#HttHtt tu3HHffH+Ir"tf: u,HIuMIuMt: u HIuH3ÐIt7HH; u[HAH;D uLHAH;D u=HAH;D u.H IuIMItHH; uHIuIHHHH HHH;ffMtuH+LItB H:uWItNtJHuJ ffwHJ H;uHII~vHLI3It H3HHÄt't#HttHtt tuH3H(wHHt)H(H(H S1HH(H H H H H H%1H\$Ht$WATAUAVAWH03|$`3уtbtMtXtSt.tt5y`@L%H L%H |L%H lHHurHHLcgK9YtHIHHH;rIHHH;s9Yt3LaM,$ L%H }|$`0LIu3Mu AM̅t3货t t tL|$(,LL|$(HuDdžDt$`u9 JщL$ J;}*HcHHHd‰T$ XJْI$t3;u AA;t t L; DH\$hHt$pH0A_A^A]A\_H iH i@SH J艠HH.HjH[HuCH#3H [H\$Ht$H|$ATAUAVH LkH #e.LH U.HI;HI+LoII&HI;sUH;HBHH;rIQ3Hu3HV H;rII5Ht[*3[}t HMH$H`_^]Ht0Hu)[_"A@8}eHMUHt}HEe(AKHE(HD$8Hd$0LE8A3҉|$(Ht$ Xt}(3HtzHtHt L3H苎BZ"@}HEH8Hd$ -H8HHXHhHpHx ATH0IcIDE~HIHHcD$hH|$`~ HH܋t tD$(DLƋAH|$ +ظHHDH\$@Hl$HHt$PH|$XH0A\H\$T$UVWATAUAVAWH03LHu5Y?L!M=I0LHL;f9hH5XhDAH;5Rhuo3HHuZHH9uHcTHHHuH zHEHtH+HHHH/Hu3H/|$xH5gHHgtIHtD軴Hawyy覲tp1XH$H0A_A^A]A\_^]Et3Hu#HHOgHtH(H5PgHu H:gHtH(H5+gHHtHM+HILl$pMcHtE=MHIG3ɅuHfB94hfB9 hHHHuH5fH+HE3L9mHcHL.EtfL9ltHLHHHDHL9luHcHH;H TfA艀Ht{rH5;fH+HsLdM.\EyߍG;tLcHL;^H(HHHcL$LlM.HeD9l$xtuI螰H ~HHtUI~MHHIuQHD$pHHGfD*HEIEo uU*H-Et I-M.E3E333Ll$ ;IY-M.H\$Ht$WH@HHL$ AA-HD$(DA|utHD$ H@BY#3t|$8t HL$0H\$PHt$XH@_̋AE33rHHXHhHpHx ATAUAWH H33DG5LHtPDG3ҋHt=HH+H AWHE# HHu1T TH\$@Hl$HHt$PH|$XH A_A]A\úDDI;HEMNjt HH+H~MT8u #T HAԋm LH3w Uy]E3HՋ:HT>H< HHHHu%S SH HE3IՋH3H\$LcHwMAIMkXJ BD8F\ADA@tXtHt(t uHBLJBd8BL80BLJBd8BL8BdBLJ Bd8EuA%@H\$H(HuRW9 "l3H(H\$Hl$VWATH DHH6SHc‚uLR K 8@t .R"3t{HCHSC{C u/'-H0H;t-H`H;u uHC+HS+kHBHC$C~DŋUW S6R6^6p666666667787H7^7x777777748333%@a@`@̧@@&@ bM"S@T@ccsUTF-8UTF-16LEUNICODEHH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSunHH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSun  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~CorExitProcessmscoree.dllruntime error TLOSS error SING error DOMAIN error R6033 - Attempt to use MSIL code from this assembly during native code initialization This indicates a bug in your application. It is most likely the result of calling an MSIL-compiled (/clr) function from a native constructor or from DllMain. R6032 - not enough space for locale information R6031 - Attempt to initialize the CRT more than once. This indicates a bug in your application. R6030 - CRT not initialized R6028 - unable to initialize heap R6027 - not enough space for lowio initialization R6026 - not enough space for stdio initialization R6025 - pure virtual function call R6024 - not enough space for _onexit/atexit table R6019 - unable to open console device R6018 - unexpected heap error R6017 - unexpected multithread lock error R6016 - not enough space for thread data R6010 - abort() has been called R6009 - not enough space for environment R6008 - not enough space for arguments R6002 - floating point support not loaded `@@ @ P@@@@@ @p @ @ @@ @ @ @ @ p @!@x`@y@@z @@@Microsoft Visual C++ Runtime Library ...<program name unknown>Runtime Error! Program: (null)(null)EEE50P( 8PX700WP `h````xpxxxx ((((( H h(((( H H  !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~  GetProcessWindowStationGetUserObjectInformationWGetLastActivePopupGetActiveWindowMessageBoxWUSER32.DLLEEE00P('8PW700PP (`h`hhhxppwppCONOUT$Fatal error in launcher: %s Fatal error in launcher: %s rbFailed to open executableUnable to find an appended archive.Unable to read from file#!PATHEXT;Job creation failedJob information querying failedJob information setting failedmaking stdin inheritable failedmaking stdout inheritable failedmaking stderr inheritable failedUnable to create process using '%ls': %lscontrol handler setting failedFailed to get exit code of process.exe/usr/bin/envExpected to find a command ending in '.exe' in shebang line: %lsExpected to find whitespace after '/usr/bin/env': %lsUnable to find executable in environment: %lsExpected terminating double-quote for executable in shebang line: %ls<launcher_dir>\Terminating quote without starting quote for executable in shebang line: %lsFailed to find shebangExpected to find terminator in shebang lineExpected to decode shebang line using UTF-8Expected to find '#' at start of shebang lineExpected to find '!' following '#' in shebang lineExpected to find executable in shebang lineExpected to find arguments (even if empty) in shebang lineExpected to be able to allocate command line memory"%ls" %ls "%ls" %lsRSDS|+GE Z0C:\Users\Vinay\Projects\simple_launcher\dist\t64.pdbd 4 R pC @ @2p`0C!!Z t d T 4 r- 5td 4 3 rP|0  4 rp  b 42 p`P  t d 4RC,,tbBp`0C-F.@  20. d4 p P|  4 2p42 pC33T42 p  td42C*6K6 td 4 rCI7p768 td4P d T 4 Rpd42pCI<X<RP  t d 4RC]==>>42 pCc?m?4 2 p `C@@  4 RpCAWBWB   d T 4 rp dT4 Rpd 4R pCHI7t42   20dT42p/ td4P|p  4 2pC[\Rr04r p`P|8  t d 4RC``R20Cddm  4 2pCFePeeee2P42pCfg+gbg4  P42 p d4rpCjk20- dQTP4OJp|@ tT4 t dT42d 4R pCtu7 t d 4 RCfvv-vvtd42td42Cxx2 t d T 4Rd 4R pd T42p  p ` 0 P d 4 2pC d T 42p4 2 p `CϖdT42p0 4 p`P|dT 4 pT 4 R pd T 4Rpd 4 R p  4 2p BCWP$ $4$p`P  4 rpCr!T4r p `|0 td 4 P- Etd4C P|Hd4 p- 5td43r P|0d4 p2 0 BC d4 R pCU t d 42C p`P0|Hd42 p6 %4q%f p`P| 4 2 p `C4 2 p `Ck0 4dZ p`P|4 2 p `C4p ` P 4R p ` Pd 4 r p t d T 424T 42 p `  b,|0, |0  4 Rp`P td4rP d T 42p t d T 423 "te"dd"4c"^P| 7 &t&d&4&P|B /3131122(2:2R2j222222233,3<3L3\3j333(888734 4$4<4N4j444444445&5N5`5n55555555555 6 6,6>6R6^6p666666667787H7^7x777777748333ExitProcessGetCommandLineW+SearchPathW~SetInformationJobObjectCreateProcessW}SetHandleInformation WaitForSingleObjectExAssignProcessToJobObjectdFormatMessageWGetExitCodeProcessGetModuleFileNameWQueryInformationJobObjectiMultiByteToWideCharjGetStartupInfoWGetTempPathWCreateJobObjectAkGetStdHandleGetLastErrorSetStdHandleGetFileType;SetConsoleCtrlHandler[SetCurrentDirectoryWRCloseHandleKERNEL32.dllPathRemoveFileSpecWEStrStrIW:PathCombineWSHLWAPI.dllHeapFreeHeapAllocEnterCriticalSection;LeaveCriticalSectionpGetStringTypeWUnhandledExceptionFilterSetUnhandledExceptionFilterIsDebuggerPresent&RtlVirtualUnwindRtlLookupFunctionEntryRtlCaptureContextEncodePointerDecodePointerTerminateProcessGetCurrentProcessInitializeCriticalSectionAndSpinCountGetCommandLineA%RtlUnwindExHeapSetInformationGetVersionHeapCreatexGetCPInfonGetACP>GetOEMCP IsValidCodePageZFlsGetValue[FlsSetValueYFlsFreeSetLastErrorGetCurrentThreadIdXFlsAllocLGetProcAddressGetModuleHandleW4WriteFile|SetHandleCountDeleteCriticalSectionSleep/LCMapStringWReadFiletSetFilePointergFreeEnvironmentStringsWGetEnvironmentStringsWGetModuleFileNameA WideCharToMultiByteQueryPerformanceCounterGetTickCountGetCurrentProcessIdGetSystemTimeAsFileTimeCreateFileWALoadLibraryWHeapReAllocGetConsoleCPGetConsoleMode]FlushFileBuffersdCompareStringWeSetEnvironmentVariableWaSetEndOfFileQGetProcessHeapHeapSize3WriteConsoleW@q@@q@2-+] f        ! 5A CPR S WY l m pr   )     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZPE@`y!@~ڣ @ڣ AϢ[@~QQ^ _j21~CH@D@@@<@8@4@0@(@ @@@@@@@@@@@@@@@@@@@@@@@|@t@l@`@X@H@8@0@,@ @@@ @@@@@@@@@@x@`@P@8@0@(@ @@@@@@@@@@@@@@@@p@`@H@8@ @@@@@@@J@J@J@J@J@R@`@@p@J@PM@d@ @@`@b@p@p@p@p@p@p@p@p@p@p@..R@R@c@c@c@c@c@c@c@c@c@R@c@c@c@c@c@c@c@.Kr .t.O,P@.}0.. (b$d(t.(O.P\\.\..  P# T#!S!(t!"#"E"$H""("-#(0#F$#H$$.$%,%&(&&.& '( '='(')#))#***$,*c,$d, - $ -)-P$,-Z.X$\..$....?0$@0{0$|00.01* 1>1P$@11.23t.34$44&4j5$l55.56$68%89X%9!;p%$;B;P$D;;&;r<%t</=,0=2>%4>>(>?$??%?@@$@@A&A{BD&|BB.BCh&CEp&EH&HJ&\J|J.|JJ.JJ(K$K&0KHK&PKQK&`KaK&dK5L&8LoL&pL M* MXt*XVX.XXX$XY&Yp['p[*\0',\\T'\1_\'4_ax'aM[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq_______CCBABADL*(%$""&GABJIIDDJC??AD_-(''%$GG#_DIJIIJIIIDIJDD_+)(''%GG _JMCEJ?ABAECA?BA_,+)('%%"!_IM@AJACDCIDI____.,++>________JMJJJJJJJJJF_..,+)('%$"! #GIMCEJJJI?J__...,+(('%$"! &_JM?AJJJJCJ__...,+)(''%$" _IMJJJJJJJJ_K=...,++(''%$! _JMCDJJJJAJ_K_______L*%$"!_I_?AMNJJEJ_ L'%$"_I_MMMMMMJJ_ _('%%_I_CEMIJJMEM _(''*_I_?BMDJEMDMF _)(-LI_MMMCMMMMM_________< ____I_CEM1;MACDMEJM__I_?A_D1@IJJMJMM_FF_I__6_@/EMMMNMM_FF_I_C80EJDMJCJFF?:AA20?_M_MIEM______MIC4BC/ N______MMMMJIDAI_9/ HJJMMJMMMJ??BAI_A7503 IJMMCBCBA44CCI___BAO;6___C_MJIDIDMI_CI___O; @__C_C?J?AJI_?A_DJJI@ O_CM?;I?JI_________H9MAIJJIJI_CD_____JJI73C;IJIJI_?AMDJJDIMJIE5:JIJI__________MMIA:IJJJJJJJJJJJJJJJJMJ???( 444MMMRRRYYY~eGuuu}}}x>iErCE~7?HVbx/!P7pLcyϏ1Qq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq!%///* !*/"(%%'%/) /!#''////+/+"*(/ / !%(/ ( (*/ (///) */   )*  /"///// /+/// # + !%//  +"*+'#+//+'!%*++$++!+! !!!<(0`  !!$& &"   WܱNj+<<:5pi$!!!4888&SNJFB=950,(%%%5ǎVRNIEѹ|Jq?o@ZUQMID@<73/+&"-Ɍ444"ք̴wq?]YUPLHC?;72.*%!"""4888&dya\XTPKGC>:61-)% OÑ%%%5ʎA{;ys`\WSOJFB>951,($(9|:z;xq?o@mAkBiCgCdDuZ;73.*Ǒ%%%5̎È>89}:{;yq?o@mAjBhCfD~dEuZ?:625Оa789}:{;yr>p?n@lAjBhCfD}dEB>:5^ό555"܄ѳ„7789}:{;xr?p@n@lAjBgCeDFA=9"""4;;;&ŌC89~9|:z;xr?p@mAkAiBgCIELɑQQQJnnn~~~Įq?o@mAkBiCHHH"""vvv9~:{;yq?o@mAkB:::'''___sssttt8Ϧuǣvyp?n@lA888T---6fffnddd%%%"""OOOSSSċDҧuʤv{;yr?p?xM<<r?̷999VVV###)))444ѳ͝b@~9|:z;}Cg˶Ӎ󑑑ZZZlll;;;!!!""")))___&&&2:::ppp{{{___WWWWWW:::!!!"""***www"""4888&,̑999ԖJJJ|||lll:::"""BBBXXX|||$$$6,uuuKKKCCC@@@...www* Ҍ666"KKKaaaLLLWWW@$"""4;;;&nnnfff!!!$$$999444]ttt|ȇɑ&&&5ЎvvvmmmVVV{{{OOOڥaaaCCCpppό555"ބ|||MMMfff"""4;;;&\\\Ǒ%%%5Ύݧܦ͌555"ۄۥ"""4:::&ڤÑ%%%5ˎܤׇ ww?ww??ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwwwwwwwwwwwwwww?ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwww?wwwwwwww( @ !!!0\\\JeeeQ666D555A0$ vH<602\7qڴvvvj&&&D1"gLF@;5?кVQKE?9(aV[UPJD>82-uuun{{{j`ZTOVo@lAb_YSMGB<60+%<aVmt>q?n@bb^XRLFA;5/)$Ywwwn}}}j~Cv=s>p?bba]WQKE@:4.(+{;xbba[VPJD?93-'aV~:{;xr?o@lAiBfD}dE|cE|cEiB<61?9}:z;w=t>q?n@kAiCfD}cE|cE}dFGA;=aVңi89|:yq?n@kBhCeD|cE|cELF@mzzznjʓP8~9{;yp?mAjBgCeD|cEPJ\¯mAjBgC~dEaV}:z;xq?n@lAiCJJJőPyq?n@~YVVV???wwwiiiΡkA|;y>>3@@@y~~~obbb000kkk@@@---U<<<&&&[[[6uuusssWWWIII$$$&&&qqqlllg|||s4bbbhhhƑUUU000QQQ}}}---{bbb(iiiLLLBBBaVggg+++NNN|||nj]]]tttkkkrrrwwwӦaVӦzzznjӦˉ ?????(  C﫫oIਨS=?_QF4aZмoEcaXMB70~Iq?c`VK@58z;u=qv>3Eyn@iB~dE}dFGHYYYuuuġ=xmAhC}dE}eGS444|||gC}cEMMM~>q?kAiGRRR¢Gu=xKʺ~AAAAAAAAAAAAAAAA ( h00 %   h4VS_VERSION_INFO?fStringFileInfoB080904b0JCompanyNameSimple Launcher User^FileDescriptionSimple Launcher Executable2 FileVersion1.1.0.140InternalNamet64.exej#LegalCopyrightCopyright (C) Simple Launcher User8OriginalFilenamet64.exe@ProductNameSimple Launcher6 ProductVersion1.1.0.14DVarFileInfo$Translation  PAPADDINGXXPADآPX4Ƞؠ(8HXhxȡء@ȪЪت (08@HPX`hpxȫЫث (08@HPX`hpxȬЬج (08@Hح8xPLpxȢТآ (08HPX`hpx././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780566268.0 distlib-0.4.3/distlib/util.py0000664000175000017500000020510715210244374015240 0ustar00vinayvinay# # Copyright (C) 2012-2026 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # import codecs from collections import deque import contextlib import csv from glob import iglob as std_iglob import io import json import logging import os import py_compile import re import socket try: import ssl except ImportError: # pragma: no cover ssl = None import subprocess import sys import tarfile import tempfile import textwrap try: import threading except ImportError: # pragma: no cover import dummy_threading as threading import time from . import DistlibException from .compat import (string_types, text_type, shutil, raw_input, StringIO, cache_from_source, urlopen, urljoin, httplib, xmlrpclib, HTTPHandler, BaseConfigurator, valid_ident, Container, configparser, URLError, ZipFile, fsdecode, unquote, urlparse) logger = logging.getLogger(__name__) # # Requirement parsing code as per PEP 508 # IDENTIFIER = re.compile(r'^([\w\.-]+)\s*') VERSION_IDENTIFIER = re.compile(r'^([\w\.*+-]+)\s*') COMPARE_OP = re.compile(r'^(<=?|>=?|={2,3}|[~!]=)\s*') MARKER_OP = re.compile(r'^((<=?)|(>=?)|={2,3}|[~!]=|in|not\s+in)\s*') OR = re.compile(r'^or\b\s*') AND = re.compile(r'^and\b\s*') NON_SPACE = re.compile(r'(\S+)\s*') STRING_CHUNK = re.compile(r'([\s\w\.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]+)') def parse_marker(marker_string): """ Parse a marker string and return a dictionary containing a marker expression. The dictionary will contain keys "op", "lhs" and "rhs" for non-terminals in the expression grammar, or strings. A string contained in quotes is to be interpreted as a literal string, and a string not contained in quotes is a variable (such as os_name). """ def marker_var(remaining): # either identifier, or literal string m = IDENTIFIER.match(remaining) if m: result = m.groups()[0] remaining = remaining[m.end():] elif not remaining: raise SyntaxError('unexpected end of input') else: q = remaining[0] if q not in '\'"': raise SyntaxError('invalid expression: %s' % remaining) oq = '\'"'.replace(q, '') remaining = remaining[1:] parts = [q] while remaining: # either a string chunk, or oq, or q to terminate if remaining[0] == q: break elif remaining[0] == oq: parts.append(oq) remaining = remaining[1:] else: m = STRING_CHUNK.match(remaining) if not m: raise SyntaxError('error in string literal: %s' % remaining) parts.append(m.groups()[0]) remaining = remaining[m.end():] else: s = ''.join(parts) raise SyntaxError('unterminated string: %s' % s) parts.append(q) result = ''.join(parts) remaining = remaining[1:].lstrip() # skip past closing quote return result, remaining def marker_expr(remaining): if remaining and remaining[0] == '(': result, remaining = marker(remaining[1:].lstrip()) if remaining[0] != ')': raise SyntaxError('unterminated parenthesis: %s' % remaining) remaining = remaining[1:].lstrip() else: lhs, remaining = marker_var(remaining) while remaining: m = MARKER_OP.match(remaining) if not m: break op = m.groups()[0] remaining = remaining[m.end():] rhs, remaining = marker_var(remaining) lhs = {'op': op, 'lhs': lhs, 'rhs': rhs} result = lhs return result, remaining def marker_and(remaining): lhs, remaining = marker_expr(remaining) while remaining: m = AND.match(remaining) if not m: break remaining = remaining[m.end():] rhs, remaining = marker_expr(remaining) lhs = {'op': 'and', 'lhs': lhs, 'rhs': rhs} return lhs, remaining def marker(remaining): lhs, remaining = marker_and(remaining) while remaining: m = OR.match(remaining) if not m: break remaining = remaining[m.end():] rhs, remaining = marker_and(remaining) lhs = {'op': 'or', 'lhs': lhs, 'rhs': rhs} return lhs, remaining return marker(marker_string) def parse_requirement(req): """ Parse a requirement passed in as a string. Return a Container whose attributes contain the various parts of the requirement. """ remaining = req.strip() if not remaining or remaining.startswith('#'): return None m = IDENTIFIER.match(remaining) if not m: raise SyntaxError('name expected: %s' % remaining) distname = m.groups()[0] remaining = remaining[m.end():] extras = mark_expr = versions = uri = None if remaining and remaining[0] == '[': i = remaining.find(']', 1) if i < 0: raise SyntaxError('unterminated extra: %s' % remaining) s = remaining[1:i] remaining = remaining[i + 1:].lstrip() extras = [] while s: m = IDENTIFIER.match(s) if not m: raise SyntaxError('malformed extra: %s' % s) extras.append(m.groups()[0]) s = s[m.end():] if not s: break if s[0] != ',': raise SyntaxError('comma expected in extras: %s' % s) s = s[1:].lstrip() if not extras: extras = None if remaining: if remaining[0] == '@': # it's a URI remaining = remaining[1:].lstrip() m = NON_SPACE.match(remaining) if not m: raise SyntaxError('invalid URI: %s' % remaining) uri = m.groups()[0] t = urlparse(uri) # there are issues with Python and URL parsing, so this test # is a bit crude. See bpo-20271, bpo-23505. Python doesn't # always parse invalid URLs correctly - it should raise # exceptions for malformed URLs if not (t.scheme and t.netloc): raise SyntaxError('Invalid URL: %s' % uri) remaining = remaining[m.end():].lstrip() else: def get_versions(ver_remaining): """ Return a list of operator, version tuples if any are specified, else None. """ m = COMPARE_OP.match(ver_remaining) versions = None if m: versions = [] while True: op = m.groups()[0] ver_remaining = ver_remaining[m.end():] m = VERSION_IDENTIFIER.match(ver_remaining) if not m: raise SyntaxError('invalid version: %s' % ver_remaining) v = m.groups()[0] versions.append((op, v)) ver_remaining = ver_remaining[m.end():] if not ver_remaining or ver_remaining[0] != ',': break ver_remaining = ver_remaining[1:].lstrip() # Some packages have a trailing comma which would break things # See issue #148 if not ver_remaining: break m = COMPARE_OP.match(ver_remaining) if not m: raise SyntaxError('invalid constraint: %s' % ver_remaining) if not versions: versions = None return versions, ver_remaining if remaining[0] != '(': versions, remaining = get_versions(remaining) else: i = remaining.find(')', 1) if i < 0: raise SyntaxError('unterminated parenthesis: %s' % remaining) s = remaining[1:i] remaining = remaining[i + 1:].lstrip() # As a special diversion from PEP 508, allow a version number # a.b.c in parentheses as a synonym for ~= a.b.c (because this # is allowed in earlier PEPs) if COMPARE_OP.match(s): versions, _ = get_versions(s) else: m = VERSION_IDENTIFIER.match(s) if not m: raise SyntaxError('invalid constraint: %s' % s) v = m.groups()[0] s = s[m.end():].lstrip() if s: raise SyntaxError('invalid constraint: %s' % s) versions = [('~=', v)] if remaining: if remaining[0] != ';': raise SyntaxError('invalid requirement: %s' % remaining) remaining = remaining[1:].lstrip() mark_expr, remaining = parse_marker(remaining) if remaining and remaining[0] != '#': raise SyntaxError('unexpected trailing data: %s' % remaining) if not versions: rs = distname else: rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions])) return Container(name=distname, extras=extras, constraints=versions, marker=mark_expr, url=uri, requirement=rs) def get_resources_dests(resources_root, rules): """Find destinations for resources files""" def get_rel_path(root, path): # normalizes and returns a lstripped-/-separated path root = root.replace(os.path.sep, '/') path = path.replace(os.path.sep, '/') assert path.startswith(root) return path[len(root):].lstrip('/') destinations = {} for base, suffix, dest in rules: prefix = os.path.join(resources_root, base) for abs_base in iglob(prefix): abs_glob = os.path.join(abs_base, suffix) for abs_path in iglob(abs_glob): resource_file = get_rel_path(resources_root, abs_path) if dest is None: # remove the entry if it was here destinations.pop(resource_file, None) else: rel_path = get_rel_path(abs_base, abs_path) rel_dest = dest.replace(os.path.sep, '/').rstrip('/') destinations[resource_file] = rel_dest + '/' + rel_path return destinations def in_venv(): if hasattr(sys, 'real_prefix'): # virtualenv venvs result = True else: # PEP 405 venvs result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix) return result def get_executable(): # The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as # changes to the stub launcher mean that sys.executable always points # to the stub on OS X # if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__' # in os.environ): # result = os.environ['__PYVENV_LAUNCHER__'] # else: # result = sys.executable # return result # Avoid normcasing: see issue #143 # result = os.path.normcase(sys.executable) result = sys.executable if not isinstance(result, text_type): result = fsdecode(result) return result def proceed(prompt, allowed_chars, error_prompt=None, default=None): p = prompt while True: s = raw_input(p) p = prompt if not s and default: s = default if s: c = s[0].lower() if c in allowed_chars: break if error_prompt: p = '%c: %s\n%s' % (c, error_prompt, prompt) return c def extract_by_key(d, keys): if isinstance(keys, string_types): keys = keys.split() result = {} for key in keys: if key in d: result[key] = d[key] return result def read_exports(stream): if sys.version_info[0] >= 3: # needs to be a text stream stream = codecs.getreader('utf-8')(stream) # Try to load as JSON, falling back on legacy format data = stream.read() stream = StringIO(data) try: jdata = json.load(stream) result = jdata['extensions']['python.exports']['exports'] for group, entries in result.items(): for k, v in entries.items(): s = '%s = %s' % (k, v) entry = get_export_entry(s) assert entry is not None entries[k] = entry return result except Exception: stream.seek(0, 0) def read_stream(cp, stream): if hasattr(cp, 'read_file'): cp.read_file(stream) else: cp.readfp(stream) cp = configparser.ConfigParser() try: read_stream(cp, stream) except configparser.MissingSectionHeaderError: stream.close() data = textwrap.dedent(data) stream = StringIO(data) read_stream(cp, stream) result = {} for key in cp.sections(): result[key] = entries = {} for name, value in cp.items(key): s = '%s = %s' % (name, value) entry = get_export_entry(s) assert entry is not None # entry.dist = self entries[name] = entry return result def write_exports(exports, stream): if sys.version_info[0] >= 3: # needs to be a text stream stream = codecs.getwriter('utf-8')(stream) cp = configparser.ConfigParser() for k, v in exports.items(): # TODO check k, v for valid values cp.add_section(k) for entry in v.values(): if entry.suffix is None: s = entry.prefix else: s = '%s:%s' % (entry.prefix, entry.suffix) if entry.flags: s = '%s [%s]' % (s, ', '.join(entry.flags)) cp.set(k, entry.name, s) cp.write(stream) @contextlib.contextmanager def tempdir(): td = tempfile.mkdtemp() try: yield td finally: shutil.rmtree(td) @contextlib.contextmanager def chdir(d): cwd = os.getcwd() try: os.chdir(d) yield finally: os.chdir(cwd) @contextlib.contextmanager def socket_timeout(seconds=15): cto = socket.getdefaulttimeout() try: socket.setdefaulttimeout(seconds) yield finally: socket.setdefaulttimeout(cto) class cached_property(object): def __init__(self, func): self.func = func # for attr in ('__name__', '__module__', '__doc__'): # setattr(self, attr, getattr(func, attr, None)) def __get__(self, obj, cls=None): if obj is None: return self value = self.func(obj) object.__setattr__(obj, self.func.__name__, value) # obj.__dict__[self.func.__name__] = value = self.func(obj) return value def convert_path(pathname): """Return 'pathname' as a name that will work on the native filesystem. The path is split on '/' and put back together again using the current directory separator. Needed because filenames in the setup script are always supplied in Unix style, and have to be converted to the local convention before we can actually use them in the filesystem. Raises ValueError on non-Unix-ish systems if 'pathname' either starts or ends with a slash. """ if os.sep == '/': return pathname if not pathname: return pathname if pathname[0] == '/': raise ValueError("path '%s' cannot be absolute" % pathname) if pathname[-1] == '/': raise ValueError("path '%s' cannot end with '/'" % pathname) paths = pathname.split('/') while os.curdir in paths: paths.remove(os.curdir) if not paths: return os.curdir return os.path.join(*paths) class FileOperator(object): def __init__(self, dry_run=False): self.dry_run = dry_run self.ensured = set() self._init_record() def _init_record(self): self.record = False self.files_written = set() self.dirs_created = set() def record_as_written(self, path): if self.record: self.files_written.add(path) def newer(self, source, target): """Tell if the target is newer than the source. Returns true if 'source' exists and is more recently modified than 'target', or if 'source' exists and 'target' doesn't. Returns false if both exist and 'target' is the same age or younger than 'source'. Raise PackagingFileError if 'source' does not exist. Note that this test is not very accurate: files created in the same second will have the same "age". """ if not os.path.exists(source): raise DistlibException("file '%r' does not exist" % os.path.abspath(source)) if not os.path.exists(target): return True return os.stat(source).st_mtime > os.stat(target).st_mtime def copy_file(self, infile, outfile, check=True): """Copy a file respecting dry-run and force flags. """ self.ensure_dir(os.path.dirname(outfile)) logger.info('Copying %s to %s', infile, outfile) if not self.dry_run: msg = None if check: if os.path.islink(outfile): msg = '%s is a symlink' % outfile elif os.path.exists(outfile) and not os.path.isfile(outfile): msg = '%s is a non-regular file' % outfile if msg: raise ValueError(msg + ' which would be overwritten') shutil.copyfile(infile, outfile) self.record_as_written(outfile) def copy_stream(self, instream, outfile, encoding=None): assert not os.path.isdir(outfile) self.ensure_dir(os.path.dirname(outfile)) logger.info('Copying stream %s to %s', instream, outfile) if not self.dry_run: if encoding is None: outstream = open(outfile, 'wb') else: outstream = codecs.open(outfile, 'w', encoding=encoding) try: shutil.copyfileobj(instream, outstream) finally: outstream.close() self.record_as_written(outfile) def write_binary_file(self, path, data): self.ensure_dir(os.path.dirname(path)) if not self.dry_run: if os.path.exists(path): os.remove(path) with open(path, 'wb') as f: f.write(data) self.record_as_written(path) def write_text_file(self, path, data, encoding): self.write_binary_file(path, data.encode(encoding)) def set_mode(self, bits, mask, files): if os.name == 'posix' or (os.name == 'java' and os._name == 'posix'): # Set the executable bits (owner, group, and world) on # all the files specified. for f in files: if self.dry_run: logger.info("changing mode of %s", f) else: mode = (os.stat(f).st_mode | bits) & mask logger.info("changing mode of %s to %o", f, mode) os.chmod(f, mode) set_executable_mode = lambda s, f: s.set_mode(0o555, 0o7777, f) def ensure_dir(self, path): path = os.path.abspath(path) if path not in self.ensured and not os.path.exists(path): self.ensured.add(path) d, f = os.path.split(path) self.ensure_dir(d) logger.info('Creating %s' % path) if not self.dry_run: os.mkdir(path) if self.record: self.dirs_created.add(path) def byte_compile(self, path, optimize=False, force=False, prefix=None, hashed_invalidation=False): if not optimize: optimization = '' else: optimization = '1' dpath = cache_from_source(path, optimization=optimization) logger.info('Byte-compiling %s to %s', path, dpath) if not self.dry_run: if force or self.newer(path, dpath): if not prefix: diagpath = None else: assert path.startswith(prefix) diagpath = path[len(prefix):] compile_kwargs = {} if hashed_invalidation and hasattr(py_compile, 'PycInvalidationMode'): if not isinstance(hashed_invalidation, py_compile.PycInvalidationMode): hashed_invalidation = py_compile.PycInvalidationMode.CHECKED_HASH compile_kwargs['invalidation_mode'] = hashed_invalidation py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error self.record_as_written(dpath) return dpath def ensure_removed(self, path): if os.path.exists(path): if os.path.isdir(path) and not os.path.islink(path): logger.debug('Removing directory tree at %s', path) if not self.dry_run: shutil.rmtree(path) if self.record: if path in self.dirs_created: self.dirs_created.remove(path) else: if os.path.islink(path): s = 'link' else: s = 'file' logger.debug('Removing %s %s', s, path) if not self.dry_run: os.remove(path) if self.record: if path in self.files_written: self.files_written.remove(path) def is_writable(self, path): result = False while not result: if os.path.exists(path): result = os.access(path, os.W_OK) break parent = os.path.dirname(path) if parent == path: break path = parent return result def commit(self): """ Commit recorded changes, turn off recording, return changes. """ assert self.record result = self.files_written, self.dirs_created self._init_record() return result def rollback(self): if not self.dry_run: for f in list(self.files_written): if os.path.exists(f): os.remove(f) # dirs should all be empty now, except perhaps for # __pycache__ subdirs # reverse so that subdirs appear before their parents dirs = sorted(self.dirs_created, reverse=True) for d in dirs: flist = os.listdir(d) if flist: assert flist == ['__pycache__'] sd = os.path.join(d, flist[0]) os.rmdir(sd) os.rmdir(d) # should fail if non-empty self._init_record() def resolve(module_name, dotted_path): if module_name in sys.modules: mod = sys.modules[module_name] else: mod = __import__(module_name) if dotted_path is None: result = mod else: parts = dotted_path.split('.') result = getattr(mod, parts.pop(0)) for p in parts: result = getattr(result, p) return result class ExportEntry(object): def __init__(self, name, prefix, suffix, flags): self.name = name self.prefix = prefix self.suffix = suffix self.flags = flags @cached_property def value(self): return resolve(self.prefix, self.suffix) def __repr__(self): # pragma: no cover return '' % (self.name, self.prefix, self.suffix, self.flags) def __eq__(self, other): if not isinstance(other, ExportEntry): result = False else: result = (self.name == other.name and self.prefix == other.prefix and self.suffix == other.suffix and self.flags == other.flags) return result __hash__ = object.__hash__ ENTRY_RE = re.compile( r'''(?P([^\[]\S*)) \s*=\s*(?P(\w+)([:\.]\w+)*) \s*(\[\s*(?P[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? ''', re.VERBOSE) def get_export_entry(specification): m = ENTRY_RE.search(specification) if not m: result = None if '[' in specification or ']' in specification: raise DistlibException("Invalid specification " "'%s'" % specification) else: d = m.groupdict() name = d['name'] path = d['callable'] colons = path.count(':') if colons == 0: prefix, suffix = path, None else: if colons != 1: raise DistlibException("Invalid specification " "'%s'" % specification) prefix, suffix = path.split(':') flags = d['flags'] if flags is None: if '[' in specification or ']' in specification: raise DistlibException("Invalid specification " "'%s'" % specification) flags = [] else: flags = [f.strip() for f in flags.split(',')] result = ExportEntry(name, prefix, suffix, flags) return result def get_cache_base(suffix=None): """ Return the default base location for distlib caches. If the directory does not exist, it is created. Use the suffix provided for the base directory, and default to '.distlib' if it isn't provided. On Windows, if LOCALAPPDATA is defined in the environment, then it is assumed to be a directory, and will be the parent directory of the result. On POSIX, and on Windows if LOCALAPPDATA is not defined, the user's home directory - using os.expanduser('~') - will be the parent directory of the result. The result is just the directory '.distlib' in the parent directory as determined above, or with the name specified with ``suffix``. """ if suffix is None: suffix = '.distlib' if os.name == 'nt' and 'LOCALAPPDATA' in os.environ: result = os.path.expandvars('$localappdata') else: # Assume posix, or old Windows result = os.path.expanduser('~') # we use 'isdir' instead of 'exists', because we want to # fail if there's a file with that name if os.path.isdir(result): usable = os.access(result, os.W_OK) if not usable: logger.warning('Directory exists but is not writable: %s', result) else: try: os.makedirs(result) usable = True except OSError: logger.warning('Unable to create %s', result, exc_info=True) usable = False if not usable: result = tempfile.mkdtemp() logger.warning('Default location unusable, using %s', result) return os.path.join(result, suffix) def path_to_cache_dir(path, use_abspath=True): """ Convert an absolute path to a directory name for use in a cache. The algorithm used is: #. On Windows, any ``':'`` in the drive is replaced with ``'---'``. #. Any occurrence of ``os.sep`` is replaced with ``'--'``. #. ``'.cache'`` is appended. """ d, p = os.path.splitdrive(os.path.abspath(path) if use_abspath else path) if d: d = d.replace(':', '---') p = p.replace(os.sep, '--') return d + p + '.cache' def ensure_slash(s): if not s.endswith('/'): return s + '/' return s def parse_credentials(netloc): username = password = None if '@' in netloc: prefix, netloc = netloc.rsplit('@', 1) if ':' not in prefix: username = prefix else: username, password = prefix.split(':', 1) if username: username = unquote(username) if password: password = unquote(password) return username, password, netloc def get_process_umask(): result = os.umask(0o22) os.umask(result) return result def is_string_sequence(seq): result = True i = None for i, s in enumerate(seq): if not isinstance(s, string_types): result = False break assert i is not None return result PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-' '([a-z0-9_.+-]+)', re.I) PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)') def split_filename(filename, project_name=None): """ Extract name, version, python version from a filename (no extension) Return name, version, pyver or None """ result = None pyver = None filename = unquote(filename).replace(' ', '-') m = PYTHON_VERSION.search(filename) if m: pyver = m.group(1) filename = filename[:m.start()] if project_name and len(filename) > len(project_name) + 1: m = re.match(re.escape(project_name) + r'\b', filename) if m: n = m.end() result = filename[:n], filename[n + 1:], pyver if result is None: m = PROJECT_NAME_AND_VERSION.match(filename) if m: result = m.group(1), m.group(3), pyver return result # Allow spaces in name because of legacy dists like "Twisted Core" NAME_VERSION_RE = re.compile(r'(?P[\w .-]+)\s*' r'\(\s*(?P[^\s)]+)\)$') def parse_name_and_version(p): """ A utility method used to get name and version from a string. From e.g. a Provides-Dist value. :param p: A value in a form 'foo (1.0)' :return: The name and version as a tuple. """ m = NAME_VERSION_RE.match(p) if not m: raise DistlibException('Ill-formed name/version string: \'%s\'' % p) d = m.groupdict() return d['name'].strip().lower(), d['ver'] def get_extras(requested, available): result = set() requested = set(requested or []) available = set(available or []) if '*' in requested: requested.remove('*') result |= available for r in requested: if r == '-': result.add(r) elif r.startswith('-'): unwanted = r[1:] if unwanted not in available: logger.warning('undeclared extra: %s' % unwanted) if unwanted in result: result.remove(unwanted) else: if r not in available: logger.warning('undeclared extra: %s' % r) result.add(r) return result # # Extended metadata functionality # def _get_external_data(url): result = {} try: # urlopen might fail if it runs into redirections, # because of Python issue #13696. Fixed in locators # using a custom redirect handler. resp = urlopen(url) headers = resp.info() ct = headers.get('Content-Type') if not ct.startswith('application/json'): logger.debug('Unexpected response for JSON request: %s', ct) else: reader = codecs.getreader('utf-8')(resp) # data = reader.read().decode('utf-8') # result = json.loads(data) result = json.load(reader) except Exception as e: logger.exception('Failed to get external data for %s: %s', url, e) return result _external_data_base_url = 'https://www.red-dove.com/pypi/projects/' def get_project_data(name): url = '%s/%s/project.json' % (name[0].upper(), name) url = urljoin(_external_data_base_url, url) result = _get_external_data(url) return result def get_package_data(name, version): url = '%s/%s/package-%s.json' % (name[0].upper(), name, version) url = urljoin(_external_data_base_url, url) return _get_external_data(url) class Cache(object): """ A class implementing a cache for resources that need to live in the file system e.g. shared libraries. This class was moved from resources to here because it could be used by other modules, e.g. the wheel module. """ def __init__(self, base): """ Initialise an instance. :param base: The base directory where the cache should be located. """ # we use 'isdir' instead of 'exists', because we want to # fail if there's a file with that name if not os.path.isdir(base): # pragma: no cover os.makedirs(base) if (os.stat(base).st_mode & 0o77) != 0: logger.warning('Directory \'%s\' is not private', base) self.base = os.path.abspath(os.path.normpath(base)) def prefix_to_dir(self, prefix, use_abspath=True): """ Converts a resource prefix to a directory name in the cache. """ return path_to_cache_dir(prefix, use_abspath=use_abspath) def clear(self): """ Clear the cache. """ not_removed = [] for fn in os.listdir(self.base): fn = os.path.join(self.base, fn) try: if os.path.islink(fn) or os.path.isfile(fn): os.remove(fn) elif os.path.isdir(fn): shutil.rmtree(fn) except Exception: not_removed.append(fn) return not_removed class EventMixin(object): """ A very simple publish/subscribe system. """ def __init__(self): self._subscribers = {} def add(self, event, subscriber, append=True): """ Add a subscriber for an event. :param event: The name of an event. :param subscriber: The subscriber to be added (and called when the event is published). :param append: Whether to append or prepend the subscriber to an existing subscriber list for the event. """ subs = self._subscribers if event not in subs: subs[event] = deque([subscriber]) else: sq = subs[event] if append: sq.append(subscriber) else: sq.appendleft(subscriber) def remove(self, event, subscriber): """ Remove a subscriber for an event. :param event: The name of an event. :param subscriber: The subscriber to be removed. """ subs = self._subscribers if event not in subs: raise ValueError('No subscribers: %r' % event) subs[event].remove(subscriber) def get_subscribers(self, event): """ Return an iterator for the subscribers for an event. :param event: The event to return subscribers for. """ return iter(self._subscribers.get(event, ())) def publish(self, event, *args, **kwargs): """ Publish a event and return a list of values returned by its subscribers. :param event: The event to publish. :param args: The positional arguments to pass to the event's subscribers. :param kwargs: The keyword arguments to pass to the event's subscribers. """ result = [] for subscriber in self.get_subscribers(event): try: value = subscriber(event, *args, **kwargs) except Exception: logger.exception('Exception during event publication') value = None result.append(value) logger.debug('publish %s: args = %s, kwargs = %s, result = %s', event, args, kwargs, result) return result # # Simple sequencing # class Sequencer(object): def __init__(self): self._preds = {} self._succs = {} self._nodes = set() # nodes with no preds/succs def add_node(self, node): self._nodes.add(node) def remove_node(self, node, edges=False): if node in self._nodes: self._nodes.remove(node) if edges: for p in set(self._preds.get(node, ())): self.remove(p, node) for s in set(self._succs.get(node, ())): self.remove(node, s) # Remove empties for k, v in list(self._preds.items()): if not v: del self._preds[k] for k, v in list(self._succs.items()): if not v: del self._succs[k] def add(self, pred, succ): assert pred != succ self._preds.setdefault(succ, set()).add(pred) self._succs.setdefault(pred, set()).add(succ) def remove(self, pred, succ): assert pred != succ try: preds = self._preds[succ] succs = self._succs[pred] except KeyError: # pragma: no cover raise ValueError('%r not a successor of anything' % succ) try: preds.remove(pred) succs.remove(succ) except KeyError: # pragma: no cover raise ValueError('%r not a successor of %r' % (succ, pred)) def is_step(self, step): return (step in self._preds or step in self._succs or step in self._nodes) def get_steps(self, final): if not self.is_step(final): raise ValueError('Unknown: %r' % final) result = [] todo = [] seen = set() todo.append(final) while todo: step = todo.pop(0) if step in seen: # if a step was already seen, # move it to the end (so it will appear earlier # when reversed on return) ... but not for the # final step, as that would be confusing for # users if step != final: result.remove(step) result.append(step) else: seen.add(step) result.append(step) preds = self._preds.get(step, ()) todo.extend(preds) return reversed(result) @property def strong_connections(self): # http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm index_counter = [0] stack = [] lowlinks = {} index = {} result = [] graph = self._succs def strongconnect(node): # set the depth index for this node to the smallest unused index index[node] = index_counter[0] lowlinks[node] = index_counter[0] index_counter[0] += 1 stack.append(node) # Consider successors try: successors = graph[node] except Exception: successors = [] for successor in successors: if successor not in lowlinks: # Successor has not yet been visited strongconnect(successor) lowlinks[node] = min(lowlinks[node], lowlinks[successor]) elif successor in stack: # the successor is in the stack and hence in the current # strongly connected component (SCC) lowlinks[node] = min(lowlinks[node], index[successor]) # If `node` is a root node, pop the stack and generate an SCC if lowlinks[node] == index[node]: connected_component = [] while True: successor = stack.pop() connected_component.append(successor) if successor == node: break component = tuple(connected_component) # storing the result result.append(component) for node in graph: if node not in lowlinks: strongconnect(node) return result @property def dot(self): result = ['digraph G {'] for succ in self._preds: preds = self._preds[succ] for pred in preds: result.append(' %s -> %s;' % (pred, succ)) for node in self._nodes: result.append(' %s;' % node) result.append('}') return '\n'.join(result) # # Unarchiving functionality for zip, tar, tgz, tbz, whl # ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz', '.whl') def unarchive(archive_filename, dest_dir, format=None, check=True): def check_path(path, base=None): if not isinstance(path, text_type): path = path.decode('utf-8') if base is None: base = dest_dir p = os.path.abspath(os.path.join(base, path)) if not p.startswith(dest_dir) or p[plen] != os.sep: raise ValueError('path outside destination: %r' % p) def check_link(member): # A symlink/hardlink member's name is validated like any other # member, but its target (linkname) is not covered by extractall's # name-based handling. An unchecked target lets a later member be # written through the link to a location outside dest_dir. Validate # the resolved target stays within dest_dir. Symlink targets are # relative to the member's own directory; hardlink targets are # relative to the archive root (i.e. dest_dir). if not (member.issym() or member.islnk()): return if member.issym(): link_base = os.path.dirname(os.path.join(dest_dir, member.name)) else: link_base = dest_dir check_path(member.linkname, base=link_base) dest_dir = os.path.abspath(dest_dir) plen = len(dest_dir) archive = None if format is None: if archive_filename.endswith(('.zip', '.whl')): format = 'zip' elif archive_filename.endswith(('.tar.gz', '.tgz')): format = 'tgz' mode = 'r:gz' elif archive_filename.endswith(('.tar.bz2', '.tbz')): format = 'tbz' mode = 'r:bz2' elif archive_filename.endswith('.tar'): format = 'tar' mode = 'r' else: # pragma: no cover raise ValueError('Unknown format for %r' % archive_filename) try: if format == 'zip': archive = ZipFile(archive_filename, 'r') if check: names = archive.namelist() for name in names: check_path(name) else: archive = tarfile.open(archive_filename, mode) if check: for member in archive.getmembers(): check_path(member.name) check_link(member) if format != 'zip' and sys.version_info[0] < 3: # See Python issue 17153. If the dest path contains Unicode, # tarfile extraction fails on Python 2.x if a member path name # contains non-ASCII characters - it leads to an implicit # bytes -> unicode conversion using ASCII to decode. for tarinfo in archive.getmembers(): if not isinstance(tarinfo.name, text_type): tarinfo.name = tarinfo.name.decode('utf-8') # Limit extraction of dangerous items, if this Python # allows it easily. If not, just trust the input. # See: https://docs.python.org/3/library/tarfile.html#extraction-filters def extraction_filter(member, path): """Run tarfile.tar_filter, but raise the expected ValueError""" # This is only called if the current Python has tarfile filters try: return tarfile.tar_filter(member, path) except tarfile.FilterError as exc: raise ValueError(str(exc)) archive.extraction_filter = extraction_filter archive.extractall(dest_dir) finally: if archive: archive.close() def zip_dir(directory): """zip a directory tree into a BytesIO object""" result = io.BytesIO() dlen = len(directory) with ZipFile(result, "w") as zf: for root, dirs, files in os.walk(directory): for name in files: full = os.path.join(root, name) rel = root[dlen:] dest = os.path.join(rel, name) zf.write(full, dest) return result # # Simple progress bar # UNITS = ('', 'K', 'M', 'G', 'T', 'P') class Progress(object): unknown = 'UNKNOWN' def __init__(self, minval=0, maxval=100): assert maxval is None or maxval >= minval self.min = self.cur = minval self.max = maxval self.started = None self.elapsed = 0 self.done = False def update(self, curval): assert self.min <= curval assert self.max is None or curval <= self.max self.cur = curval now = time.time() if self.started is None: self.started = now else: self.elapsed = now - self.started def increment(self, incr): assert incr >= 0 self.update(self.cur + incr) def start(self): self.update(self.min) return self def stop(self): if self.max is not None: self.update(self.max) self.done = True @property def maximum(self): return self.unknown if self.max is None else self.max @property def percentage(self): if self.done: result = '100 %' elif self.max is None: result = ' ?? %' else: v = 100.0 * (self.cur - self.min) / (self.max - self.min) result = '%3d %%' % v return result def format_duration(self, duration): if (duration <= 0) and self.max is None or self.cur == self.min: result = '??:??:??' # elif duration < 1: # result = '--:--:--' else: result = time.strftime('%H:%M:%S', time.gmtime(duration)) return result @property def ETA(self): if self.done: prefix = 'Done' t = self.elapsed # import pdb; pdb.set_trace() else: prefix = 'ETA ' if self.max is None: t = -1 elif self.elapsed == 0 or (self.cur == self.min): t = 0 else: # import pdb; pdb.set_trace() t = float(self.max - self.min) t /= self.cur - self.min t = (t - 1) * self.elapsed return '%s: %s' % (prefix, self.format_duration(t)) @property def speed(self): if self.elapsed == 0: result = 0.0 else: result = (self.cur - self.min) / self.elapsed for unit in UNITS: if result < 1000: break result /= 1000.0 return '%d %sB/s' % (result, unit) # # Glob functionality # RICH_GLOB = re.compile(r'\{([^}]*)\}') _CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]') _CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$') def iglob(path_glob): """Extended globbing function that supports ** and {opt1,opt2,opt3}.""" if _CHECK_RECURSIVE_GLOB.search(path_glob): msg = """invalid glob %r: recursive glob "**" must be used alone""" raise ValueError(msg % path_glob) if _CHECK_MISMATCH_SET.search(path_glob): msg = """invalid glob %r: mismatching set marker '{' or '}'""" raise ValueError(msg % path_glob) return _iglob(path_glob) def _iglob(path_glob): rich_path_glob = RICH_GLOB.split(path_glob, 1) if len(rich_path_glob) > 1: assert len(rich_path_glob) == 3, rich_path_glob prefix, set, suffix = rich_path_glob for item in set.split(','): for path in _iglob(''.join((prefix, item, suffix))): yield path else: if '**' not in path_glob: for item in std_iglob(path_glob): yield item else: prefix, radical = path_glob.split('**', 1) if prefix == '': prefix = '.' if radical == '': radical = '*' else: # we support both radical = radical.lstrip('/') radical = radical.lstrip('\\') for path, dir, files in os.walk(prefix): path = os.path.normpath(path) for fn in _iglob(os.path.join(path, radical)): yield fn if ssl: from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname, CertificateError) # # HTTPSConnection which verifies certificates/matches domains # class HTTPSConnection(httplib.HTTPSConnection): ca_certs = None # set this to the path to the certs file (.pem) check_domain = True # only used if ca_certs is not None # noinspection PyPropertyAccess def connect(self): sock = socket.create_connection((self.host, self.port), self.timeout) if getattr(self, '_tunnel_host', False): self.sock = sock self._tunnel() context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) if hasattr(ssl, 'OP_NO_SSLv2'): context.options |= ssl.OP_NO_SSLv2 if getattr(self, 'cert_file', None): context.load_cert_chain(self.cert_file, self.key_file) kwargs = {} if self.ca_certs: context.verify_mode = ssl.CERT_REQUIRED context.load_verify_locations(cafile=self.ca_certs) if getattr(ssl, 'HAS_SNI', False): kwargs['server_hostname'] = self.host self.sock = context.wrap_socket(sock, **kwargs) if self.ca_certs and self.check_domain: try: match_hostname(self.sock.getpeercert(), self.host) logger.debug('Host verified: %s', self.host) except CertificateError: # pragma: no cover self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() raise class HTTPSHandler(BaseHTTPSHandler): def __init__(self, ca_certs, check_domain=True): BaseHTTPSHandler.__init__(self) self.ca_certs = ca_certs self.check_domain = check_domain def _conn_maker(self, *args, **kwargs): """ This is called to create a connection instance. Normally you'd pass a connection class to do_open, but it doesn't actually check for a class, and just expects a callable. As long as we behave just as a constructor would have, we should be OK. If it ever changes so that we *must* pass a class, we'll create an UnsafeHTTPSConnection class which just sets check_domain to False in the class definition, and choose which one to pass to do_open. """ result = HTTPSConnection(*args, **kwargs) if self.ca_certs: result.ca_certs = self.ca_certs result.check_domain = self.check_domain return result def https_open(self, req): try: return self.do_open(self._conn_maker, req) except URLError as e: if 'certificate verify failed' in str(e.reason): raise CertificateError('Unable to verify server certificate ' 'for %s' % req.host) else: raise # # To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The- # Middle proxy using HTTP listens on port 443, or an index mistakenly serves # HTML containing a http://xyz link when it should be https://xyz), # you can use the following handler class, which does not allow HTTP traffic. # # It works by inheriting from HTTPHandler - so build_opener won't add a # handler for HTTP itself. # class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler): def http_open(self, req): raise URLError('Unexpected HTTP request on what should be a secure ' 'connection: %s' % req) # # XML-RPC with timeouts # class Transport(xmlrpclib.Transport): def __init__(self, timeout, use_datetime=0): self.timeout = timeout xmlrpclib.Transport.__init__(self, use_datetime) def make_connection(self, host): h, eh, x509 = self.get_host_info(host) if not self._connection or host != self._connection[0]: self._extra_headers = eh self._connection = host, httplib.HTTPConnection(h) return self._connection[1] if ssl: class SafeTransport(xmlrpclib.SafeTransport): def __init__(self, timeout, use_datetime=0): self.timeout = timeout xmlrpclib.SafeTransport.__init__(self, use_datetime) def make_connection(self, host): h, eh, kwargs = self.get_host_info(host) if not kwargs: kwargs = {} kwargs['timeout'] = self.timeout if not self._connection or host != self._connection[0]: self._extra_headers = eh self._connection = host, httplib.HTTPSConnection(h, None, **kwargs) return self._connection[1] class ServerProxy(xmlrpclib.ServerProxy): def __init__(self, uri, **kwargs): self.timeout = timeout = kwargs.pop('timeout', None) # The above classes only come into play if a timeout # is specified if timeout is not None: # scheme = splittype(uri) # deprecated as of Python 3.8 scheme = urlparse(uri)[0] use_datetime = kwargs.get('use_datetime', 0) if scheme == 'https': tcls = SafeTransport else: tcls = Transport kwargs['transport'] = t = tcls(timeout, use_datetime=use_datetime) self.transport = t xmlrpclib.ServerProxy.__init__(self, uri, **kwargs) # # CSV functionality. This is provided because on 2.x, the csv module can't # handle Unicode. However, we need to deal with Unicode in e.g. RECORD files. # def _csv_open(fn, mode, **kwargs): if sys.version_info[0] < 3: mode += 'b' else: kwargs['newline'] = '' # Python 3 determines encoding from locale. Force 'utf-8' # file encoding to match other forced utf-8 encoding kwargs['encoding'] = 'utf-8' return open(fn, mode, **kwargs) class CSVBase(object): defaults = { 'delimiter': str(','), # The strs are used because we need native 'quotechar': str('"'), # str in the csv API (2.x won't take 'lineterminator': str('\n') # Unicode) } def __enter__(self): return self def __exit__(self, *exc_info): self.stream.close() class CSVReader(CSVBase): def __init__(self, **kwargs): if 'stream' in kwargs: stream = kwargs['stream'] if sys.version_info[0] >= 3: # needs to be a text stream stream = codecs.getreader('utf-8')(stream) self.stream = stream else: self.stream = _csv_open(kwargs['path'], 'r') self.reader = csv.reader(self.stream, **self.defaults) def __iter__(self): return self def next(self): result = next(self.reader) if sys.version_info[0] < 3: for i, item in enumerate(result): if not isinstance(item, text_type): result[i] = item.decode('utf-8') return result __next__ = next class CSVWriter(CSVBase): def __init__(self, fn, **kwargs): self.stream = _csv_open(fn, 'w') self.writer = csv.writer(self.stream, **self.defaults) def writerow(self, row): if sys.version_info[0] < 3: r = [] for item in row: if isinstance(item, text_type): item = item.encode('utf-8') r.append(item) row = r self.writer.writerow(row) # # Configurator functionality # class Configurator(BaseConfigurator): value_converters = dict(BaseConfigurator.value_converters) value_converters['inc'] = 'inc_convert' def __init__(self, config, base=None): super(Configurator, self).__init__(config) self.base = base or os.getcwd() def configure_custom(self, config): def convert(o): if isinstance(o, (list, tuple)): result = type(o)([convert(i) for i in o]) elif isinstance(o, dict): if '()' in o: result = self.configure_custom(o) else: result = {} for k in o: result[k] = convert(o[k]) else: result = self.convert(o) return result c = config.pop('()') if not callable(c): c = self.resolve(c) props = config.pop('.', None) # Check for valid identifiers args = config.pop('[]', ()) if args: args = tuple([convert(o) for o in args]) items = [(k, convert(config[k])) for k in config if valid_ident(k)] kwargs = dict(items) result = c(*args, **kwargs) if props: for n, v in props.items(): setattr(result, n, convert(v)) return result def __getitem__(self, key): result = self.config[key] if isinstance(result, dict) and '()' in result: self.config[key] = result = self.configure_custom(result) return result def inc_convert(self, value): """Default converter for the inc:// protocol.""" if not os.path.isabs(value): value = os.path.join(self.base, value) with codecs.open(value, 'r', encoding='utf-8') as f: result = json.load(f) return result class SubprocessMixin(object): """ Mixin for running subprocesses and capturing their output """ def __init__(self, verbose=False, progress=None): self.verbose = verbose self.progress = progress def reader(self, stream, context): """ Read lines from a subprocess' output stream and either pass to a progress callable (if specified) or write progress information to sys.stderr. """ progress = self.progress verbose = self.verbose while True: s = stream.readline() if not s: break if progress is not None: progress(s, context) else: if not verbose: sys.stderr.write('.') else: sys.stderr.write(s.decode('utf-8')) sys.stderr.flush() stream.close() def run_command(self, cmd, **kwargs): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout')) t1.start() t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr')) t2.start() p.wait() t1.join() t2.join() if self.progress is not None: self.progress('done.', 'main') elif self.verbose: sys.stderr.write('done.\n') return p def normalize_name(name): """Normalize a python package name a la PEP 503""" # https://peps.python.org/pep-0503/#normalized-names # Emulates re.sub(r"[-_.]+", "-", name).lower() # Much faster than re, and even faster than str.translate value = name.lower().replace("_", "-").replace(".", "-") while "--" in value: value = value.replace("--", "-") return value # def _get_pypirc_command(): # """ # Get the distutils command for interacting with PyPI configurations. # :return: the command. # """ # from distutils.core import Distribution # from distutils.config import PyPIRCCommand # d = Distribution() # return PyPIRCCommand(d) class PyPIRCFile(object): DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/' DEFAULT_REALM = 'pypi' def __init__(self, fn=None, url=None): if fn is None: fn = os.path.join(os.path.expanduser('~'), '.pypirc') self.filename = fn self.url = url def read(self): result = {} if os.path.exists(self.filename): repository = self.url or self.DEFAULT_REPOSITORY config = configparser.RawConfigParser() config.read(self.filename) sections = config.sections() if 'distutils' in sections: # let's get the list of servers index_servers = config.get('distutils', 'index-servers') _servers = [server.strip() for server in index_servers.split('\n') if server.strip() != ''] if _servers == []: # nothing set, let's try to get the default pypi if 'pypi' in sections: _servers = ['pypi'] else: for server in _servers: result = {'server': server} result['username'] = config.get(server, 'username') # optional params for key, default in (('repository', self.DEFAULT_REPOSITORY), ('realm', self.DEFAULT_REALM), ('password', None)): if config.has_option(server, key): result[key] = config.get(server, key) else: result[key] = default # work around people having "repository" for the "pypi" # section of their config set to the HTTP (rather than # HTTPS) URL if (server == 'pypi' and repository in (self.DEFAULT_REPOSITORY, 'pypi')): result['repository'] = self.DEFAULT_REPOSITORY elif (result['server'] != repository and result['repository'] != repository): result = {} elif 'server-login' in sections: # old format server = 'server-login' if config.has_option(server, 'repository'): repository = config.get(server, 'repository') else: repository = self.DEFAULT_REPOSITORY result = { 'username': config.get(server, 'username'), 'password': config.get(server, 'password'), 'repository': repository, 'server': server, 'realm': self.DEFAULT_REALM } return result def update(self, username, password): # import pdb; pdb.set_trace() config = configparser.RawConfigParser() fn = self.filename config.read(fn) if not config.has_section('pypi'): config.add_section('pypi') config.set('pypi', 'username', username) config.set('pypi', 'password', password) with open(fn, 'w') as f: config.write(f) def _load_pypirc(index): """ Read the PyPI access configuration as supported by distutils. """ return PyPIRCFile(url=index.url).read() def _store_pypirc(index): PyPIRCFile().update(index.username, index.password) # # get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor # tweaks # def get_host_platform(): """Return a string that identifies the current platform. This is used mainly to distinguish platform-specific build directories and platform-specific built distributions. Typically includes the OS name and version and the architecture (as supplied by 'os.uname()'), although the exact information included depends on the OS; eg. on Linux, the kernel version isn't particularly important. Examples of returned values: linux-i586 linux-alpha (?) solaris-2.6-sun4u Windows will return one of: win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) win32 (all others - specifically, sys.platform is returned) For other non-POSIX platforms, currently just returns 'sys.platform'. """ if os.name == 'nt': if 'amd64' in sys.version.lower(): return 'win-amd64' if '(arm)' in sys.version.lower(): return 'win-arm32' if '(arm64)' in sys.version.lower(): return 'win-arm64' return sys.platform # Set for cross builds explicitly if "_PYTHON_HOST_PLATFORM" in os.environ: return os.environ["_PYTHON_HOST_PLATFORM"] if os.name != 'posix' or not hasattr(os, 'uname'): # XXX what about the architecture? NT is Intel or Alpha, # Mac OS is M68k or PPC, etc. return sys.platform # Try to distinguish various flavours of Unix (osname, host, release, version, machine) = os.uname() # Convert the OS name to lowercase, remove '/' characters, and translate # spaces (for "Power Macintosh") osname = osname.lower().replace('/', '') machine = machine.replace(' ', '_').replace('/', '-') if osname[:5] == 'linux': # At least on Linux/Intel, 'machine' is the processor -- # i386, etc. # XXX what about Alpha, SPARC, etc? return "%s-%s" % (osname, machine) elif osname[:5] == 'sunos': if release[0] >= '5': # SunOS 5 == Solaris 2 osname = 'solaris' release = '%d.%s' % (int(release[0]) - 3, release[2:]) # We can't use 'platform.architecture()[0]' because a # bootstrap problem. We use a dict to get an error # if some suspicious happens. bitness = {2147483647: '32bit', 9223372036854775807: '64bit'} machine += '.%s' % bitness[sys.maxsize] # fall through to standard osname-release-machine representation elif osname[:3] == 'aix': from _aix_support import aix_platform return aix_platform() elif osname[:6] == 'cygwin': osname = 'cygwin' rel_re = re.compile(r'[\d.]+', re.ASCII) m = rel_re.match(release) if m: release = m.group() elif osname[:6] == 'darwin': import _osx_support try: from distutils import sysconfig except ImportError: import sysconfig osname, release, machine = _osx_support.get_platform_osx(sysconfig.get_config_vars(), osname, release, machine) return '%s-%s-%s' % (osname, release, machine) _TARGET_TO_PLAT = { 'x86': 'win32', 'x64': 'win-amd64', 'arm': 'win-arm32', } def get_platform(): if os.name != 'nt': return get_host_platform() cross_compilation_target = os.environ.get('VSCMD_ARG_TGT_ARCH') if cross_compilation_target not in _TARGET_TO_PLAT: return get_host_platform() return _TARGET_TO_PLAT[cross_compilation_target] def is_in_directory(path, target): """ Check if a path is inside a target directory. This doesn't check case (might be an issue on Windows) """ path = os.path.abspath(path) target = os.path.abspath(target) return path == target or path.startswith(target + os.sep) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710843043.0 distlib-0.4.3/distlib/version.py0000644000175000017500000005625714576262243015771 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2023 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # """ Implementation of a flexible versioning scheme providing support for PEP-440, setuptools-compatible and semantic versioning. """ import logging import re from .compat import string_types from .util import parse_requirement __all__ = ['NormalizedVersion', 'NormalizedMatcher', 'LegacyVersion', 'LegacyMatcher', 'SemanticVersion', 'SemanticMatcher', 'UnsupportedVersionError', 'get_scheme'] logger = logging.getLogger(__name__) class UnsupportedVersionError(ValueError): """This is an unsupported version.""" pass class Version(object): def __init__(self, s): self._string = s = s.strip() self._parts = parts = self.parse(s) assert isinstance(parts, tuple) assert len(parts) > 0 def parse(self, s): raise NotImplementedError('please implement in a subclass') def _check_compatible(self, other): if type(self) != type(other): raise TypeError('cannot compare %r and %r' % (self, other)) def __eq__(self, other): self._check_compatible(other) return self._parts == other._parts def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): self._check_compatible(other) return self._parts < other._parts def __gt__(self, other): return not (self.__lt__(other) or self.__eq__(other)) def __le__(self, other): return self.__lt__(other) or self.__eq__(other) def __ge__(self, other): return self.__gt__(other) or self.__eq__(other) # See http://docs.python.org/reference/datamodel#object.__hash__ def __hash__(self): return hash(self._parts) def __repr__(self): return "%s('%s')" % (self.__class__.__name__, self._string) def __str__(self): return self._string @property def is_prerelease(self): raise NotImplementedError('Please implement in subclasses.') class Matcher(object): version_class = None # value is either a callable or the name of a method _operators = { '<': lambda v, c, p: v < c, '>': lambda v, c, p: v > c, '<=': lambda v, c, p: v == c or v < c, '>=': lambda v, c, p: v == c or v > c, '==': lambda v, c, p: v == c, '===': lambda v, c, p: v == c, # by default, compatible => >=. '~=': lambda v, c, p: v == c or v > c, '!=': lambda v, c, p: v != c, } # this is a method only to support alternative implementations # via overriding def parse_requirement(self, s): return parse_requirement(s) def __init__(self, s): if self.version_class is None: raise ValueError('Please specify a version class') self._string = s = s.strip() r = self.parse_requirement(s) if not r: raise ValueError('Not valid: %r' % s) self.name = r.name self.key = self.name.lower() # for case-insensitive comparisons clist = [] if r.constraints: # import pdb; pdb.set_trace() for op, s in r.constraints: if s.endswith('.*'): if op not in ('==', '!='): raise ValueError('\'.*\' not allowed for ' '%r constraints' % op) # Could be a partial version (e.g. for '2.*') which # won't parse as a version, so keep it as a string vn, prefix = s[:-2], True # Just to check that vn is a valid version self.version_class(vn) else: # Should parse as a version, so we can create an # instance for the comparison vn, prefix = self.version_class(s), False clist.append((op, vn, prefix)) self._parts = tuple(clist) def match(self, version): """ Check if the provided version matches the constraints. :param version: The version to match against this instance. :type version: String or :class:`Version` instance. """ if isinstance(version, string_types): version = self.version_class(version) for operator, constraint, prefix in self._parts: f = self._operators.get(operator) if isinstance(f, string_types): f = getattr(self, f) if not f: msg = ('%r not implemented ' 'for %s' % (operator, self.__class__.__name__)) raise NotImplementedError(msg) if not f(version, constraint, prefix): return False return True @property def exact_version(self): result = None if len(self._parts) == 1 and self._parts[0][0] in ('==', '==='): result = self._parts[0][1] return result def _check_compatible(self, other): if type(self) != type(other) or self.name != other.name: raise TypeError('cannot compare %s and %s' % (self, other)) def __eq__(self, other): self._check_compatible(other) return self.key == other.key and self._parts == other._parts def __ne__(self, other): return not self.__eq__(other) # See http://docs.python.org/reference/datamodel#object.__hash__ def __hash__(self): return hash(self.key) + hash(self._parts) def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self._string) def __str__(self): return self._string PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|alpha|b|beta|c|rc|pre|preview)(\d+)?)?' r'(\.(post|r|rev)(\d+)?)?([._-]?(dev)(\d+)?)?' r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$', re.I) def _pep_440_key(s): s = s.strip() m = PEP440_VERSION_RE.match(s) if not m: raise UnsupportedVersionError('Not a valid version: %s' % s) groups = m.groups() nums = tuple(int(v) for v in groups[1].split('.')) while len(nums) > 1 and nums[-1] == 0: nums = nums[:-1] if not groups[0]: epoch = 0 else: epoch = int(groups[0][:-1]) pre = groups[4:6] post = groups[7:9] dev = groups[10:12] local = groups[13] if pre == (None, None): pre = () else: if pre[1] is None: pre = pre[0], 0 else: pre = pre[0], int(pre[1]) if post == (None, None): post = () else: if post[1] is None: post = post[0], 0 else: post = post[0], int(post[1]) if dev == (None, None): dev = () else: if dev[1] is None: dev = dev[0], 0 else: dev = dev[0], int(dev[1]) if local is None: local = () else: parts = [] for part in local.split('.'): # to ensure that numeric compares as > lexicographic, avoid # comparing them directly, but encode a tuple which ensures # correct sorting if part.isdigit(): part = (1, int(part)) else: part = (0, part) parts.append(part) local = tuple(parts) if not pre: # either before pre-release, or final release and after if not post and dev: # before pre-release pre = ('a', -1) # to sort before a0 else: pre = ('z',) # to sort after all pre-releases # now look at the state of post and dev. if not post: post = ('_',) # sort before 'a' if not dev: dev = ('final',) return epoch, nums, pre, post, dev, local _normalized_key = _pep_440_key class NormalizedVersion(Version): """A rational version. Good: 1.2 # equivalent to "1.2.0" 1.2.0 1.2a1 1.2.3a2 1.2.3b1 1.2.3c1 1.2.3.4 TODO: fill this out Bad: 1 # minimum two numbers 1.2a # release level must have a release serial 1.2.3b """ def parse(self, s): result = _normalized_key(s) # _normalized_key loses trailing zeroes in the release # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0 # However, PEP 440 prefix matching needs it: for example, # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0). m = PEP440_VERSION_RE.match(s) # must succeed groups = m.groups() self._release_clause = tuple(int(v) for v in groups[1].split('.')) return result PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev']) @property def is_prerelease(self): return any(t[0] in self.PREREL_TAGS for t in self._parts if t) def _match_prefix(x, y): x = str(x) y = str(y) if x == y: return True if not x.startswith(y): return False n = len(y) return x[n] == '.' class NormalizedMatcher(Matcher): version_class = NormalizedVersion # value is either a callable or the name of a method _operators = { '~=': '_match_compatible', '<': '_match_lt', '>': '_match_gt', '<=': '_match_le', '>=': '_match_ge', '==': '_match_eq', '===': '_match_arbitrary', '!=': '_match_ne', } def _adjust_local(self, version, constraint, prefix): if prefix: strip_local = '+' not in constraint and version._parts[-1] else: # both constraint and version are # NormalizedVersion instances. # If constraint does not have a local component, # ensure the version doesn't, either. strip_local = not constraint._parts[-1] and version._parts[-1] if strip_local: s = version._string.split('+', 1)[0] version = self.version_class(s) return version, constraint def _match_lt(self, version, constraint, prefix): version, constraint = self._adjust_local(version, constraint, prefix) if version >= constraint: return False release_clause = constraint._release_clause pfx = '.'.join([str(i) for i in release_clause]) return not _match_prefix(version, pfx) def _match_gt(self, version, constraint, prefix): version, constraint = self._adjust_local(version, constraint, prefix) if version <= constraint: return False release_clause = constraint._release_clause pfx = '.'.join([str(i) for i in release_clause]) return not _match_prefix(version, pfx) def _match_le(self, version, constraint, prefix): version, constraint = self._adjust_local(version, constraint, prefix) return version <= constraint def _match_ge(self, version, constraint, prefix): version, constraint = self._adjust_local(version, constraint, prefix) return version >= constraint def _match_eq(self, version, constraint, prefix): version, constraint = self._adjust_local(version, constraint, prefix) if not prefix: result = (version == constraint) else: result = _match_prefix(version, constraint) return result def _match_arbitrary(self, version, constraint, prefix): return str(version) == str(constraint) def _match_ne(self, version, constraint, prefix): version, constraint = self._adjust_local(version, constraint, prefix) if not prefix: result = (version != constraint) else: result = not _match_prefix(version, constraint) return result def _match_compatible(self, version, constraint, prefix): version, constraint = self._adjust_local(version, constraint, prefix) if version == constraint: return True if version < constraint: return False # if not prefix: # return True release_clause = constraint._release_clause if len(release_clause) > 1: release_clause = release_clause[:-1] pfx = '.'.join([str(i) for i in release_clause]) return _match_prefix(version, pfx) _REPLACEMENTS = ( (re.compile('[.+-]$'), ''), # remove trailing puncts (re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start (re.compile('^[.-]'), ''), # remove leading puncts (re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion) (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion) (re.compile('[.]{2,}'), '.'), # multiple runs of '.' (re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha (re.compile(r'\b(pre-alpha|prealpha)\b'), 'pre.alpha'), # standardise (re.compile(r'\(beta\)$'), 'beta'), # remove parentheses ) _SUFFIX_REPLACEMENTS = ( (re.compile('^[:~._+-]+'), ''), # remove leading puncts (re.compile('[,*")([\\]]'), ''), # remove unwanted chars (re.compile('[~:+_ -]'), '.'), # replace illegal chars (re.compile('[.]{2,}'), '.'), # multiple runs of '.' (re.compile(r'\.$'), ''), # trailing '.' ) _NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)') def _suggest_semantic_version(s): """ Try to suggest a semantic form for a version for which _suggest_normalized_version couldn't come up with anything. """ result = s.strip().lower() for pat, repl in _REPLACEMENTS: result = pat.sub(repl, result) if not result: result = '0.0.0' # Now look for numeric prefix, and separate it out from # the rest. # import pdb; pdb.set_trace() m = _NUMERIC_PREFIX.match(result) if not m: prefix = '0.0.0' suffix = result else: prefix = m.groups()[0].split('.') prefix = [int(i) for i in prefix] while len(prefix) < 3: prefix.append(0) if len(prefix) == 3: suffix = result[m.end():] else: suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():] prefix = prefix[:3] prefix = '.'.join([str(i) for i in prefix]) suffix = suffix.strip() if suffix: # import pdb; pdb.set_trace() # massage the suffix. for pat, repl in _SUFFIX_REPLACEMENTS: suffix = pat.sub(repl, suffix) if not suffix: result = prefix else: sep = '-' if 'dev' in suffix else '+' result = prefix + sep + suffix if not is_semver(result): result = None return result def _suggest_normalized_version(s): """Suggest a normalized version close to the given version string. If you have a version string that isn't rational (i.e. NormalizedVersion doesn't like it) then you might be able to get an equivalent (or close) rational version from this function. This does a number of simple normalizations to the given string, based on observation of versions currently in use on PyPI. Given a dump of those version during PyCon 2009, 4287 of them: - 2312 (53.93%) match NormalizedVersion without change with the automatic suggestion - 3474 (81.04%) match when using this suggestion method @param s {str} An irrational version string. @returns A rational version string, or None, if couldn't determine one. """ try: _normalized_key(s) return s # already rational except UnsupportedVersionError: pass rs = s.lower() # part of this could use maketrans for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), ('beta', 'b'), ('rc', 'c'), ('-final', ''), ('-pre', 'c'), ('-release', ''), ('.release', ''), ('-stable', ''), ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), ('final', '')): rs = rs.replace(orig, repl) # if something ends with dev or pre, we add a 0 rs = re.sub(r"pre$", r"pre0", rs) rs = re.sub(r"dev$", r"dev0", rs) # if we have something like "b-2" or "a.2" at the end of the # version, that is probably beta, alpha, etc # let's remove the dash or dot rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs) # 1.0-dev-r371 -> 1.0.dev371 # 0.1-dev-r79 -> 0.1.dev79 rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) # Clean: v0.3, v1.0 if rs.startswith('v'): rs = rs[1:] # Clean leading '0's on numbers. # TODO: unintended side-effect on, e.g., "2003.05.09" # PyPI stats: 77 (~2%) better rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers # zero. # PyPI stats: 245 (7.56%) better rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) # the 'dev-rNNN' tag is a dev tag rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) # clean the - when used as a pre delimiter rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) # a terminal "dev" or "devel" can be changed into ".dev0" rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) # a terminal "dev" can be changed into ".dev0" rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) # a terminal "final" or "stable" can be removed rs = re.sub(r"(final|stable)$", "", rs) # The 'r' and the '-' tags are post release tags # 0.4a1.r10 -> 0.4a1.post10 # 0.9.33-17222 -> 0.9.33.post17222 # 0.9.33-r17222 -> 0.9.33.post17222 rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) # Clean 'r' instead of 'dev' usage: # 0.9.33+r17222 -> 0.9.33.dev17222 # 1.0dev123 -> 1.0.dev123 # 1.0.git123 -> 1.0.dev123 # 1.0.bzr123 -> 1.0.dev123 # 0.1a0dev.123 -> 0.1a0.dev123 # PyPI stats: ~150 (~4%) better rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: # 0.2.pre1 -> 0.2c1 # 0.2-c1 -> 0.2c1 # 1.0preview123 -> 1.0c123 # PyPI stats: ~21 (0.62%) better rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) # Tcl/Tk uses "px" for their post release markers rs = re.sub(r"p(\d+)$", r".post\1", rs) try: _normalized_key(rs) except UnsupportedVersionError: rs = None return rs # # Legacy version processing (distribute-compatible) # _VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I) _VERSION_REPLACE = { 'pre': 'c', 'preview': 'c', '-': 'final-', 'rc': 'c', 'dev': '@', '': None, '.': None, } def _legacy_key(s): def get_parts(s): result = [] for p in _VERSION_PART.split(s.lower()): p = _VERSION_REPLACE.get(p, p) if p: if '0' <= p[:1] <= '9': p = p.zfill(8) else: p = '*' + p result.append(p) result.append('*final') return result result = [] for p in get_parts(s): if p.startswith('*'): if p < '*final': while result and result[-1] == '*final-': result.pop() while result and result[-1] == '00000000': result.pop() result.append(p) return tuple(result) class LegacyVersion(Version): def parse(self, s): return _legacy_key(s) @property def is_prerelease(self): result = False for x in self._parts: if (isinstance(x, string_types) and x.startswith('*') and x < '*final'): result = True break return result class LegacyMatcher(Matcher): version_class = LegacyVersion _operators = dict(Matcher._operators) _operators['~='] = '_match_compatible' numeric_re = re.compile(r'^(\d+(\.\d+)*)') def _match_compatible(self, version, constraint, prefix): if version < constraint: return False m = self.numeric_re.match(str(constraint)) if not m: logger.warning('Cannot compute compatible match for version %s ' ' and constraint %s', version, constraint) return True s = m.groups()[0] if '.' in s: s = s.rsplit('.', 1)[0] return _match_prefix(version, s) # # Semantic versioning # _SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)' r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?' r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I) def is_semver(s): return _SEMVER_RE.match(s) def _semantic_key(s): def make_tuple(s, absent): if s is None: result = (absent,) else: parts = s[1:].split('.') # We can't compare ints and strings on Python 3, so fudge it # by zero-filling numeric values so simulate a numeric comparison result = tuple([p.zfill(8) if p.isdigit() else p for p in parts]) return result m = is_semver(s) if not m: raise UnsupportedVersionError(s) groups = m.groups() major, minor, patch = [int(i) for i in groups[:3]] # choose the '|' and '*' so that versions sort correctly pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*') return (major, minor, patch), pre, build class SemanticVersion(Version): def parse(self, s): return _semantic_key(s) @property def is_prerelease(self): return self._parts[1][0] != '|' class SemanticMatcher(Matcher): version_class = SemanticVersion class VersionScheme(object): def __init__(self, key, matcher, suggester=None): self.key = key self.matcher = matcher self.suggester = suggester def is_valid_version(self, s): try: self.matcher.version_class(s) result = True except UnsupportedVersionError: result = False return result def is_valid_matcher(self, s): try: self.matcher(s) result = True except UnsupportedVersionError: result = False return result def is_valid_constraint_list(self, s): """ Used for processing some metadata fields """ # See issue #140. Be tolerant of a single trailing comma. if s.endswith(','): s = s[:-1] return self.is_valid_matcher('dummy_name (%s)' % s) def suggest(self, s): if self.suggester is None: result = None else: result = self.suggester(s) return result _SCHEMES = { 'normalized': VersionScheme(_normalized_key, NormalizedMatcher, _suggest_normalized_version), 'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s), 'semantic': VersionScheme(_semantic_key, SemanticMatcher, _suggest_semantic_version), } _SCHEMES['default'] = _SCHEMES['normalized'] def get_scheme(name): if name not in _SCHEMES: raise ValueError('unknown scheme name: %r' % name) return _SCHEMES[name] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659773740.0 distlib-0.4.3/distlib/w32.exe0000600000175000017500000026300014273421454015015 0ustar00vinayvinayMZ@ !L!This program cannot be run in DOS mode. $>imimim'vmimEmimpmimDmim}mimimim@mimtmimsmimRichimPEL b  I>@i @P@Sl @.text: `.rdataX-.@@.data\7@.rsrcS@T@@.relocV@BUVh\@T@%Aut6hh@VL@%Ath jhh@uj V@3^]UVh\@T@%Aut6h|@VL@%Ath jhh@uj V@3^]UA3ʼnE}u4EPu hhP&Pej@M3 UA3ʼnE}u4EPu hhPQVPj@M3@ UMW3FPAPQ! tA ;uHE+D0ϋ_]U,VWh@E3hAP}} 3;h@Q0jWu;&uoE$UE;}}Wuu&uhjSoS9 ;t+EjY}E~VEEYE;}}Wuu%uVjuu3;h@uPfE+EjEYu}E,MMj)MuuA%uWjS3; @VPf <@$f;u]TEy3jPu$uWjSU3;VP$Df<@f;tH;sEui'EY_^U Vj.u3u" YYtVhAhVuV@ttFqWh@@EPEPn%uu9;uIEPP@Wu VhAhPuV@uEPWV ;u3FuY_^U}t }v)}w#hAt%AjphpAj P @3@]UVW3VVVVd@VVVEPt@j5Ah@VVVjVVVVVhX@hp@Vp@VVVWEPl@Wx@_^UuX@tuEujju@]3@]UQf}rotkUW8k;rWSVh@t1<^ YE};s++ttt P&Y;]ruY^[_UVWh@upYYx P)YWY_^]UVWP Pu@P Et; ;t;;t;;5D@SjjjE։EP$ P{$5P@YY;t9]u3Sj֋E;t9Eu jjփeE;t9Eu jjփeE;u3;Eue;Eue=d@tt Sjj39]t}t uSj9]t}t uSj[_^Ud A3ʼnEESVW3WW@@3;h@hAQYYPjppAVj 5hA0@tpu3@3h@P A0YYjpVj 5hA @h@PYjDWP/ZP8@5D@jjjS$@PVh@Ph@P AVPWWWjWWW@;u=H@WQRQPWh$@Ph@@W5A5hA @YPh<@H=w P`@jh-@\@h@PV4 5Ad@Wj5A@P5A(@h@PYY@UQSVW=X@@Su׋t>FEft"tjPYYu Su׋uӅt ]]j h@S 3ɅSh@Q {ftjPYYt3@3ShD@P ftjPjYYuf?tjPQYYuf9ujP8YYt3fWSh|@jXAPdf;"u&3f>"Sh@PF3f jh@S tf{"t3f>"Sh@P ft&jPYYt3fjPYYt fuE 0_^[U0A3ʼnESVW@f8"tj j"YQP8YYu DžT@$pjPYYt fu牵WAVj,@f=A"t DžA3ɍf BAP3hh@PY3Y;s t tA;r3h@PYYWP+VSjh4@33;h@P3fuYYf9tjPYYtf9u3f>#h@P?YYj_jPYYt f;u3f>!h@P YYjPYYt f;u荅PVS3h<@P39hh@Pj[SWh@G$uvhAhP P\@f"u+j[G j"P 3Wh@PM3f_PPPT@ǍPffu+Hffu+Hffu+ȋPffu+jt1V3h@PWh@VVS 0Sq; Auj h@%3}3];;u$3{3u ;;t3f9>;t#E;u x$ɉ}f9;u c$jEPhA& PuVS EE E$uYËUVuu$j^0P$huu  t3#^]ËUEfU f;tfuf9t3]ËUh@@th@PL@tu]ËUuYu@je(Yj'YËV)+V*V V*V*V(V(^ËUVu3utу;u r^]ËU=T7AthT7Ao1Yt uT7AY0h@h@YYuTVWh7Q@$0@@Y;stЃ;r=X7A_^thX7A1Yt jjjX7A3]j h@"jY'Ye3@9A AEA} 5L7A5@֋؉]Ѕth5H7A֋}ԉ]܉}؃};rK)9t;r>7֋)5L7A֋5H7A9]u9Et]܉]ЉE؋}ԋ]E@}@sEtЃEE@}@sEtЃEE }u)Ajq%Yu}tj[%Y!ËUjju ]ËUjju ]jjj jjjz ËU1u60Yh̋U}t-uj5A@uV H@PI Y^]ËUQeVEPu u1 u9EtX t O M^ËUEVF uc)FHlHhN; At p AHpu;F;x AtF p AHpu3FF@puHpF  @F^]U3SW9E]u } tVuM0ExuA+;Ar Zw Ar Zw MtDft?f;t8EPP.;EPP;Mt ftf;t+}^tMap_[ËU= AVuy39Euu cM t+Ar Zw Ar Zw Mt ftf;t+juu u^]ËUS]woVW=Au7/j-hYYt3@Pj5A@u&j ^9$At S%Yu00_^Sl%Y 3[]øAá@7AVj^u;}ƣ@7AjP<YY,'AujV5@7A<YY,'AujX^3ҹA,'A  A|j^3ҹAW &At;tu1 BpA|_3^7?=At=5,'AYËUVuA;r"`Aw+Q!N Y V@^]ËUE}P~!E H Y]ËE P@]ËUEA;r=`Aw` +P\ Y]à P@]ËUME }` Q- Y]à P@]̋D$ StRT$3ۊ\$ t 2trt2urWߋ_t 2t@u[Ãr 3˿~3σtJ2t#2t2t 2t_B[ÍB_[ÍB_[ÍB_[ËUEt8uP^Y]ËUQQA3ʼnESV3W;uj^0C,uV;?YY;Er3fՋE @;u)f9tAr Zw ff9u3SSjVWP>ȃM;u|*q9Ms3f[j"];~Cj3Xr7D =w>;tPY;t M؅u  놋E QSjVWp>tSuV` j*YSYƍe_^[M3ËUuMRMEPu n}YYtMapËUju u ]ËUEPjuuu uD ]ËUMS] VW}M]t}tu[3_^[Ëut 39Ev!t SjQHG t39Ew}F }tFEEF tDFt=;r;}W6uuVF)~>}+߃)}};]r\}t3;v uu+ ;w;Ew[PuVYPD t{tdE+)E$V>YtR}t"MEFKME&E} tu juF "N +3uN j h@3u9ut/9ut*9uu-} tu VuE I3u^Yuuuuu uEEEuYËUuuu juZ]ËUQf9Eu3øf9EsE AAEPjEPj@u!EEM #ËUEur]Ë@]ËUEA]ËU(A3ʼnES]WtS EYjLjPzD0 ffffffEMDž0IM M@j@P@uu tSDYM_3[BËUV5A@u@A^]ËVjVj V@P@^ËU5A@t]uuuu u3PPPPPËUQEu3Vu t Muua3Wft.>u ftf;t fuf>t fuf9Mt tat0rt#wt53a 3ۃM M3AWf;y@St  tRHtCt- t!9EE @@EE ljE}utE nTtZtEHt0 tuE G}u;eE1}u% UEut3 ؃f}j _f9>tjVh @BQ f9>tf>=uuf9>tjh@VS u Ajh @V4 u "jh4@V uf> t3f9>thuE SuPhPt3 EAMH M x8xxH_^[jh(@33}j`Y]3u;5@7A,'A9t[@ uHuAFwFP\Y,'A4VsYY,'A@ t PVYYF둋}cj8Y ,'A;tNh,'A P@,'Au4Y,'A P@,'A<}_ ;tg ___OE Ë}jpYËUE3;͐AtA-rHwj X]Ë͔A]DjY;#] uAÃuAà ËUVMQY0^]h0D@d5D$l$l$+SVWA1E3PeuEEEEdËMd Y__^[]Q̋US] Vs35AWEE{t N3 89N F3 8)E@fMUS[ EMt_I[LDEEtEx@GE؃u΀}t$t N3 8N V3 :E_^[]EɋM9csmu)= 'At h 'A tUjR 'AM UE 9X thAWӋE MH t N3 8 N V3 :EH*9S OhAWASVWT$D$L$URPQQhPF@d5A3ĉD$d%D$0XL$,3p t;T$4t;v.4v\ H {uhCMCMd_^[ËL$At3D$H32Uhp pp> ]D$T$UL$)qqq( ]UVWS33333[_^]ËjL33333USVWjRhF@Q>_^[]Ul$RQt$ ]VW3PA<AuA8h0@t F$|3@_^Ã$A3S@VAW>t~t WWG&Y A|ܾA_t ~uPӃ A|^[ËUE4A@]j hH@3G}39Au j h YYu4A9tmjeY;ud 3Pj XY]9u+hW@uWvY/ ] >W[YE Ej )YËUEV4A>uP#YujY6@^]jhh@C@xte3@ËeEJhH@@AËUEAAAA]ËUE @V9Ptk u ;rk M^;s9Pt3]5A@j h@Y3}}؋] KtjY+t"+t+tY+uC}؅uTAAUw\]YpQÃt2t!Ht빾AAAA AAEP@E3}9Euj89EtPCY3Et tuO`MԉG`u>OdMGdu, @M܋ @ @9M}Mk W\DEEuwdSUY]}؃}tjYSUYt tuEԉG`uEЉGd3ËUEA]ËUEA]ËUEA]ËU5A@tuYt3@]3]j@@V5$A@u5A@V5$A@^á AtP5A@Ѓ A$AtP@ $Avjh@hD@@uF\X@f3G~~pƆCƆKCFhPAj \Yevh@E>j ;Y}E Flu AFlvlYE3Guj $Yj YËVWH@5 AЋuNhjYYt:V5 A5A@ЅtjVYY@N VY3W@_^ËVujY^jh@uF$tPwYF,tPiYF4tP[YFPuYYt/P4@L7Au=@׉VףH7AE3_^[ËVjj (YYV@L7AH7AujX^Ã&3^j h@QeuYEE E"0ËUuYH]ËV@@W;stЃ;r_^ËV@@W;stЃ;r_^ËVW3(A@(A(r_^ËUMMZf9t3]ËA<8PEu3ҹ f9H‹]̋UEH<ASVq3WDt} H ;r X;r B(;r3_^[]̋Ujh@h0D@dPSVWA1E3PEdeEh@*tTE-@Ph@Pt:@$ЃEMd Y_^[]ËE3ҁ9‹ËeE3Md Y_^[]ËU3M; @t @r3]Ë@]ËUA3ʼnESVuWV3Y;ljGYjGYu =A6hL@hAW h AVSfA,@uh@SVf t 3PPPPPV,.@YCYPPV@M_^3[jQFYtjDFYu=Auh%hYYjhj@3ɅAËUMtj3X;E sr 3]M VuF3wVj5A@u2=$AtVYuҋEt 3 Mt ^]-t"t t Ht3øøøøËVWh3FWP03ȋ~~~  ~PA F+ο@Ou@Nu_^ËUA3ʼnESWPv@3@;rƅ t0;w+@P j Rm Cujv vPWPjjG3SvWPWPWv SOFDSvWPWPhv S*F$3EtLtL @;rRDž3)ЍZ w LQ w L QA;rƋM_3[j h8@'ep AGptltwhuj Y?j Yewhu;5x At6tV@uPAtVYx AGh5x AuV@E뎋uj tYËUS3SMAuA@8]tEMapE| AD;FG;v}>uЋuE}urlj{CijC C AZf1f0JuL@;v~0C@IuCC Ss3ȋ {95ATM_^3[jhX@MX}_huqE;CWh Y؅Fwh#SuYYEuvh@uFh=PAtPY^hS=@Fpp Aj +YeCACAC A3E}fLCf EA@3E=} LpA@3E=}xA@5x A@ux A=PAtPYx ASE0j Y%u PAtSY\eEÃ=P7AujVYP7A3ËUSV5@W}W֋tP֋tP֋tP֋tP֍_PE{t At tPփ{t CtPփMu֋P_^[]ËUW}SV5@W֋tP֋tP֋tP֋tP֍_PE{t At tPփ{t CtPփMu֋P^[_]ËUSVu3W;to=8Ath;t^9uZ;t9uPDYY;t9uPDYYYY;tD9u@-P+P+P|q=x At9uP@HYY~PEt At;t 9uP#Y9_tG;t 9uP YMuVY_^[]ËUW} t;Et4V0;t(W8jYtV>Yu AtVsY^3_]j hx@p AFpt"~ltpluj VYj (Ye5 AlVYYYEEj !YuËUf9ESVu MuN3;uEHfwf Kjf9EsuYEYt, URjURPQEtE8]tMap^[ËULVEP8@j@j ^VlYY3;u &A5&A;s6Hf@ Hf@ @! H3H/5 &A@P;rSWf9ME;EþE;|9&A}k$&Aj@j YYtQ&A ;s1H```3f@ f@ @/@΍P;r҃9&A|&A3~rEt\tWM tMu PX@t=4 &AEEFhF P@FEGE;|3ۋ5 &At tNqFujX CPD@tBt>WX@t3%>uN@ uNhF P@t,F N@Ch5&A@3_[^ÃUVW3uYu'9(AvV@;(Avuʋ_^]ËUVW3ju u! u'9(AvV@;(AvuË_^]ËUVW3u uAYYu,9E t'9(AvV@;(Avu_^]ËUVW3uu u+B u,9Et'9(AvV@;(Avu_^]jh@3ۉ]jY]j_};=@7A}T,'A9tE@ tPYtE|(,'A P@,'A4詾Y,'AGE EjlYËUSVuF Ȁ3ۀu@t9FW>+~,WPVYPH ;uF yF N _Ff^[]ËUVuu V5Y/V|YtF @tVPHYY3^]jh@3}}jxY}3u;5@7A,'A98t^@ tVPVYY3BU,'AH t/9UuPJYtE9}utP/Yu E܉}F3u,'A4VYYE}EtELjYjYËU SW3j3Y}]9]u}Vu ;t;uakE;w}uEuEBuuPuUE;t5;|"MxEEPSYYtE39]\>^_[ËU}u]S]VuWuu9u u3t} uuuu;v*8CSVhJ@uG[8"uYQ8PWVhJ@D>u}u'8"u%yu"Q_^[]ËUujuuu u]ËUE~ PuYYuuPuu u@]ËU39E vMf9t @;E r]QL$+ȃ YZSQL$+ȃ YDSUQVu VE F Yu+ N /@t "S3ۨt^NF F F ^] u,& ;t @;u u TYuV:TYF WF>HN+IN;~WPu C EM F yM tt &A A@ tjSSQR#ƒt%FM3GWEPu C E9}t N E%_[^ËUVuuF @t F F u V#SYFvvVYPg FF uQVYt0VYt$WVV< &AYY_ A@$Yu?*uj?~Y} Ѓ?uE^̋UxA3ʼnES]Vu3Wu} 臮u+t `p 3;tf; jY9x BfXw@3k 0@j^;O $@3 ƒ tHt4+t$+t   f*u+f T k ʍDЉ9 - f*u%  k ʍDЉƒItQht@ltwf?lu 6uf4um3uf2uOdFi=o4u+x"XRDžYƒd1StAt+tZ+t+ Dž@Dž0 0u u [u AQPGYYtFF9|X++3F tBPƅPPEyf6t:Ht3t+DžAPYpegitnnt$otbV[Dx t ffDž@Dž ދCSufguWDžK;~ȁ~7]VYt Dž5@CPPWP5@AЋtuPW5LAYYfguuPW5HAYY?-uGWDž$s{+Dž'Dž|j0XfQfWW t@tCC@Ct3҉@t|s؃ځu3} Dž9~ u! t-RPWSC0؋9~N뽍+Ft^tƀ80tS0=Yxt"jWYYtAVWP uI4~f>u5 A蛟% A#D7A3Y[_^5Au%A3PPPPP̋V(@3;u3^f9tf9uf9uS+ƍXWSYu V$@_[^SVWa UVuF ttvf 3YFF^]ËUE8csmu*xu$@= t=!t="t=@u3]h@@3ËUVJ2N\UW9t ;r;s9t3tPu3u `3@M S^`N`Hj$Y~\d9 |~d=u Fd~=u Fdn=u Fd^=u FdN=u Fd>=u Fd.=u Fd=u Fd=uFdvdjY~d`QY^`[_^]ËVW39=P7Au5$'AuO@< wt.t$<"u 3ɅPU@YtFF< wFu_^Ã=P7AuDV5AW3u<=tGVYtujGWYY=Atˋ5AS3Vr>=YXt"jSbYYt?VSPG uG>u5A=%A'D7A3Y[_^5A%A3PPPPP̋UQMS3VU 9Et ]EE>"u39E"FE<tBU PF?Yt} t M E FU Mt2}u t utBe>< t< uFN>}t EE3C3FA>\t>"u&u}t F8"u 339EEtIt\BuU tU}u< tK< tGt=Pt#!>Yt M E FM E  =YtFU FVtBU ME^[t ËU S3VW9P7Auh0AVS4A,@$'A5A;tE8uuUEPSS} E =?sJMsB;r6PY;t)UEPWV}E HA5A3_^[ËU SV(@3;u3wf93tf90uf90uW=0@VVV+V@PSVVE׉E;t8PYE;t*VVuPuSVVׅu u0YuS$@E S$@3_^[ËUAeeSWN@;t t УAeVEP@@u3u<@3@38@3EP4@E3E3;uO@u G 5A։5A^_[ËU4S3EV]܈]]E ]t ]E E]EP>YEuE@u9EtME+ùtCHt(Ht ¸襸j^0MEt EuE@UEjY+t7+t*+t+t@u9UEEE E]E#¹W;3t(;t$;t=tT=u-ETEKEB=t4=t$;t)÷j^0 _^[EEEEt A#MxE@tMMMt } t M tM貧;u!F )`E=D@juuEPuuu ׉E;upM#;u+Et%ejuEuPuuu ׉E;u76 &AD0 H@P谶Y脶EfuX@uD6 &AD0 H@VjYud@u1 렃uM@ uMu6FЃ &AYYMLЃ &AD$ MeHMuEtpjS6 ;u谵8tP6ZejEP6 uf}uǙRP68 ;tjj67 ;tE(@@}uE#u M EE#;tD=t)=@t"=t)=@t"=t=@uEM#;u EEE3E@}E#=@=tq;yE;nvv+[E3HHGEjjWW6e, tWWW6T,#ƒ;jEP6Q ;vtj}uXEE;iWjWW6+ JWWW6+#;E%=u6RY耳j^0u_=uWj6M ;E>WW63 Ej[+PD=P6 ;݃ &AD$2M0 &AD$M ʀ}u!Etȃ &AD M#;u~Etxud@juEjPuE%Pu D@;u4H@Pyȃ &AD 6Y6 &AEUSSSSSjh @>3}3u;;uݱj^0'Y39};t9}tE%@tʉ}uuu uEP\EEE;t3u9}t(9}t &AD 6艡YËUjuuuuu !]ËU}u3]ËU MMtft f;u +]USVWUjjhȒ@ul;]_^[]ËL$At2D$H3貋UhP(RP$R]D$T$SVWD$UPjhВ@d5A3PD$dD$(Xp t:|$,t;t$,v-4v L$ H |uhDID_뷋L$d _^[3d yВ@uQ R 9QuSQA SQAL$ KCk UQPXY]Y[rtjtY Atjh@j* j]UWVu M};v;r=&AtWV;^_u|4ur)$@Ǻr $@$@$$@@@@#ъFGFGr$@I#ъFGr$@#ъr$@I@t@l@d@\@T@L@D@DDDDDDDDDDDDDD$@@@@ȕ@E^_ÐE^_ÐFGE^_ÍIFGFGE^_Ðt1|9u$r $,@$ܖ@IǺr +$0@$,@@@d@@F#шGr$,@IF#шGFGr$,@F#шGFGFGV$,@I@@@@@@@#@DDDDDDDDD D DDDD$,@<@D@T@h@E^_ÐFGE^_ÍIFGFGE^_ÐFGFGFGE^_ËU}u?]uj5AH@]jY̋L$t$tNu$$~Ѓ3ƒtAt2t$tt͍AL$+ÍAL$+ÍAL$+ÍAL$+ËU$A3ʼnEESEE VWEe=<AEu}h\@@؅=L@hP@Sׅ5@Ph@@S<APh,@S@APh@SDAP֣LAth@SP֣HAHAM5@;tG9 LAt?P5LA֋؅t,t(ׅtMQj MQjPӅtEu M 3@A;Et)Pօt"ЉEtDA;EtPօtuЉE5<Aօtuuuu3M_^3[ËUVuWt} u趩j^0_^]ËMu3f݋f:tOut+f ftOu3ufdj"Y몋UUS]VWuu9U u3_^[]Åt} u)j^0s݅u3fЋMu3fԋƒu+fft'Ou"+ fftOtKuu3fy3uM jPfDJXdf蚨j"YjUMx~ u$A]á$A $A]_訓]ËUA3ʼnEUS3VW;~EI8t@;u+H;}@E]9]$u E@E$54@39](SSuuPu$֋};u3R~Cj3Xr7D?=w7;tP_Y;t E]9]tWuuuju$օ5@SSWuuu ։E;Mt)E ;9EPuWuuu };~Bj3Xr6D?;w};thP袈Y;t 3;t?uWuuuu օt"SS9] uSSu uuWSu$0@EWMYuDEYe_^[M3腁ËUuMu(Eu$u uuuuu P$}tMapËUQQA3ʼnES3VW]9]u E@E54@39] SSuuPu֋;u3~<w4D?=w;;tPcY;t ؅t?PjS WSuujuօtuPSu @ESEYe_^[M3XËUuMu$Euuuuu P}tMapËUVucv#vv v vv6v v$v(܃v,ԃv0̃v4ăv較v8贃v<謃@v@衃vD虃vH葃vL艃vP聃vTyvXqv\iv`avdYvhQvlIvpAvt9vx1v|)@ق΂Â踂譂袂藂茂聂v@h]RG<1&ف΁Á@ 赁誁蟁蔁艁 ~$s(h,]0R4G8<<1@&DH@LPTX\ր`ˀ^]ËUVutY;8AtP言YF;<AtP薀YF;@AtP脀YF0;hAtPrYv4;5lAtV`Y^]ËUVuF ;DAtP:YF;HAtP(YF;LAtPYF;PAtPYF;TAtPYF ;XAtPYF$;\AtPYF8;pAtPYF<;tAtPYF@;xAtPYFD;|AtPYFH;AtPtYvL;5AtVbY^]UV3PPPPPPPPU I t $uI t $s ^ËUUVWt} u跟j^03Eu+ @tOuu聟j"Y3_^]̋T$L$u<:u. t&:au% t:Au t:au uҋ3Ðt:u ttf:u t:au tUV3PPPPPPPPU I t $u t $sF ^ËU}u u IY]Vu u u}Y3MW0uFVuj5A@u^9$At@VYtvVY( 3_^]H@PǝYH@P话YʋUM S3;vj3X;Esʝ 3AMVW9]t uYVuYYt;s+VjS _^[]ËUfA3ʼnEE VuW34809}u3;uE8+tS &AL8$$?tu'Mu ̜D8 tjjjVV|YD@l39H P4@3;`;t 8?P@4 3,9E#@?g $3 ǃx8tP4UM`8jEPKPYt:4+M3@;jDSP C@jSDPn n3PPjMQjDQP C@0@=j,PVEP$4@ @089,j,PjEP$E 4@,08<t<u!33Ƀ @D<t<uRD Yf;DI8t)j XPD Yf;D80E9@8T4D83ɋD8?D49M3+4H;Ms&CA u 0 @F@FrՋH+j(PVHP$4@C(8;;+4;El%?49MH@+4jH^;MsCΉ u0j [f @@fƁ@rH+j(PVHP$4@i(8;a+4;EGK4,9Mu,@+4jH^;Ms;,,΃ uj [f@@fƁ@r3VVhU QH++PPVh0@;j(P+P5P$4@t (; H@D;\,+48;E ?Q(Qu448@t(D8 H@D8ulDt-j^9Du 0?DY1$D@t48u3$譖赖 8+0[M_3^qjh@@輖]uy ^ x;&ArR 7 老ҋ< &AD0tS诅YeD0tuu Sn Eݕ ME EEË]SYjh`@]u蒕 x;&Ars 輀ڋ< &ADtSYeDt1SnYP|@u H@Ee}tM ME ElË]SYA @tyt$Ix  QPaYYu ËUQC @VEt {uE >'} ~0EM E>u?*u˰?~} Ճ?uE^ËUA3ʼnES] Vu3W}usѓu+“ t `pa F @u^V }Y Attȃ &AA$uttȃ &A@$q3;g C9 B@Dž GW @} DžjugucDžW9~~=]V,Yt Dž5@GPPSP5@AЋtuPS5LAYYguuPS5HAYY;-uCS*stHH[Dž'Dž5Qƅ0Dž t@tGGG@t3҉@t|s؃ځu3} Dž9~ u!u t-RPWSc0؋9~N뽍E+Ftct΀90tX0@@If8tu+(u AI8t@u+@t5t ƅ-t ƅ+ tƅ Dž++ u% OtPYYt.u%˰0Otヽtu~qPjEPP5u69t.EPsYYu#PEYY|2t) Ot߃tgYt3dt t `pM_^3[IcÍI@@@q@@ɱ@@A@QL$+#ȋ%;r Y$-UQQE VuEEWVEwY;u豇 NjJuMQuP @E;uH@t P裇Yϋ &AD0 EU_^jh@蔇]܉]EuH - Ëx;&Ar  Nrы< &AL1tP}vYeD0tuuu uE܉U裆 諆 ]܉]E E܋UuvYËUAhMYMAt I AI AAAAa]ËUEu 3]Åx;&Ar @qދȃ &AD@]ËUA3ʼnESVu F @W6V#oY At.VoYt"VoV< &AnYYÊ@$$<VnYt.VnYt"VnV< &AnYYÊ@$$<VnYt.VnYt"VtnV< &AdnYY@t]uEjPEP t]39}~0NxL=AD=VP>YYtG;}|fE Fx Ef EVPh YYM_^3[d_áA39XAËUSVu 3;t9]t8uE;t3f3^[uMcE9XuE;tf8]tE`p3@ˍEPPYYt}E~%9M| 39]RuQVj p4@EuM;r 8^t8]fMapZ\*8]tE`p;39]PuEjVj p4@:뺋Ujuu u]ËUu MbEMA%}tMapËUjuYY]VD$ u(L$D$ 3؋D$d$ȋd$Gȋ\$T$ D$ ud$ȋD$r;T$ wr;D$v N+D$T$3+D$T$ ؃ʋӋًȋ^fQSuƒt7$ffAfA fA0fA@fAPfA`fApHuЅt7tIfIHut3tIJutAHu[XËۃ+3RӃtAJutIKuZUj x@&A3ËUVu~ Vu茥YYE~ PuxYYttPuVuu ut@+ujX 3D^]ËUV5A$WPuz ux=tftu֋+A^]Ë+AUQW3υtL9t @9uV@jP袟YYuuj y_Yt+P 7Yu&E^_ËUEV3u;u_kSW];tej=S\YY;tU;tQ3f9wEA;Au 6A;uxA9u t3;t/菿 A詾yWtN_[^9u;ujaYA;tډ095AujFYA;t0A;t+}u}FY;9346O^3Y9EMM39u twj[SuLYPYY;tZuu0YPWGu E3ɍGfM#QWp@uM~*W]Y9utu]EY0ENG49u?NjW5A 5A+9uuY;}ߍG;=?zPj5Aȝ ;aU qM1AVVVVVhu\EY03+UuMX]E MUTu}tMA#E3t3@}tMapËUjjuj]ËUSVW3jSSu]]JE#ƒUtYjSSu.#ʃtAu }+;Sjh@P@Eu| |_^[huYYE| ;rPuu t6+xӅuϋuuuYYujh@P@3S|8u 6| u;q|;skSuu u3#ƒDu;kYPl@HE#‰Uu){ {H@u#uSuuu#ƒ3US] Vu &A ΊA$Wy@tPtBt&tu=I L1$⁀'I L1$₀a I L1$!_^[u]%@]ËUEuz fjX]Ë dA3]WƃуtefofoNfoV fo^0ffOfW f_0fof@fonPfov`fo~pfg@foPfw`fpJutItfofvJut$t vIuȃt FGIuX^_]ú++Q‹ȃt FGIut vHuY UQ=AuAujMQjMQPL@tfEËUSVu 3W};u;v E;t3{E;tv#yj^0mdVuMXE9XfEf;v6;t;v WSVI x*x8]tMap_^[;t&;w xj"^0c8]tE`pyE;t8]HN++ˉN~WPu EN F =M tt &A A@ tSjjQs#ƒt-F]fjEPu ]f] E9}t N %_[^ËUS39]u3AVWuȴpjVU ;tuVWl u SSSSS0a3_^[]UWVSM tMu} AZ I& t' t#:r:w:r:w:u u3:t rً[^_3PPjPjh@hL@D@AáAt tPd@̋D$L$ ȋL$ u D$S؋D$d$؋D$[%@lz 2H^z&>V6& 4DP\t$L^t0<JV`l~&@PfHr%@P@@\@/@7@@&@ bMHHCorExitProcessmscoree.dll(AAccsUTF-8UTF-16LEUNICODEKERNEL32.DLLFlsFreeFlsSetValueFlsGetValueFlsAllocruntime error TLOSS error SING error DOMAIN error R6033 - Attempt to use MSIL code from this assembly during native code initialization This indicates a bug in your application. It is most likely the result of calling an MSIL-compiled (/clr) function from a native constructor or from DllMain. R6032 - not enough space for locale information R6031 - Attempt to initialize the CRT more than once. This indicates a bug in your application. R6030 - CRT not initialized R6028 - unable to initialize heap R6027 - not enough space for lowio initialization R6026 - not enough space for stdio initialization R6025 - pure virtual function call R6024 - not enough space for _onexit/atexit table R6019 - unable to open console device R6018 - unexpected heap error R6017 - unexpected multithread lock error R6016 - not enough space for thread data R6010 - abort() has been called R6009 - not enough space for environment R6008 - not enough space for arguments R6002 - floating point support not loaded @X@ @ @`@@@`@@@0@@p@0@h@ @!@x@y@z@@@Microsoft Visual C++ Runtime Library ...<program name unknown>Runtime Error! Program: HH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSunHH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSun  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ((((( H h(((( H H  !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~  GetProcessWindowStationGetUserObjectInformationWGetLastActivePopupGetActiveWindowMessageBoxWUSER32.DLL(null)(null)EEE50P( 8PX700WP `h````xpxxxxEEE00P('8PW700PP (`h`hhhxppwppCONOUT$user32.dllMessageBoxTimeoutAMessageBoxTimeoutWFatal Error in LauncherFatal Error in LauncherrbFailed to open executableUnable to find an appended archive.Unable to read from file#!PATHEXT;PyLauncherSTATICJob creation failedJob information querying failedJob information setting failedmaking stdin inheritable failedmaking stdout inheritable failedmaking stderr inheritable failedUnable to create process using '%ls': %lscontrol handler setting failedFailed to get exit code of process.exe/usr/bin/envExpected to find a command ending in '.exe' in shebang line: %lsExpected to find whitespace after '/usr/bin/env': %lsUnable to find executable in environment: %lsExpected terminating double-quote for executable in shebang line: %ls<launcher_dir>\Terminating quote without starting quote for executable in shebang line: %lsFailed to find shebangExpected to find terminator in shebang lineExpected to decode shebang line using UTF-8Expected to find '#' at start of shebang lineExpected to find '!' following '#' in shebang lineExpected to find executable in shebang lineExpected to find arguments (even if empty) in shebang lineExpected to be able to allocate command line memory"%ls" %ls "%ls" %lsHA@RSDS.9vH'yHa7C:\Users\Vinay\Projects\simple_launcher\dist\w32.pdb0DPFВe@K"@,@1@3@5@4@8@:@H;@<@ >@>@9C@H@H@H@J@L@L@DN@PN@P@R@R@5X@\@_@@d@e@e@dq@s@@ѭ@@@dPd@Tlz 2H^z&>V6& 4DP\t$L^t0<JV`l~&@PfHrExitProcessGetCommandLineWSearchPathWqSetInformationJobObjectbFreeLibraryCreateProcessWpSetHandleInformationWaitForSingleObjectExAssignProcessToJobObject^FormatMessageWGetExitCodeProcessGetModuleFileNameWQueryInformationJobObjectgMultiByteToWideCharcGetStartupInfoWGetTempPathWCreateJobObjectAdGetStdHandleGetLastErrorEGetProcAddressSetStdHandle<LoadLibraryAGetFileType-SetConsoleCtrlHandlerMSetCurrentDirectoryWRCloseHandleKERNEL32.dllnCreateWindowExW3PeekMessageW&WaitForInputIdle6PostMessageW]GetMessageWDestroyWindowUSER32.dllPathRemoveFileSpecWEStrStrIW:PathCombineWSHLWAPI.dllGetModuleHandleWDecodePointerHeapFreeHeapAllocEnterCriticalSection9LeaveCriticalSectioniGetStringTypeWUnhandledExceptionFilterSetUnhandledExceptionFilterIsDebuggerPresentEncodePointerTerminateProcessGetCurrentProcessInitializeCriticalSectionAndSpinCountGetCommandLineAHeapSetInformationRtlUnwindDeleteCriticalSection?LoadLibraryWTlsAllocTlsGetValueTlsSetValueTlsFreeInterlockedIncrementsSetLastErrorGetCurrentThreadIdInterlockedDecrement%WriteFileHeapCreaterGetCPInfohGetACP7GetOEMCP IsValidCodePageoSetHandleCountSleep-LCMapStringWReadFilefSetFilePointeraFreeEnvironmentStringsWGetEnvironmentStringsWGetModuleFileNameAWideCharToMultiByteQueryPerformanceCounterGetTickCountGetCurrentProcessIdyGetSystemTimeAsFileTimeCreateFileWHeapSizeHeapReAllocGetConsoleCPGetConsoleModeWFlushFileBuffersIsProcessorFeaturePresentdCompareStringWWSetEnvironmentVariableWSSetEndOfFileJGetProcessHeap$WriteConsoleW@'A@'AN@D        ! 5A CPR S WY l m pr   )    @@@@@@@@@@ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZPA`y!@~ڣ @ڣ AϢ[@~QQ^ _j21~C@@@@@@@@@@@@|@p@l@h@d@`@\@X@T@P@L@H@D@@@8@,@$@@\@@ @@@@@@@@@@@ @@@@|@t@l@\@L@<@(@@@@@@@@@@@@@@@@@l@`@T@@H@<@,@@@@@@@@@@t At At At At A8AP@@X@x A AT@ P@R@ ..0APAPAPAPAPAPAPAPAPA4ATATATATATATATA8A.@t@K0x 8Pehf 0 @PB8E(`FOhpT%zh(hZ( @...GGGWWWiiiuuu ̀ ww ww||wwwwwwwwfwwwww|fwwwwwwffwwwwwwffwwwwwfflwwffffffwfffffflwffffffwffffff|ffffffffR\Ilffl)ffح2L18Ϥ"A< ΣTπU  ؀ ???????( 444MMMSSSzzzp p{p{ffkof_fffk_fff[f}UUUfL4UUU_kJqO_~'[U_Uwwwww{~( @%%%+++000===AAAIIIMMMQQQVVVYYY]]]~dFgggjjjmmmrrruuu}}}u?z=~;gEkCoAr@:IbpƏKУk(,1?5<\CKQsU[cbð/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq_______CCBABADL*(%$""&GABJIIDDJC??AD_-(''%$GG#_DIJIIJIIIDIJDD_+)(''%GG _JMCEJ?ABAECA?BA_,+)('%%"!_IM@AJACDCIDI____.,++>________JMJJJJJJJJJF_..,+)('%$"! #GIMCEJJJI?J__...,+(('%$"! &_JM?AJJJJCJ__...,+)(''%$" _IMJJJJJJJJ_K=...,++(''%$! _JMCDJJJJAJ_K_______L*%$"!_I_?AMNJJEJ_ L'%$"_I_MMMMMMJJ_ _('%%_I_CEMIJJMEM _(''*_I_?BMDJEMDMF _)(-LI_MMMCMMMMM_________< ____I_CEM1;MACDMEJM__I_?A_D1@IJJMJMM_FF_I__6_@/EMMMNMM_FF_I_C80EJDMJCJFF?:AA20?_M_MIEM______MIC4BC/ N______MMMMJIDAI_9/ HJJMMJMMMJ??BAI_A7503 IJMMCBCBA44CCI___BAO;6___C_MJIDIDMI_CI___O; @__C_C?J?AJI_?A_DJJI@ O_CM?;I?JI_________H9MAIJJIJI_CD_____JJI73C;IJIJI_?AMDJJDIMJIE5:JIJI__________MMIA:IJJJJJJJJJJJJJJJJMJ???( 444MMMRRRYYY~eGuuu}}}x>iErCE~7?HVbx/!P7pLcyϏ1Qq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq!%///* !*/"(%%'%/) /!#''////+/+"*(/ / !%(/ ( (*/ (///) */   )*  /"///// /+/// # + !%//  +"*+'#+//+'!%*++$++!+! !!!<(0`  !!$& &"   WܱNj+<<:5pi$!!!4888&SNJFB=950,(%%%5ǎVRNIEѹ|Jq?o@ZUQMID@<73/+&"-Ɍ444"ք̴wq?]YUPLHC?;72.*%!"""4888&dya\XTPKGC>:61-)% OÑ%%%5ʎA{;ys`\WSOJFB>951,($(9|:z;xq?o@mAkBiCgCdDuZ;73.*Ǒ%%%5̎È>89}:{;yq?o@mAjBhCfD~dEuZ?:625Оa789}:{;yr>p?n@lAjBhCfD}dEB>:5^ό555"܄ѳ„7789}:{;xr?p@n@lAjBgCeDFA=9"""4;;;&ŌC89~9|:z;xr?p@mAkAiBgCIELɑQQQJnnn~~~Įq?o@mAkBiCHHH"""vvv9~:{;yq?o@mAkB:::'''___sssttt8Ϧuǣvyp?n@lA888T---6fffnddd%%%"""OOOSSSċDҧuʤv{;yr?p?xM<<r?̷999VVV###)))444ѳ͝b@~9|:z;}Cg˶Ӎ󑑑ZZZlll;;;!!!""")))___&&&2:::ppp{{{___WWWWWW:::!!!"""***www"""4888&,̑999ԖJJJ|||lll:::"""BBBXXX|||$$$6,uuuKKKCCC@@@...www* Ҍ666"KKKaaaLLLWWW@$"""4;;;&nnnfff!!!$$$999444]ttt|ȇɑ&&&5ЎvvvmmmVVV{{{OOOڥaaaCCCpppό555"ބ|||MMMfff"""4;;;&\\\Ǒ%%%5Ύݧܦ͌555"ۄۥ"""4:::&ڤÑ%%%5ˎܤׇ ww?ww??ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwwwwwwwwwwwwwww?ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwww?wwwwwwww( @ !!!0\\\JeeeQ666D555A0$ vH<602\7qڴvvvj&&&D1"gLF@;5?кVQKE?9(aV[UPJD>82-uuun{{{j`ZTOVo@lAb_YSMGB<60+%<aVmt>q?n@bb^XRLFA;5/)$Ywwwn}}}j~Cv=s>p?bba]WQKE@:4.(+{;xbba[VPJD?93-'aV~:{;xr?o@lAiBfD}dE|cE|cEiB<61?9}:z;w=t>q?n@kAiCfD}cE|cE}dFGA;=aVңi89|:yq?n@kBhCeD|cE|cELF@mzzznjʓP8~9{;yp?mAjBgCeD|cEPJ\¯mAjBgC~dEaV}:z;xq?n@lAiCJJJőPyq?n@~YVVV???wwwiiiΡkA|;y>>3@@@y~~~obbb000kkk@@@---U<<<&&&[[[6uuusssWWWIII$$$&&&qqqlllg|||s4bbbhhhƑUUU000QQQ}}}---{bbb(iiiLLLBBBaVggg+++NNN|||nj]]]tttkkkrrrwwwӦaVӦzzznjӦˉ ?????(  C﫫oIਨS=?_QF4aZмoEcaXMB70~Iq?c`VK@58z;u=qv>3Eyn@iB~dE}dFGHYYYuuuġ=xmAhC}dE}eGS444|||gC}cEMMM~>q?kAiGRRR¢Gu=xKʺ~AAAAAAAAAAAAAAAA ( h00 %   h4VS_VERSION_INFO?fStringFileInfoB080904b0JCompanyNameSimple Launcher User^FileDescriptionSimple Launcher Executable2 FileVersion1.1.0.140InternalNamew32.exej#LegalCopyrightCopyright (C) Simple Launcher User8OriginalFilenamew32.exe@ProductNameSimple Launcher6 ProductVersion1.1.0.14DVarFileInfo$Translation  PAPADDINGXXPAD0 000&0+0@0P0^0d0k0x00000001T1t1111122 3[3333333C4M4X4a4z44444444444$5526f66717=7B7X7a7g77777777 8!878g88888888899!9&9;9A9J9P9]9c9h9|99999%::::::U;c;;;;;;;>>>1????? 000000000011#1=1H1P1`1f1w111112232222l3t3334^555555666)696@6O6[6h6666666$737<7`77u8<<<3=E=%>/><>j>p>{>>>>>>0L00`11112V2i2222353Q3~333333;4M4444455[56 7$888888;9:j;;;;;$e>k>q>w>}>>>>>>>>>>>>>>>>>???%?*?0?6?L?S?b?@D1Q1p111272B2x222222222R3n33333B45$5<5W555567 7*7<7S7a7g777777778D8888889999!919`9f9n99999:: ::::::;&;5;B;H;g;n;z;;;;;;;;;;;;;;;;<%<:<`<<<<<<< =0====>>`>f>|>>>>>>>>>>>>>>>>>>>>>??)?/?7?>?!?2?k?`F0l0r00000D1N1y111122C2f2l22222220393E3333333444!445*5M557?77@9G9U:l:;3;;>>>??p<>0|0002l222223D3`4K5c5589;R;~;;=???000 00;12&2222$3/393J3U3q3384>45555576A666666s888888 99+9S9999999: ::@:H:;u<<<=$=7=I===~0001 1*1A1f11622!3-33333J4|4444444445$5(5,5054585<5@5555556 6(6/64686<6]66666666666&7,707478777T8w8888888888888888999X9r99;;;A;;#<h2&282J2\2222222233$363H3Z3+515=5t556z67=7-8V88::;;)>_>i>V??T0=00003466678^;b;f;j;n;r;v;z;;;<3N>>A?L??\^032822222333334)484D44F5[55566077777 8899:/;M;<<=====6>H111111122;;$;,;4;<;D;L;T;\;d;l;t;|;;;;;;;;;X<0@000181X1x1111122 2@2`2|22222223,303P3p333333484X4x4400(5,5054585<5@5D5H5L5x9x:|:::::::::::::::::::::::::::::::::;;; ;;;;; ;0;4;8;<;@;D;H;L;P;T;X;\;`;d;h;l;p;t;x;|;;;;;;;;;;;;;;;;;;;;;;;;8>8><>@>D>H>L>P>T>X>\>h>l>p>t>x>|>>>>>>././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659773740.0 distlib-0.4.3/distlib/w64-arm.exe0000644000175000017500000051100014273421454015603 0ustar00vinayvinayMZ@ !L!This program cannot be run in DOS mode. $Lơ---F-F3-F-X-X-X-F---X-XW--?-X-Rich-PEdb" "5@`'PTp @T8.textl `.rdata֑@@.dataH%@ @.pdata p (@@.rsrcTV4@@.reloc@@B@AT_{1"@1@G>c2 __S[cks{CsP@uXAw`ByhC{pDa(@@?{EsDkCcB[ASƨ_S[cks{CsP@uXAw`ByhC{pDa(@@?{EsDkCcB[ASƨ_ T!_ "Tb6 ` b6 ` b6"@9b9_BTBcT! BT? @aT @`T L$ L!@!`` Ld LB"Tb06 @" A$B&C!!@!``b dfcBbTb06 L` Lb(6 L`Lb 6 pL`pLb6 p `p b6 ` b6 ` b6"@9b9_!!_ T6!c ` ` 6!c ` ` b6"_8b8_BTBT!BT? @aT @T! @L!$ @L!!c` Lcd LBT!&C$Bc" A @!!fdb `BbT06!c @L` L(6!c @L`L 6!@c@ p@L`pL6! c p@ `p 6!c ` ` 6!c ` ` b6"_8b8_{S[cks!@SP@UXAW`BYhC[pD](@?@BsFkEcD[CSB{A_@xb 4@7 @`T,@_?Tt@Lqn"&"4@| ՠtLqn"&5@`N!Npn&BR@_!@ˢ$@x4!T$@x5ѠA_`7? #T @T,@_?Tc@ˠt@Lqn"&4!˥? CT#CtLqn"&4caT!@T! ˥ˠtLqn"&b4$@xB4!TA_c@ˢ$@x"4!TclT_@8 4 @@T,@_? Tp@L1n"&4@|pL1n"&5@a N!N0n&B R_֥Ѡ_!@ˢ@8B4!T ?@#T @T,@_?lTc@ˠp@L1n"&4!˥?@CT#DpL1n"&B4caT! @T!@˥ˠpL1n"&4@84!T_c@ˢ@84!TclT_C1@c1_@1@c0TC_ {C{A_Q @s/3CT_t1@?@?T_ NNNN_Tf ib8 * $! )&&&&&&&&&&&&&&&&@q @! @! _@q @! @ _@q @! A9_@q @! _@q @! _@q @ _@q A9_@q _H@ qL@qL_R_TIE)@LTB@_H @TAB@qLIF     )@!LTB@B_+T@LBT__@lTChhb8c`(($TJ ) @ $Z_@(@Tc  `$Z_D@(D@Tc `$Z_@8(@8`$Z_@(@Tc  `$Z_D@(D@Tc `$Z_$@x($@xTc`$Z_@8(@8`$Z_@(@Tc  `$Z_D@(D@Tc `$Z_$@x($@xc`$Z__@(@Tc  `$Z_D@(D@c `$Z_@(@c  `$Z_@(@Tc  `$Z_@8(@8`$Z_@(@Tc  `$Z_$@x($@xc`$Z_@(@Tc  `$Z_$@x($@xTc`$Z_@8(@8`$Z__+TB(($!T_ !T(($aT_ aTBjTB_@mTB @(@TB lT TB chhb8c`J ) @ $Z_c  `$Z_ΐ|ؚtl\J(_S {@q{ @S¨_S[{@q{@[ASŨ_S {@q{ @S¨_S {(9@?5BA(M@?(]@?R  "wRR#?{ @S¨_ S {(9@?5B(M@?(]@?R "wRR#?{ @S¨_ { `5 OU@ R? {Ĩ_{`5oU@ R?{Ĩ_S[c{9* n[( q"ѓ{cB[ASè_S[{! !)Cґ qA @BRR @V R|@ @Q?q3* @ !M@$@c @h%h~@ j3$n R K?q @4*R @!0}h&@cj@h%~@ j3D)R @( sB(* @ !bbB`5iQ @?q!R @ !bJhBҁ4s"T @O {è@[ASè_S[{RR/@2R?44R$C@* @~S5 @a"c @@2R?5ca"ҏ `4R @|q2{è@[ASè_{qTQqT(B`(H@R!R y @I? R{_{qAR?uARR?5F(iA?HAeA!RRRRRR?mARRR?aA?{ĨA_{a@?ր4qaT@T@"R!R? R{A__S{ S7  {S_S[c{R  R @R TS TTS TTdT@ T@ ?@@?@`?R Q R N XTE@ ?ҿWTE@@?ҿVTE@`? (TA@?E@ ?ֈ HTA@?E@@?h HTA@?E@`?{cB[ASè_{S[C <S_q$(@T+@t}@ _ T(+ b vtbbT҈@hT`Cs"TQ \)@ R?Q qTI@?Cz[BSA{è_S[C"{CQ@?1V( @VDR!R?֠4#@ RAq@TRAh@RVD!R 2 @i?}}}}}}1@?g@rT@ ?@@?@`?A ` [a Vg@G2g[Y@R$R?ր55@?1@*RCRR? CR!@@VD?;@@y8!(@!R?a EA@@?=@@R?e@@? U@#@?S[{YA ?s`@y4q@TR$ 5ZA `"?s"8 ~Aqb@y4R `4 RRa .@x4R* @5h@y4R* `5h.@xh5`@yR `4ys A@yqTh@yqys !9`4>@yqTh@yq`@y4R `4ys h@y4R* `4h.@xh5{@[ASè_S[c{@?@yqATqJR RAm1 @y4R* `4.@xh5)%@ R?ֈ@y |@q`T )  Rz)x H(9~@j BT 8?5qT?)qT CTѿa--@K9 RcR R?!*qh~!Tci)xc3@y4R*D `4h.@xh5h@y!q R*7 `4h.@xh5h@y!qR** `4h.@xh5C  @A!|5 ! UA?֨>@yqTARy ҨQA ?yh4(-x5y-h4H-x5yLh4(-x5y+h4H-x5H A*AKt"IA!""{C@R`>C_{R{_C@`>C_{@R* RS4/a2Z54'GSH4k5R{A_R{R{_{yV{_S { RS( 4RC9dSBNq` Th5(RB!! a }`4RBA  YHRB4RC9*qh@SH4s@}A?AR`?eh@Sh4`@Ob<S*d*pS4T58R R* *eS4C@9H5(*{¨ @S¨_R?R=*7*#{{_{`4 !9 @H_hK ȩ5`T!9H_hK ȩ5;R{_ R{ Hy9q)R)I99Sh5RNS5R R{_S{z9*h5qT4s5A9594R A9 )9 )*) * (R:9 R{S_֠R{IR (@y kT,=@*, RH@ kATH1@y-qT ),()@y a) @y,) _ @TI @A)TH @  A)TJ jR H%@h6R RRR{¨_{S445!9ȿ;{A_{y9S)SH45**X R{A_{ ()GaT* A9Hq`{A_{Z{_S{@sXAT c ?@) (? @ |@)ʨ ? @ |@)ʨ ?#@C@ @H  @?X( {èS_ 2-+3-+R_ R_R_A:(! :_{@ @  {_@q___`>_{C}C}}}}}1 ?O@@yrHR {Ȩ_ּ{a ?֠@yIR k!T<@ R (I@? kaTH1@y-qTH@9qTH@h4 RR{_.(A S{@)h@ kTh@q!Ti"@H(K q T?kTR{S_ֲ@csm @S[{a.5.!!BTt@~A?ր?#T{[AS¨_S[{.5.!!BTt@~A?ր?#T{[AS¨_)Eq_@y-<S54|= A  `N `N+>N7)>N S%Ț*`TIh 86<`N0:n>N1 `N(>N  H!Q qS%ɚ( @T  J(>N )(}@Ӊ H AIh5F N |=  `NsnR `N1 `NN>N/>N7 SI>NJ%Ț(>N  ( `T  ki _ k+T-T(}@ $(!Q qSJ%ɚHI?TT K}@ Ap<`NsnNQ:n(>NHR `NH>Nh H>N )(}@i H+Aj@y_ k`_S[ck#{f@@ ˨@h6 y@52A9HJ@?qZ@ R jT@kBTR4@_C(T @_C("T@4@q T*@"C( c*`7qmT@) kThA`S 4sA}A?!R`?@"R @ C(@@"@@!C(h? @@kTRB@@˟ kT *R4 *@_C(#T @_C(T@r`T R4R4R4R+@B(T @B("T@@ kT @ @ kTk kT * kT @@4B(@z!T *@J @ R"C)@ * @ k *T R{è#@kCcB[ASŨ_csm{Sh5RSh5 R{_{Sh5 R{_S{C9@ kT@qT @  K qT@ @4@)@@ 6 @4@4@ @}A?`?{èS_ csm  {{_{ {_;@T_{`{_lS[{@1aT8 ?֨*@ ? T@  ?`5 @ ȳ ?4(hz@  ? * ?{[AS¨_{ ( ?1T;  ?֠4(hz RR{A_{`@1T! ?h R{A_{=( RR ?4 HiO R IiR{_S[{jO4=sQ`R ?jO Qj35 R{@[ASè_S{ 3RA{¨S_U{@TR*N*S{R"Lvh@*H6"R*gh@;rTh@(9~@j@ , F @yk ҩ!h@"9Oi@;?rT_q!Tyk!*@96yk!*96a@gh@; SH5JRh@;6 _qATA{S_S[ck#{*~@ ;F @h{z +! (Ix9q6h@h5L }@h@*i@h9U 7V>ԩ֚>BR*aT`@*h@;Sh4 S?q*HH#R*raT!lTh@;S4h@;!Sh5@o"h{z @)! *@9 6 S?q*HHV> ֚(֚  {¨#@kCcB[ASŨ_{S[ck Ҫs/h@*h5Ii"@ R*)*I@ 3F@hzuWA"!@%hzu"I@? AT@@ R# ?4R*`@B( T#,A(# _ BTH95qT_ "TH9)qTJS8J)kJT#H@CkDcC[BSA{Ũ_H *S_q( T *Aӟ %@x)qkaT` 郈 8J)q_ aT_ֲn!S[c{ e!s @tTuWRRRW# RR\8x"828B8R8*I *9?qcT!Tl!{cB[ASè_S[{@t T?ֵ"aTP!{[AS¨_S[c{* @qcT(R} R{¨cB[ASè_RRl!ҿ E k+TzsH z3RR!*E s  |@*F)@yj ! iA(S[c{*7EkbTh~@ 6F@zu "*@_TqT34qT qaT`@ !  ?zuR"4(Ry{cB[ASè_ |@*F)@yj ! ia(S[c{R RqTs6!3 HE IufS*~@ F +yj(Rh9R!*{¨cB[ASè_@T@9H6A ?ֈ@9(6a ?ֵ@"  ɚ ~@ F  @ Hyli! (R(9Hyli! -sS[{*S7EkTh~@ 5F@zt "I@96H@ TqT34qT qaT`@ !  ?֨zt R"*(R{@[ASè_{1T(R 7EkT |@*F)@yj +! i@9i6`@(RV{_S[ck{hOh5@R qiR(h}@GhRh`Gh 7tR 9RR`!GF@j(ziI!+@h  HTy"sbQ5R{kCcB[ASĨ_ |@ ! _S[{F#A"tRG`jh?#Gijh @?s"Q5G{[AS¨_A a S {*T8R{¨ @S¨_h@y4@y5*RC!# @"R*M$s @*# @{ RR R`@R{A_S{Rh@;5 S4"*" `6`@`7"*{S_S{ 3Rc{¨S_h@;1 Sh4"q*r*{ <SR?k3<S`T?qTEA yix  C'y"R#yK R#4#@y R{¨A__S[c{*\%@A}A?*`?{cB[ASè_ @hG*)3-ʚS*R @GjlR K ,Ț( ʨ-ʚ_S { *SIR{¨ @S¨_֟ qT*K**S{_q Th@; R jTh@;rTh@qT`@; |@*F)@yj +! i9)7h955"RҐ@h@ (ˋ )ӟ kThӟkT j"@  ?lTh@( THhi@ R(KhR{S_S {h@*;5 S5R8jRI_)yI5;*Sh5qTRc!h@hi@;( S4jRI_)uI5; h@;)R qTh@;!Sh5@Rh"`@;*.@TR{ @S¨_!|@O{$@TR2RbyJHiix(%xh4!сyHR @RR{_@@@@y4*@y * 4 * *kT-@x *i5i4,@x5@yH4)@y *)4 @y * * kT-@x*h55,@xh5$x @@_{SRA`H@{_{S[c9R?T5 @x9c9SW kCyS9OS#*V6Ts5 @T73bTWs69 @@Tj(8SBZ@9S4@HC yI*cC[BSA{Ĩ_t @!T38{S[cRn@T5 @x9c9SW kC9yS9OS#*V6Ts5 @T73bTWs6y @@Tz(xSB@9S4@HC yI*icC[BSA{Ĩ_t @!T3xS {TxRRB3hTBhT R#uRB3Rҹ*{ @S¨_S {TNRRB3~hTBhT Rn#uRB3Rҏ*{ @S¨__qT_ qDLz T_4q T(S QR? j RR__q@T_ qTH0QqiT(<S ~ QR? jT RR_ 0B `@,BA h $ R 8@q @zTQ8B5>*I *( qTh +$@ 9 $@) $ K P $@H$_ 0B ` ,BB( h $ R 8@q @zTQ8B5>*I *( =SqTh *K=S $@h(y$@ $ A P$@ $_ 0B `@,BA h $R O|@8@q @TQ8O>+Ϛi ( LqT ,$@ 9 $@) $ K P $@H$_ 0B ` ,BB(O|@ h $R 8@q @TQ8O>+Ϛi (=S L=SqT *L=S $@(y$@ $ A P$@ $_S[{*q7RmT*s@1TskT{@[ASè_S[{*q7RmT>Sw@1TskT{@[ASè_{b9($@h}A5 )@im!a"`H@h D@i @#`@aBQ#i@(C72*R(jb9{A_(@ }@9*@L94* *? kT8*h5 4h94RS Q? j`Th8h5i*9_qT*8_qT(_ k,h885_-@ 9@Sh8?qT@ 8Sihx7Sh8?qAT 9?q }@m @*9j98 *8*5_S[{ @ u@BR@C ! @` @qT@ @?T RRh@h5U4u{¨[AS¨_S[{ @r u@BR@C ! @`d @qT@ @?T RRh@h5U4u{¨[AS¨_S{h6Bhh@FR}hrD ir? qTR.b j*@i 7l9 S?iqT}@ +}ӊhijR  h.@) *}Kitk.!q TqhTi (I8( RS4a9b`F:~)h~)9R94/qTa[)j@ @* K!kl_l66h2@ Kj6 2i2(R:q`Taj@ @* K!kh_ qh:)i:S4h@ 9i9h@i5 ih.@qGzThrD ir? qT`*@{S_֩S{h6Bhh@RMzhrD ir? qTR._ j*@iJ 7k@yRh =S?iqTh }ӉLii Rh.@ ) *}Litl.!q TqhT (I8( *RbjR9 *`:~)h~)9R94d/qTa)j@ @* K!kl_l66h2@ Kj6 2i2(R:q`Taj@ @* K!kh_ qh:)i:%S4h@ @yiyh@5 ih.@qGzThrD ir? qT`*@{S_֬9qTqTq`TqTqT0@ 2 0@ 2 0@ 20@ 20@ 2 0 R_@yqTqTq`TqTqT0@ 2 0@ 2 0@ 20@ 20@ 2 0 R_{a9R9?1Th@ @*@KaxhR4b`h@ 8i9h5RyR R{A_{ 9?qT@ 7R ,S`?9qaT@h 7 R ,R]RT<@(5?qTT?%qT?1q@T?QqT?qT @(9qT( )R=IR;R<9 R6 @*9_qT(9qaTJR_q!T(9qTjR( < $HaQ S?qTiX(%Ț6)RR?qT?q T?qT?q!TRRR  @(9qT( RiR < R{_  { @y?qT@ 7R ,9Sa?9qaT@ 7 R ,VRRU<@(5?qTT?%qT?1q@T?QqT?qT @(@yqT(  )R>IR<R<: R7 @*@y_qT(@yqaTJR_q!T(@yqTjR( < %RH =S?qTHX%ɚ6)RR?qT?q T?qT?q!TRRR  @(@yqT(  RiR < R{_ S {h9 Q_qT (Y ( R$!h2@ 2i2ARROi2@(Sh4(2h2R"RRR RhRi")^S 4hA9H 5l2@Cy  9S4Sh4Rl6hRS4RC9+m9RS aQ? jTSh4.RRS QR* N55CRhi)8aqTqRAT RGhi)8k i6@ *hR@r)K4KTc*R`8dBc*C`Ti2@( S4( S5c*R`(Rh*@(7h2@ S4c*R` RR{¨ @S¨_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{S[h@y Q_qhT (Y ( R[$!h2@ 2i2ARR i2@(Sh4(2h2R"RRR RhRi")S 4hA9( 5k2@  yhS4hSh4Rk6hRhS4Ry*l@yRaQ jThSh4-R RQR  M55R(y*xaqTqRAT R (y*xJ i6@ *hR@r)K5KTc*R`tdBc*i2@( S4( S5c*RrRGh*@(7h2@ S4c*Re RRi[BSA{è_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxS {j@ @* K!ku_@b>@a9`@t&@yS4(RhR9)}S Hh&I(85H)R9iR R{ @S¨_S {j@ @* K!ku_@b>@a@y`@t&@yS4(RhR9)}S Hh&I(85H)R9iR R{ @S¨_S{Ch2@4Rj:@ 2i2J6hA9R Q? jRR*j: *5h9qT RqaTt:*RHu}@`b`S(5h2Bh@h.BA uQi:ib(B(h&j@ @* K!kp_a2Ba@h.BAӡ@abcb h.B#H"Ag"@ e9f:@ h2@ S4h:@5a@`&@hA9R Q? jTh2@ S5a@`&@j&@K9qTh2@Jj& 2i2K9h%Q S?qTX(%Țh79qTh2@kRk9 yi2K9 k4(85( hR RC{¨S_ !S{Ch2@4Rj:@ 2i2J6h@yR Q? jRR*j: *5h@yqT RqaTt:*RHu}@`bS(5h2Bh@h.BA uQi:ib(B(h&j@ @* K!kp_a2Ba@h.BAӡ@abcb h.B#H"Ag"@ e 9f:@] h2@ S4h:@5a@`&@]h@yR Q? jTh2@ S5a@`&@,j&@K9qTh2@Jj& 2i2K9h%Q S?qTX(%Țh79qTh2@kRky yi2K9 k4(85( hR RC{¨S_ !{b>@a9`@j@S4 @* K!kc_xa2B@abh.BAd@`B 4(Rh9ib(B, @* K!kh89(RhRib(B R(h&{A_S {5RuR9j@ @* K!kb>@a@y`@t_xibS5(BC9Cc@G9 h@ 6u9(B(yib(B RuR(h&{¨ @S¨_S[c{j>@*XS_-q(TI(I8( t@6 Sh@4 !i)85}@7 !i5_83tj@@V S 4 @* K!khx}@% @* K!ku_xtj@@ S @* K!ku_tj@@ S 4 @* K!ku @* K!ku_҉@(S4)2j:@6(Rh:(yA}@`b@ yR9"**Tr*@ S4hR@4h&@ 9?q Th&@ R i&*9hR@ iR RRR{cB[ASè_ֱS[c{j>@*XS_-q(Ti(I8( t@6 Sh@4 !i)85}@7 !i5_83tj@@V S 4 @* K!khx}@% @* K!ku_xtj@@ S @* K!ku_tj@@ S 4 @* K!ku @* K!ku_҉@(S4)2j:@9Rj6y:(yA}@`bA@ yyR9"**T*@ S4hR@4h&@ @y?q Th&@ R i&*yhR@ iR RtR R{@cB[ASĨ_ְS{j@ @* K!kt_4j>@_-qTi(I8( j* h*@hRyyh99(Rh9 RCRR{S_S {@ @* K! G) s_9@15&S4(&3(R~@R9P (&3~@*R R{ @S¨_S {@ @* K! G) s_@y@15&S4(&3(R~@R9 *(&3 RqTh94 S@ @*@KkxrhkTR R{ @S¨_ @I@ THa@94H@ IH@a@9H@ I @H @ I @H@9 @ Rh@ i_ @I@ THa@94H@ IH@a@9H@ I @H @ I @H@y @ Rh@ i_{SVChRA94hR@qMTu&@R&@xd@#F5@b4dBc#`DhR@kT h*bR@dBa&@c`6 RC5@SA{è_S[{hRA95hR@qMTu&@Rc@#yCh@ *qmT#@yb`hR@6kT h*bR@dBa&@c`8 R{¨[AS¨_S[{*4@*@ T(a@94h@  H @~@12@H@ I@H @ I @Ha@95Thh@ i{@[ASè_S[{*4@*@ T(a@94h@  H @~@1@H@ I@H @ I @Ha@95Thh@ i{@[ASè_S[ck{7R9sTiT @a1T @qT " @Oh81AT!T @qT  691T HR R{kCcB[ASĨ_S[ck{7R9sTiT] @q1TR @qTN "K @_h1xAT!T< @qT8  6y1T1 HR. R{kCcB[ASĨ_S{ @[h@@ |@*F)@ yj)! *@96:* (R@*{¨S_{1T  (R7EkT |@*F)@yj )! *@9J6S)Ccs  (Rr{è_S {*aTR@qaTH!C97 qATHB96 R@RT* ?5 ?**~@ F @(yk I! ?94* R{ @S¨_{ ? ? R{_!_A_S[{`Rt R R"S)4(yh4) ( A6AY RR`5SvR`R*{[AS¨_RS {I3yh4(-x5(i@A(y h4H-x5H ˿"T`@xuxqT`4i@h@  { @S¨_R#R{ R@;{_{S[c RRl$cTy@@@yJ4RH =S?eqhTHy.@x*5RUR R*5 HR @G~@Ty HR]@R= A? T<LT|DӠs/sRRhsB R @@*@ R4* HRTRh_R kaT`B*[cC[BSA{Ĩ_S{C_co@94 @HC yI{èS_C߈CbR%@x+$@xHQeqIhQLieqhK5l4BB_{}Ah5D RA{_S[{- R=VsR9C @h@(&@xqBT @ ylx6h@il8ScA@<S&@xqBT @ ylx6h@ il8 Sc1@ <S K5t4sѓ@94 @HC yI{è@[ASè_S[ck#{ Rn{è#@kCcB[ASŨ_ֳ ؚTTR3 ؚTh@; R j`Ti"@ R  Rh@; R j Tw@4 77BT*_C7 Ta@~@ӂ  aR +h@B7j@ZC7 KHA7ih~@B)T{@ W4I5>   K_C7CTh@hf@* 4@7 |@Z @@ O1T@Z(@9i"@  TRJQ HRzjRI_)2I5jRI_)2I5;@ ؚlS[{ uTR# R{¨@[ASè_,<S[{*sj@K_  T(AkT? T @ T Ra(AaT[w@v@"q TI+A?)A? T@I v@ kT@T  kT  kTI  kT  k*!T(R4R%RR"hRtRHRTRRR kT kTI kT  k*TRR RRRRHRTRh}A?*R?v}A?*?wR{@[ASè_֑1@_1_ @h@*)*-ʚ___S{ @h@*)3-ʚsR}A?`?{S_ @ RIi K,ɚ h_S[c{((v"R8qTqUR@  8h9s8* 4@  9h9s545q`T&qT8R9(4q`T%qT94v"(@ R )9+R R8JqqTqTJ749qaTq RJ}S*4JQsw9s@  *59 45qIzTK4`99s 4@  9h9s@  s9s@  V(@ ){@cB[ASĨ_{bTB> š?T)|  )T !  {A_S[{*u 4QqTR$RSe RV(3@sh9H5c,A"kR R0cq!T@t QH C *4 @   @ H@ h@)i Җ  R*{è[AS¨_S[{!`@h@HN@4t@@RRR|@vAE @*RR@4R b @`@Z {[AS¨_S{@hRs /`R< : *{S_S{@hR<s Z`R  *{S_S[{9 4q)h4H85H8H5  94i4H85H?qT! u`5" h@ @  {@[ASè_RS[{@y Ҩ4qk h4(-x5( A* @y5` 3@y4= =Sh4(-x5( A7_qTAu u`5" h@ @ Ҋ ҇ {@[ASè_RҡS{@@`TSh@m @i {S_S{@@`TSh@W @S {S_S{!`"@Sh@< @8 @Sh@0 @, {S_S{ @H?Ri@9?qhT)4t4?qaTq *@4ss?qTh@8*5{S_S[{( *Cӿv@~A?ր?sT{@[ASè_S[{`Tu@~A?֠?ր5TR{[AS¨_S{ @ `@ {¨S_S {E95)R ;Ո@ @5 @K(@ `TI3-˚}A?R`?!?qTp@ @5 A  @ @5(R9 @)R 9{ @S¨_k_csm{*)b5a ?֠@yIR k!T<@ R (I@? kaTH1@y-qTH@9qTH@h4*3cC9SC#HRIR)sGy@4{ĨA_*{*qT1@ @*!SJ5A ?ֈa * ?* * ?S{ ( *CR ?4 @a ?}A?*`? @ ?{¨S__"RARR"RRR}RARz @@ RIk K T ,˚* ʊ_gRRh{{RR J;R{_{@Qur!T0@qT!(;RaRR{_|@?@q- F@yl !*@9+9OT? @q T(@@Qyr@T?AqTH2(9(Ryl%H9 H2(9HRH(9H2(9yl%_9o5Rk5RqR(R _S[{ @ C9'г"`Tɂ h@ T - `s"@ {è[AS¨_)R ;_{SCcCRR){¨_{*ZCJR)RrI1 Ts4q`T qTRz y2H  *{A_A;_{qTR_ H_@ 5;*{__S{ @? *@` *{¨S_S{ @- y*@N *{¨S_S[c{@ @- @@* ʩ@V-˚* ʩ @N-˚* M-˚ TC @(  U#TTuҼ @  *C 胊kT @@ RJ KR @*-ʚK  @ RI K@.ɚ  !I@ R+ @J K -ʚ@+ H@ R  @HK@-Ț+ H@ {cB[ASè_S[ck#{@ @k?L@i@* i@U-Κ* ʩ? T-ΚHT {"џT@`T  ʙhA3-Κ?`?@L@+@h@  h@M-Κ  K-Κ`XT    T=L@@ @,@H@ @*@L R{#@kCcB[ASŨ_!{ScCIRHR){è_`  @@ T@ R_{ScC'HRIR){Ĩ_ ( R_{! R{_{ R{_{@6  R{A_{RS{_S{jDH_QH 5;(5`DBTBh R{S_{`D`D` @ `@ R{A_{AS{_{S4GH RAS{_{  @h}A?`?"{$@T RRb9JHi8(8h4!с9HR@RR{_{`R )AH6`>`RO7 66R?kT R_RS[c{h@*wSRb4 QqTCy@R@-49m @S ix69 lq2q `TqT,49l Ru4BqaTB S?%qhTQ ~ S?eqhT^Q S?eqQITh4HR9RkS aQ? jTqRlѵl49 kTxl@lt9lRqU5> KB S?%qhTQ ~ S?eqhT^Q S?eqQIT1@TkT kT k KziTR" Rh@7* 9lh49kT=7@9y4 @HC yIi@ih@(R1**HS4'HRw7@964 @HC yIi@ih@(R4 @HC yIi@ih@(W6K@94 @HC yIi@ih@(*{è@cB[ASĨ_S[c{@*uSRo4 QqTC@R3@y( @%@x*R!@52qhQR jT@%@xzrRTqTqbThQp R k Tq TqbThQgq TqbThQa%q# T%qbTh%Q['qc T'qbTh'QU)q T)qbTh)QO+q T+qbTh+QI-q# T-qbTh-QC1qcT1qbTh1Q=3qT3qbTh3Q75qT5qbTh5Q1B9q#Tj9qbThB9Q+B;qcTj;qbThB;Q% KqTqbThQp R k Tq TqbThQgq TqbThQa%q# T%qbTh%Q['qc T'qbTh'QU)q T)qbTh)QO+q T+qbTh+QI-q# T-qbTh-QC1qcT1qbTh1Q=3qT3qbTh3Q75qT5qbTh5Q1B9q#Tj9qbThB9Q+B;qcTj;qbThB;Q%hA>CRl3h}A?`?{S_ %{h:h:CRQh}A?`? R{A_S[ck#{Ch;*h!;*C R*.9h}A?;@*** ? Rh *** ?C{#@kCcB[ASŨ_S{ha;h;C `Rh}A?`?ha ?{S_S{h;h;*C Rh}A?*?h * ?{S_S{h{ƨ[AS¨_{SC9Cc#R) R)sGC@9@q@ {Ĩ_S{i@;( q!T?rTh.@ Kkq-TIa@*k TjRI_)2I5; h@; S4jRI_)yI5;R{S_{R`5h@;- S4c`5R{A_ R{h@;5 S4h@;S4`@mjR (I_) I5;~{A_{RC@h  R{¨A_|P;_S[{G)ᏹa( ѶT@Sh@;5 S(5kRh_ 2i 5;7h*7} _h  _RR_jRI_)2I5;^{[AS¨_S {C @y A ,@x?qT5R?q`T?qT?q!T3`RR*3!RJR RRRR)RDRREҨ@y** 4MqTTq TqTqTq@T9qTIqa T5s2s2=rTs22'R4f5&R37j3305s2/Rq$QqTqTqTqTq TqTr`Ts2rTs2 5Jy5J2.RqraTs2s`6 Rs2)R?q  5 q@y.@xqT5@yH4R@ @{¨ @S¨_h>b~ 5(@y(-@xqTq!T4 @y.@xqTh?5h2IhA?5h2  h?`5h2jixqT H-@xqT5c9S {*&#@9h5@0R*CB5HO@ IjRI_) *I5;@~h{è @S¨_hA {CH@  @@{¨_S{ @@ @*E@I_)I5;`@9{¨S_S{ @h@@`J@0`J@DT T@H5J@{¨S_S{ @@ @@!@@@`@{¨S_S{ @@ @ E@_Q 5;5A@T`@{¨S_{cR#c#RR)'+R'R (/h A@C @AK@(E@iRIyy@ y@pC{Ũ_{O{A_{RRRR) @hA?T 7@8@4@-@1@1@.@5@+@%@(@)@%@=@"@A@@AScCssC:{Ĩ_S{J@kDJ@T T@H5Js{S_S {h ?*РB1 T}TB`4y ҬB҇5B~>h * ?{ @S¨_nS{ЀB1TG@TB[4y wBRҜM5BI ҏ{S_?S {h ?*РB1 TTB&`4y BBg5BZh * ?{ @S¨_{"1aTRR R{_{`B1Th R{A_S {hT(hE R ?`h`4w4ER?R{ @S¨_{A #RC{¨_{A #RCҞ{¨_S { 95 yKCl@*R( @ kaT' q (@s@9hy R@9H4 @HC yI-@9c4@# @qTkKT @!R% @5( @(T94 @@#R @!R 5?HRR{è @S¨_ҡS {#y @TSR7sh{@TRR+@)R @ kT#@yc-S`qmT@@#@yqT@TRHR@R@94@HC yI{Ĩ @S¨_tu9S(Rh*S#RCR 4@5S`RH ?qT@TRHRZ@R{Di@?TI C?jaT`{A_{Di@?TI C?jaT `{A_S[ck#{CK@9qS*l.(T|HR@R@HtaT@*R* `49 R}&  R*iSKA 9 9Rh9@sRIS6y_)LRT{9@@8(Rh9ws t5R@}@ @(9(9@ @IRҟqmT@ +@h%š =S+j=S_qiTH =SHQj9=s!DӔQ6b7@S4kRl9S Q? jaT{8Tq@T i9h8h8qT*RsB49RqHkSk Ai9@l Ht ?*?hRh9_9 T }H ɚ  9 aT_ T H ɚ  i9k D J TIH ɚ  i9kH i99R@94@HC yIC{è#@kCcB[ASŨ_S[{*  `@_C1CS*@[@*Cqqן  H %`49 +@RC***{¨@[ASè_S[ck{*qhŸ %)wS*STpHR@R{èkCcB[ASĨ_CO64@q*qmTH9 h4(85(  @r@RqaT9q Th9@h9(}@ @*9j8@ 33 Hi"1#) 5` w4Rh9@ 9?q T@ Qm6 Kz9qkT}( `h 9+}l}K R h 9)qkT}( `h9+}l}K  h9 Kh9 h9? qT9qTb&@94 @HC yIRRz QgfffS[c{C*S@Rw4HQkT@q**H_i388@qTRh9s@q,T5@ 9(R?q@TR4h4t h9h4(85(tx9t(qT9h4(85(@}@ @*99@6Kq VzԢt4h9h4(85(`4~@R@94 @HC yIR{ècB[ASè_S[c{@*C?}C*S*S@c@*C*q@3Q `49@ Q?1$UzT kTh8587@$RC*\ 7@&RC***{¨@cB[ASĨ_{)S(*T9R{_4R98@TcшS R-ySNQo ,yk9 h4H85H 臟  +  yk`5RRҏS_{@W< *@K%ךs=S5"q7*n5@h @*%ךL=S!qiT RT(!ך * _@TqTK @*%ךKS)L` qT34@qTs4@HR{_AS¨_S[{C*RZRwT+qTQ qRHT&RG7@tT@aT R   aTRs(*K`2S*]SD 2[@{qlTQyrTqTq+@ * *  :qTqT+@ **  +@} *J#@**q'@   ˁ`49+@R*_ +@ * *  C{¨[AS¨_֨@)D @? _{}A54R A{@_T{_S {sR*R#U{@TC@@**!Rw5RQ@94 @HC yI{è @S¨_S[c{C*q**Ta~@k***%C{cB[ASè_{<SR#y kaTR%c#@yqTh @ ykx6@@ik8S@ @%RK#RC R `5#@y'@y@94@HC yI{Ĩ_S{3R*{S_h@;5 S(4h@;1 S5h@;SjR4I_)2I5;I_)2I5;h@; R jaTh@h[b"@a@f`qTq RR+jRI_) *I5;h@;rTB1`T>1T:*7~@) F @)yk H% ( @9IR  qTjRI_)2I5;h"@qaTh@;S4h@;!Sh5Rh"h@j@ QiH@@9hbT_S[{*4h@94` @_9~N95h@hh@94` @S9@$R` qqh9i 5h @yR!R*|@5H ?ֶ@%i@ iTh@94` @*9R` qqh9i5d @ *!R*|@I(hR{[AS¨_S {*4h@94` @9~R@y5h@hh@94` @9 R` qqh9i5h @9RR*n|@5H ?Qr@'i@ iTh@94` @9R` qqh9i@5d @ *R*F|@I(hR{@ @S¨_S[ck#{:RRq@HSRRCC9G9K96@i*t5Ȏ@hWB(C( C탉 ҭj@H9 h4(85(  T"H WB T˂@H9h4(85(I!@5@wk4"TR1 *C@'T"*{Ĩ#@kCcB[ASŨ_R:S[c{9h4(85(4 TR{@cB[ASĨ_!h5b 5i@?T`@Ҁұ`i@ (iC  TҪ@R* `" i i@5h@ !iRR{S[c# X 9Q S?qhTi&Ț7xT 9?qATTy(Q S?qTh&Țh6)R R?qKc9@)R @ k!Tc@94@HC yI"Ruc@954@HC yI"R4@HC yIRC@RqRH R ?T1*^&@)7C39@)R @ k!T@94@HC yI"R5@954@HC yI"R4@HC yIR3+@q9qT9h4qaT94*35A9h4H  ?֠5"@  +C T!@ aeRA9h4Ha  ?cB9h4C@* Y#@cC[BSA{Ũ_ 4S {*h@9H49~8@y5h@hh@9H49aHR@R9+h @9RR*C |@)H ?&G@h@?Td @*R*-|@I(hR{ @S¨_{S H! T|@Ӣ RC ?5H ?R)SS##9@)R @ k!Tc@94@HC yI"Rkc@954@HC yI"R4@HC yIRC{#@ SA{¨_S[{ @t@ @*E@Aa'@ " RR" RRh@ @*E@Le@%h% Th}TT@ @*@H_QH 5;(5@ @ @A@T@ @@*@hE@H@*@KE@i_)i5;@Q{¨[AS¨_qTq`TqTHA>q H>H=H=_{*Cf?  1T(R( I( 1!T(R( I( ?*1T(R( @ @@94 @HC yI*{èA_S{b" RRF AhaB) R ˟His8)Qh85fhe  Rhii8JQ(85{S_{ `@(Rk TH! C ? 4R(8qT R[@99[4*@9qTI(8 kiT(-@85e@R  R!Rғg@ RaA R R)g@ RaA R@Rof,R R%@x6iB,Kig8(a@92(a9 6iB,Kif8(a@92(a9 RKi/8JQ5  R RhQeqTh}QjB(nSIa@9(2Ha9 hQeq(Th}QjB(Ia@9(2iHa9.SRh e9kQ5 @{¨_S[{*6SS*%@*E@ @ kaTRE@ERE@**1TR*{Ĩ[AS¨_V5z@ E@H_QH 5;5@E@(A@T(R@E@I*C_jT'RR)ScCqV4@ @IS {CI?jTJ@hF@RF@@Th_Qh 5;5A`T@Fi_)i5;ՠR{ @S¨_5S{h9h5'(Ah )Ah&@cB!R](R(9 R{S_{n'{_{S[ɿCv*4! R U}@Ө~ iv?k@ TJ_q#TRk@ TH Ȁ>S ?ր4(RkaTh:y pH! * ?֠4ub" RR@t qT@9(4(@94+@9kHT Kj Jj8kQ2J*8Jk5(-@8(5)R)Ji8?q2J)8cT`@)R` Ri J59wb" RRA! ҎRB2 j@9j4h@9(4_kTMqBTJm8Jig8(*J-8h@9_kTj-@85Q5(Rt)* `l2R*kQJyhx%xk5 RC8@[BSA{Ĩ_S {*C**=@jS* e@9?jaT4@@ jx*  Rj5R R@94 @HC yI{è @S¨_*҃RRRkTRK%qTTR(%h7qaTRӚRkT(՛RkiTh֛RkTRk`T(RkAT!Ha RKqR釟kHTRK%qTTRH%7qӚRkT(՛RkITh֛Rk)TRkT(Rk`T!xR?q?qi4GHA S[{H ?U@y4 =h4(-x5( A*k h@y5h 7A*RRR|@**RR`4Ha  ?{@[ASè_S {H ?t#@y4 =h4(-x5( A*k h@y5h 5AӦsHa  ?{ @S¨_S[ck{*WRR lV`TT@@y(@TT@4G T@(@ATT4h Z҃@(T@4 P@yT@A@5@ yyx?q$@z`T@C˕@Hzu[x5zu(@((@h+JT z54ˡ ?T?T6 }TW4yh4(-x5(A AuR !5(HAq?x  ? 5HR @  @RR*{¨kCcB[ASĨ_RS[{{[AS¨_h@ H@) ti@(y h4H-x5H AAҡj3js b@5i@Rm{ 1@*@K @7C@Rq@T R{¨_H J RRq(H _S{A%`@@@T`@@@T`@@@T`@@@T`@@@T{`"@"@@Tv`&@&@@Tq`6@6@@Tl`:@:@@Tg`>@>@@Tb`B@B@@T]`F@F@@TX`J@J@@TS{S_S{A%`@@@TC`@@@T>` @ @@T9`.@.@@T4`2@2@@T/{S_S {(@ҟ @sT{ @S¨_{```BA``@`@`@``b`B`A`B `RA`VA`ZA`^A{A_bRII @y 4(@y_kT!) @y(@y K_{S[ck+=****Nt5H@ @q)R(R!R**'*v5R;~@ A? T<LT|D/s/sRmRhsBR)**!R*`4HA ** ?*Rh_R kaT`BsHc@94J@HC yI*_+@kDcC[BSA{ƨ_ @I_)I5; p@I_)I5; x@I_)I5; t@I_)I5; @I_)I5;Ո ! RH_ TL@_)5;H^L__)5;JkQk5@US[{i~@iA%?Thr@@h5`z@@5`~@r`v@@5`~@`r@`~@h@@5h@h@h@`@`@2!vtR_T@@5@^_@H5"Q5{@[ASè_֠H# T pH_H 5;*_{sH#Th^A;Ո5{A_֠H# T pH_QH 5;*_ @I_)QI5; p@I_)QI5; x@I_)QI5; t@I_)QI5; @I_)QI5;Ո ! RH_ TL@_)Q5;H^L__)Q5;JkQk5@_S{gCI?j`TJ@SRDB R{S_S{@aTt@5 `T{S_S[{U ՚TRҵ~nUTR{[AS¨_{H! ?ֈ {_ֈ  R_S[{@TWv@~A??S4sBT`TTs"h_u@~A?R?sBh"TR R{@[ASè_S[{TVu_~A?R?sBT R{[AS¨_ֈ_S{SH}A?`?`4 RR{S_{R @+HE*S-˚R{A_S{ @ @+HE*T-˚`@{¨S_qTq T<q`TTqTXq`T(! (a((_{SCcChRiR){¨_ֈ(_S[c{*R3RC9"q,T`T qTqTq T$.q@T>qTVQqT*!5{è@@cB[ASĨ_֩@H)C*(@kT)A? aT R:7!RC9s4`R*@4(@ V-˚`T.qT"R%6@"qT@R"qTH -C@  Ki1CH _T_JA(@s4`R!aTR"qaT(}A?a@R?(}A?*?֟.qhT"R%6"qTs4`R`R{1T>(R7EkT |@*F)@yj )! *@9@*(RR{_S[c{UH(RtR }H 6}~@|iw 4`6Qk-T |"(ih7qT }|JH> ihR{cB[ASè_S{ @-h@@ |@*F)@ yj)! *@9 6( ?`4R ( ?*(R@R*{¨S_{1T(R7EkT |@*F)@yj )! *@9J6S)Ccs(R<{è_{S[ck+g*~@:F@H{y xC"3" (@/hC")( ?#iC@RR @'T~@)F+R{9_ k8R T+yh R h-"*94qKT@q TH{y"*@9 8. K6Tch"c *,i8kQH85 Rq-Tch8I J_k)8kT H{yQ  &k_9M5cHRq)R8@`TQi@98(kTq*RIRX@#CTs6*I{y & l@9qT& j9B 9hR 9h99_j9HS x6@v TB1T"1 T#@ҥR*Rs* 4(!/@ s* ? 43@@jK@T k T+qTR3y(! s"Rc ? 4@4@@TF'@@RT H{y Ji8 )&j9}@_ T ! RmT H{y"+ Ji8j9}@_ TH{y " *9J{y* h@92h9( ? @CB94?@HC yI@*J+@kDcC[BSA{ƨ_{S[5/(Os/ |@ *F)@yj RtB")! 7@TccRc1bTi9s?)qT 9! 8 TK (! C*c ?@4@)@ ?kCTT( ? @@/(s/@[BSA{Ĩ_{S[/(s/ |@ *F)@yj RtB")! 6@%cRтc1bTi@ys ?)qT y! %x Tc  A(! CzSc ?@4@)@ ?kCTcT( ? @@/(s/[BSA{è_{S[ck+O(ҳs/ |@*F)@yj vB")!  9@"T(!cR*cBTi@ys ?)qaTy %x T AҥR#cR R*4R4#A4ȢKC?@4@ kTiKT( ?@ @O(s/Q+@kDcC[BSA{ƨ_S[c{** 1TYa(R,7Ek"Th~@F8@ {w)#*@96*{w)#*@97G(R:*****.6(R{¨@@cB[ASĨ_{S[ck+**43R~@;F@h{z "T9 S?qHTU7H@9(6BR**4h{z"*9*6bH@ @h{z"*9*4h{z"( @ ?4T4qBz TyB5 RRT&@x*} " k!T* *q!TRt <S5qT)T( ? @C2**h{z"I964qT q!T** ****8@@ (! * ?5( ?3(@) @HI @@h5@4qT(RsRdWbh{z"*@906h9iq TpRcUKR>+@kDcC[BSA{ƨ_S {SWRR{¨ @S¨_ue4trAT****C*@44i*F)@ yj*! I@9(H9`@T4u*S[ck{*~@*F9)@yj *)! *96uj!TC5@ jATs2i ?@q:RXRT(A@Qyr`T(@QyrT(AQyrT9 3*(`Rh  qT99 r T9 07@ R RR kT R kT R k! T @ 4 qTq Tq! T3R4bRC*q`Az1T@qT qTkaT9*BR*?R*:T@7~S=R kTRߟR k!TRA*$`T9R*`TS49RqT qTߟRSRsR4RC4bK*1T k TR{¨kCcB[ASĨ_R@S[{*9r**R TqT q T] 6 r`TRRR h?qT?yr`T?qT?qT? qTIRRiR?qT?q T?q T8h@ R)Ri BqTqTqTqTqT$  R khRHR(RR Rh)86h92h9x7HjTC5@!@qTh9a2h9@6IJ(** j87(RhT06h@j@ 2ii@H2h*2j`6h@ 2ih6h@ 2i(6h@ 2 6h@ 2i{¨@[ASè_RV @S{*i~@*F yj)@)!  R+@9jaTK86BR?T@ qT@"R#yC*#@yq ZzT*[ 1TR*#@TR{¨S_S[ck#{****?@H%@cJ @1h%j Tw}@ 1!TlrR(RG@+RF)Ks* C@[#*E*(  ?kE) D)T7@ R( kT6(6y E*#* ?aT *Fyj)@*! I@9(+iH9(?@(  ?`5( ?* *F)@yj*! I@9(H9)( ?5RqTc92 qTc92c9@N Ҹ2*Fc9yj)@)!  89KFykI@)! ?936@*4@*gc@C9H%@J @C*h%j @*u4*}@ F @(yk C9K! i9KFykI@,! @9(ASJ*S R h J9 jTs6 *Fyj)@+! i@9(2h9 R kT6(  ?( z D)E*# ?aT( ?t *Fyj)@+! i@9(h9@r  *Fyj)@)! R{Ȩ#@kCcB[ASŨ_***%R{*CWhqT@@ sx R@94 @HC yI{èA_{Cs T@R kR)Siy{¨A_S[ck#{@6yҚRh9h5"h9h5Bh 9q"CF T`@kTIT A@QR)J*yR U3.xy UA ҟyHR{¨#@kCcB[ASŨ_uҖh9h5"h9h5Bh 9q @TAT{_ .a59?`raT9?PrT* R?<rTR(KqTJ R?( rATkTj R ( a2i 8!|Slh*9@__HR{_S {**5h9qෟ& @5h9qTTiU4(9(8qT5h_8 RqTh9qTq RaT qTh9qTq R@TR{ @S¨_S[{iRRDa_q9H ?)T\SRXR*3R@v9u9h4R8BQ_q T97@**SH4(969(8qT(9h9qT@  jH9 h4(85( aBR{[AS¨_S[C@H4'@4 QQ 5*@_qT@C[AS¨_5@J5> ( q藟 C[AS¨_ ҟ1T }@  xiK N>h ΚQ }@ 1Th` )q(RKRh C[AS¨_ kT K k *Th}@ H}@.xi xikTJQ_ kkQTBT 4Q .xjQ 5xj(R RZJK S?qTRK4&!.*"qT Q +xjh%*ҥQ% 7}@Ӧ kT|@ xiRQI xiQ  xj4@%֚! q(!ԚOT Q  xjh%  *@C> Ú@Ƚ }@өT` %@@?T~@ӯ}@j}@ _Ti ? J )T R'4}@ *xiH-* `  xl_ kH Kk%x,k#TA$IT R4Ҩ }@ H xl+xhk(A+  x,O`ӃT*}@QQe6@ *_kTH}@ x)@J_kCT 4kQi xij5 k5C[AS¨_C[AS¨_{S[ck+,*c@7R}qaT9c9@RRRR(it)@`T+@6H1?Th@aT(R   aTRhs(*Kh2qT qT q`TqTH!253RH2H1H1@5Rh@g t)R_)@9 @3(RLRJ)T dl`* )ZR x(Kyrq)RHR+KqcT3XRR, 4 R33YjYj kA TJ_ qATQQ }S'RK)(!ƚQ3hQ(Yh*RR%* ZHIK K S_qZ ?q T k藟  6 qHTQQkT K Q kT3NYhR? kT3YiR  %(!J*3jY,Qk`TK@O@R43?Y( kTO@KRKRQQ }S'RK)(!ƚQ3hQ(Yh*RR%* ZHIK K S_qZ ?q T k藟  6 qHTQQkT K Q kT3NYhR? kT3YiR  %(!J*3jY,Qk`TK@O@R43?Y( kTO@KRKq Ts%XRR[ c 4 Rs%3YjYj kTJ_ qATZ(RIKR K S_qZ ? q'V qTRKQ?1T(Q? kT3JYi R kT3hYhRHy3hY))Q?1`TK@O@KȆRKt~S~~Rh)R)!*IY4}~s_0hT6q Z*RHIKR K S_qZ ?q'V qTRKQ?1T(Q? kT3JYi R kT3hYhRH}3hY))Q?1`TK@O@KR9RbiBBHU!7~`YS4IR?q Q).3 Rt @9u@9~ӈ Gi@yH ~ @ gC?qTC5RqTw4 R RsYk} A)Y+kkI`!T4BqTsIY(BBR RqT *@"}~_0sT59R HRBB34q T 4 R RsYk} A)Y+kkI`!T?k'?ksc1?ksb1?k$1qRR4fXn5kT?Y.1R*Re4qTkTY- LXg+Xmk} A/=*A+  )X-7@ET/4q TkTY-HYm A/IY-/`E/5q TkT*}~_0sTī9RHR6BB)R4 @I9K5@HS uS* Z  K4H!1iQ Yi 4_qT4 R R sYk} A)Y+kkI`!T)4BqTsIY(B"4 R R3M҈Yk} A)Y+kkI`!T)!4K@qT3IY(K@KKH`Sw4IRq Q).3 Rt @9u@9~ӈ ;i@yH ~ @ [C?qTC5RKq`TV4 R R3Yk} A)Y+kkI`!Ti4K@qT3IY(K@KO@RKRqT *K3@"}~_03T)9RHRgI)S4q T 4 R R3Yk} A)Y+kkI`!T?k'?k3c1?k3b1?k$1qRR4fXn5kTY.2R*R4qTkTY- LXg+Xmk| A+, *A/  )X-7@ETO4q@ TkTY-iYm}@ A)jY-O`E5q TkTK~~_03T9RHR+K@O@(RH4 @IK5S uS* X K K4H!1iQ Yi4_qT4 R R 3Yk} A)Y+kkI`!TI4K@q"T3IY(K@KK@KK93c#<@(qT)RBi9s4 R RsNҨYk} A)Y+k kI`!T)4BqTsIY(B9sz5Qh9s@@7_k  #@ ?A*H}@)145TK@ N4 R R3 XYk} A)Y+kkI`!T)4K@qT3IY(K@KK93Ac#K@ R*Kq9h} k `ӊ}SHuS L k KjH}STqhJ-8Q1T9%)(T@99q@9h4c*,+@kDcC[BSA{ƨ_R?ʚ;S {sR@RDR{ @S¨_ֿSTRRTSR+*{{_S[c{C*q**T~@*qTa~@*44***kh} y* HRC{cB[ASè_{jHO! I|`ҥh@jRI_)2I5;R I_)2I5;hrhHRh"h@h{A_{S[ck+ R+@kDcC[BSA{ƨ_vY?T(ci# 7 ֚*_!HTTT~A? ?qt”TTH9kщi8i*8I8kT@HA}~At? ?qT_Tjh8I9H9i8L~A? ?qT_TLH9kщi8i*8I8k~A? ?qTTH9kщi8i*8I8kTBT~A? ?qT (T~A? ?qT;˿)ThA? ?qTTTlH9kщi8i*8I8k7 @TIT(}A? ?4 ; ThA? ?4 @ W@ T_Twk{#7T9Txk{#_bT,7kѵ"W{# +@x@#{AR c9"9RR{_LTi8h84)щ i8h84)iJJJ9iT8 R9HR@R{R }@?BT?i*8qKT)@9i4-R+C( iil8!H *)@8hi,8)5 @9i4+R( )Cj!(il8_jT @8)5{_`S{RSSTC@ @h5` kkџ Th@9 e@9I7h @j @@94 @HC yI{èS_{S[ck+_CC*Eq***Ta~@kw5@ @Hs@*R)RRqA**A*x5R A? RT<LT|DIs/CtRB **!R* 4R***U 49P66 4kTD@****u59~ A? T<LT|D s/CsRGRhsB***[4*R*5R*5h_R kaT`BNR_R kaTBF*_CC+@kDcC[BSA{ƨ_D@*h_R kT`B2S[c{C****@****#@94@HC yIC{è@cB[ASĨ_{Sq{_{R6 h E ( R ?{_S[{$HTvE R ?ր4- 4ER?֠eR{[AS¨_{#y4S!RC`4#@yR{¨_S[c{"R**BR T TMT!d1RR*@ R*"*1TRs *mT@1T @qTR@i!*dRR*@T*!a ? 5R ( ?*@R*j*{cB[ASè_{ChA)j .?.?(G?g [H @y5@987OqQh qaTLR h qaTlRhqTRR)R K(! Q+  * L@9 SK@ S? qAzT kT 8qaT  * T? T)KKL y SI yRhKqiTADq"TR R(R) (yhkTOq__vHRCI{_{*Ax   jTRR9`_RR0`-R{A_{``R{A_{`@`@IC @5@h@ kT@Rh@ k@T R{¨A_{ C5 @C@h(25R R{¨A_f )L T@`T %RR#RH/&RaR;@TERRCR X X Tn`t \8pp \ \ \+qf }`r;g;{y*sz | \8 yOT \RX yBXu+qR }yK]Yg^`g^;"Tz \gR1:p.fRn2\ 2lȵm}\l ~\b(-Lgt+KiQ}@8b HA2u rymH::uP\VQU\q\st\KT7 yBUymFW\t wfY*u_VX }yc]Rg^`K^_ V]4x?Q#Ib?ƺ??^?AG;BIb?WUUUUU?B.?_D; R J}S rTPq T`qTpq `R R@RRr R_qRqR**qKRI*q**} SK * j *K*`*_(D;*r R_qRqOR**qJ*}SK * j *@ *_rTq TqT q  D; }@(X+Sjq Iq q *q @Iq * qI)* H D_`? r(D; X S KqjqHK qiq *I)D_`(94Th85_{s`DTH ( RdRbRR ?`{A_hD T _S[{AwD ** ?*5 ?qTD T ?H ( RdRbRR ?A * ?**{@[ASè_|S H2rKh2rjH2rKh2rjH 2rK rTPq T`qTpqTk2k2k2rh2`_*RjRTSH2rKh2rjH2rKh2r`L86 2_(D;}@(D{, *D; R J*,**  N*k T mSH2rKh2rjH2rKh2rjH2 rKrTq TqT qTk 2k 2k 2h2rRj @*DՋ{_S {*6@6 Ҥsz6H6@Ҟsz 6P6Ҙsz6X6u 6ґsz 6`6Ҍszq{ @S¨_{S[c#'yX (@`****bpg*5 qT@iR;(3#C**)"S(44@mm@Ac#`5*&@*"@5@V'@#@cC[BSA{Ũ_Mg_RS[c{  @** @? 6 @H@ 2I6 @H@ 2I6 @TH@ 2I6 @H@ 2I6 @H@ 2Ih@ @ HӨ @)*iJ*, J h@ @ I @)*iJ*, J h@ @ JӨ @ )*iJ*, J h@ @ K @)*iJ*, J h@ @ LӨ @)*h3 6 @H @ 2I 6 @H @ 2I 6 @H @ 2I 6 @H @ 2I 6 @H @ 2I h@  r`T_Pq`T_`qT_pq!T @H@ 2 *RJR @(@H3( @H@ uI @@ J*+-h J @H!@ 2I! @h!@V4 mi! @@P @ha@ 2ia @Ha@ mIa @@QQ)R( 3h! @@0 @Ha@ 2Ia @)Rha@( 3ha @@1)Z C"RR* ? @H @ 6h@ wiH @6h@ viH @6h@ uiH @6h@ tiH @6h@ siH@ r T?qT? qT? qTh@ j )h@(jhIh@ hi4PQ@Q)@{¨cB[ASè_ {qTQqT[HRX(R{_(D;}@ *D_D; R J!*-)|@|@  i D_(D; }@K+D_(D;}@_<@ R (I)@yL @y( a4 I@?@)TH@  ?@)Tk kJT_S{T`5R aˀ`R$@h* R{S_@yIR kaT<@ R (I@? kTH1@y R-q@TR_{h@ @+u(kj}@I*@! R{_!594  |j7p= S %Ț N2 NI>N*`TIH>N )I87(}@` Hw }@H S %Ț 1 )>N*TH }@Ӏ IilA< N0:n>N1 N(>N }S(>N +j}SA*AV4 N|=   N4n1 Nr N,>NO>N 7 S)>NJ%ȚH>N-  (!Q qSJ%ɚOL ҆RkL ki _ kcTl>Nk>NJ86ki  K H <4n Nq NR N+>N/>NM>NL>N) (!ʚ * KډR) K HQ(!Ț * `TI  K(}@ H__ BT@8!k`TBт_t3 @3.1 (>N ?qT(}@ _ @1gJ _A cT6 N_TpE@ir = mAn6n0=+A76nJT6n6nNN1N2:nH>N(_T_A#TpN_A"T_!Tp@3.1 (>N ?qT(}@` 53;nh>N(:n>Nh N  N :n>N N  N>Nh >N )(}@ 1 N(>Nh (>N )(}@i A J!k!h@8!k`TJъh__{(@@ {_a{@ @? k{_a{ ȨC@94( @c)ȱc*H@ kaTc( @?qTc(ȩ!@ kTc( !@ kTc(i!@ kT5c()3@.p ձS?S ߈{A_a csm !"{ @4 {_a{R* {_a{@ @| ՠ @ {_a{ @@ {_a{`R  {_a{ @@ {_a{@@$ {_a{@@ {_a{@ {_a{R {_a{ @@گ {_a{@@ {_a{R {_a{R {_a{R {_a{C@94`R {_a{@H4@@4i*Fh)@ yj*! I@9(H9`@c {_a{@ @R r? k{_a+"+:+T+p+~+++++++,",2,B,Z,h,x,,,,,,,,11111x1d1R18111--- .$.:.X.l.z........ /"/4/@/R/^/r////////00,0:0D0V0h0x000000000---L-:-&--\-l-&@4@83@3@Q@u@#@@@R@J@J@B@@@Ȫ@ت@ @ @ @ @ $@0@ @@ P@X@ h@ x@ @@@@@@@@@@@ī@ȫ@̫@Ы@ԫ@ث@ܫ@@@@@@@@@@@@ @@@@@ @$@(@,@0@4@8@<@@@ P@ `@h@x@@@@ج@@@8@X@#@@ ȭ@@&@0@@@D@P@`@#@@ @@Ю@@%@$@@%h@+@@ @"@(8@*h@@ @@ @@ذ@@@0@@ȫ@ܫ@@@@@P@__based(__cdecl__pascal__stdcall__thiscall__fastcall__vectorcall__clrcall__eabi__swift_1__swift_2__ptr64__restrict__unalignedrestrict( new delete=>><<!==!=[]operator->*++---+&->*/%<<=>>=,()~^|&&||*=+=-=/=%=>>=<<=&=|=^=`vftable'`vbtable'`vcall'`typeof'`local static guard'`string'`vbase destructor'`vector deleting destructor'`default constructor closure'`scalar deleting destructor'`vector constructor iterator'`vector destructor iterator'`vector vbase constructor iterator'`virtual displacement map'`eh vector constructor iterator'`eh vector destructor iterator'`eh vector vbase constructor iterator'`copy constructor closure'`udt returning'`EH`RTTI`local vftable'`local vftable constructor closure' new[] delete[]`omni callsig'`placement delete closure'`placement delete[] closure'`managed vector constructor iterator'`managed vector destructor iterator'`eh vector copy constructor iterator'`eh vector vbase copy constructor iterator'`dynamic initializer for '`dynamic atexit destructor for '`vector copy constructor iterator'`vector vbase copy constructor iterator'`managed vector copy constructor iterator'`local static thread guard'operator "" operator co_awaitoperator<=> Type Descriptor' Base Class Descriptor at ( Base Class Array' Class Hierarchy Descriptor' Complete Object Locator'`anonymous namespace'(null)(null)   mscoree.dllCorExitProcess0@@h@@:@:@p@@(.@X.@@@(@@@@@:@@:@`@@:@x@H@:@        ! 5A CPR S WY l m pr  )   Y* @@ @`@@@`@@@ @`@@@P@@@@ @0@x@api-ms-win-core-datetime-l1-1-1api-ms-win-core-fibers-l1-1-1api-ms-win-core-file-l1-2-2api-ms-win-core-localization-l1-2-1api-ms-win-core-localization-obsolete-l1-2-0api-ms-win-core-processthreads-l1-1-2api-ms-win-core-string-l1-1-0api-ms-win-core-synch-l1-2-0api-ms-win-core-sysinfo-l1-2-1api-ms-win-core-winrt-l1-1-0api-ms-win-core-xstate-l2-1-0api-ms-win-rtcore-ntuser-window-l1-1-0api-ms-win-security-systemfunctions-l1-1-0ext-ms-win-ntuser-dialogbox-l1-1-0ext-ms-win-ntuser-windowstation-l1-1-0advapi32kernel32ntdllapi-ms-win-appmodel-runtime-l1-1-2user32api-ms-ext-ms-AreFileApisANSICompareStringExFlsAllocFlsFreeFlsGetValueFlsSetValueInitializeCriticalSectionExLCMapStringExLocaleNameToLCIDAppPolicyGetProcessTerminationMethodccsUTF-8UTF-16LEUNICODE ((((( H   !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~ ((((( H ( 0@@@@@@@@@@@@@@@@INFinfNANnanNAN(SNAN)nan(snan)NAN(IND)nan(ind)e+000@@@@@@@@@@@@@@ @@@@@ @$@(@,@0@4@8@@@H@T@\@@d@l@t@@@@@@@@@@@@@@@ @(@0@@@P@`@x@@@@@@@@@@@@@@@@(@@@P@@`@p@@@@@@@@@@8@P@SunMonTueWedThuFriSatSundayMondayTuesdayWednesdayThursdayFridaySaturdayJanFebMarAprMayJunJulAugSepOctNovDecJanuaryFebruaryMarchAprilJuneJulyAugustSeptemberOctoberNovemberDecemberAMPMMM/dd/yydddd, MMMM dd, yyyyHH:mm:ssSunMonTueWedThuFriSatSundayMondayTuesdayWednesdayThursdayFridaySaturdayJanFebMarAprMayJunJulAugSepOctNovDecJanuaryFebruaryMarchAprilJuneJulyAugustSeptemberOctoberNovemberDecemberAMPMMM/dd/yydddd, MMMM dd, yyyyHH:mm:ssen-USja-JPzh-CNko-KRzh-TWuk@@@@@ @(@0@ 8@ @@ H@ P@ X@`@h@p@x@@@@@@@@@@@@@@ @!@"@#@$@%@&@'@) @*(@+0@,8@-@@/H@6P@7X@8`@9h@>p@?x@@@A@C@D@F@G@I@J@K@N@O@P@V@W@Z@e@@@@ @@0@@@P@`@ P@ p@ @ @@@@`@@@@@@@ @0@@@P@`@p@@ @!@"@#@$@%@&@'@)@* @+0@,@@-X@/h@2x@4@5@6@7@8@9@:@;@>@?@@(@A8@CH@D`@Ep@F@G@I@J@K@L@N@O@P@R@V @W0@Z@@eP@k`@lp@@@p@@ @ @ @@@@@ @8@,H@;`@>p@C@k@ @ @ @ @ @ @ @; @k 0@@@P@`@ p@ @ @@;@@@@ @ @ @ @;8@H@ X@ h@ x@@;@@ @ @@;@ @ @ (@; 8@$H@ $X@ $h@;$x@(@ (@ (@,@ ,@ ,@0@ 0@ 0@4@ 4(@ 48@8H@ 8X@<h@ <x@@@ @@ D@ H@ L@ P@|@|@arbgcazh-CHScsdadeelenesfifrhehuisitjakonlnoplptroruhrsksqsvthtruridbesletlvltfavihyazeumkafkafohimskkkyswuzttpagutateknmrsamnglkoksyrdivar-SAbg-BGca-EScs-CZda-DKde-DEel-GRfi-FIfr-FRhe-ILhu-HUis-ISit-ITnl-NLnb-NOpl-PLpt-BRro-ROru-RUhr-HRsk-SKsq-ALsv-SEth-THtr-TRur-PKid-IDuk-UAbe-BYsl-SIet-EElv-LVlt-LTfa-IRvi-VNhy-AMaz-AZ-Latneu-ESmk-MKtn-ZAxh-ZAzu-ZAaf-ZAka-GEfo-FOhi-INmt-MTse-NOms-MYkk-KZky-KGsw-KEuz-UZ-Latntt-RUbn-INpa-INgu-INta-INte-INkn-INml-INmr-INsa-INmn-MNcy-GBgl-ESkok-INsyr-SYdiv-MVquz-BOns-ZAmi-NZar-IQde-CHen-GBes-MXfr-BEit-CHnl-BEnn-NOpt-PTsr-SP-Latnsv-FIaz-AZ-Cyrlse-SEms-BNuz-UZ-Cyrlquz-ECar-EGzh-HKde-ATen-AUes-ESfr-CAsr-SP-Cyrlse-FIquz-PEar-LYzh-SGde-LUen-CAes-GTfr-CHhr-BAsmj-NOar-DZzh-MOde-LIen-NZes-CRfr-LUbs-BA-Latnsmj-SEar-MAen-IEes-PAfr-MCsr-BA-Latnsma-NOar-TNen-ZAes-DOsr-BA-Cyrlsma-SEar-OMen-JMes-VEsms-FIar-YEen-CBes-COsmn-FIar-SYen-BZes-PEar-JOen-TTes-ARar-LBen-ZWes-ECar-KWen-PHes-CLar-AEes-UYar-BHes-PYar-QAes-BOes-SVes-HNes-NIes-PRzh-CHTsr@BP@,@@q@P@`@p@@@@@@@@@@@C @0@@@8@)P@h@k@!@c@@D@}@@@E@@G@ @@H(@@@(@I8@H@@AX@0@h@J8@x@@@@@@@@@@@K(@8@@@ H@X@h@x@@@@@@@@@@@(@8@H@X@h@@#x@e@@*@l @&@hH@ @L`@.@sP@ @@@@M@@@>(@@78@X@ H@Nh@/X@t@h@x@Z`@ @O0@(@j@@ah@@Pp@@@Qx@@RX@-@rx@1@x@:@@@?(@8@S@2H@y@%X@g@$h@fx@H@+@m@@=@@;@p@0@@w@u@U@@@T(@@8@@6H@~@X@V@h@Wx@@@@@@X@@Y@<@@@v@@@[@"(@d8@H@X@h@x@@@@\@@@@@@@@]@3(@z@@8@@8H@@9X@@h@^x@n@@_@5@|@ @b@@`@4@@{(@'@i@o@(@8@H@X@h@x@F@paf-zaar-aear-bhar-dzar-egar-iqar-joar-kwar-lbar-lyar-maar-omar-qaar-saar-syar-tnar-yeaz-az-cyrlaz-az-latnbe-bybg-bgbn-inbs-ba-latnca-escs-czcy-gbda-dkde-atde-chde-dede-lide-ludiv-mvel-gren-auen-bzen-caen-cben-gben-ieen-jmen-nzen-phen-tten-usen-zaen-zwes-ares-boes-cles-coes-cres-does-eces-eses-gtes-hnes-mxes-nies-paes-pees-pres-pyes-sves-uyes-veet-eeeu-esfa-irfi-fifo-fofr-befr-cafr-chfr-frfr-lufr-mcgl-esgu-inhe-ilhi-inhr-bahr-hrhu-huhy-amid-idis-isit-chit-itja-jpka-gekk-kzkn-inkok-inko-krky-kglt-ltlv-lvmi-nzmk-mkml-inmn-mnmr-inms-bnms-mymt-mtnb-nonl-benl-nlnn-nons-zapa-inpl-plpt-brpt-ptquz-boquz-ecquz-pero-roru-rusa-inse-fise-nose-sesk-sksl-sisma-nosma-sesmj-nosmj-sesmn-fisms-fisq-alsr-ba-cyrlsr-ba-latnsr-sp-cyrlsr-sp-latnsv-fisv-sesw-kesyr-syta-inte-inth-thtn-zatr-trtt-ruuk-uaur-pkuz-uz-cyrluz-uz-latnvi-vnxh-zazh-chszh-chtzh-cnzh-hkzh-mozh-sgzh-twzu-za Tc-^k@tFМ, a\)cd4҇f;lDِe,BbE"&'O@V$gmsmrd'c%{pk>_nj f29.EZ%qVJ.C|!@Ί Ą' |Ô%I@T̿aYܫ\ DgR)`*! VG6K]_܀ @َЀk#cd8L2WBJa"=UD~ $s%rс@b;zO]3AOmm!3VV%(w;I-G 8NhU]i<$qE}A'JnWb쪉"f37>,ެdNj5jVg@;*xh2kůid&_U JW {,Ji)Ǫv6 UړǚK%v t:H孎cY˗i&>r䴆["93uzKG-wn@  _l%Bɝ s|-Ciu+-,W@zbjUUYԾX1EL9MLy;-"m^8{yrvxyN\lo};obwQ4Y+XW߯_w[R/=OB R E]B.4o?nz(wKgg;ɭVl H[=J6RMq! EJjت|Lu<@rd 6x)Q9%0+L ;<(wXC=sF|bt!ۮ.P9B4Ҁy7P,=87MsgmQĢR:#שsDp:RRN/M׫ Ob{!@fu)/wdq=v/}fL3. iLs&`@< q!-7ڊ1BALlȸ|Rabڇ3ah𔽚j-6zƞ) ?IϦw#[/r5D¨N2Lɭ3v2!L.2>p6\BF8҇i>o@@w,=q/ cQrFZ**F΍$'#+GK ŎQ1VÎX/4Bycg6fvPbag ;s?.❲ac*&pa%¹u !,`j;҉s}`+i7$fnIoۍut^6n16B(Ȏy$dAՙ,C瀢.=k=yICyJ"pפldNnEtTWtøBncW[5laQۺNPqc+/ޝ"^̯p?m- }oi^,dH94X<H'W&|.ڋu;-Hm~$P  %-5 > H R ] i u -C Y p        %  d'@Bʚ;01#INF1#QNAN1#SNAN1#IND??Xt?0 ?A??m?'?~?R?+M?0??ZR???'??$?\g?A)??@L?a???Ȇ?9??3?G?#??@D?M??@2?j?@Bt?@?X?R%?@>r?r? ?T??@_?01?^y?? ?(N??`??a?@?? -*?k?@@??B.?y|6>gsh>ƈ[^>/T,X>0S:zb>|si\a>酲j%M>zz[>ܥY#4o>wHn>G2k>)l>YHȆ>0AvVL>3/c>v ka>zzk>RUWk>q9a>de>B!Ѳm>dNpm>6/EiU>[ >$'<>F(o>BĮ^>&qN]k>YWm>bj ;>L!\>G[%_f>PHIZ>w_FGm>{]>`hj>>duo>3>ҾC`>\_>h_=N>qCF>WN@l>^b @>L-n>4b>\ngd>Z=@pn>6ނBg>?xb>8rZ>Oc>m>Bĩ*Vc>LHT>z\P>[0>'>m>[a>"߹oI>bAe>USY>kX>,HR>log10CONOUT$user32.dllMessageBoxTimeoutAMessageBoxTimeoutWFatal Error in LauncherFatal Error in LauncherrbFailed to open executableUnable to find an appended archive.Unable to read from file#!PATHEXT;PyLauncherSTATICJob creation failedJob information querying failedJob information setting failedmaking stdin inheritable failedmaking stdout inheritable failedmaking stderr inheritable failedUnable to create process using '%ls': %lscontrol handler setting failedFailed to get exit code of process.exe/usr/bin/envExpected to find a command ending in '.exe' in shebang line: %lsExpected to find whitespace after '/usr/bin/env': %lsUnable to find executable in environment: %lsExpected terminating double-quote for executable in shebang line: %ls<launcher_dir>\Terminating quote without starting quote for executable in shebang line: %lsFailed to find shebangExpected to find terminator in shebang lineExpected to decode shebang line using UTF-8Expected to find '#' at start of shebang lineExpected to find '!' following '#' in shebang lineExpected to find executable in shebang lineExpected to find arguments (even if empty) in shebang lineExpected to be able to allocate command line memory"%ls" %ls "%ls" %lsbZb b 8@@@N@RSDS=IפU+C:\Users\Vinay\Projects\simple_launcher\ARM64\Release\w64-arm.pdbGCTL.textHs.text$mnP.text$x.idata$5.00cfg.CRT$XCA.CRT$XCAA.CRT$XCZ.CRT$XIA .CRT$XIAA(.CRT$XIAC0.CRT$XICH.CRT$XIZP.CRT$XPAX.CRT$XPXh.CRT$XPXAp.CRT$XPZx.CRT$XTA.CRT$XTZt.rdata.rdata$zzzdbg.rtc$IAA.rtc$IZZ.rtc$TAA.rtc$TZZ .xdata'<.idata$2(.idata$3(.idata$4+.idata$6@ .dataJ8.bssp .pdataP.rsrc$01PQ.rsrc$02*Ȃ*Ȃ*،@،90Ȅ،BȂ&@tȂ&@@ -@(@hP\@Ђ$?P4x5Px555Px5 @4p?X78x8 @@@'@"@"!@@".P(@"?dBxBxBB@9@@0@ JɆȂ,JɆȂ,@0@ JɆȂ,JɆȂ,@@9H)@8!@ ɊȆB ɊȆBb0ɈȄ،P @"? FLF@VP$@Ȃ&? OlO̒O@P̒.P @Ȃ&?P4@Ȃ$?̎lDD0Ȅ، *P@Ȃ&?D`@@ɆȂ*J@?@Ȃ&R@G@Ȃ&5@0@CP>@Ђ$?L p"?d!pȂ$? lp"?dp"?8Dd ?H`` FpцȂ(?\ГNpцȂ(?TГp?̒5pȂ$?01p"?;pȂ$?4p?@@Ђ$;@6@Ђ$.@*@"p"?xdp"?Phdp"?dp"?ds@E@Ђ$/7*52@ @@@ɆȂ(=`$@ @"h`Ђ$@@ɆȂ*d@@цȂ(PшȄ،B0،J`$0Ȅ،2P-@Ђ$?X\@+@Ȃ$0،VpȂ$?8d@@ɆȂ(:@@Ȃ$o0 ɈȄ،P@"?,,xp?/0P%@цȂ(?D2233p"?L0h0d-p"?4l5 FpцȂ(?t??Г0 ɈȄ،40 ɈȄ،N0Ȅ،L0Ȅ،_0 ɈȄ،6P @Ђ$?CC@@@Ȃ&@@ɆȂ(j@F@ɆȂ**@@Ђ$gP V ɈȄ،@=Ȃ$P  ɈȄ،8@@+P'،P ɈȄ،r0،NP D шȄ،p"?@@ @@ @@@BB c(-*z-*-+"+:+T+p+~+++++++,",2,B,Z,h,x,,,,,,,,11111x1d1R18111--- .$.:.X.l.z........ /"/4/@/R/^/r////////00,0:0D0V0h0x000000000---L-:-&--\-l-GetStartupInfoWSetConsoleCtrlHandler.SetInformationJobObjectAQueryInformationJobObjectSearchPathW-SetHandleInformationGetCommandLineWGetStdHandle$AssignProcessToJobObjecttGetModuleFileNameWGetTempPathWMultiByteToWideCharFormatMessageWaGetLastErrorLoadLibraryAWaitForSingleObjectExCloseHandleKSetStdHandle SetCurrentDirectoryWGetProcAddressCreateJobObjectA`ExitProcessCreateProcessWFreeLibraryOGetFileType=GetExitCodeProcessKERNEL32.dllPeekMessageWWaitForInputIdlevCreateWindowExWDestroyWindowPostMessageWGetMessageWUSER32.dll=PathCombineWPathRemoveFileSpecWOStrStrIWSHLWAPI.dllDQueryPerformanceCounterGetCurrentProcessIdGetCurrentThreadIdGetSystemTimeAsFileTimebInitializeSListHeadnSetUnhandledExceptionFilterxGetModuleHandleWRtlUnwindEx1SetLastErrorFlsAllocFlsGetValueFlsSetValueFlsFree3EnterCriticalSectionLeaveCriticalSection_InitializeCriticalSectionExDeleteCriticalSectionYRaiseExceptionjReadFileGetCommandLineAWriteFileGetCurrentProcessTerminateProcesswGetModuleHandleExW#SetFilePointerExGetConsoleModegReadConsoleWDHeapAllocHHeapFree^InitializeCriticalSectionAndSpinCountTlsAllocTlsGetValueTlsSetValueTlsFreeLoadLibraryExWCompareStringWLCMapStringWGetStringTypeWwFindClose}FindFirstFileExWFindNextFileWIsValidCodePageGetACPGetOEMCPGetCPInfoWideCharToMultiByte8GetEnvironmentStringsWFreeEnvironmentStringsWSetEnvironmentVariableWGetProcessHeapFlushFileBuffersGetConsoleOutputCPCreateFileWMHeapSizeKHeapReAllocSetEndOfFileWriteConsoleW2-+] f     @@D@D@D@D@D@PI@p@@@B@D@C abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`y!@~ڣ @ڣ AϢ[@~QQ^ _j21~I@ Z@ Z@ Z@ Z@ Z@ Z@ Z@ Z@ Z@I@Z@Z@Z@Z@Z@Z@Z@..uKDPH,d8p@`  #|#`#$#''0'P'' ']x]P  !`!f!Qe8$ eH%]%&]a&UbP'fH)0*,-e /,2<23 383X34(4h55P6]6P7 89aX8Ea8Ia89b8:5:Y`;};0<e<e?iA=A%BBB C@CXDyaDEaEUpEmeE`FebGIKfLeLPMfN@Pe0QQ!RuS`TaaTbXUUyXVXWW bX- ZH[A[]4^H_pcmecmePdafdfdhg5bi)bla@mopectdPwwxz|a}c~5f=bxP@deeEЊE|P@Eh5UbxMȓAe( ȗ x e]bQ؝qaH}b@yb< L (UbUbbxbqex]ئ x @h \ AQ 1`ЬapMЭ !`f Iix=A`H-xMa]b`]a%E0$!hH!Hȸaax))P!cix!gIi!Eb`aapyme%ieb@Yaimbqbpqbhiue!!"a`bea(ia," P"h(-`X|""0""-a`mbH`"8p"M(9a`-`0-``u"MaMaPqi8#fg,#pL#h1dYf`@X#hd#p# #X ###8$aHb$$$P#qb!#8(%eP \$P"p$@#E`#-b$bh%Q%a&$)Qe+Ia,$-yb-(.1h.e(/a/Qb/$00 %01`01$3}4f4@%5(6%:%0<%`=%>`%?%(C&DL&F<&`I8JNaa@OIO`&0QRRQ0T& X&it&hjjfHkak&p&p&qbHr'Puu!vMhv0w=`pwf y$'zyah{1a{e|]eae4'=0T'،9Px  ̒  D dГ  4X x  0&@t'0x 8Pehf 0 @P8(`hp%h(h}( @...GGGWWWiiiuuu ̀ ww ww||wwwwwwwwfwwwww|fwwwwwwffwwwwwwffwwwwwfflwwffffffwfffffflwffffffwffffff|ffffffffR\Ilffl)ffح2L18Ϥ"A< ΣTπU  ؀ ???????( 444MMMSSSzzzp p{p{ffkof_fffk_fff[f}UUUfL4UUU_kJqO_~'[U_Uwwwww{~( @%%%+++000===AAAIIIMMMQQQVVVYYY]]]~dFgggjjjmmmrrruuu}}}u?z=~;gEkCoAr@:IbpƏKУk(,1?5<\CKQsU[cbð/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq_______CCBABADL*(%$""&GABJIIDDJC??AD_-(''%$GG#_DIJIIJIIIDIJDD_+)(''%GG _JMCEJ?ABAECA?BA_,+)('%%"!_IM@AJACDCIDI____.,++>________JMJJJJJJJJJF_..,+)('%$"! #GIMCEJJJI?J__...,+(('%$"! &_JM?AJJJJCJ__...,+)(''%$" _IMJJJJJJJJ_K=...,++(''%$! _JMCDJJJJAJ_K_______L*%$"!_I_?AMNJJEJ_ L'%$"_I_MMMMMMJJ_ _('%%_I_CEMIJJMEM _(''*_I_?BMDJEMDMF _)(-LI_MMMCMMMMM_________< ____I_CEM1;MACDMEJM__I_?A_D1@IJJMJMM_FF_I__6_@/EMMMNMM_FF_I_C80EJDMJCJFF?:AA20?_M_MIEM______MIC4BC/ N______MMMMJIDAI_9/ HJJMMJMMMJ??BAI_A7503 IJMMCBCBA44CCI___BAO;6___C_MJIDIDMI_CI___O; @__C_C?J?AJI_?A_DJJI@ O_CM?;I?JI_________H9MAIJJIJI_CD_____JJI73C;IJIJI_?AMDJJDIMJIE5:JIJI__________MMIA:IJJJJJJJJJJJJJJJJMJ???( 444MMMRRRYYY~eGuuu}}}x>iErCE~7?HVbx/!P7pLcyϏ1Qq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq!%///* !*/"(%%'%/) /!#''////+/+"*(/ / !%(/ ( (*/ (///) */   )*  /"///// /+/// # + !%//  +"*+'#+//+'!%*++$++!+! !!!<(0`  !!$& &"   WܱNj+<<:5pi$!!!4888&SNJFB=950,(%%%5ǎVRNIEѹ|Jq?o@ZUQMID@<73/+&"-Ɍ444"ք̴wq?]YUPLHC?;72.*%!"""4888&dya\XTPKGC>:61-)% OÑ%%%5ʎA{;ys`\WSOJFB>951,($(9|:z;xq?o@mAkBiCgCdDuZ;73.*Ǒ%%%5̎È>89}:{;yq?o@mAjBhCfD~dEuZ?:625Оa789}:{;yr>p?n@lAjBhCfD}dEB>:5^ό555"܄ѳ„7789}:{;xr?p@n@lAjBgCeDFA=9"""4;;;&ŌC89~9|:z;xr?p@mAkAiBgCIELɑQQQJnnn~~~Įq?o@mAkBiCHHH"""vvv9~:{;yq?o@mAkB:::'''___sssttt8Ϧuǣvyp?n@lA888T---6fffnddd%%%"""OOOSSSċDҧuʤv{;yr?p?xM<<r?̷999VVV###)))444ѳ͝b@~9|:z;}Cg˶Ӎ󑑑ZZZlll;;;!!!""")))___&&&2:::ppp{{{___WWWWWW:::!!!"""***www"""4888&,̑999ԖJJJ|||lll:::"""BBBXXX|||$$$6,uuuKKKCCC@@@...www* Ҍ666"KKKaaaLLLWWW@$"""4;;;&nnnfff!!!$$$999444]ttt|ȇɑ&&&5ЎvvvmmmVVV{{{OOOڥaaaCCCpppό555"ބ|||MMMfff"""4;;;&\\\Ǒ%%%5Ύݧܦ͌555"ۄۥ"""4:::&ڤÑ%%%5ˎܤׇ ww?ww??ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwwwwwwwwwwwwwww?ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwww?wwwwwwww( @ !!!0\\\JeeeQ666D555A0$ vH<602\7qڴvvvj&&&D1"gLF@;5?кVQKE?9(aV[UPJD>82-uuun{{{j`ZTOVo@lAb_YSMGB<60+%<aVmt>q?n@bb^XRLFA;5/)$Ywwwn}}}j~Cv=s>p?bba]WQKE@:4.(+{;xbba[VPJD?93-'aV~:{;xr?o@lAiBfD}dE|cE|cEiB<61?9}:z;w=t>q?n@kAiCfD}cE|cE}dFGA;=aVңi89|:yq?n@kBhCeD|cE|cELF@mzzznjʓP8~9{;yp?mAjBgCeD|cEPJ\¯mAjBgC~dEaV}:z;xq?n@lAiCJJJőPyq?n@~YVVV???wwwiiiΡkA|;y>>3@@@y~~~obbb000kkk@@@---U<<<&&&[[[6uuusssWWWIII$$$&&&qqqlllg|||s4bbbhhhƑUUU000QQQ}}}---{bbb(iiiLLLBBBaVggg+++NNN|||nj]]]tttkkkrrrwwwӦaVӦzzznjӦˉ ?????(  C﫫oIਨS=?_QF4aZмoEcaXMB70~Iq?c`VK@58z;u=qv>3Eyn@iB~dE}dFGHYYYuuuġ=xmAhC}dE}eGS444|||gC}cEMMM~>q?kAiGRRR¢Gu=xKʺ~AAAAAAAAAAAAAAAA ( h00 %   h4VS_VERSION_INFO?fStringFileInfoB080904b0JCompanyNameSimple Launcher User^FileDescriptionSimple Launcher Executable2 FileVersion1.1.0.140InternalNamew32.exej#LegalCopyrightCopyright (C) Simple Launcher User8OriginalFilenamew32.exe@ProductNameSimple Launcher6 ProductVersion1.1.0.14DVarFileInfo$Translation   (08@X`hУ 0@P`pФ 0@P`pХ 0@P`pЦ 0@P`pЧ 0@P`pШ 0@P`pЩ 0@P`pdȦЦئ (0HXhpx (08@HPX`hpx (08@HPX`hpx (08@HPX`hpxȩЩة (0@HPX`hpxȪЪت (08@HPX`hpxȯد(8HXhxȠؠ(8HXhxȡء(8HXhxȢآ(8HXhxȣأ(8HXhxȤؤ(8HXhxȥإ(8HXhxȦئ(8HXhxȧا(8HXhxȨب(8HXhxȩة(8HXhxȪت(8HXhxȫث(8HXhxȬج(8HXhxȭح 0@P`pЪ 0@P`pЫ 0@P`pЬ 0@P`pЭ 0@P`pЮ 0@P`pЯ 0@P`pР 0@P`pС 0@P`pТ 0@P`pУ 0@P`pФ 0@P`pХ 0@P`pЦ 0@P`pЧ 0h@@H(HhPX`hpxȩЩة././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659773740.0 distlib-0.4.3/distlib/w64.exe0000600000175000017500000030700014273421454015021 0ustar00vinayvinayMZ@ !L!This program cannot be run in DOS mode. $ܭg*ddd,!dd(d/RichPEd b"  F@@8PS d.text `.rdata9:@@.data0A0@.pdata  *@@.rsrcST6@@.relocJ@BLISMCMK SWHHH#H3H$0LʺICHL$0DB3HD$ =.H 6HHHNNHu,HtJH=HH-NHu H#L.HT$0A3D$( f|$ й7̅LISMCMK SWHHH"H3H$0LʺICHL$0DB3HD$ )cH j|HHzMHu,HtJHYHHHYMHu H#LHT$0A3D$( f|$ йkH\$Ht$WH  2HH3LLBILHt1HA;IDII+LD9Ht$8HH\$0H _H\$Ht$H|$ UATAUAVAWHH@LLHpBHM8E3Le80A̅HxHM8ED$3-HM8$HM8LcAAHE3,LM8E|$AIDIHHHtHMED$HœHcI+HHM8AHAHE3a,LM8DIHHHxAHHHHMHAFHcHH+HIEHM8E3EH++LM8LIIAHH.AEf;uMfHM8AHE3+LM8LIIAHHHN\+M;rfA9t M+M;sMHM8h.L\$@II[0Is@I{HIA_A^A]A\]H\$UVWH03HW.H|$`bH-GBHt-AE3H3H|$(Hl$ }LHT$hHL$X+HT$hHL$XuRHL$X$ALH3H|$(Hl$ %u3LD$`HHuHL$XH\$PHH#H0_^]H(t v1w,H >Ht 5> L>A H(@SHE3E333;HL$`E3E33HH I> Hd$XHd$PHd$HHD$@d$8d$0d$(d$ LHE33d$ HL$`HE3E3HHHĐ[@SH Hqt#uuHDH [H%H [H\$WH HH HHx 1"HHHHH\$0H _HHXHhHpHx ATAUAVH JHH:HH0uH)HH`dHHtH;GH;>HtH;/H;&HtH;H;LLHH*HH0*E3I;tI;u3ҹMI;tI;u3ҹMH;tH;u3ҹII;MDI;MDH;IDMtItI3ҹuMtItI3ҹTHtHtHh3ҹ3H\$@Hl$HHt$PH|$XH A^A]A\HHXHpHx UATAUHH HH3H L333HHH;H ;L\$P L ;DˋL\$ Lt 9\$PuO3HH : :0L:DˋHj3HL$`DBh]HL$`Eu}HHHHxHaHbHkHLH}mHuH}H]HD$`H9E3H\$HHD$@Hd$8Hd$0d$(E3I3D$ uKCHd$0HAD3DL$(HL$ LHM3nH_9H P9f}r{HUHtrHc:EHLH;raH HtH^L$LI;s1HH+H;tH;tH;t HH(HHI;rIH8HUй<=w HMqH WH]HH 8DH m8E3H Z8HT$PHXL$P?H\$Hl$Ht$WATAVH HHJHE3AHHtDKft+f"t%AuHHKHHuHt IHHA H AHL\HwftADuAHL-Hft AuHfD9&t Au HfD9#u AtfD#HHLAHHLHf?"u)f;"AHLfD#HHHHAH t f"tf;"AHLQ ft%A=tfD#A(t HfuH]H\$@Hl$HHHt$PH A^A\_HHXHpHxUATAUAVAWH8HHH3H"f98tOHH3DfHu L-\#LhAutIAEfuH46A3f9=6t L56 L5 6HHpfA4V %POHHHHHHH;s9 t9 t HH;rHHH@7HD$P+LAԹDD$(HD$ 8΅HHcft|PH|$Pf9t$PtAft Hf97uf?#HHA7t Hfuf?!HAt HfuHT$@HHt$@HHLL|$@MHH 7DIu|Li4H`X!H`kSfA9T$u*IL$ ZWHHMHEf7HcH`HMD L3IIIɍPfIHLAIfIHHLIfIHHLIfHI\HHHH@ƋLl$8Lt$0L -LHHL|$(Ld$ a HAH({ffH; QuHfuH$H\$Ht$WATAUH0Hd$ EHHE3AHu&,3AHtAfD9*tT*HHD$PHu +fD9.u"+H HL$ \,3LEHH!'HH HH\$XHt$`H0A]A\_@SH ILHHuc+$AHIHHt31+H [ f;tHfuf;uH3@SH H HtHHHtH [@SH ̹-̹,@SH 0HH0HH~0Hn0H.HH [-H;s-H\$WH HHHHtHH;rH\$0H _H\$WH 3HHH;suH HtHH;rH\$0H _H\$WH H= MtH L5tL%5HH ~uZH 4z4HH=HHtHH;rH=LtH L5tE33APL3H\$0H _H\$Ht$DD$WATAUAVAWH@ED,=&D%H LHHD$0HH KHHD$ LHt$(LHD$8HH|$ H;rp.H9uH;r_HH.HH KHH pKzL;uL;tLH\$(HH\$0LHD$8HHD$ HH pHH }Et*Eu&)AAH\$pHt$xH@A_A^A]A\_E33fE3APX33DBK̺3D9@SH g64E3APHt7SH LH +3`u#'H&H [@SH d$@LD$@6HHu9D$@t&Ht &L$@HH [@SH HAHui.HCHHHHKH;tuBHHH9CtHC u 9HCHCuCHH [HHXHhHpHx ATH@E3IHHAMHu%z HtHL$ IL\$ E9cu?H+>fAr fZwf fAr fZwf HHt@ft;f;t4HT$ gAHT$ WAHHHt ftf;t+D8d$8t HL$0H\$PHl$XHt$`H|$hH@A\H(3LL9)uwMtzHu$z H(HtL+C fAr fZwf AfAr fZwf IIt ftf;t+ȋH(E3UH(H\$Ht$WH HHw|HHEH (Hu ?30[H (L3QHHu,9(tH*t # # H)# 3H\$0Ht$8H _H@SH Fu;LHcȺFCHy6Hu$PHˉxFCH\6Huv3HWHH0HHt H/6E3HOEHIL4IHIHkXLIt ItMuIH0Iu3H [H(F=tyDH 5H(a@SH HH H;r>H0H;w2HH*H+HHHH?Lt%kH [HK0H [H%@SH Hڃ}B%kH [HJ0H [H%WHH;r5HH;w)qH+H*HHHH?L#H0H%} r#HJ0H%Mt 8tHIuIHH#@UAUAVH@Hl$0H]0Hu8H}@LeHH>H3HEE3MHHHuE!oHH;rfD3IEHtTALúDt$(Lt$ GLcu? * fAr fZwf fHfD93u3II;sfD3 "]~g3HBHHrXKL$Hw1HAH;w HHGH+H|$0Ht)HHtHIHu% /IEALËHDd$(H|$ FtLHH *HO9u}HMH3;H]0Hu8H}@LeHHeA^A]]H\$WH@HHHL$ ILD$ HH|$8t HL$0H\$PH@_E3LMK H8IC(ICIcH8H\$ UVWATAUAVAWH LLHL$hHMMHHT$pMtMtMu'u3H\$xH A_A^A]A\_^]H$Ht3HIL;v)HtL3IHHHt3HIL;wIIG HtDO$ADL$`HXG At^LcwEtP I;DBEL;LHHL$hRD)wALt$hH+HHL$pDL$`LH+Lt$hEI;rhEt 3I;v AEA HDID+ DI;EGAH;woHELt$hI֋oPtkH+vHGtXHL$pHt'ADO$HIHDL$`Lt$hHL$pHHt L3IF"5O H+3HI(OIHHXHpHxL` AUH0IMHLMt`Mt[H\$`Hu"Ht L3'F3/HH\$ LMHINHHWH3H\$@Ht$HH|$PLd$XH0A]H8LL$ MLH@H8fL$SH f;u3Ef;sHH&LL$@HT$0D+3ɅtL$@#H [H(HuCAH(H H\$Ht$UWATH$HHDH3HAكtOd$pHL$t3ADL\$pHEHML\$HHD$PLHT$@IE3Ht7Hd$8HT$@HL$`HL$0HL$XLHL$(HMMHL$ 3躱HHHHHt$p|$tHE3ɋHL$Hؽuu tNHH3L$I[(Is0IA\_]H\$WH HH XRHHH?HH\$0H _H(AAH`HH(H%gH\$Hl$Ht$WH0HH AIHϼDLHHHt!LT$`LT$ H\$@Hl$HHt$PH0_HD$`HD$ ^H8Hd$ E3E333wH8H(E3MLMu)3HtHu5IHu-IfE9tEfD;tHDfEufD9t HfuH+IfE9tEfD;tHDfEufD9u HfD9ufDHH;I IDH(HHXHhHpHx ATAUAVH HHuVZ{Lc}c3ADBOMxҋSu +s;L(+kt=Gu'tt u 3HDH 37 L H\$0Hl$8H _H(u&  Mx1; %s)HcH %HƒHHkXHDtH  IHH(HHXHpHxL` AVH HcLIL5j%HkXK4|3 u4O k|3 uHL3#D3 ?tK HL\H\$0Ht$8H|$@Ld$HH A^HcH $HƒHHkXHHLH%H\$Ht$H|$ATAUAVH@HDE3^ u3H{$|$$@LcJHH\$0JH H;C{ u< L{ u!HKƶu DhDl$(C EudHK;Ct HK3HHEu?CH3L#K+H.袋.HLIIH?LDDd$ HXHH#D;XJ2HD$0HtsHcLL#IЃ%# I H H;s@H0@ ` HXHD$0|$ HcHHHkXID[DD|$ AH\$`Ht$hH|$pH@A^A]A\H\$Ht$H|$UATAUHHP3MLHHMDC(3IH];HuMtHtEBHuHuI?v EC$ELMHLE@HMHAՋHt\xIMxHEЈHEHHEHU38t!HEMxHU38t9]fB\fÍCL\$PI[ Is(I{0IA]A\]H\$Hl$Ht$WATAUH0E3IIHHMMuHuH3HHI;vPFLFH oGHD$xLHD$(HD$pHHD$ Rup8"H #GLD HD$xLHD$(HD$pHHD$ fDl_uHu8"u4D *y)fD/u" H\$PHl$XHt$`H0A]A\_H8HD$`HD$(Hd$ H8H\$Hl$Ht$WH AAH٨uj3AAu vHV1D[Ey AD[AtAtA rC$HDNj֋C@ōEH\$0Hl$8Ht$@H _H\$Ht$HL$WH A؋H3Hu[-t tuDËHHH\$8Ht$@H _H\$Ht$WH =1HHHu3H9tu;THRy &RuWHHtKHtFHQHH Ht3QH;vH f &A@tavH5HH\$8H _H\$WH HcHtYHu @u ;u@`tHH;tHsu 13ۋLHHAH_H MkXBDt 3H\$0H _H\$L$VWATH HcكuB  xe;s]HHHL%HkXIL8t76ID8t   .H\$PH A\_^H\$WHHL$0AMZH=kfD9ct31HcHǁ8PEu f9Hu3ۃv 9É$Tu"=Euu"=ui%y 2HHXVHTUy ARy qtQD$lT$pA DEL3HD$ uo$uawH$HĠ_H(VH(RHL$HH 7HHD$XE3HT$`HL$X荞HD$PH|$PtAHD$8HD$HHD$0HD$@HD$(HXHD$ LL$PLD$XHT$`3;"H$H$H$HHH H{H$H|R LHHD$hHHD$pBR;3"H  =u *;4 HHĈLI[IkIs ISWATAUAVAWH@MyM1AIy8M+MLHfIcqHIKMCH;7HH\ CL;CL;{;tHL$0IIЅ~t}csmu(H=tH tHKAIInID$@SLcMHD$(ID$(ILIHD$ $oH;793MA 3E3M+Ǩ t;39v5HOAL;rL;v H;sHLul DIcqHH;7sUHHHߋCL;r9CL;s1EtD;+t1t;kt(;uHT$xFAD$HDCMAH;7rL\$@I[0Ik@IsHIA_A^A]A\_H\$Hl$Ht$ WATAUAVAWH05 E3IE~ EHLEEEfD9:u HfD9;tAat0rt#wt3C AA  HAfȃSytjA+ tGt>t' tuEEЃ@@@E|@uvpEubEA cTtOtHfDHL$HT$DD$I ffH\$Ht$H|$ATH L%@33Iu&HcƺH H~H HQt&H$|ɸH\$0Ht$8H|$@H A\HcHI$3H\$Hl$Ht$WH $HH+Ht{tHHH#HHuHHKHt ;uǡHHuH\$0Hl$8Ht$@H _HcHFHH H% H\$Ht$H|$AUH HcپH=u ND HHL-I|ty(HHuf 3X fHI|u-ߠuH* 3 I\H RH\$0Ht$8H|$@H A]H\$WH HcH=<HH<uuHMH H\$0H _H%H([HHtTH(H(H H`H(H YH ZH [H \H IH%bH\$Ht$WATAUAVAWH03|$`3уtbtMtXtSt.tt5v@L%H L%H |L%H lHHurHHLc9YtHIHHH;rIHHH;s9Yt3LaM,$ L%8H 1|$`RLIu3Mu AMl̅t3t t tL|$(,LL|$(HuDdžDt$`u9 ͸щL$ Ÿ;}*HcHHHd‰T$ I$t3v;u AA;t t L; DH\$hHt$pH0A_A^A]A\_H H H @SH HH HtHЅt3H [3H%7H( Bt o 0H(H\$WH HHHͶHaAǁƁtCƁCH<H ;H & HHuHXHHe H\$0H _H\$WH \ VvHHuHHHHt3 +HRH˅t3VHK3ۋ6HH\$0H _@SH qHHuHHH [H)H\$WH HHI8HtHKHHtHKXHtHKhHtHKpHt|HKxHtnHHt]HHH;tE HHL$0Ht uHgHL$0H;t ` VHHt+H=H;=tH!H;t?u H HH\$8H _@SH Et`H itHHHt1 GHnt3H rHK3H [@SH JHHՙHfHWHuCH#3H [H\$Ht$H|$ATAUAVH LOH !LH HI;HI+LoIIQHI;sUH;HBHH;rII3Hu3HV H;rII-HtKHcu K 4@t "3t{HCHKC{C u/ӳH0H;tųH`H;u ]uHQ]C+HS+kHBHC$ȉC~DŋMW K?t#tHHHHHkXH H dA t3ҋDB[HHKD$0HT$0DkM;D$0H\$8Hl$@Ht$HH _ffHIrSIII@rHكtL+HHMI?Iu9MIItfffHHIuMt HIu@fffffIs0HHQHQH@HQHQIHQHQHQufDHHQHQH@HQHQIHQHQHQu $TH\$Ht$WH HHut A@t AA u [HAHHcDG$HWGGu_H2t6HtDl18AEAADm`tu ШtL`ШtA;AB؋LHu# 8MH3DBsWL LK^L\0@K^IA D0HL0 A:AK^AAIVE`DD0 EtbK^L09A:tQtM K^AHE`DD09Au.K^L0:A:tt K^HE`ADD0:K^LMDH 1H|$ gvHcU܅|H;qLDK^D05At A> uL0d0IcIMIHEL;A AE<A:t HIHEHL;sIE8 uI~LK^LMHUXH 1AIH|$ uu suf9}taL)K^D0Ht}X tD#K^EXD1 ;I;u }X u +MHAH>U}X LӂtLȂD#HL;m K^D0@uL0 AEHDE+}`EHuHI;rHB8At BAu"*u;uHcH]K^D0Ht;HÈL0 |K^HÈD19uK^HÈD1:HcH+MHAHcTELmPA+DMƉD$(3ҹLl$ qDur;Ë]HmJ^@E|0HҺ t fA9uL0d0IcIMIHE`L;eA AEf.fA;tfHI HE`HL;sIEf9u ILK^LMHUH 1AIH|$ ru,q9}LK^D0Ht; f9Ut@fD#EK^D1 EK^D19K^T0:LI;u f9Uuf7MHHDBrR Lf9Ut LfD#HL;m`"K^D0@uL0 AEfHDE+]LmPM;tIFAD܋Y!pus Amu뵋,3_88 ͳHXA_A^A]A\_^[]H\$Ht$L$WATAUAVAWH ALHcu 3ۉ 3ۅ;=LLIL=AMkXKBL tiAu|WͷKBD tDIՋJ _;J# 踲H\$XHt$`H A_A^A]A\_H\$Ht$WH IIHMu3VHue S .=  誰H\$XH A^A]A\_^@SH B@It HzuAxPf;u H [̅~LH\$Hl$Ht$WH IILH?tH\$0Hl$8Ht$@H _H\$Hl$Ht$WATAUH A@@H\$`ID#ILt IxuAC#~9AMLHI?u;*u?LH̃;uD#H\$@Hl$HHt$PH A]A\_H\$UVWATAUAVAWH$ HHH3H3HHL$xHUHMIMLL$PD$pDD$XD$HD$LD$lD$D虡XE3HEHu,GܮE3D8]t HE\ LEMtE(AEELUT$@fE! H]NYD^!ILE AfA+f;wH ALAHcH IcHHՆD ADL$hA AEJM E'P AŹd;5ACQEGSX{ZtOac"Dl$D-D9T$l]A@/Af|$\|$LIILt$PHt9HXHt0-A sDoԙDl$D+DD8EDT$DHH"E3L_A0uE 9t$HIDILt$PESHEHDHD8HU!PE3҅tHAHD;|A0uE AIAfD$`Dl$DLt$PEt4D$dHEDT$eLc LMHT$dHM3NE3҅y Dl$lfEH]E}EfEgA@H]Ћ\AD|$He8g;~ǹi; no;ptasu;x;AHHAHDkDl$DH fD9tHuH+HDAAD$pEi Ey|fQfDl$\AQfD$^kAEyQD LI>ILt$PLE3҅D$@EZ EtfD$lA@A A0T$LDt MI9A rIEtLt$PA@tMFEFA@tMcFEFLt$PA@t MyIADu A rEy A;ODt$pIHH#ʉL$LυMt 3IIcHLB09~AƈHLt$PH|$H+HDDt D8+HAD+ufD;u?A;AO|$HA;~']HcMHEHhH؋D|$HDIH _ILt$PAHcHEeHMHL$0MDωL$(HMLHD|$ AtEuH "LeHUHйgfD;uuH 'eHUHп-@8;uAHH E3D-Dl$DAt+fD$\At fD$\x|$L |$L Dt$XHt$xE+D+A uLL$@LAHELL$@HL$\LƋHD$ AtAuLL$@0LAhEufE~aHAHELMHL$`Lc HIE3Lc~%HT$xL$`LD$@IE3҅Ht$x-Ht$xT$@#HELL$@LAHHD$ 5E3ҋT$@x_AtYLL$@ LALt$PE3A T$@HEHSH|$HE3LUT$@A 5Lt$PA AŃItPhtCl;tXwA fA9XuIA AE AfA86ufAx4uIAfA83ufAx2uIAdfA9ifA9{ofA9luHfA9_A fA9RfA9HDT$hHT$xLD$@AD$D$E3fA*u(A>IXLt$P|$Ht$H Aō|HЉ|$HADT$HfA*u,AIXLt$PD$XA؉D$XD$X AōDHЉD$XAA;t>#t2+;t#-;t0;XuWAQAFA@A9A3DUDT$lDT$XDT$LEt$HDT$D|$HDL$hLEXE(fEEtAu=D8Ut HMHH39H$0HA_A^A]A\_^]蕽*E3D8]t HEH\$Hl$Ht$WHP3IHHMu3Hu;УHtIwHL$0IGL\$0AKuGH+fAr fZwf  fAr fZwf HHt ftf;t+GDLǺt$(H\$ jGu'蕼@8l$HTHD$@C@8l$Ht HL$@H\$`Hl$hHt$pHP_H(E3LD9 uxHu+H(HtIwL+AfAr fZwf  fAr fZwf HIt ftf;t+H(H(DHHfuH+HHH\$Hl$WH0Hd$@HhHHtud$(Hd$ DL33]HctxHϺnHD$@HtaLD33ɉ|$(HD$ ]t:HL$@3FxHHHu3H\$HHl$PH0_HL$@Ht HL$@菓HHXHhHpHx ATH0HE3AHuf=tHH\CfuGHcHHbHtHfD9#tSHf;=pt.HcH_HHtxLHHѭuQHHcHCfD9#uH=H豒L%.L'3H\$@Hl$HHt$PH|$XH0A\E3E333Ld$ 谟H _L%H\$Hl$Ht$WH ]3HHtLf9tHf9uHf9u+ǃHcHHHtLHHHH]HH\$0Hl$8Ht$@H _@SH AHt"AtHI誑c3HHCCH [H(H8csmu+xu%@ = t=!t="t=@u3H(H(H [3H(H\$Hl$Ht$WH H薿E3HHHH99tHHH;rHH;s99tIHRLAMEIu LIA@4Iu&HHy0HHLLH|9uǃ9uǃ9u ǃz9u ǃf9u ǃR9u ǃ>9u ǃ*9u ǃ9ǺD‰AЉ LIIAH3H\$0Hl$8Ht$@H _H\$WH 39=uHHkHHD؀; w ;t3t);"u 3 FtHH< w HÊuHH\$0H _H\$Hl$Ht$WH0=AujH;3Hu<=tHH\uGHcHHHtH;tPH;=pt.HcHHHtsLHH uKHHcH؀;uHHH%H'^3H\$@Hl$HHt$PH0_Hd$ E3E333H ōH%HHXHhHpHx ATAUAVH Ll$`MIAeLHAHtLI3;"u3@"HË9AEHtH3HË1DtAEHtHH@tu@ t@ uHt GH3;; t; uH;MtI<$IA3H;\t;"u6utHC8"uH 33҅Ht\HAEutOu< tG< tCt7TCHttHÈHAEH tHAEAEHYHtHAEMtI$$AH\$@Hl$HHt$PH|$XH A^A]A\H\$Ht$ WH0=FuoH=A3HhWHH=Ht;uHHD$HLL$@E33HHD$ Hct$@HH;s\HcL$HHsQHH;rHH9HHt8LHD$HLL$@HHHD$ gD\$@H=7A3D'H\$PHt$XH0_HHXHhHpHx ATH@VE3HHHfD9 tHfD9#uHfD9#uLd$8H+Ld$0HL3DK3Dd$(Ld$ >VHctQH_HHtALd$8Ld$0DKL33ɉl$(HD$ Vu H3IHUH HU3H\$PHl$XHt$`H|$hH@A\H\$WH HHd$0H2-+H;t HHvHL$0UH\$0UDI3TDI3pUHL$8DI3WUL\$8L3HL#H3-+L;LDLILH\$@H _H\$LD$HL$UVWATAUAVAWHH3AAHEDg}@}XH}Et}ADeDHMVB)u@u9EtE AA#At[tAt88 ޯrH$HĀA_A^A]A\_^]D@tuA@EM`DuAt-t#tt@uE;A AA׋ƹU#AtFA;t9=t*=t=t,=t(=t;t$EA AEṀMAt"EhADȉM@@tAUĉMDu sA ȉM@ t @tMu!褮8 zoHE@DEHMPEH|$0D$(LMADd$ RHEHA#;uC@t9EDEHMPAH|$0D$(LMADd$ DuRHEHu:Hc L5þHHHkXIƀdVP覭8HZPuKHc L5|HHHkXIƀdPȋ袭HM4PuO A;uA@ uAHU 踖Hc L5HAHE IHkXD|Hc HHHkXIƀd8A$HEEAtu EŃOEȃuά8tM 裡 HUAf}Iuf}uHcUȋ /<tċ E33tAE"@@u E#u  #;tC=t)=@t"=t(=@t!=t=@uDEX#;u DmX@}XA@D}}Aǹ#=@ =tp;{ErE;vAv-A]MXDFE E3/Ht̋ E33/H HUAwA;tua}u D@DEXE=u =u E3AXDmX E33:=EtxE;A E3.H E33.Hu>EA IcEHTE+ DE;D}AHc DeXHAHHkXIƀd8Dd8HcHƒHHkXI Ƌƀd8D8@8}u @tHc HHHkXIƀL A#;AHMbLEDEHMPH|$0D$(ALMD$ AMHu5K腩LcIAHMkXIBd brHcHʃHHkXI H -E3E333H|$ H\$WH@d$03H|$pHuèW3Htك|$xtAtDL$(DD$ DLHHL$0؉D$4|$0t,t!HcHHH<HkXH€d9rjH\$PH@_H8ADL$`ELD$(HL$ DAIH8Mu3ftf;u HHIu +H(gHt hytA@AHffLH+Irat6t  IȈHtf IfHt  IHMIuQMItH HHIuIMuI@ HIuIffffffffffffI sBH LT H HALQHD LT IHALQuIqffffHr  D @HuH@L LT L LQLL LT LILQLL LT (H@LILQLL LT LILQuIIq $fffffffffffIIrat6t HɊ IȈtHf Ift H IMIuPMItHH IHuIMuIHɊ IȈuIffffffffffffI sBHD LT H HALQHD L IHALuIsfffffHw H D @uH@LL LT LILQLL LT LILQLL LT H@LILQLL L LILuIIq $H(Hu诊HH(LH h3H(H%H̹r|ffHHHtfHt_uI~IHMHLHI3I#tHPtQtGHt9t/Ht!tt uHDHDHDHDHDHDHDHD@SUVWATAUAVHPHH3HD$HALLD3H9HH aFHHHZaHiEHzH7FH(aHHN@EHFH`HH6 EHEH`HHEHELHHt"Hi`HDHEHHߧH֧LקH;tbL;t]HEH H ELHtLcu3~gHL;wXKL$Hw1HAH;w HH H+H\$0HtvHHtHHHtM3HMEMƺDd$(H\$ =tLM`DHAQ>HK9usHMH3`nH]@HuHH}PHeA_A^A]A\]H\$Ht$WH`HHL$@AIsD$$HL$@D\$0D$(H$DLNjHD$ E|$Xt HL$PH\$pHt$xH`_HSH HHIrHKrHKrHK rHK(rHK0rH rHK@rHKHrHKPrHKXrHK`|rHKhsrHK8jrHKparHKxXrHLrH@rH4rH(rHrHrHrHqHqHqHqHqHqHqHqHqHqHqHtqHhqH \qH(PqH0DqH88qH@,qHH qHPqHpqHxpHpHpHpHpHhpHpHpHpHpHpHxpHlpH`pHTpHHpH#~4AMLHIŃ?u;*uLHձ?у;uD#H\$@Hl$HHt$PH A]A\_H\$UVWATAUAVAWH$0HHlH3H3HHL$pHT$hHMIMD$dDD$XD$DD$LD$\D$T-_E3HEHu,ۅplE3D8]t HEAC@L 7HiHyA;t(t#LcL 7IAHMkXM^ LL m7A@8u)A;ttHcHƒHHkXI^B8t,$kE3D8]t HEA$E3HT$hHtD"ADT$@DT$HELUEH]AHHT$hAD$Hl$$OxHEHHHMH踟ȃtH;\$ |@HcOHOx&Hf1HcGHGxHf0H H HL$0H3JH\$pHl$xH@A\_^H 1]3HH9 ,}HHXHpHxL` UHHPE3IHHHtMtD8"u%HtfD!3H\$`Ht$hH|$pLd$xHP]HMIPL]E9cu#HtfD8et HEHUHMD A~0A;|+IAHLǺ D$(H\$ HMuHc H;r&D8gt D8e6HM&$v*D8et HEAAHAQLljD$(HEH\$ HU E3@SH@HL$ NHD$ DH@BY%|$8t HL$0H@[@SH@HL$ 3NHD$ DH@BY%|$8t HL$0H@[HHXHhHpHx ATH0IcIDE~HI`HHcD$hH|$`~ HHDt tD$(DLƋAH|$ +ظHHDH\$@Hl$HHt$PH|$XH0A\H\$T$UVWATAUAVAWH03LHuatZL!M=IILHL;f9hH5jDAH;5juo3HHuZHH9uHc HHHuH ]LHEHtH+H HHH/Hu3H/|$xH5iHHuitIHtDHi蟸yyηtp]sH$H0A_A^A]A\_^]Et3Hu#HҒH iHtH(H5 iHu 譒HhHtH(H5hHHtHM+HILl$pMcHtE=MHIo3ɅuHfB94hfB9 hHHHuH5hH+HE3L9mHcHLKEtfL9ltHLHHHDHL9luHcHH;H hAAHt{rH5gH+HsLdM.\EyߍG;tLcHL;^HHHHcL$LlM.HgD9l$xtuIƵH 薑HHtUI覵MHHduQHD$pHHGfD*HEIEuq*HIEt IIM.E3E333Ll$ VIIM.H\$Ht$WH@HHL$ AAIHD$(DA|utHD$ H@BY#3t|$8t HL$0H\$PHt$XH@_̋AE33rHHXHhHpHx ATAUAWH H33DGLHtPDG3ҋoHt=HH+HAWHEHHu1o oH\$@Hl$HHt$PH|$XH A_A]A\úDDI;HEMNjt HH+H~yo8u Oo HAԋLH3[Uy]E3HՋHTYHHHHHu%n nHvHE3IՋ-H3H\$LcHMAIMkXJ BD8F\ADA@tXtHt(t uHBLJBd8BL80BLJBd8BL8BdBLJ Bd8EuA%@H\$H(Hum_T s3H(fL$H8H \cHu !H JcHu%Hd$ LL$HHT$@AtD$@H8H\$fDL$ UVWHH`IHHHuMtHt!3Ht IvmSdHU@HMFL]A{E8f;vJHtHt L3H3l*l}t HMH$H`_^]Ht0Hu)ml_"S@8}eHMUHt}HEe(AKHE(HD$8Hd$0LE8A3҉|$(Ht$ lt}(3HtwzHtHt L3H7k"BR}HEH8Hd$ -H8H\$Hl$VWATH DHHOSHc‚uDk K 8@t &k"3t{HCHSC{C u/+GH0H;tGH`H;u uHC+HS+kHBHC$C~Dŋ=W S&R&b&v&&&&&&&'' '0'F'R'^'t''''''''''' (&(@(V(l((((((r)(%%%$$$$$$<*@W@Hf@X@@+@ bMCorExitProcessmscoree.dll@D@D@ccsUTF-8UTF-16LEUNICODEruntime error TLOSS error SING error DOMAIN error R6033 - Attempt to use MSIL code from this assembly during native code initialization This indicates a bug in your application. It is most likely the result of calling an MSIL-compiled (/clr) function from a native constructor or from DllMain. R6032 - not enough space for locale information R6031 - Attempt to initialize the CRT more than once. This indicates a bug in your application. R6030 - CRT not initialized R6028 - unable to initialize heap R6027 - not enough space for lowio initialization R6026 - not enough space for stdio initialization R6025 - pure virtual function call R6024 - not enough space for _onexit/atexit table R6019 - unable to open console device R6018 - unexpected heap error R6017 - unexpected multithread lock error R6016 - not enough space for thread data R6010 - abort() has been called R6009 - not enough space for environment R6008 - not enough space for arguments R6002 - floating point support not loaded @ @ @ p@@@`@@@@@@`@@@@ @!@x@y`@z@@8@@Microsoft Visual C++ Runtime Library ...<program name unknown>Runtime Error! Program: HH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSunHH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSun  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ((((( H h(((( H H  !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~  GetProcessWindowStationGetUserObjectInformationWGetLastActivePopupGetActiveWindowMessageBoxWUSER32.DLL(null)(null)EEE50P( 8PX700WP `h````xpxxxxEEE00P('8PW700PP (`h`hhhxppwppCONOUT$user32.dllMessageBoxTimeoutAMessageBoxTimeoutWFatal Error in LauncherFatal Error in LauncherrbFailed to open executableUnable to find an appended archive.Unable to read from file#!PATHEXT;PyLauncherSTATICJob creation failedJob information querying failedJob information setting failedmaking stdin inheritable failedmaking stdout inheritable failedmaking stderr inheritable failedUnable to create process using '%ls': %lscontrol handler setting failedFailed to get exit code of process.exe/usr/bin/envExpected to find a command ending in '.exe' in shebang line: %lsExpected to find whitespace after '/usr/bin/env': %lsUnable to find executable in environment: %lsExpected terminating double-quote for executable in shebang line: %ls<launcher_dir>\Terminating quote without starting quote for executable in shebang line: %lsFailed to find shebangExpected to find terminator in shebang lineExpected to decode shebang line using UTF-8Expected to find '#' at start of shebang lineExpected to find '!' following '#' in shebang lineExpected to find executable in shebang lineExpected to find arguments (even if empty) in shebang lineExpected to be able to allocate command line memory"%ls" %ls "%ls" %lsRSDSŁU&{Aނ܉oC:\Users\Vinay\Projects\simple_launcher\dist\w64.pdbd 4 R plG"5":"N"42 p d4rplG$%  t d T 4 r- 5td 4 3 rPt0  4 rp  b 42 p`P  t d 4RlGd1}1.. d4 p Ptd T 4Rp42 plG]7g7T42 p  td42lG99H td 4 rlG::c\:; td4P d T 4 Rpd42plG??  t d 4RlG@`AnAA42 plGBB4 2 p `lGD;D  4 plG7EEE   d T 4 rp dT4 Rpd 4R plGfLMtd42dT42ptd42lGPP BlGYQ]Q]Q d4 R plGRS  4 2plGTT-T UM42plGlVV-VVM2P t d 42lGWXh  4 2p BlG'ZZZZZ- dQTP4OJpt@/ td4Ptp  4 2plG`9ar04r p`Pt8  t d 4RlGHee20lGii4  P tT4 t dT42d 4R plGo:p t d 4 RlGqqIqq#bd T42p  p ` 0 P d 4 2plG>d42 p4 2 p `lG>0 4 p`PtdT 4 pT 4 R p t d T 42d 4 R p  4 2p$ $4$p`PRP  4 rplGeW p`P0tH20- Etd4C PtHd4 p- 5td43r Pt0d4 p2 0  206 %4q%f p`Pt 4 2 p `lGK>4 2 p `lG/dT42p d T 42p0 4dZ p`Pt4 2 p `lGm>!T4r p `t0 td 4 P t d T 4R 4R p ` Pd 4 r p t d T 424  b4p ` PT 42 p `d 4R p  0  4 Rp`P2 p0t02  p0t0 d T 42p td4rP3 "te"dd"4c"^Pt 7 &t&d&4&PtB$X"$8"8%"""""""#(#D#V#l######## $$,$<$J$b$z$`)P)6)$))(((D%X%h%t%%%%%%%&$&>&R&b&v&&&&&&&'' '0'F'R'^'t''''''''''' (&(@(V(l((((((r)(%%%$$$$$$ExitProcessGetCommandLineW+SearchPathW~SetInformationJobObjecthFreeLibraryCreateProcessW}SetHandleInformation WaitForSingleObjectExAssignProcessToJobObjectdFormatMessageWGetExitCodeProcessGetModuleFileNameWQueryInformationJobObjectiMultiByteToWideCharjGetStartupInfoWGetTempPathWCreateJobObjectAkGetStdHandleGetLastErrorLGetProcAddressSetStdHandle>LoadLibraryAGetFileType;SetConsoleCtrlHandler[SetCurrentDirectoryWRCloseHandleKERNEL32.dllnCreateWindowExW7PeekMessageW.WaitForInputIdle:PostMessageW_GetMessageWDestroyWindowUSER32.dllPathRemoveFileSpecWEStrStrIW:PathCombineWSHLWAPI.dllGetModuleHandleWDecodePointerHeapFreeHeapAllocEnterCriticalSection;LeaveCriticalSectionpGetStringTypeWUnhandledExceptionFilterSetUnhandledExceptionFilterIsDebuggerPresent&RtlVirtualUnwindRtlLookupFunctionEntryRtlCaptureContextEncodePointerTerminateProcessGetCurrentProcessInitializeCriticalSectionAndSpinCountGetCommandLineA%RtlUnwindExDeleteCriticalSectionALoadLibraryWZFlsGetValue[FlsSetValueYFlsFreeSetLastErrorGetCurrentThreadIdXFlsAlloc4WriteFileHeapSetInformationGetVersionHeapCreatexGetCPInfonGetACP>GetOEMCP IsValidCodePage|SetHandleCountSleep/LCMapStringWReadFiletSetFilePointergFreeEnvironmentStringsWGetEnvironmentStringsWGetModuleFileNameA WideCharToMultiByteQueryPerformanceCounterGetTickCountGetCurrentProcessIdGetSystemTimeAsFileTimeCreateFileWHeapSizeHeapReAllocGetConsoleCPGetConsoleMode]FlushFileBuffersdCompareStringWeSetEnvironmentVariableWaSetEndOfFileQGetProcessHeap3WriteConsoleWa@a@2-+] f        ! 5A CPR S WY l m pr   )    @@@@@@@@@@ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ7@`y!@~ڣ @ڣ AϢ[@~QQ^ _j21~C@@@@@@@@@@p@`@T@H@D@@@<@8@4@0@,@(@$@ @@@@@@@4@@@@@@@@@@@p@`@ X@P@H@@@8@0@(@@@@@@@@@@@@x@p@h@`@X@P@H@@@0@@@@x@@@@@@@p@d@\@H@ @@$=@$=@$=@$=@$=@B@@@@P @0=@?@D@ @@B@..B@B@S@S@S@S@S@S@S@S@S@B@S@S@S@S@S@S@S@. @ @KAD-x0y0|.l0qxXL!L!V!0p!!!m"p"""# #6#P####0#$$$$C&<|&&&&&%'('''(l(|)0|)2*<*++C+0D++++d,O.P.....01111`1.202V20`233334044(44`4k50l5"7X$7|7<|7)8,88`8L90L99p:$<$<>=@=>>>`>b?d???@@A8A B BBBBxBCCoDpD F FF0 FjGlGiIlIILLLM,M N0 N,N0,NrNNNXNN`NNdNNhNxOlxOOPPQDQDQgQhQQ0QST7TDTiT0lT!U$UUUUUVDWWWWWXXX0X$Y$Y\Y\YY ZaZZ\\3]04]]0]&^(^^^``^a `aaDaedLhdFfhHfpf0giisitiiijjmmnnnn!o$oo(oVpXppp q q r rrsHtHtft`htttt0tIuTuuhu]wppwZx\xyyӀԀw/0( z|480THQTٔܔU0Xp0p@@(Xtl[\֥إ `8x0ĩĩ0 ȪȪЬЬUX'(h0h24ʱ0̱*@,hxxu@P0BD[\(_@`d/00(mDpD^`DD C0D(d0dx`x@}Td0 x .x.HHcxc~xxxxx-x-HxMhxh~xxxxxx#>x>WxWx0x 8Pehf 0 @P8(`hp%h(hZ( @...GGGWWWiiiuuu ̀ ww ww||wwwwwwwwfwwwww|fwwwwwwffwwwwwwffwwwwwfflwwffffffwfffffflwffffffwffffff|ffffffffR\Ilffl)ffح2L18Ϥ"A< ΣTπU  ؀ ???????( 444MMMSSSzzzp p{p{ffkof_fffk_fff[f}UUUfL4UUU_kJqO_~'[U_Uwwwww{~( @%%%+++000===AAAIIIMMMQQQVVVYYY]]]~dFgggjjjmmmrrruuu}}}u?z=~;gEkCoAr@:IbpƏKУk(,1?5<\CKQsU[cbð/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq_______CCBABADL*(%$""&GABJIIDDJC??AD_-(''%$GG#_DIJIIJIIIDIJDD_+)(''%GG _JMCEJ?ABAECA?BA_,+)('%%"!_IM@AJACDCIDI____.,++>________JMJJJJJJJJJF_..,+)('%$"! #GIMCEJJJI?J__...,+(('%$"! &_JM?AJJJJCJ__...,+)(''%$" _IMJJJJJJJJ_K=...,++(''%$! _JMCDJJJJAJ_K_______L*%$"!_I_?AMNJJEJ_ L'%$"_I_MMMMMMJJ_ _('%%_I_CEMIJJMEM _(''*_I_?BMDJEMDMF _)(-LI_MMMCMMMMM_________< ____I_CEM1;MACDMEJM__I_?A_D1@IJJMJMM_FF_I__6_@/EMMMNMM_FF_I_C80EJDMJCJFF?:AA20?_M_MIEM______MIC4BC/ N______MMMMJIDAI_9/ HJJMMJMMMJ??BAI_A7503 IJMMCBCBA44CCI___BAO;6___C_MJIDIDMI_CI___O; @__C_C?J?AJI_?A_DJJI@ O_CM?;I?JI_________H9MAIJJIJI_CD_____JJI73C;IJIJI_?AMDJJDIMJIE5:JIJI__________MMIA:IJJJJJJJJJJJJJJJJMJ???( 444MMMRRRYYY~eGuuu}}}x>iErCE~7?HVbx/!P7pLcyϏ1Qq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq!%///* !*/"(%%'%/) /!#''////+/+"*(/ / !%(/ ( (*/ (///) */   )*  /"///// /+/// # + !%//  +"*+'#+//+'!%*++$++!+! !!!<(0`  !!$& &"   WܱNj+<<:5pi$!!!4888&SNJFB=950,(%%%5ǎVRNIEѹ|Jq?o@ZUQMID@<73/+&"-Ɍ444"ք̴wq?]YUPLHC?;72.*%!"""4888&dya\XTPKGC>:61-)% OÑ%%%5ʎA{;ys`\WSOJFB>951,($(9|:z;xq?o@mAkBiCgCdDuZ;73.*Ǒ%%%5̎È>89}:{;yq?o@mAjBhCfD~dEuZ?:625Оa789}:{;yr>p?n@lAjBhCfD}dEB>:5^ό555"܄ѳ„7789}:{;xr?p@n@lAjBgCeDFA=9"""4;;;&ŌC89~9|:z;xr?p@mAkAiBgCIELɑQQQJnnn~~~Įq?o@mAkBiCHHH"""vvv9~:{;yq?o@mAkB:::'''___sssttt8Ϧuǣvyp?n@lA888T---6fffnddd%%%"""OOOSSSċDҧuʤv{;yr?p?xM<<r?̷999VVV###)))444ѳ͝b@~9|:z;}Cg˶Ӎ󑑑ZZZlll;;;!!!""")))___&&&2:::ppp{{{___WWWWWW:::!!!"""***www"""4888&,̑999ԖJJJ|||lll:::"""BBBXXX|||$$$6,uuuKKKCCC@@@...www* Ҍ666"KKKaaaLLLWWW@$"""4;;;&nnnfff!!!$$$999444]ttt|ȇɑ&&&5ЎvvvmmmVVV{{{OOOڥaaaCCCpppό555"ބ|||MMMfff"""4;;;&\\\Ǒ%%%5Ύݧܦ͌555"ۄۥ"""4:::&ڤÑ%%%5ˎܤׇ ww?ww??ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwwwwwwwwwwwwwww?ww?ww?ww?ww?ww?ww?ww?ww?ww?ww?wwwwwwwwwwwwwwww?wwwwwwww( @ !!!0\\\JeeeQ666D555A0$ vH<602\7qڴvvvj&&&D1"gLF@;5?кVQKE?9(aV[UPJD>82-uuun{{{j`ZTOVo@lAb_YSMGB<60+%<aVmt>q?n@bb^XRLFA;5/)$Ywwwn}}}j~Cv=s>p?bba]WQKE@:4.(+{;xbba[VPJD?93-'aV~:{;xr?o@lAiBfD}dE|cE|cEiB<61?9}:z;w=t>q?n@kAiCfD}cE|cE}dFGA;=aVңi89|:yq?n@kBhCeD|cE|cELF@mzzznjʓP8~9{;yp?mAjBgCeD|cEPJ\¯mAjBgC~dEaV}:z;xq?n@lAiCJJJőPyq?n@~YVVV???wwwiiiΡkA|;y>>3@@@y~~~obbb000kkk@@@---U<<<&&&[[[6uuusssWWWIII$$$&&&qqqlllg|||s4bbbhhhƑUUU000QQQ}}}---{bbb(iiiLLLBBBaVggg+++NNN|||nj]]]tttkkkrrrwwwӦaVӦzzznjӦˉ ?????(  C﫫oIਨS=?_QF4aZмoEcaXMB70~Iq?c`VK@58z;u=qv>3Eyn@iB~dE}dFGHYYYuuuġ=xmAhC}dE}eGS444|||gC}cEMMM~>q?kAiGRRR¢Gu=xKʺ~AAAAAAAAAAAAAAAA ( h00 %   h4VS_VERSION_INFO?fStringFileInfoB080904b0JCompanyNameSimple Launcher User^FileDescriptionSimple Launcher Executable2 FileVersion1.1.0.140InternalNamew64.exej#LegalCopyrightCopyright (C) Simple Launcher User8OriginalFilenamew64.exe@ProductNameSimple Launcher6 ProductVersion1.1.0.14DVarFileInfo$Translation  PAPADDINGXXPADD (08PXȣУ(8HXhxȭح(80ȧЧا 08@HPX`hpxȭЭح (08@HPX`hpxȮЮخ (08@HPX`hpxȯЯد@PXxؠ08@HPXȢ (08HPX`hpx././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780570481.0 distlib-0.4.3/distlib/wheel.py0000664000175000017500000013050415210254561015364 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2013-2026 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # from __future__ import unicode_literals import base64 import codecs import datetime from email import message_from_file import hashlib import json import logging import os import posixpath import re import shutil import sys import tempfile import zipfile from . import __version__, DistlibException from .compat import sysconfig, ZipFile, fsdecode, text_type, filter from .database import InstalledDistribution from .metadata import Metadata, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, cached_property, get_cache_base, read_exports, tempdir, get_platform, is_in_directory) from .version import NormalizedVersion, UnsupportedVersionError logger = logging.getLogger(__name__) cache = None # created when needed if hasattr(sys, 'pypy_version_info'): # pragma: no cover IMP_PREFIX = 'pp' elif sys.platform.startswith('java'): # pragma: no cover IMP_PREFIX = 'jy' elif sys.platform == 'cli': # pragma: no cover IMP_PREFIX = 'ip' else: IMP_PREFIX = 'cp' VER_SUFFIX = sysconfig.get_config_var('py_version_nodot') if not VER_SUFFIX: # pragma: no cover VER_SUFFIX = '%s%s' % sys.version_info[:2] PYVER = 'py' + VER_SUFFIX IMPVER = IMP_PREFIX + VER_SUFFIX ARCH = get_platform().replace('-', '_').replace('.', '_') ABI = sysconfig.get_config_var('SOABI') if ABI and ABI.startswith('cpython-'): ABI = ABI.replace('cpython-', 'cp').split('-')[0] else: def _derive_abi(): parts = ['cp', VER_SUFFIX] if sysconfig.get_config_var('Py_DEBUG'): parts.append('d') if IMP_PREFIX == 'cp': vi = sys.version_info[:2] if vi < (3, 8): wpm = sysconfig.get_config_var('WITH_PYMALLOC') if wpm is None: wpm = True if wpm: parts.append('m') if vi < (3, 3): us = sysconfig.get_config_var('Py_UNICODE_SIZE') if us == 4 or (us is None and sys.maxunicode == 0x10FFFF): parts.append('u') if bool(sysconfig.get_config_var("Py_GIL_DISABLED")): parts.append('t') return ''.join(parts) ABI = _derive_abi() del _derive_abi FILENAME_RE = re.compile( r''' (?P[^-]+) -(?P\d+[^-]*) (-(?P\d+[^-]*))? -(?P\w+\d+(\.\w+\d+)*) -(?P\w+) -(?P\w+(\.\w+)*) \.whl$ ''', re.IGNORECASE | re.VERBOSE) NAME_VERSION_RE = re.compile(r''' (?P[^-]+) -(?P\d+[^-]*) (-(?P\d+[^-]*))?$ ''', re.IGNORECASE | re.VERBOSE) SHEBANG_RE = re.compile(br'\s*#![^\r\n]*') SHEBANG_DETAIL_RE = re.compile(br'^(\s*#!("[^"]+"|\S+))\s+(.*)$') SHEBANG_PYTHON = b'#!python' SHEBANG_PYTHONW = b'#!pythonw' if os.sep == '/': to_posix = lambda o: o else: to_posix = lambda o: o.replace(os.sep, '/') if sys.version_info[0] < 3: import imp else: imp = None import importlib.machinery import importlib.util def _get_suffixes(): if imp: return [s[0] for s in imp.get_suffixes()] else: return importlib.machinery.EXTENSION_SUFFIXES def _load_dynamic(name, path): # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly if imp: return imp.load_dynamic(name, path) else: spec = importlib.util.spec_from_file_location(name, path) module = importlib.util.module_from_spec(spec) sys.modules[name] = module spec.loader.exec_module(module) return module class Mounter(object): def __init__(self): self.impure_wheels = {} self.libs = {} def add(self, pathname, extensions): self.impure_wheels[pathname] = extensions self.libs.update(extensions) def remove(self, pathname): extensions = self.impure_wheels.pop(pathname) for k, v in extensions: if k in self.libs: del self.libs[k] def find_module(self, fullname, path=None): if fullname in self.libs: result = self else: result = None return result def load_module(self, fullname): if fullname in sys.modules: result = sys.modules[fullname] else: if fullname not in self.libs: raise ImportError('unable to find extension for %s' % fullname) result = _load_dynamic(fullname, self.libs[fullname]) result.__loader__ = self parts = fullname.rsplit('.', 1) if len(parts) > 1: result.__package__ = parts[0] return result _hook = Mounter() class Wheel(object): """ Class to build and install from Wheel files (PEP 427). """ wheel_version = (1, 1) hash_kind = 'sha256' def __init__(self, filename=None, sign=False, verify=False): """ Initialise an instance using a (valid) filename. """ self.sign = sign self.should_verify = verify self.buildver = '' self.pyver = [PYVER] self.abi = ['none'] self.arch = ['any'] self.dirname = os.getcwd() if filename is None: self.name = 'dummy' self.version = '0.1' self._filename = self.filename else: m = NAME_VERSION_RE.match(filename) if m: info = m.groupdict('') self.name = info['nm'] # Reinstate the local version separator self.version = info['vn'].replace('_', '-') self.buildver = info['bn'] self._filename = self.filename else: dirname, filename = os.path.split(filename) m = FILENAME_RE.match(filename) if not m: raise DistlibException('Invalid name or ' 'filename: %r' % filename) if dirname: self.dirname = os.path.abspath(dirname) self._filename = filename info = m.groupdict('') self.name = info['nm'] self.version = info['vn'] self.buildver = info['bn'] self.pyver = info['py'].split('.') self.abi = info['bi'].split('.') self.arch = info['ar'].split('.') @property def filename(self): """ Build and return a filename from the various components. """ if self.buildver: buildver = '-' + self.buildver else: buildver = '' pyver = '.'.join(self.pyver) abi = '.'.join(self.abi) arch = '.'.join(self.arch) # replace - with _ as a local version separator version = self.version.replace('-', '_') return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver, pyver, abi, arch) @property def exists(self): path = os.path.join(self.dirname, self.filename) return os.path.isfile(path) @property def tags(self): for pyver in self.pyver: for abi in self.abi: for arch in self.arch: yield pyver, abi, arch @cached_property def metadata(self): pathname = os.path.join(self.dirname, self.filename) name_ver = '%s-%s' % (self.name, self.version) info_dir = '%s.dist-info' % name_ver wrapper = codecs.getreader('utf-8') with ZipFile(pathname, 'r') as zf: self.get_wheel_metadata(zf) # wv = wheel_metadata['Wheel-Version'].split('.', 1) # file_version = tuple([int(i) for i in wv]) # if file_version < (1, 1): # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, # LEGACY_METADATA_FILENAME] # else: # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME] fns = [WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME] result = None for fn in fns: try: metadata_filename = posixpath.join(info_dir, fn) with zf.open(metadata_filename) as bf: wf = wrapper(bf) result = Metadata(fileobj=wf) if result: break except KeyError: pass if not result: raise ValueError('Invalid wheel, because metadata is ' 'missing: looked in %s' % ', '.join(fns)) return result def get_wheel_metadata(self, zf): name_ver = '%s-%s' % (self.name, self.version) info_dir = '%s.dist-info' % name_ver metadata_filename = posixpath.join(info_dir, 'WHEEL') with zf.open(metadata_filename) as bf: wf = codecs.getreader('utf-8')(bf) message = message_from_file(wf) return dict(message) @cached_property def info(self): pathname = os.path.join(self.dirname, self.filename) with ZipFile(pathname, 'r') as zf: result = self.get_wheel_metadata(zf) return result def process_shebang(self, data): m = SHEBANG_RE.match(data) if m: end = m.end() shebang, data_after_shebang = data[:end], data[end:] # Preserve any arguments after the interpreter if b'pythonw' in shebang.lower(): shebang_python = SHEBANG_PYTHONW else: shebang_python = SHEBANG_PYTHON m = SHEBANG_DETAIL_RE.match(shebang) if m: args = b' ' + m.groups()[-1] else: args = b'' shebang = shebang_python + args data = shebang + data_after_shebang else: cr = data.find(b'\r') lf = data.find(b'\n') if cr < 0 or cr > lf: term = b'\n' else: if data[cr:cr + 2] == b'\r\n': term = b'\r\n' else: term = b'\r' data = SHEBANG_PYTHON + term + data return data def get_hash(self, data, hash_kind=None): if hash_kind is None: hash_kind = self.hash_kind try: hasher = getattr(hashlib, hash_kind) except AttributeError: raise DistlibException('Unsupported hash algorithm: %r' % hash_kind) result = hasher(data).digest() result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii') return hash_kind, result def write_record(self, records, record_path, archive_record_path): records = list(records) # make a copy, as mutated records.append((archive_record_path, '', '')) with CSVWriter(record_path) as writer: for row in records: writer.writerow(row) def write_records(self, info, libdir, archive_paths): records = [] distinfo, info_dir = info # hasher = getattr(hashlib, self.hash_kind) for ap, p in archive_paths: with open(p, 'rb') as f: data = f.read() digest = '%s=%s' % self.get_hash(data) size = os.path.getsize(p) records.append((ap, digest, size)) p = os.path.join(distinfo, 'RECORD') ap = to_posix(os.path.join(info_dir, 'RECORD')) self.write_record(records, p, ap) archive_paths.append((ap, p)) def build_zip(self, pathname, archive_paths): with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf: for ap, p in archive_paths: logger.debug('Wrote %s to %s in wheel', p, ap) zf.write(p, ap) def build(self, paths, tags=None, wheel_version=None): """ Build a wheel from files in specified paths, and use any specified tags when determining the name of the wheel. """ if tags is None: tags = {} libkey = list(filter(lambda o: o in paths, ('purelib', 'platlib')))[0] if libkey == 'platlib': is_pure = 'false' default_pyver = [IMPVER] default_abi = [ABI] default_arch = [ARCH] else: is_pure = 'true' default_pyver = [PYVER] default_abi = ['none'] default_arch = ['any'] self.pyver = tags.get('pyver', default_pyver) self.abi = tags.get('abi', default_abi) self.arch = tags.get('arch', default_arch) libdir = paths[libkey] name_ver = '%s-%s' % (self.name, self.version) data_dir = '%s.data' % name_ver info_dir = '%s.dist-info' % name_ver archive_paths = [] # First, stuff which is not in site-packages for key in ('data', 'headers', 'scripts'): if key not in paths: continue path = paths[key] if os.path.isdir(path): for root, dirs, files in os.walk(path): for fn in files: p = fsdecode(os.path.join(root, fn)) rp = os.path.relpath(p, path) ap = to_posix(os.path.join(data_dir, key, rp)) archive_paths.append((ap, p)) if key == 'scripts' and not p.endswith('.exe'): with open(p, 'rb') as f: data = f.read() data = self.process_shebang(data) with open(p, 'wb') as f: f.write(data) # Now, stuff which is in site-packages, other than the # distinfo stuff. path = libdir distinfo = None for root, dirs, files in os.walk(path): if root == path: # At the top level only, save distinfo for later # and skip it for now for i, dn in enumerate(dirs): dn = fsdecode(dn) if dn.endswith('.dist-info'): distinfo = os.path.join(root, dn) del dirs[i] break assert distinfo, '.dist-info directory expected, not found' for fn in files: # comment out next suite to leave .pyc files in if fsdecode(fn).endswith(('.pyc', '.pyo')): continue p = os.path.join(root, fn) rp = to_posix(os.path.relpath(p, path)) archive_paths.append((rp, p)) # Now distinfo. It may contain subdirectories (e.g. PEP 639) for root, _, files in os.walk(distinfo): for fn in files: if fn not in ('RECORD', 'INSTALLER', 'SHARED', 'WHEEL'): p = fsdecode(os.path.join(root, fn)) r = os.path.relpath(root, distinfo) ap = to_posix(os.path.normpath(os.path.join(info_dir, r, fn))) archive_paths.append((ap, p)) wheel_metadata = [ 'Wheel-Version: %d.%d' % (wheel_version or self.wheel_version), 'Generator: distlib %s' % __version__, 'Root-Is-Purelib: %s' % is_pure, ] if self.buildver: wheel_metadata.append('Build: %s' % self.buildver) for pyver, abi, arch in self.tags: wheel_metadata.append('Tag: %s-%s-%s' % (pyver, abi, arch)) p = os.path.join(distinfo, 'WHEEL') with open(p, 'w') as f: f.write('\n'.join(wheel_metadata)) ap = to_posix(os.path.join(info_dir, 'WHEEL')) archive_paths.append((ap, p)) # sort the entries by archive path. Not needed by any spec, but it # keeps the archive listing and RECORD tidier than they would otherwise # be. Use the number of path segments to keep directory entries together, # and keep the dist-info stuff at the end. def sorter(t): ap = t[0] n = ap.count('/') if '.dist-info' in ap: n += 10000 return (n, ap) archive_paths = sorted(archive_paths, key=sorter) # Now, at last, RECORD. # Paths in here are archive paths - nothing else makes sense. self.write_records((distinfo, info_dir), libdir, archive_paths) # Now, ready to build the zip file pathname = os.path.join(self.dirname, self.filename) self.build_zip(pathname, archive_paths) return pathname def skip_entry(self, arcname): """ Determine whether an archive entry should be skipped when verifying or installing. """ # The signature file won't be in RECORD, # and we don't currently don't do anything with it # We also skip directories, as they won't be in RECORD # either. See: # # https://github.com/pypa/wheel/issues/294 # https://github.com/pypa/wheel/issues/287 # https://github.com/pypa/wheel/pull/289 # return arcname.endswith(('/', '/RECORD.jws')) def install(self, paths, maker, **kwargs): """ Install a wheel to the specified paths. If kwarg ``warner`` is specified, it should be a callable, which will be called with two tuples indicating the wheel version of this software and the wheel version in the file, if there is a discrepancy in the versions. This can be used to issue any warnings to raise any exceptions. If kwarg ``lib_only`` is True, only the purelib/platlib files are installed, and the headers, scripts, data and dist-info metadata are not written. If kwarg ``bytecode_hashed_invalidation`` is True, written bytecode will try to use file-hash based invalidation (PEP-552) on supported interpreter versions (CPython 3.7+). The return value is a :class:`InstalledDistribution` instance unless ``options.lib_only`` is True, in which case the return value is ``None``. """ dry_run = maker.dry_run warner = kwargs.get('warner') lib_only = kwargs.get('lib_only', False) bc_hashed_invalidation = kwargs.get('bytecode_hashed_invalidation', False) pathname = os.path.join(self.dirname, self.filename) name_ver = '%s-%s' % (self.name, self.version) data_dir = '%s.data' % name_ver info_dir = '%s.dist-info' % name_ver metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') record_name = posixpath.join(info_dir, 'RECORD') wrapper = codecs.getreader('utf-8') with ZipFile(pathname, 'r') as zf: with zf.open(wheel_metadata_name) as bwf: wf = wrapper(bwf) message = message_from_file(wf) wv = message['Wheel-Version'].split('.', 1) file_version = tuple([int(i) for i in wv]) if (file_version != self.wheel_version) and warner: warner(self.wheel_version, file_version) if message['Root-Is-Purelib'] == 'true': libdir = paths['purelib'] else: libdir = paths['platlib'] records = {} with zf.open(record_name) as bf: with CSVReader(stream=bf) as reader: for row in reader: p = row[0] records[p] = row data_pfx = posixpath.join(data_dir, '') info_pfx = posixpath.join(info_dir, '') script_pfx = posixpath.join(data_dir, 'scripts', '') # make a new instance rather than a copy of maker's, # as we mutate it fileop = FileOperator(dry_run=dry_run) fileop.record = True # so we can rollback if needed bc = not sys.dont_write_bytecode # Double negatives. Lovely! outfiles = [] # for RECORD writing # for script copying/shebang processing workdir = tempfile.mkdtemp() # set target dir later # we default add_launchers to False, as the # Python Launcher should be used instead maker.source_dir = workdir maker.target_dir = None try: for zinfo in zf.infolist(): arcname = zinfo.filename if isinstance(arcname, text_type): u_arcname = arcname else: u_arcname = arcname.decode('utf-8') if self.skip_entry(u_arcname): continue row = records[u_arcname] if row[2] and str(zinfo.file_size) != row[2]: raise DistlibException('size mismatch for ' '%s' % u_arcname) if row[1]: kind, value = row[1].split('=', 1) with zf.open(arcname) as bf: data = bf.read() _, digest = self.get_hash(data, kind) if digest != value: raise DistlibException('digest mismatch for ' '%s' % arcname) if lib_only and u_arcname.startswith((info_pfx, data_pfx)): logger.debug('lib_only: skipping %s', u_arcname) continue is_script = (u_arcname.startswith(script_pfx) and not u_arcname.endswith('.exe')) if u_arcname.startswith(data_pfx): _, where, rp = u_arcname.split('/', 2) base = paths[where] cp = convert_path(rp) else: # meant for site-packages. if u_arcname in (wheel_metadata_name, record_name): continue base = libdir cp = convert_path(u_arcname) # outfile = os.path.join(base, cp) outfile = os.path.abspath(os.path.join(base, cp)) if not is_in_directory(outfile, base): raise DistlibException('Wheel member escapes installation directory: %r' % cp) if not is_script: with zf.open(arcname) as bf: fileop.copy_stream(bf, outfile) # Issue #147: permission bits aren't preserved. Using # zf.extract(zinfo, libdir) should have worked, but didn't, # see https://www.thetopsites.net/article/53834422.shtml # So ... manually preserve permission bits as given in zinfo if os.name == 'posix': # just set the normal permission bits os.chmod(outfile, (zinfo.external_attr >> 16) & 0x1FF) outfiles.append(outfile) # Double check the digest of the written file if not dry_run and row[1]: with open(outfile, 'rb') as bf: data = bf.read() _, newdigest = self.get_hash(data, kind) if newdigest != digest: raise DistlibException('digest mismatch ' 'on write for ' '%s' % outfile) if bc and outfile.endswith('.py'): try: pyc = fileop.byte_compile(outfile, hashed_invalidation=bc_hashed_invalidation) outfiles.append(pyc) except Exception: # Don't give up if byte-compilation fails, # but log it and perhaps warn the user logger.warning('Byte-compilation failed', exc_info=True) else: fn = os.path.basename(convert_path(arcname)) workname = os.path.join(workdir, fn) with zf.open(arcname) as bf: fileop.copy_stream(bf, workname) dn, fn = os.path.split(outfile) maker.target_dir = dn filenames = maker.make(fn) fileop.set_executable_mode(filenames) outfiles.extend(filenames) if lib_only: logger.debug('lib_only: returning None') dist = None else: # Generate scripts # Try to get pydist.json so we can see if there are # any commands to generate. If this fails (e.g. because # of a legacy wheel), log a warning but don't give up. commands = None file_version = self.info['Wheel-Version'] if file_version == '1.0': # Use legacy info ep = posixpath.join(info_dir, 'entry_points.txt') try: with zf.open(ep) as bwf: epdata = read_exports(bwf) commands = {} for key in ('console', 'gui'): k = '%s_scripts' % key if k in epdata: commands['wrap_%s' % key] = d = {} for v in epdata[k].values(): s = '%s:%s' % (v.prefix, v.suffix) if v.flags: s += ' [%s]' % ','.join(v.flags) d[v.name] = s except Exception: logger.warning('Unable to read legacy script ' 'metadata, so cannot generate ' 'scripts') else: try: with zf.open(metadata_name) as bwf: wf = wrapper(bwf) commands = json.load(wf).get('extensions') if commands: commands = commands.get('python.commands') except Exception: logger.warning('Unable to read JSON metadata, so ' 'cannot generate scripts') if commands: console_scripts = commands.get('wrap_console', {}) gui_scripts = commands.get('wrap_gui', {}) if console_scripts or gui_scripts: script_dir = paths.get('scripts', '') if not os.path.isdir(script_dir): raise ValueError('Valid script path not ' 'specified') maker.target_dir = script_dir for k, v in console_scripts.items(): script = '%s = %s' % (k, v) filenames = maker.make(script) fileop.set_executable_mode(filenames) if gui_scripts: options = {'gui': True} for k, v in gui_scripts.items(): script = '%s = %s' % (k, v) filenames = maker.make(script, options) fileop.set_executable_mode(filenames) p = os.path.join(libdir, info_dir) dist = InstalledDistribution(p) # Write SHARED paths = dict(paths) # don't change passed in dict del paths['purelib'] del paths['platlib'] paths['lib'] = libdir p = dist.write_shared_locations(paths, dry_run) if p: outfiles.append(p) # Write RECORD dist.write_installed_files(outfiles, paths['prefix'], dry_run) return dist except Exception: # pragma: no cover logger.exception('installation failed.') fileop.rollback() raise finally: shutil.rmtree(workdir) def _get_dylib_cache(self): global cache if cache is None: # Use native string to avoid issues on 2.x: see Python #20140. base = os.path.join(get_cache_base(), str('dylib-cache'), '%s.%s' % sys.version_info[:2]) cache = Cache(base) return cache def _get_extensions(self): pathname = os.path.join(self.dirname, self.filename) name_ver = '%s-%s' % (self.name, self.version) info_dir = '%s.dist-info' % name_ver arcname = posixpath.join(info_dir, 'EXTENSIONS') wrapper = codecs.getreader('utf-8') result = [] with ZipFile(pathname, 'r') as zf: try: with zf.open(arcname) as bf: wf = wrapper(bf) extensions = json.load(wf) cache = self._get_dylib_cache() prefix = cache.prefix_to_dir(self.filename, use_abspath=False) cache_base = os.path.join(cache.base, prefix) if not os.path.isdir(cache_base): os.makedirs(cache_base) for name, relpath in extensions.items(): # See verify()/update(): reject any '..' in the # directory portions of the entry, then confine the # resolved destination to the cache directory so a # crafted EXTENSIONS entry cannot point _load_dynamic # at a path outside the dylib cache. if '..' in relpath.split('/'): raise DistlibException('invalid extension entry ' 'in wheel: %r' % relpath) dest = os.path.join(cache_base, convert_path(relpath)) if not is_in_directory(dest, cache_base): raise DistlibException('extension escapes dylib ' 'cache: %r' % relpath) if not os.path.exists(dest): extract = True else: file_time = os.stat(dest).st_mtime file_time = datetime.datetime.fromtimestamp(file_time) info = zf.getinfo(relpath) wheel_time = datetime.datetime(*info.date_time) extract = wheel_time > file_time if extract: zf.extract(relpath, cache_base) result.append((name, dest)) except KeyError: pass return result def is_compatible(self): """ Determine if a wheel is compatible with the running system. """ return is_compatible(self) def is_mountable(self): """ Determine if a wheel is asserted as mountable by its metadata. """ return True # for now - metadata details TBD def mount(self, append=False): pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) if not self.is_compatible(): msg = 'Wheel %s not compatible with this Python.' % pathname raise DistlibException(msg) if not self.is_mountable(): msg = 'Wheel %s is marked as not mountable.' % pathname raise DistlibException(msg) if pathname in sys.path: logger.debug('%s already in path', pathname) else: if append: sys.path.append(pathname) else: sys.path.insert(0, pathname) extensions = self._get_extensions() if extensions: if _hook not in sys.meta_path: sys.meta_path.append(_hook) _hook.add(pathname, extensions) def unmount(self): pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) if pathname not in sys.path: logger.debug('%s not in path', pathname) else: sys.path.remove(pathname) if pathname in _hook.impure_wheels: _hook.remove(pathname) if not _hook.impure_wheels: if _hook in sys.meta_path: sys.meta_path.remove(_hook) def verify(self): pathname = os.path.join(self.dirname, self.filename) name_ver = '%s-%s' % (self.name, self.version) # data_dir = '%s.data' % name_ver info_dir = '%s.dist-info' % name_ver # metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') record_name = posixpath.join(info_dir, 'RECORD') wrapper = codecs.getreader('utf-8') with ZipFile(pathname, 'r') as zf: with zf.open(wheel_metadata_name) as bwf: wf = wrapper(bwf) message_from_file(wf) # wv = message['Wheel-Version'].split('.', 1) # file_version = tuple([int(i) for i in wv]) # TODO version verification records = {} with zf.open(record_name) as bf: with CSVReader(stream=bf) as reader: for row in reader: p = row[0] records[p] = row for zinfo in zf.infolist(): arcname = zinfo.filename if isinstance(arcname, text_type): u_arcname = arcname else: u_arcname = arcname.decode('utf-8') # See issue #115: some wheels have .. in their entries, but # in the filename ... e.g. __main__..py ! So the check is # updated to look for .. in the directory portions p = u_arcname.split('/') if '..' in p: raise DistlibException('invalid entry in ' 'wheel: %r' % u_arcname) if self.skip_entry(u_arcname): continue row = records[u_arcname] if row[2] and str(zinfo.file_size) != row[2]: raise DistlibException('size mismatch for ' '%s' % u_arcname) if row[1]: kind, value = row[1].split('=', 1) with zf.open(arcname) as bf: data = bf.read() _, digest = self.get_hash(data, kind) if digest != value: raise DistlibException('digest mismatch for ' '%s' % arcname) def update(self, modifier, dest_dir=None, **kwargs): """ Update the contents of a wheel in a generic way. The modifier should be a callable which expects a dictionary argument: its keys are archive-entry paths, and its values are absolute filesystem paths where the contents the corresponding archive entries can be found. The modifier is free to change the contents of the files pointed to, add new entries and remove entries, before returning. This method will extract the entire contents of the wheel to a temporary location, call the modifier, and then use the passed (and possibly updated) dictionary to write a new wheel. If ``dest_dir`` is specified, the new wheel is written there -- otherwise, the original wheel is overwritten. The modifier should return True if it updated the wheel, else False. This method returns the same value the modifier returns. """ def get_version(path_map, info_dir): version = path = None key = '%s/%s' % (info_dir, LEGACY_METADATA_FILENAME) if key not in path_map: key = '%s/PKG-INFO' % info_dir if key in path_map: path = path_map[key] version = Metadata(path=path).version return version, path def update_version(version, path): updated = None try: NormalizedVersion(version) i = version.find('-') if i < 0: updated = '%s+1' % version else: parts = [int(s) for s in version[i + 1:].split('.')] parts[-1] += 1 updated = '%s+%s' % (version[:i], '.'.join(str(i) for i in parts)) except UnsupportedVersionError: logger.debug('Cannot update non-compliant (PEP-440) ' 'version %r', version) if updated: md = Metadata(path=path) md.version = updated legacy = path.endswith(LEGACY_METADATA_FILENAME) md.write(path=path, legacy=legacy) logger.debug('Version updated from %r to %r', version, updated) pathname = os.path.join(self.dirname, self.filename) name_ver = '%s-%s' % (self.name, self.version) info_dir = '%s.dist-info' % name_ver record_name = posixpath.join(info_dir, 'RECORD') with tempdir() as workdir: with ZipFile(pathname, 'r') as zf: path_map = {} for zinfo in zf.infolist(): arcname = zinfo.filename if isinstance(arcname, text_type): u_arcname = arcname else: u_arcname = arcname.decode('utf-8') if u_arcname == record_name: continue if '..' in u_arcname: raise DistlibException('invalid entry in ' 'wheel: %r' % u_arcname) zf.extract(zinfo, workdir) path = os.path.join(workdir, convert_path(u_arcname)) path_map[u_arcname] = path # Remember the version. original_version, _ = get_version(path_map, info_dir) # Files extracted. Call the modifier. modified = modifier(path_map, **kwargs) if modified: # Something changed - need to build a new wheel. current_version, path = get_version(path_map, info_dir) if current_version and (current_version == original_version): # Add or update local version to signify changes. update_version(current_version, path) # Decide where the new wheel goes. if dest_dir is None: fd, newpath = tempfile.mkstemp(suffix='.whl', prefix='wheel-update-', dir=workdir) os.close(fd) else: if not os.path.isdir(dest_dir): raise DistlibException('Not a directory: %r' % dest_dir) newpath = os.path.join(dest_dir, self.filename) archive_paths = list(path_map.items()) distinfo = os.path.join(workdir, info_dir) info = distinfo, info_dir self.write_records(info, workdir, archive_paths) self.build_zip(newpath, archive_paths) if dest_dir is None: shutil.copyfile(newpath, pathname) return modified def _get_glibc_version(): import platform ver = platform.libc_ver() result = [] if ver[0] == 'glibc': for s in ver[1].split('.'): result.append(int(s) if s.isdigit() else 0) result = tuple(result) return result def compatible_tags(): """ Return (pyver, abi, arch) tuples compatible with this Python. """ class _Version: def __init__(self, major, minor): self.major = major self.major_minor = (major, minor) self.string = ''.join((str(major), str(minor))) def __str__(self): return self.string versions = [ _Version(sys.version_info.major, minor_version) for minor_version in range(sys.version_info.minor, -1, -1) ] abis = [] for suffix in _get_suffixes(): if suffix.startswith('.abi'): abis.append(suffix.split('.', 2)[1]) abis.sort() if ABI != 'none': abis.insert(0, ABI) abis.append('none') result = [] arches = [ARCH] if sys.platform == 'darwin': m = re.match(r'(\w+)_(\d+)_(\d+)_(\w+)$', ARCH) if m: name, major, minor, arch = m.groups() minor = int(minor) matches = [arch] if arch in ('i386', 'ppc'): matches.append('fat') if arch in ('i386', 'ppc', 'x86_64'): matches.append('fat3') if arch in ('ppc64', 'x86_64'): matches.append('fat64') if arch in ('i386', 'x86_64'): matches.append('intel') if arch in ('i386', 'x86_64', 'intel', 'ppc', 'ppc64'): matches.append('universal') while minor >= 0: for match in matches: s = '%s_%s_%s_%s' % (name, major, minor, match) if s != ARCH: # already there arches.append(s) minor -= 1 # Most specific - our Python version, ABI and arch for i, version_object in enumerate(versions): version = str(version_object) add_abis = [] if i == 0: add_abis = abis if IMP_PREFIX == 'cp' and version_object.major_minor >= (3, 2): limited_api_abi = 'abi' + str(version_object.major) if limited_api_abi not in add_abis: add_abis.append(limited_api_abi) for abi in add_abis: for arch in arches: result.append((''.join((IMP_PREFIX, version)), abi, arch)) # manylinux if abi != 'none' and sys.platform.startswith('linux'): arch = arch.replace('linux_', '') parts = _get_glibc_version() if len(parts) == 2: if parts >= (2, 5): result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux1_%s' % arch)) if parts >= (2, 12): result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2010_%s' % arch)) if parts >= (2, 17): result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2014_%s' % arch)) result.append((''.join( (IMP_PREFIX, version)), abi, 'manylinux_%s_%s_%s' % (parts[0], parts[1], arch))) # where no ABI / arch dependency, but IMP_PREFIX dependency for i, version_object in enumerate(versions): version = str(version_object) result.append((''.join((IMP_PREFIX, version)), 'none', 'any')) if i == 0: result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any')) # no IMP_PREFIX, ABI or arch dependency for i, version_object in enumerate(versions): version = str(version_object) result.append((''.join(('py', version)), 'none', 'any')) if i == 0: result.append((''.join(('py', version[0])), 'none', 'any')) return set(result) COMPATIBLE_TAGS = compatible_tags() del compatible_tags def is_compatible(wheel, tags=None): if not isinstance(wheel, Wheel): wheel = Wheel(wheel) # assume it's a filename result = False if tags is None: tags = COMPATIBLE_TAGS for ver, abi, arch in tags: if ver in wheel.pyver and abi in wheel.abi and arch in wheel.arch: result = True break return result ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5235376 distlib-0.4.3/distlib.egg-info/0000775000175000017500000000000015212736416015403 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781251341.0 distlib-0.4.3/distlib.egg-info/PKG-INFO0000644000175000017500000001230515212736415016476 0ustar00vinayvinayMetadata-Version: 2.4 Name: distlib Version: 0.4.3 Summary: Distribution utilities Home-page: https://github.com/pypa/distlib Author: Vinay Sajip Author-email: vinay_sajip@red-dove.com License: PSF-2.0 Project-URL: Documentation, https://distlib.readthedocs.io/ Project-URL: Source, https://github.com/pypa/distlib Project-URL: Tracker, https://github.com/pypa/distlib/issues Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development License-File: LICENSE.txt Dynamic: license-file |badge1| |badge2| .. |badge1| image:: https://img.shields.io/github/actions/workflow/status/pypa/distlib/package-tests.yml :alt: GitHub Workflow Status (with event) .. |badge2| image:: https://img.shields.io/codecov/c/github/pypa/distlib :target: https://app.codecov.io/gh/pypa/distlib :alt: GitHub coverage status What is it? ----------- Distlib is a library which implements low-level functions that relate to packaging and distribution of Python software. It is intended to be used as the basis for third-party packaging tools. The documentation is available at https://distlib.readthedocs.io/ Main features ------------- Distlib currently offers the following features: * The package ``distlib.database``, which implements a database of installed distributions, as defined by :pep:`376`, and distribution dependency graph logic. Support is also provided for non-installed distributions (i.e. distributions registered with metadata on an index like PyPI), including the ability to scan for dependencies and building dependency graphs. * The package ``distlib.index``, which implements an interface to perform operations on an index, such as registering a project, uploading a distribution or uploading documentation. Support is included for verifying SSL connections (with domain matching) and signing/verifying packages using GnuPG. * The package ``distlib.metadata``, which implements distribution metadata as defined by :pep:`643`, :pep:`566`, :pep:`345`, :pep:`314` and :pep:`241`. * The package ``distlib.markers``, which implements environment markers as defined by :pep:`508`. * The package ``distlib.manifest``, which implements lists of files used in packaging source distributions. * The package ``distlib.locators``, which allows finding distributions, whether on PyPI (XML-RPC or via the "simple" interface), local directories or some other source. * The package ``distlib.resources``, which allows access to data files stored in Python packages, both in the file system and in .zip files. * The package ``distlib.scripts``, which allows installing of scripts with adjustment of shebang lines and support for native Windows executable launchers. * The package ``distlib.version``, which implements version specifiers as defined by :pep:`440`, but also support for working with "legacy" versions and semantic versions. * The package ``distlib.wheel``, which provides support for building and installing from the Wheel format for binary distributions (see :pep:`427`). * The package ``distlib.util``, which contains miscellaneous functions and classes which are useful in packaging, but which do not fit neatly into one of the other packages in ``distlib``.* The package implements enhanced globbing functionality such as the ability to use ``**`` in patterns to specify recursing into subdirectories. Python version and platform compatibility ----------------------------------------- Distlib is intended to be used on and is tested on Python versions 2.7 and 3.6 or later, pypy-2.7 and pypy3 on Linux, Windows, and macOS. Project status -------------- The project has reached a mature status in its development: there is a comprehensive test suite and it has been exercised on Windows, Ubuntu and macOS. The project is used by well-known projects such as `pip `_ and `caniusepython3 `_. This project was migrated from Mercurial to Git and from BitBucket to GitHub, and although all information of importance has been retained across the migration, some commit references in issues and issue comments may have become invalid. Code of Conduct --------------- Everyone interacting in the distlib project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781251341.0 distlib-0.4.3/distlib.egg-info/SOURCES.txt0000664000175000017500000001333115212736415017267 0ustar00vinayvinayCHANGES.rst CONTRIBUTORS.txt LICENSE.txt MANIFEST.in README.rst pyproject.toml setup.cfg tox.ini PC/CLISimpleLauncher.vcxproj PC/GUISimpleLauncher.vcxproj PC/ReadMe.txt PC/SimpleLauncher.sln PC/launcher.c PC/launcher.ico PC/launcher.rc distlib/__init__.py distlib/compat.py distlib/database.py distlib/index.py distlib/locators.py distlib/manifest.py distlib/markers.py distlib/metadata.py distlib/resources.py distlib/scripts.py distlib/t32.exe distlib/t64-arm.exe distlib/t64.exe distlib/util.py distlib/version.py distlib/w32.exe distlib/w64-arm.exe distlib/w64.exe distlib/wheel.py distlib.egg-info/PKG-INFO distlib.egg-info/SOURCES.txt distlib.egg-info/dependency_links.txt distlib.egg-info/top_level.txt tests/LONG_DESC.txt tests/PKG-INFO tests/SETUPTOOLS-PKG-INFO tests/SETUPTOOLS-PKG-INFO2 tests/bad.bin tests/bad.tar tests/bad.tar.bz2 tests/bad.tar.gz tests/bad.zip tests/bar.zip tests/compat.py tests/distlib_tests.py tests/dummy-0.1-py27-none-any.whl tests/evil.tar.gz tests/evilpkg-1.0-py3-none-any.whl tests/foo.zip tests/good.bin tests/good.bin.asc tests/good.tar tests/good.tar.bz2 tests/good.tar.gz tests/good.zip tests/included.json tests/keycert.pem tests/minimext-0.1-cp27-none-linux_x86_64.whl tests/minimext-0.1-cp32-cp32mu-linux_x86_64.whl tests/minimext-0.1-cp33-cp33m-linux_x86_64.whl tests/pydist.json tests/support.py tests/test_all.py tests/test_database.py tests/test_index.py tests/test_locators.py tests/test_manifest.py tests/test_markers.py tests/test_metadata.py tests/test_resources.py tests/test_scripts.py tests/test_testdist-0.1.zip tests/test_util.py tests/test_version.py tests/test_wheel.py tests/valid_wheel-0.0.1-py3-none-any.whl tests/bad_wheels/dummy-0.1-py27-none-any.whl tests/fake_archives/coverage-3.3.1.tar.gz tests/fake_archives/coverage-3.4b2.tar.gz tests/fake_archives/coverage-3.5.2.tar.gz tests/fake_archives/subdir/config-0.3.7-py27-none-any.whl tests/fake_archives/subdir/python-gnupg-0.2.9.tar.gz tests/fake_archives/subdir/subsubdir/Django-1.4.1.tar.gz tests/fake_archives/subdir/subsubdir/Flask-0.9.tar.gz tests/fake_dists/babar.cfg tests/fake_dists/babar.png tests/fake_dists/cheese-2.0.2.egg-info tests/fake_dists/nut-funkyversion.egg-info tests/fake_dists/strawberry-0.6.egg tests/fake_dists/truffles-5.0.egg-info tests/fake_dists/babar-0.1.dist-info/INSTALLER tests/fake_dists/babar-0.1.dist-info/METADATA tests/fake_dists/babar-0.1.dist-info/RECORD tests/fake_dists/babar-0.1.dist-info/REQUESTED tests/fake_dists/babar-0.1.dist-info/RESOURCES tests/fake_dists/babar-0.1.dist-info/pydist-exports.json tests/fake_dists/babar-0.1.dist-info/pydist.json tests/fake_dists/bacon-0.1.egg-info/PKG-INFO tests/fake_dists/bacon-0.1.egg-info/installed-files.txt tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt tests/fake_dists/choxie-2.0.0.9/truffles.py tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED tests/fake_dists/choxie-2.0.0.9.dist-info/pydist-exports.json tests/fake_dists/choxie-2.0.0.9.dist-info/pydist.json tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER tests/fake_dists/grammar-1.0a4.dist-info/METADATA tests/fake_dists/grammar-1.0a4.dist-info/RECORD tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED tests/fake_dists/grammar-1.0a4.dist-info/pydist.json tests/fake_dists/grammar-1.0a4/grammar/__init__.py tests/fake_dists/grammar-1.0a4/grammar/utils.py tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER tests/fake_dists/towel_stuff-0.1.dist-info/METADATA tests/fake_dists/towel_stuff-0.1.dist-info/RECORD tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED tests/fake_dists/towel_stuff-0.1.dist-info/pydist-exports.json tests/fake_dists/towel_stuff-0.1.dist-info/pydist.json tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py tests/foofoo/__init__.py tests/foofoo/foo_resource.bin tests/foofoo/bar/__init__.py tests/foofoo/bar/bar_resource.bin tests/foofoo/bar/baz.py tests/foofoo/nested/nested_resource.bin tests/keys/.gpg-v21-migrated tests/keys/pubring.gpg tests/keys/random_seed tests/keys/secring.gpg tests/keys/trustdb.gpg tests/keys/private-keys-v1.d/89643D011E83504EFB6A88F328153504C3E2C97B.key tests/keys/private-keys-v1.d/8BE462CC6083085304DBEA3FC423A1276DAF0B2C.key tests/minimext/LICENSE tests/minimext/README.txt tests/minimext/build_wheels.sh tests/minimext/calculate.c tests/minimext/setup.py tests/minimext/minimext/__init__.py tests/minimext/minimext/calculate_py.py tests/scripts/foo.py tests/scripts/script1.py tests/scripts/script2.py tests/scripts/script3.py tests/scripts/script4.py tests/scripts/script5.py tests/scripts/script6.py tests/scripts/script7.pyw tests/scripts/script8.py tests/scripts/shell.sh tests/scripts/uwsgi_part tests/test_testdist-0.1/LICENSE tests/test_testdist-0.1/PKG-INFO tests/test_testdist-0.1/README.txt tests/test_testdist-0.1/package.json tests/test_testdist-0.1/testdist.py tests/testdist-0.1/LICENSE tests/testdist-0.1/PKG-INFO tests/testdist-0.1/README.txt tests/testdist-0.1/package.json tests/testdist-0.1/testdist.py tests/testdist-0.1/doc/index.html tests/testsrc/.hidden tests/testsrc/LICENSE tests/testsrc/README.txt tests/testsrc/keep/keep.txt tests/testsrc/subdir/somedata.txt tests/testsrc/subdir/lose/lose.txt tests/testsrc/subdir/subsubdir/somedata.bin././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781251341.0 distlib-0.4.3/distlib.egg-info/dependency_links.txt0000664000175000017500000000000115212736415021450 0ustar00vinayvinay ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781251341.0 distlib-0.4.3/distlib.egg-info/top_level.txt0000664000175000017500000000001015212736415020123 0ustar00vinayvinaydistlib ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1781249216.0 distlib-0.4.3/pyproject.toml0000664000175000017500000000033515212732300015160 0ustar00vinayvinay[build-system] requires = [ # "setuptools >= 68,<75", # For metadata 2.1 # "setuptools==75.8.0", # for metadata 2.2 "setuptools >= 68", # for metadata 2.4 "wheel" ] build-backend = 'setuptools.build_meta' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5235376 distlib-0.4.3/setup.cfg0000664000175000017500000000251615212736416014104 0ustar00vinayvinay[metadata] name = distlib version = attr: distlib.__version__ description = Distribution utilities long_description = file: README.rst url = https://github.com/pypa/distlib author = Vinay Sajip author_email = vinay_sajip@red-dove.com license = PSF-2.0 license_files = LICENSE.txt requires_python > = 2.7 classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers License :: OSI Approved :: Python Software Foundation License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 3 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: 3.14 Topic :: Software Development project_urls = Documentation = https://distlib.readthedocs.io/ Source = https://github.com/pypa/distlib Tracker = https://github.com/pypa/distlib/issues platforms = any [options] packages = distlib include_package_data = True [bdist_wheel] python_tag = py2.py3 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5075343 distlib-0.4.3/tests/0000775000175000017500000000000015212736416013421 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/LONG_DESC.txt0000644000175000017500000000236613453041443015516 0ustar00vinayvinayCLVault ======= CLVault uses Keyring to provide a command-line utility to safely store and retrieve passwords. Install it using pip or the setup.py script:: $ python setup.py install $ pip install clvault Once it's installed, you will have three scripts installed in your Python scripts folder, you can use to list, store and retrieve passwords:: $ clvault-set blog Set your password: Set the associated username (can be blank): tarek Set a description (can be blank): My blog password Password set. $ clvault-get blog The username is "tarek" The password has been copied in your clipboard $ clvault-list Registered services: blog My blog password *clvault-set* takes a service name then prompt you for a password, and some optional information about your service. The password is safely stored in a keyring while the description is saved in a ``.clvault`` file in your home directory. This file is created automatically the first time the command is used. *clvault-get* copies the password for a given service in your clipboard, and displays the associated user if any. *clvault-list* lists all registered services, with their description when given. Project page: http://bitbucket.org/tarek/clvault ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/PKG-INFO0000644000175000017500000000371013453041443014507 0ustar00vinayvinayMetadata-Version: 1.2 Name: CLVault Version: 0.5 Summary: Command-Line utility to store and retrieve passwords Home-page: http://bitbucket.org/tarek/clvault Author: Tarek Ziade Author-email: tarek@ziade.org License: PSF Keywords: keyring,password,crypt Requires-Dist: foo; sys.platform == 'okook' Requires-Dist: bar; sys.platform == '%s' Platform: UNKNOWN Description: CLVault |======= | |CLVault uses Keyring to provide a command-line utility to safely store |and retrieve passwords. | |Install it using pip or the setup.py script:: | | $ python setup.py install | | $ pip install clvault | |Once it's installed, you will have three scripts installed in your |Python scripts folder, you can use to list, store and retrieve passwords:: | | $ clvault-set blog | Set your password: | Set the associated username (can be blank): tarek | Set a description (can be blank): My blog password | Password set. | | $ clvault-get blog | The username is "tarek" | The password has been copied in your clipboard | | $ clvault-list | Registered services: | blog My blog password | | |*clvault-set* takes a service name then prompt you for a password, and some |optional information about your service. The password is safely stored in |a keyring while the description is saved in a ``.clvault`` file in your |home directory. This file is created automatically the first time the command |is used. | |*clvault-get* copies the password for a given service in your clipboard, and |displays the associated user if any. | |*clvault-list* lists all registered services, with their description when |given. | | |Project page: http://bitbucket.org/tarek/clvault | ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/SETUPTOOLS-PKG-INFO0000644000175000017500000002116313453041443016230 0ustar00vinayvinayMetadata-Version: 1.0 Name: setuptools Version: 0.6c9 Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! Home-page: http://pypi.org/pypi/setuptools Author: Phillip J. Eby Author-email: distutils-sig@python.org License: PSF or ZPL Description: =============================== Installing and Using Setuptools =============================== .. contents:: **Table of Contents** ------------------------- Installation Instructions ------------------------- Windows ======= Install setuptools using the provided ``.exe`` installer. If you've previously installed older versions of setuptools, please delete all ``setuptools*.egg`` and ``setuptools.pth`` files from your system's ``site-packages`` directory (and any other ``sys.path`` directories) FIRST. If you are upgrading a previous version of setuptools that was installed using an ``.exe`` installer, please be sure to also *uninstall that older version* via your system's "Add/Remove Programs" feature, BEFORE installing the newer version. Once installation is complete, you will find an ``easy_install.exe`` program in your Python ``Scripts`` subdirectory. Be sure to add this directory to your ``PATH`` environment variable, if you haven't already done so. RPM-Based Systems ================= Install setuptools using the provided source RPM. The included ``.spec`` file assumes you are installing using the default ``python`` executable, and is not specific to a particular Python version. The ``easy_install`` executable will be installed to a system ``bin`` directory such as ``/usr/bin``. If you wish to install to a location other than the default Python installation's default ``site-packages`` directory (and ``$prefix/bin`` for scripts), please use the ``.egg``-based installation approach described in the following section. Cygwin, Mac OS X, Linux, Other ============================== 1. Download the appropriate egg for your version of Python (e.g. ``setuptools-0.6c9-py2.4.egg``). Do NOT rename it. 2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. Setuptools will install itself using the matching version of Python (e.g. ``python2.4``), and will place the ``easy_install`` executable in the default location for installing Python scripts (as determined by the standard distutils configuration files, or by the Python installation). If you want to install setuptools to somewhere other than ``site-packages`` or your default distutils installation locations for libraries and scripts, you may include EasyInstall command-line options such as ``--prefix``, ``--install-dir``, and so on, following the ``.egg`` filename on the same command line. For example:: sh setuptools-0.6c9-py2.4.egg --prefix=~ You can use ``--help`` to get a full options list, but we recommend consulting the `EasyInstall manual`_ for detailed instructions, especially `the section on custom installation locations`_. .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations Cygwin Note ----------- If you are trying to install setuptools for the **Windows** version of Python (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make sure that an appropriate executable (``python2.3``, ``python2.4``, or ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg. For example, doing the following at a Cygwin bash prompt will install setuptools for the **Windows** Python found at ``C:\\Python24``:: ln -s /cygdrive/c/Python24/python.exe python2.4 PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg rm python2.4 Downloads ========= All setuptools downloads can be found at `the project's home page in the Python Package Index`_. Scroll to the very bottom of the page to find the links. .. _the project's home page in the Python Package Index: http://pypi.org/pypi/setuptools In addition to the PyPI downloads, the development version of ``setuptools`` is available from the `Python SVN sandbox`_, and in-development versions of the `0.6 branch`_ are available as well. .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev -------------------------------- Using Setuptools and EasyInstall -------------------------------- Here are some of the available manuals, tutorials, and other resources for learning about Setuptools, Python Eggs, and EasyInstall: * `The EasyInstall user's guide and reference manual`_ * `The setuptools Developer's Guide`_ * `The pkg_resources API reference`_ * `Package Compatibility Notes`_ (user-maintained) * `The Internal Structure of Python Eggs`_ Questions, comments, and bug reports should be directed to the `distutils-sig mailing list`_. If you have written (or know of) any tutorials, documentation, plug-ins, or other resources for setuptools users, please let us know about them there, so this reference list can be updated. If you have working, *tested* patches to correct problems or add features, you may submit them to the `setuptools bug tracker`_. .. _setuptools bug tracker: http://bugs.python.org/setuptools/ .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ ------- Credits ------- * The original design for the ``.egg`` format and the ``pkg_resources`` API was co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first version of ``pkg_resources``, and supplied the OS X operating system version compatibility algorithm. * Ian Bicking implemented many early "creature comfort" features of easy_install, including support for downloading via Sourceforge and Subversion repositories. Ian's comments on the Web-SIG about WSGI application deployment also inspired the concept of "entry points" in eggs, and he has given talks at PyCon and elsewhere to inform and educate the community about eggs and setuptools. * Jim Fulton contributed time and effort to build automated tests of various aspects of ``easy_install``, and supplied the doctests for the command-line ``.exe`` wrappers on Windows. * Phillip J. Eby is the principal author and maintainer of setuptools, and first proposed the idea of an importable binary distribution format for Python application plug-ins. * Significant parts of the implementation of setuptools were funded by the Open Source Applications Foundation, to provide a plug-in infrastructure for the Chandler PIM application. In addition, many OSAF staffers (such as Mike "Code Bear" Taylor) contributed their time and stress as guinea pigs for the use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!) Keywords: CPAN PyPI distutils eggs package management Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Archiving :: Packaging Classifier: Topic :: System :: Systems Administration Classifier: Topic :: Utilities ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/SETUPTOOLS-PKG-INFO20000644000175000017500000002121013453041443016303 0ustar00vinayvinayMetadata-Version: 1.1 Name: setuptools Version: 0.6c9 Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! Home-page: http://pypi.python.org/pypi/setuptools Author: Phillip J. Eby Author-email: distutils-sig@python.org License: PSF or ZPL Description: =============================== Installing and Using Setuptools =============================== .. contents:: **Table of Contents** ------------------------- Installation Instructions ------------------------- Windows ======= Install setuptools using the provided ``.exe`` installer. If you've previously installed older versions of setuptools, please delete all ``setuptools*.egg`` and ``setuptools.pth`` files from your system's ``site-packages`` directory (and any other ``sys.path`` directories) FIRST. If you are upgrading a previous version of setuptools that was installed using an ``.exe`` installer, please be sure to also *uninstall that older version* via your system's "Add/Remove Programs" feature, BEFORE installing the newer version. Once installation is complete, you will find an ``easy_install.exe`` program in your Python ``Scripts`` subdirectory. Be sure to add this directory to your ``PATH`` environment variable, if you haven't already done so. RPM-Based Systems ================= Install setuptools using the provided source RPM. The included ``.spec`` file assumes you are installing using the default ``python`` executable, and is not specific to a particular Python version. The ``easy_install`` executable will be installed to a system ``bin`` directory such as ``/usr/bin``. If you wish to install to a location other than the default Python installation's default ``site-packages`` directory (and ``$prefix/bin`` for scripts), please use the ``.egg``-based installation approach described in the following section. Cygwin, Mac OS X, Linux, Other ============================== 1. Download the appropriate egg for your version of Python (e.g. ``setuptools-0.6c9-py2.4.egg``). Do NOT rename it. 2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. Setuptools will install itself using the matching version of Python (e.g. ``python2.4``), and will place the ``easy_install`` executable in the default location for installing Python scripts (as determined by the standard distutils configuration files, or by the Python installation). If you want to install setuptools to somewhere other than ``site-packages`` or your default distutils installation locations for libraries and scripts, you may include EasyInstall command-line options such as ``--prefix``, ``--install-dir``, and so on, following the ``.egg`` filename on the same command line. For example:: sh setuptools-0.6c9-py2.4.egg --prefix=~ You can use ``--help`` to get a full options list, but we recommend consulting the `EasyInstall manual`_ for detailed instructions, especially `the section on custom installation locations`_. .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations Cygwin Note ----------- If you are trying to install setuptools for the **Windows** version of Python (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make sure that an appropriate executable (``python2.3``, ``python2.4``, or ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg. For example, doing the following at a Cygwin bash prompt will install setuptools for the **Windows** Python found at ``C:\\Python24``:: ln -s /cygdrive/c/Python24/python.exe python2.4 PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg rm python2.4 Downloads ========= All setuptools downloads can be found at `the project's home page in the Python Package Index`_. Scroll to the very bottom of the page to find the links. .. _the project's home page in the Python Package Index: http://pypi.org/pypi/setuptools In addition to the PyPI downloads, the development version of ``setuptools`` is available from the `Python SVN sandbox`_, and in-development versions of the `0.6 branch`_ are available as well. .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev -------------------------------- Using Setuptools and EasyInstall -------------------------------- Here are some of the available manuals, tutorials, and other resources for learning about Setuptools, Python Eggs, and EasyInstall: * `The EasyInstall user's guide and reference manual`_ * `The setuptools Developer's Guide`_ * `The pkg_resources API reference`_ * `Package Compatibility Notes`_ (user-maintained) * `The Internal Structure of Python Eggs`_ Questions, comments, and bug reports should be directed to the `distutils-sig mailing list`_. If you have written (or know of) any tutorials, documentation, plug-ins, or other resources for setuptools users, please let us know about them there, so this reference list can be updated. If you have working, *tested* patches to correct problems or add features, you may submit them to the `setuptools bug tracker`_. .. _setuptools bug tracker: http://bugs.python.org/setuptools/ .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ ------- Credits ------- * The original design for the ``.egg`` format and the ``pkg_resources`` API was co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first version of ``pkg_resources``, and supplied the OS X operating system version compatibility algorithm. * Ian Bicking implemented many early "creature comfort" features of easy_install, including support for downloading via Sourceforge and Subversion repositories. Ian's comments on the Web-SIG about WSGI application deployment also inspired the concept of "entry points" in eggs, and he has given talks at PyCon and elsewhere to inform and educate the community about eggs and setuptools. * Jim Fulton contributed time and effort to build automated tests of various aspects of ``easy_install``, and supplied the doctests for the command-line ``.exe`` wrappers on Windows. * Phillip J. Eby is the principal author and maintainer of setuptools, and first proposed the idea of an importable binary distribution format for Python application plug-ins. * Significant parts of the implementation of setuptools were funded by the Open Source Applications Foundation, to provide a plug-in infrastructure for the Chandler PIM application. In addition, many OSAF staffers (such as Mike "Code Bear" Taylor) contributed their time and stress as guinea pigs for the use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!) Keywords: CPAN PyPI distutils eggs package management Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Archiving :: Packaging Classifier: Topic :: System :: Systems Administration Classifier: Topic :: Utilities Requires: Foo ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/bad.bin0000644000175000017500000000001513453041443014625 0ustar00vinayvinayHello, world ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/bad.tar0000644000175000017500000002400013453041443014643 0ustar00vinayvinayfile1.txt0000664000175000017500000000000012056711465011654 0ustar vinayvinaysubdir/0000775000175000017500000000000012056711513011407 5ustar vinayvinaysubdir/subsubdir/0000775000175000017500000000000012056711527013416 5ustar vinayvinaysubdir/subsubdir/file3.txt0000664000175000017500000000000012056711527015147 0ustar vinayvinaysubdir/file2.txt0000664000175000017500000000000012056711504013137 0ustar vinayvinay../file4.txt0000664000175000017500000000000012056713322012064 0ustar vinayvinay/tmp/file5.txt0000664000175000017500000000000012056713445012537 0ustar vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/bad.tar.bz20000644000175000017500000000037613453041443015351 0ustar00vinayvinayBZh91AY&SY _SU@Ԡw'_`@0hd&j=S)!mDy 24ژ) ^:/-rQtͫ5 Kdm "ׇ=S޴]])pU! JKѿC?fBVb /x 9KUY OQUS{.y5E8)7r3|Wg`(././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/bad.zip0000644000175000017500000000170413453041443014665 0ustar00vinayvinayPK iA file1.txtUT 5P}Pux PK tAsubdir/UT KPZPux PK zAsubdir/subsubdir/UT WPZPux PK zAsubdir/subsubdir/file3.txtUT WP}Pux PK pAsubdir/file2.txtUT DP}Pux PK UA ../file4.txtUT ҖPܖPux PK iA file1.txtUT5Pux PK tAACsubdir/UTKPux PK zAAsubdir/subsubdir/UTWPux PK zAsubdir/subsubdir/file3.txtUTWPux PK pA#subdir/file2.txtUTDPux PK UA m../file4.txtUTҖPux PK././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5115352 distlib-0.4.3/tests/bad_wheels/0000775000175000017500000000000015212736416015516 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/bad_wheels/dummy-0.1-py27-none-any.whl0000644000175000017500000000234013453041443022171 0ustar00vinayvinayPKC}Bdummy.pyPKL4DKmdummy-0.1.dist-info/pydist.jsonUT RRux n0 <H[ll7$&4MicJ&s¸= !Z1"?8.G0O m a5Dâ(SТэo%1X9mR1Dx%eӆrTh&ąuHa8'}̱ZR3F*m4-ixE6GmxHV0k^PϿn1WYaj42;Jy*Y+#E }ym'mENUNVХO4I)TqnR~55~} ,Xh3PKQ}BjqMZZYdummy-0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,.LR033KI-3 /, (-JJY)r$[)T&UPKQ}BJ5dummy-0.1.dist-info/RECORDm̻0@oiZ+vp@E֯7nnw'nRJ: {d0[u8{!<u'VJ8ÙN}`v Mq^70.6yO^N%ܷL=[F[(^7@Fu圱O<;tD6+BuΦE:':PKC}Bdummy.pyPKL4DKm(dummy-0.1.dist-info/pydist.jsonUTRux PKQ}BjqMZZYdummy-0.1.dist-info/WHEELPKQ}BJ5dummy-0.1.dist-info/RECORDPK*././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/bar.zip0000644000175000017500000000170013453041443014677 0ustar00vinayvinayPK B4Alib/barbar/__init__.pyUT [PT[dPux # empty PK M4Alib/barbar/baz/__init__.pyUT [P[dPux # empty PK 54Alib/barbar/baz/baz_resource.binUT }[P[dPux data PK N4Aԉ lib/barbar/bar_resource.binUT }[Pd[dPux more_data PK %w;Aԋ6## readme.txtUT 5[dP[dPux Useful stuff is in the lib folder. PK B4Alib/barbar/__init__.pyUT[Pux PK M4AXlib/barbar/baz/__init__.pyUT[Pux PK 54Alib/barbar/baz/baz_resource.binUT}[Pux PK N4Aԉ lib/barbar/bar_resource.binUT}[Pux PK %w;Aԋ6## qreadme.txtUT5[dPux PK././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1638955039.0 distlib-0.4.3/tests/compat.py0000644000175000017500000000134314154074037015253 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # import sys import unittest _ver = sys.version_info[:2] if _ver[0] < 3: import Queue as queue from SimpleXMLRPCServer import SimpleXMLRPCServer from SimpleHTTPServer import SimpleHTTPRequestHandler from BaseHTTPServer import HTTPServer text_type = unicode from urllib import unquote from urllib2 import Request from urlparse import urlparse else: import queue from xmlrpc.server import SimpleXMLRPCServer from http.server import HTTPServer, SimpleHTTPRequestHandler text_type = str from urllib.parse import urlparse, unquote from urllib.request import Request ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710848800.0 distlib-0.4.3/tests/distlib_tests.py0000644000175000017500000000157114576275440016660 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2013 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # import sys _ver = sys.version_info[:2] from test_database import (DataFilesTestCase, TestDatabase, TestDistribution, TestEggInfoDistribution, DepGraphTestCase) from test_index import PackageIndexTestCase from test_locators import LocatorTestCase from test_manifest import ManifestTestCase from test_markers import MarkersTestCase from test_metadata import MetadataTestCase, LegacyMetadataTestCase from test_resources import (ZipResourceTestCase, FileResourceTestCase, CacheTestCase) from test_scripts import ScriptTestCase from test_version import (VersionTestCase, CompatibilityTestCase, LegacyVersionTestCase, SemanticVersionTestCase) from test_wheel import WheelTestCase from test_util import (UtilTestCase, ProgressTestCase, FileOpsTestCase, GlobTestCase) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/dummy-0.1-py27-none-any.whl0000644000175000017500000000423513453041443020101 0ustar00vinayvinayPKD*dummy-0.1.data/scripts/dummy.pySV,,SuJ+U(((+Q-/*RR K2Ksr4Kt R4NC]WY.2Rׄ(7@ 桺?/HP<@=%PKD_rdummy.pyK+U(((+Q-/*RR K2Ksr4Kt R4NC]WY.2Rׄ(7@ 桺?/HP<@=%PKD_rhooks.pyK+U(((+Q-/*RR K2Ksr4Kt R4NC]WY.2Rׄ(7@ 桺?/HP<@=%PKD ]rdummy-0.1.dist-info/METADATAM-ILI,I K-*ϳR03KMRH)ͭK%BEV Y⒢̤.̂bҜT +A PKD·Zdummy-0.1.dist-info/pydist.jsonTn0+<@nŀqnAPF&‡@RN%z̬ޝЕV4^62U+C*e 2Qq?!4Jq]] dh=',tM'~c_ 9+%wN nx'Q=<n+#)G\+/Ԙ0F:e%;hِ#wCA}A5N'_EX yu-?bGeݾo8OIu5GߪVm͐3ѓ0=*u ۛ!TC9ޟ ~rO͞gtE9'1yԭ"?l' }@U8 -ߺl>DZS".CpSGbowV&'sԋl3,|PKD*dummy-0.1.data/scripts/dummy.pyPKD_rdummy.pyPKD_rThooks.pyPKD ]rdummy-0.1.dist-info/METADATAPKD·Zdummy-0.1.dist-info/pydist.jsonPKD_WYdummy-0.1.dist-info/WHEELPKDr82>dummy-0.1.dist-info/RECORDPK././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1688146843.0 distlib-0.4.3/tests/evil.tar.gz0000664000175000017500000000022414447611633015507 0ustar00vinayvinay8devil.tarA @ 'ȤvGqPEoRAZ)]hr>MUkـ5)GofQ:]qn?i7E1%my2g^=o0Gw](././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1780437042.0 distlib-0.4.3/tests/evilpkg-1.0-py3-none-any.whl0000664000175000017500000000221215207650062020316 0ustar00vinayvinayPK\٥4CD../../../victim/PWNED_LIB.pySVp vv puQ(LTH,.LRHM+.IQ$d(%'p*rPK\gŎ@64evilpkg/__init__.pySVHJLSO)IU(H,Q(/IQ(.ITHKKMVU)*MPK\xKJevilpkg-1.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/R(O /, (-JLR()*M ILJW&UrPK\6,1evilpkg-1.0.dist-info/METADATAM-ILI,I K-*ϳR03KMRH-)NKpPK\=0.9,<=1.5) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/bacon-0.1.egg-info/installed-files.txt0000644000175000017500000000012413453041443024412 0ustar00vinayvinay../dummy.py ../dummy.pyc ./ PKG-INFO SOURCES.txt top_level.txt dependency_links.txt ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.4915311 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/0000775000175000017500000000000015212736415020014 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5115352 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/0000775000175000017500000000000015212736416021150 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO0000644000175000017500000000107013453041443022233 0ustar00vinayvinayMetadata-Version: 1.0 Name: banana Version: 0.4 Summary: A yellow fruit Home-page: http://en.wikipedia.org/wiki/Banana Author: Josip Djolonga Author-email: foo@nbar.com License: BSD Description: A fruit Keywords: foo bar Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering :: GIS ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt0000644000175000017500000000000013453041443023012 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt0000644000175000017500000000000113453041443025206 0ustar00vinayvinay ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt0000644000175000017500000000004513453041443024435 0ustar00vinayvinay # -*- Entry points: -*- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe0000644000175000017500000000000113453041443023366 0ustar00vinayvinay ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt0000644000175000017500000000011013453041443023530 0ustar00vinayvinay# this should be ignored strawberry >=0.5 [section ignored] foo ==0.5 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt0000644000175000017500000000002113453041443023663 0ustar00vinayvinaybanana cavendish ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/cheese-2.0.2.egg-info0000644000175000017500000000017113453041443021032 0ustar00vinayvinayMetadata-Version: 1.2 Name: cheese Version: 2.0.2 Provides-Dist: truffles (1.0.2) Obsoletes-Dist: truffles (!=1.2,<=2.0) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5115352 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9/0000775000175000017500000000000015212736416017616 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9/choxie/0000775000175000017500000000000015212736416021075 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py0000644000175000017500000000003013453041443023167 0ustar00vinayvinay# -*- coding: utf-8 -*- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py0000644000175000017500000000032613453041443023401 0ustar00vinayvinay# -*- coding: utf-8 -*- from towel_stuff import Towel class Chocolate(object): """A piece of chocolate.""" def wrap_with_towel(self): towel = Towel() towel.wrap(self) return towel ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9/truffles.py0000644000175000017500000000015713453041443022015 0ustar00vinayvinay# -*- coding: utf-8 -*- from choxie.chocolate import Chocolate class Truffle(Chocolate): """A truffle.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9.dist-info/0000775000175000017500000000000015212736416021511 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER0000644000175000017500000000000013453041443022747 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA0000644000175000017500000000045413453041443022607 0ustar00vinayvinayMetadata-Version: 1.2 Name: choxie Version: 2.0.0.9 Summary: Chocolate with a kick! Description: Chocolate with a longer kick! Requires-Dist: towel-stuff (0.1) Requires-Dist: nut Provides-Dist: truffles (1.0) Obsoletes-Dist: truffles (<=0.8,>=0.5) Obsoletes-Dist: truffles (<=0.9,>=0.6) License: BSD ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD0000644000175000017500000000000013453041443022370 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED0000644000175000017500000000000013453041443022753 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9.dist-info/pydist-exports.json0000644000175000017500000000047713453041443025422 0ustar00vinayvinay{ "metadata_version": "2.0", "generator": "distlib (0.1.3.dev0)", "name": "choxie", "extensions": { "python.exports": { "exports": { "foo": { "bar": "crunchie" }, "bar.baz": { "foofoo": "ferrero:rocher" } } } }, "version": "2.0.0.9" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/choxie-2.0.0.9.dist-info/pydist.json0000644000175000017500000000073413453041443023714 0ustar00vinayvinay{ "run_requires": [ { "requires": [ "towel-stuff (0.1)", "nut" ] } ], "generator": "distlib (0.1.3.dev0)", "extensions": { "python.details": { "keywords": [], "license": "BSD" } }, "description": "Chocolate with a longer kick!", "summary": "Chocolate with a kick!", "metadata_version": "2.0", "version": "2.0.0.9", "provides": [ "truffles (1.0)", "choxie (2.0.0.9)" ], "name": "choxie" } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/fake_dists/coconuts-aster-10.3.egg-info/0000775000175000017500000000000015212736416022557 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO0000644000175000017500000000016513453041443023646 0ustar00vinayvinayMetadata-Version: 1.2 Name: coconuts-aster Version: 10.3 Provides-Dist: strawberry (0.6) Provides-Dist: banana (0.4) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.4955318 distlib-0.4.3/tests/fake_dists/grammar-1.0a4/0000775000175000017500000000000015212736415017703 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/fake_dists/grammar-1.0a4/grammar/0000775000175000017500000000000015212736416021332 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/grammar-1.0a4/grammar/__init__.py0000644000175000017500000000003013453041443023424 0ustar00vinayvinay# -*- coding: utf-8 -*- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/grammar-1.0a4/grammar/utils.py0000644000175000017500000000024213453041443023032 0ustar00vinayvinay# -*- coding: utf-8 -*- from random import randint def is_valid_grammar(sentence): if randint(0, 10) < 2: return False else: return True ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/fake_dists/grammar-1.0a4.dist-info/0000775000175000017500000000000015212736416021577 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER0000644000175000017500000000000013453041443023035 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/grammar-1.0a4.dist-info/METADATA0000644000175000017500000000015313453041443022671 0ustar00vinayvinayMetadata-Version: 1.2 Name: grammar Version: 1.0a4 Requires-Dist: truffles (>=1.2) Author: Sherlock Holmes ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/grammar-1.0a4.dist-info/RECORD0000644000175000017500000000000013453041443022456 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED0000644000175000017500000000000013453041443023041 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/grammar-1.0a4.dist-info/pydist.json0000644000175000017500000000050313453041443023774 0ustar00vinayvinay{ "generator": "distlib (0.1.3.dev0)", "run_requires": [ { "requires": [ "truffles (>=1.2)" ] } ], "metadata_version": "2.0", "version": "1.0a4", "provides": [ "grammar (1.0a4)" ], "extensions": { "python.details": { "keywords": [] } }, "name": "grammar" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/nut-funkyversion.egg-info0000644000175000017500000000006613453041443022512 0ustar00vinayvinayMetadata-Version: 1.2 Name: nut Version: funkyversion ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/strawberry-0.6.egg0000644000175000017500000000257213453041443020724 0ustar00vinayvinay#!/bin/sh if [ `basename $0` = "setuptools-0.6c11-py2.3.egg" ] then exec python2.3 -c "import sys, os; sys.path.insert(0, os.path.abspath('$0')); from setuptools.command.easy_install import bootstrap; sys.exit(bootstrap())" "$@" else echo $0 is not the correct name for this egg file. echo Please rename it back to setuptools-0.6c11-py2.3.egg and try again. exec false fi PK v<EGG-INFO/top_level.txtUT LLux PK z<EGG-INFO/SOURCES.txtUT LLux PKFq<EGG-INFO/PKG-INFOUT $ L$ Lux =NN@ +IʅÞ@ ڪR%\ۘ!^=)R<43{SEsvJЫtfU1%96V7ZBqn'½P6FLB<֫Zu)As '!`/^*7.̋o g_& qפԿw+ϓZ?UL PK <EGG-INFO/entry_points.txtUT LLux PKPT;2EGG-INFO/dependency_links.txt PK v<zEGG-INFO/top_level.txtUTLux PK z<EGG-INFO/SOURCES.txtUTLux PKFq<EGG-INFO/PKG-INFOUT$ Lux PK <EGG-INFO/entry_points.txtUTLux PKPT;2qEGG-INFO/dependency_links.txtPK././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.4955318 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1/0000775000175000017500000000000015212736415020371 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1/towel_stuff/0000775000175000017500000000000015212736416022733 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py0000644000175000017500000000074113453041443025036 0ustar00vinayvinay# -*- coding: utf-8 -*- class Towel(object): """A towel, that one should never be without.""" def __init__(self, color='tie-dye'): self.color = color self.wrapped_obj = None def wrap(self, obj): """Wrap an object up in our towel.""" self.wrapped_obj = obj def unwrap(self): """Unwrap whatever is in our towel and return whatever it is.""" obj = self.wrapped_obj self.wrapped_obj = None return obj ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1.dist-info/0000775000175000017500000000000015212736416022265 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER0000644000175000017500000000000013453041443023523 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA0000644000175000017500000000027113453041443023360 0ustar00vinayvinayMetadata-Version: 1.2 Name: towel-stuff Version: 0.1 Provides-Dist: truffles (1.1.2) Provides-Dist: towel-stuff (0.1) Obsoletes-Dist: truffles (!=0.8,<1.0) Requires-Dist: bacon (<=0.2) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD0000644000175000017500000000000013453041443023144 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED0000644000175000017500000000000013453041443023527 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1.dist-info/pydist-exports.json0000644000175000017500000000055513453041443026173 0ustar00vinayvinay{ "metadata_version": "2.0", "generator": "distlib (0.1.3.dev0)", "name": "towel-stuff", "extensions": { "python.exports": { "exports": { "foo": { "bar": "towel:towel", "baz": "towel:beach_towel" }, "bar.baz": { "foobar": "hoopy:frood [dent]" } } } }, "version": "0.1" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/towel_stuff-0.1.dist-info/pydist.json0000644000175000017500000000053413453041443024466 0ustar00vinayvinay{ "generator": "distlib (0.1.3.dev0)", "run_requires": [ { "requires": [ "bacon (<=0.2)" ] } ], "metadata_version": "2.0", "version": "0.1", "provides": [ "truffles (1.1.2)", "towel-stuff (0.1)" ], "extensions": { "python-details": { "keywords": [] } }, "name": "towel-stuff" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/fake_dists/truffles-5.0.egg-info0000644000175000017500000000006213453041443021272 0ustar00vinayvinayMetadata-Version: 1.2 Name: truffles Version: 5.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/foo.zip0000644000175000017500000000157313453041443014726 0ustar00vinayvinayPK N4Aԉ foo/foo_resource.binUT }[P}[Pux more_data PK B4Afoo/__init__.pyUT [P}[Pux # empty PK T4Actffoo/bar/baz.pyUT [P}[Pux # This is baz PK M4Afoo/bar/__init__.pyUT [P}[Pux # empty PK 54Afoo/bar/bar_resource.binUT }[P}[Pux data PK N4Aԉ foo/foo_resource.binUT}[Pux PK B4AXfoo/__init__.pyUT[Pux PK T4Actffoo/bar/baz.pyUT[Pux PK M4Afoo/bar/__init__.pyUT[Pux PK 54ATfoo/bar/bar_resource.binUT}[Pux PK././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/foofoo/0000775000175000017500000000000015212736416014710 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/foofoo/__init__.py0000644000175000017500000000001013453041443017000 0ustar00vinayvinay# empty ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/foofoo/bar/0000775000175000017500000000000015212736416015454 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/foofoo/bar/__init__.py0000644000175000017500000000001013453041443017544 0ustar00vinayvinay# empty ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/foofoo/bar/bar_resource.bin0000644000175000017500000000000513453041443020604 0ustar00vinayvinaydata ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/foofoo/bar/baz.py0000644000175000017500000000001613453041443016567 0ustar00vinayvinay# This is baz ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/foofoo/foo_resource.bin0000644000175000017500000000001213453041443020055 0ustar00vinayvinaymore_data ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/foofoo/nested/0000775000175000017500000000000015212736416016172 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/foofoo/nested/nested_resource.bin0000644000175000017500000000001413453041443022040 0ustar00vinayvinaynested data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/good.bin0000644000175000017500000000001613453041443015030 0ustar00vinayvinayHello, world! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/good.bin.asc0000644000175000017500000000043713453041443015604 0ustar00vinayvinayQ( b`?*?~r^=vAI C?B6Aa Sb 3tCiC,.=Ϟ32$ ZVgm6}ԒBXYgHhO¦b1H&4ÞYNU&|D^WXU!X%4!(dȴeBW,DO././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/good.tar0000644000175000017500000002400013453041443015045 0ustar00vinayvinayfile1.txt0000664000175000017500000000000012056711465011654 0ustar vinayvinaysubdir/0000775000175000017500000000000012056711513011407 5ustar vinayvinaysubdir/subsubdir/0000775000175000017500000000000012056711527013416 5ustar vinayvinaysubdir/subsubdir/file3.txt0000664000175000017500000000000012056711527015147 0ustar vinayvinaysubdir/file2.txt0000664000175000017500000000000012056711504013137 0ustar vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/good.tar.bz20000644000175000017500000000033113453041443015542 0ustar00vinayvinayBZh91AY&SY! @w%`00 da0A1 &$ Sucb#嫭oLqHP%!%:%EyrςR(M ĉYLHgg)fP& 9$iͲajn*,%DovD>9w" nt.o0P?ܑN$3HFe././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/good.tar.gz0000644000175000017500000000032213453041443015465 0ustar00vinayvinaybPgood.tarY 0@,%+мfZOE Mp h >ZhIxpT9& )ћ|RR ^Ҧ^~;չ=}qomg1Yߙ/i{boaE5LW~7.bl7(././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/good.zip0000644000175000017500000000145413453041443015071 0ustar00vinayvinayPK iA file1.txtUT 5P}Pux PK tAsubdir/UT KPZPux PK zAsubdir/subsubdir/UT WPZPux PK zAsubdir/subsubdir/file3.txtUT WP}Pux PK pAsubdir/file2.txtUT DP}Pux PK iA file1.txtUT5Pux PK tAACsubdir/UTKPux PK zAAsubdir/subsubdir/UTWPux PK zAsubdir/subsubdir/file3.txtUTWPux PK pA#subdir/file2.txtUTDPux PKm././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/included.json0000644000175000017500000000004313453041443016070 0ustar00vinayvinay{ "foo": "bar", "bar": "baz" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1610471762.0 distlib-0.4.3/tests/keycert.pem0000664000175000017500000000610013777354522015577 0ustar00vinayvinay-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGUhdmHWnkXwoU 5YTgGyiWx4hOke/le19wxJvvzD0jheljVyD3b44dhuSh35SjL2F1WACspNWLwZxS I2svD/jUgc8cM8W4LvOaNzGuBNad/wV7ZdZr5WNPqAXxRMEbbwiO/vGthnQ2MkuH aCVB7/jqdKHeWSwOG1A2xDezZrVtMr4fkcdZ0GQwWqawlnKWmqPBfSumz1SCj2zP O8jNIP8BBDUCnDxxfmi6yscxRxg7/GbgL0hfSPfuWdJ4ZXxc9htfppin9iTYjFRP TT0SYjKx+ZbK5PkKMVxi0EeA5Af1zH2UeydWckAbUbkf/Pun4UjFuS1SvsR8MZWx +33vGqzrAgMBAAECggEAKHfF2It1+Si/iOYQyB4imSncEZRL7HrRTdSAsVzuuNnx dtjnnDYCPNoyuylBTk8zxtsPQCtee+ck+8nIbFEJCGv4lBuoY94IzVYn6AeKt7C7 MKPmLhwfuLhjhgjukqeEipFRRYRjIFABA39n4t2R4F2uTg3KJJLEOnitmQkDSmWt IBJUd2sOktx7yw2FYAHSS37awhGtDXOnuK6ZVQW643F4uPuZa9oMx+bFACI87Frs UhlpMD3OOSMyQAMdyRg1TfITptFP4P3opW4Uaxa7hpoSD4mtJ+163bT+g7/ZrO7w iucQZQoWzQfz4cuqp0wkUiS8qDUn3hwMMcShVEQkeQKBgQDx3G41UZXWbLeY1Igt L/6Y0wiQcYwXbYUkUssJpsgIjj3J8ksWphla78pXZ8M+cqGifSy7+fw3LbJeryT3 1NQEPsrgzkKmlkUbadgKLvKPn0WVHcFxOJ9kYKQK780F5z1OpcS59hR7RNEzbbmp ttFSUavSFM0iHDJt4A5hAgct/wKBgQDR6g7Aa6q2ilwd2liTg3RQqdzzP5dyUQpC vJoOnq31pBmNpYMB7lJglmyik1Xs6xGDTMo+QquPP+2fj/MqHXfc2p15AXspODtv 40fe9koCV3QdDvIXBCcxW4QeOtzS/P5Ms/NzGSUW4fkzook4zrlAgyeprA7r9K6x BZPY2voZFQKBgQCaskETfXF3SuZLrHTFYlNX8HMj4FyYh4aHayVOsQx03HYM+xqo C+lfzsv/B/FLugpEYggA265EpMfX4/KjSc+qP3eIPqVG5AqjtBlougqPbnIVJIkc H/ljPKTwEd1zDyqZaVHhF9KV3bhpSp9cPAC6xF4/6uoiDSL8QDkyNxfchQKBgEnt nb5k7N2XAvQ8lt90930FJdzBc6v+IJf6ZmNpvSh638kEFoySi46ay63CKAvlieht Hfkpb9RmlmCQyPn3IOS2ValtPRv6QuyX5hL/OHd0p18oiVhTzR3T9kbTQjxIf6Mx 6TaLD4XsleB6iMtp+tJenpRiFWtCZFfR+Azyqgw1AoGBAJr2EFizv80cgIiusQWv g5uNE3RHIAE8b60nJxlHcVkjmDbaj2wjjXF6BIlslvFrDtyTee1gB5WgLiKZv1iJ znlejykUn+c9eCf21T+LnIXDjVRoFpBJI0dv2IfW9vv1UI58XW8ktn5TYZ3Kq+8h WkSluWfFv8GlfsJSHzdpvqEX -----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIID8zCCAtugAwIBAgIUZSPfUxQx4kYyAz1paKBL0AcLOw0wDQYJKoZIhvcNAQEL BQAwgYcxCzAJBgNVBAYTAkdCMRYwFAYDVQQIDA1IZXJ0Zm9yZHNoaXJlMQ8wDQYD VQQHDAZCdXNoZXkxJTAjBgNVBAoMHFJlZCBEb3ZlIENvbnN1bHRhbnRzIExpbWl0 ZWQxFDASBgNVBAsMC0RldmVsb3BtZW50MRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcN MjEwMTEyMTQxNTAwWhgPMjA1MTAxMDUxNDE1MDBaMIGHMQswCQYDVQQGEwJHQjEW MBQGA1UECAwNSGVydGZvcmRzaGlyZTEPMA0GA1UEBwwGQnVzaGV5MSUwIwYDVQQK DBxSZWQgRG92ZSBDb25zdWx0YW50cyBMaW1pdGVkMRQwEgYDVQQLDAtEZXZlbG9w bWVudDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAxlIXZh1p5F8KFOWE4BsolseITpHv5XtfcMSb78w9I4XpY1cg92+O HYbkod+Uoy9hdVgArKTVi8GcUiNrLw/41IHPHDPFuC7zmjcxrgTWnf8Fe2XWa+Vj T6gF8UTBG28Ijv7xrYZ0NjJLh2glQe/46nSh3lksDhtQNsQ3s2a1bTK+H5HHWdBk MFqmsJZylpqjwX0rps9Ugo9szzvIzSD/AQQ1Apw8cX5ousrHMUcYO/xm4C9IX0j3 7lnSeGV8XPYbX6aYp/Yk2IxUT009EmIysfmWyuT5CjFcYtBHgOQH9cx9lHsnVnJA G1G5H/z7p+FIxbktUr7EfDGVsft97xqs6wIDAQABo1MwUTAdBgNVHQ4EFgQU0qCl lcwuGbZLN7Ew54ERZa46thUwHwYDVR0jBBgwFoAU0qCllcwuGbZLN7Ew54ERZa46 thUwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEALOo0STI7LaMl oAjCuJ7OBaZ0PCtSpjBawZTXdB6fP9LJRGspIk5VvpnFa6FblkjRMMOqQWLRjMCJ wFjKFGkyFDqTr+57e4kTPAGanOa+yhXZZBRFojoPw34gWAQ3E1NWSR3GUUVKmrl3 ZfL7fAc3BSGre0Bt0MokZ1G4CrjkA9cp+Nss4aD3YVdmJzC6gmN5v7yUG/yyfJaG fPNNkJ5zVhuzPiSSnt8yfLiL+e59UTTeOMZI1Ptk2UcEkiNftDG++c384S61YaAH HTUKkLFtUegSQbjWo5XmyaMnfusKpG7BAvdmZcgdf4UmPYpOvUNMEtZCRqsmCzjq UzGvelZfPA== -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1781251341.515536 distlib-0.4.3/tests/keys/0000775000175000017500000000000015212736416014374 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1557560212.0 distlib-0.4.3/tests/keys/.gpg-v21-migrated0000644000175000017500000000000013465475624017347 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5195367 distlib-0.4.3/tests/keys/private-keys-v1.d/0000775000175000017500000000000015212736416017565 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1557758812.0 distlib-0.4.3/tests/keys/private-keys-v1.d/89643D011E83504EFB6A88F328153504C3E2C97B.key0000600000175000017500000000222613466301534024674 0ustar00vinayvinay(21:protected-private-key(3:rsa(1:n257:5dQí}//1I͇` yڸX uMնRy(JJ5RLe:fC.iq+$"%P_H s8&jj0o+҄J_>.hs&sEg£-nSӏ-#ƚ6EZG#_MQI/I[cjHl#LuO[hbdk ET{)(1:e3:)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:K]]Z8:86036480)16:.Ϊֲ Z)736:z #>}]EZ}qfެ\#`5% 7QU8V%X3>."}M;Cø٫?B53ܣEQ/LYl+Vh5/Ek^ H͢Z=K]N,N Gݰu$N!.M%d1t~u`@.( =ah0Ef k !զz8'\.izTu]rHT/+E08*!Xoa1얶}RE!ٖ`cɏ Мlzx/ 8bft;x8U+5$I#4T>TK\r^꽡{ּ)D%Ɔ]ԭq;k&|W0F[İW;MG9e-~]Қwl ol$nPAX*_rjtF{7ݜfZzz`aN4wӄ+m<ȹF. $C}vRH;EoJkF+faܕߛ:7e:2e3\iͯo d oU [ jյX=W zwEq% LL0rO=VhzArrdӖ~5|0_S촪X8u*8FഄMCpcyL(Rʩ)(12:protected-at15:20190513T144650)))././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1557560212.0 distlib-0.4.3/tests/keys/private-keys-v1.d/8BE462CC6083085304DBEA3FC423A1276DAF0B2C.key0000600000175000017500000000261113465475624024767 0ustar00vinayvinay(21:protected-private-key(3:rsa(1:n257:艬OT$ܺ#:(-vΜn^ JcK|{‘9b%, BƵ(X{fV<:(S$}#ޔ. E/ִ-vS pq챱S>{r[ ^VE*hk,y=V1n  qu!S-IH-@lTrOvQ&gkk>?za֛Eq2Śs )(1:e3:)(9:protected14:openpgp-native(19:openpgp-private-key(7:version1:4)(4:algo3:RSA)(4:skey1:_257:艬OT$ܺ#:(-vΜn^ JcK|{‘9b%, BƵ(X{fV<:(S$}#ޔ. E/ִ-vS pq챱S>{r[ ^VE*hk,y=V1n  qu!S-IH-@lTrOvQ&gkk>?za֛Eq2Śs 1:_3:1:e668:c1BǟpfI&{\%l>0:s7; ̗٧6']T>YR$ik`U_|4ft~'>IP3?$1D5Y?R{!4y}OPHf2vMc9kL|Ǧj 9RfIFkQDJ'-1+8ow,| uŴ,Mc.CM騕 rD&Sqz}]vD8?%5Z&i8R _LݢG`A;lu6ioG=-JZ&v`U3lzjY RTVʣkc8V n ߥ 8(/ >D]NxC(@U)lm#,J\W!(&Vyɪ]/ۯ.G8b3ZmA&R4t[>. Is۰k .=Q!J6a݌]q tbEvEc!PQ;/c;HZƳ;'I;oGVH\r1ժ!=[^d#Zcg`2nCᾰKR /#)(4:csum1:0)(10:protection4:sha15:CAST58:ٽoU@1:34:SHA18:[722:96)))))././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/keys/pubring.gpg0000644000175000017500000000227013453041443016532 0ustar00vinayvinay Q 5dQí}//1I͇` yڸX uMնRy(JJ5RLe:fC.iq+$"%P_H s8&jj0o+҄J_>.hs&sEg£-nSӏ-#ƚ6EZG#_MQI/I[cjHl#LuO[hbdk ET{1Test User (A test user) 8"Q    b`?'2LV)1SHg3(7cO9bCCCx)n84{SE=w 윖-!tAR25TK &E]0 Gu&d!u߆V;ӭ1!9Mc?@RQ?8kAf8@emC;AӰa32P)QV.\֊@%5b? uUר:h!3˶MuKf}Y\Y]=W$E ؄ Q 艬OT$ܺ#:(-vΜn^ JcK|{‘9b%, BƵ(X{fV<:(S$}#ޔ. E/ִ-vS pq챱S>{r[ ^VE*hk,y=V1n  qu!S-IH-@lTrOvQ&gkk>?za֛Eq2Śs  Q  b`?4hB=~/umjCәgʛ&?yOX=U Jzέ{+2FjwiV -!%o=<3b3.7Eαd)렁PzWr.w0_Np^2ZlwKyPXЪހ+/BkrʺA*C-g[aavXF]}Vyß*'մxI ha^Șs7O2n0yK1`h; 7[ɖغ:;-ucD|_9I,z1././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/keys/secring.gpg0000644000175000017500000000503213453041443016515 0ustar00vinayvinayQ 5dQí}//1I͇` yڸX uMնRy(JJ5RLe:fC.iq+$"%P_H s8&jj0o+҄J_>.hs&sEg£-nSӏ-#ƚ6EZG#_MQI/I[cjHl#LuO[hbdk ET{[72`e\s99 l(H=а.];r!,#/7~2$#Vo^yva=>DywpS^*2I-mS[bgC"rsȡ!t2IRoġ=C |bn ʹH~;S)in7Oy57+j_"N؁Hvo?u FmK]`[jG +m'^@C3^uG% Dx 2t|dSC&h}v) 1Uh)J*U0z}a_hF[0d eS,d֠n޴>_%v!niŮmɼe+7^mjCUL}@1d9 ᄰtEw v!Hl,UL)3ҀkuzR-M9 %`U&OCڬ/2z+qH܏E} {TĤdʹ7}5dfi8"Q    b`?'2LV)1SHg3(7cO9bCCCx)n84{SE=w 윖-!tAR25TK &E]0 Gu&d!u߆V;ӭ1!9Mc?@RQ?8kAf8@emC;AӰa32P)QV.\֊@%5b? uUר:h!3˶MuKf}Y\Y]=W$E ؄Q 艬OT$ܺ#:(-vΜn^ JcK|{‘9b%, BƵ(X{fV<:(S$}#ޔ. E/ִ-vS pq챱S>{r[ ^VE*hk,y=V1n  qu!S-IH-@lTrOvQ&gkk>?za֛Eq2Śs [72`ٽoU@c1BǟpfI&{\%l>0:s7; ̗٧6']T>YR$ik`U_|4ft~'>IP3?$1D5Y?R{!4y}OPHf2vMc9kL|Ǧj 9RfIFkQDJ'-1+8ow,| uŴ,Mc.CM騕 rD&Sqz}]vD8?%5Z&i8R _LݢG`A;lu6ioG=-JZ&v`U3lzjY RTVʣkc8V n ߥ 8(/ >D]NxC(@U)lm#,J\W!(&Vyɪ]/ۯ.G8b3ZmA&R4t[>. Is۰k .=Q!J6a݌]q tbEvEc!PQ;/c;HZƳ;'I;oGVH\r1ժ!=[^d#Zcg`2nCᾰKR /#‰ Q  b`?4hB=~/umjCәgʛ&?yOX=U Jzέ{+2FjwiV -!%o=<3b3.7Eαd)렁PzWr.w0_N /dev/null; then # shellcheck disable=SC2155 local temporary_directory="$(mktemp -d)" local get_pip="${temporary_directory}/get-pip-${version}.py" ${dryrun} curl --output "${get_pip}" \ "https://bootstrap.pypa.io/pip/${version}/get-pip.py" ${dryrun} "${versioned_python}" "${get_pip}" rm -rf "${temporary_directory}" fi # Install virtualenv as venv isn't available in all Python versions. ${dryrun} "${versioned_python}" -m pip install virtualenv ${dryrun} "${versioned_python}" -m virtualenv "${venv_directory}" ( cd "${THIS_DIRECTORY}" ${dryrun} source "${venv_directory}/bin/activate" # Upgrade pip and setuptools. ${dryrun} pip install -U pip ${dryrun} pip install -U setuptools # Build wheels to the wheels subdirectory. for embed_extension_metadata in 0 1; do set -x MINIMEXT_EMBED_EXTENSIONS_METADATA=${embed_extension_metadata} \ ${dryrun} pip wheel . -w "${wheels_directory}" set +x done ) done cp "${wheels_directory}"/*.whl "${THIS_DIRECTORY}/.." } main "$@" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728312190.0 distlib-0.4.3/tests/minimext/calculate.c0000664000175000017500000000470414700771576017370 0ustar00vinayvinay// Copyright (C) 2024 Stewart Miles // Licensed to the Python Software Foundation under a contributor agreement. // See LICENSE.txt and CONTRIBUTORS.txt. // Use the limited API to ensure ABI compatibility across all major Python // versions starting from 3.2 // https://docs.python.org/3/c-api/stable.html#limited-c-api #if !defined(Py_LIMITED_API) #define Py_LIMITED_API 3 #endif // !defined(Py_LIMITED_API) #define PY_SSIZE_T_CLEAN #include // Name and doc string for this module. #define MODULE_NAME calculate #define MODULE_DOCS "Calculates Fibonacci numbers." // Convert the argument into a string. #define _STRINGIFY(x) #x #define STRINGIFY(x) _STRINGIFY(x) // Calculate a Fibonacci number at the specified index of the sequence. static PyObject *fib(PyObject *self, PyObject *args) { long int index; if (!PyArg_ParseTuple(args, "l", &index)) { PyErr_SetString(PyExc_ValueError, "An index must be specified."); } long int current_value = 1; long int previous_value = 0; index--; for ( ; index > 0 ; --index) { long int next_value = current_value + previous_value; previous_value = current_value; current_value = next_value; } return PyLong_FromLong(current_value); } // Exposes methods in this module. static PyMethodDef methods[] = { { "fib", fib, METH_VARARGS, PyDoc_STR("Calculate a Fibonacci number.\n" "\n" ":param index: Index of the number in the Fibonacci sequence\n" " to calculate.\n" "\n" ":returns: Fibonacci number at the specified index.\n" " For example an index of 7 will return 13\n"), }, }; #if PY_MAJOR_VERSION >= 3 // Defines the module. static struct PyModuleDef module = { PyModuleDef_HEAD_INIT, STRINGIFY(MODULE_NAME), PyDoc_STR(MODULE_DOCS), -1, methods, }; #endif // PY_MAJOR_VERSION >= 3 // Expands to the init function name. #define _PYINIT_FUNCTION_NAME(prefix, name) prefix ## name #define PYINIT_FUNCTION_NAME(prefix, name) _PYINIT_FUNCTION_NAME(prefix, name) // Initialize this module. #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PYINIT_FUNCTION_NAME(PyInit_, MODULE_NAME)(void) { return PyModule_Create(&module); } #else PyMODINIT_FUNC PYINIT_FUNCTION_NAME(init, MODULE_NAME)(void) { // Ignore the returned module object. (void)Py_InitModule(STRINGIFY(MODULE_NAME), methods); } #endif // PY_MAJOR_VERSION >= 3 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5195367 distlib-0.4.3/tests/minimext/minimext/0000775000175000017500000000000015212736416017105 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728312190.0 distlib-0.4.3/tests/minimext/minimext/__init__.py0000664000175000017500000000000014700771576021213 0ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728312258.0 distlib-0.4.3/tests/minimext/minimext/calculate_py.py0000664000175000017500000000131414700771702022121 0ustar00vinayvinay# Copyright (C) 2024 Stewart Miles # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. """Python implementation of the calculate extension module.""" def fib(index): """Calculate a Fibonacci number. :param index: Index of the number in the Fibonacci sequence to calculate. :returns: Fibonacci number at the specified index. For example an index of 7 will return 13 """ current_value = 1 previous_value = 0 index -= 1 while index > 0: next_value = current_value + previous_value previous_value = current_value current_value = next_value index -= 1 return current_value ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728312190.0 distlib-0.4.3/tests/minimext/setup.py0000664000175000017500000000467314700771576017006 0ustar00vinayvinay# Copyright (C) 2024 Stewart Miles # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. import codecs import os import json from setuptools import Extension, setup from setuptools.command import egg_info import sys EMBED_EXTENSIONS_METADATA = ( int(os.getenv('MINIMEXT_EMBED_EXTENSIONS_METADATA', '0'))) class EggInfo(egg_info.egg_info): """egg_info command that optionally writes extensions metadata. distlib.wheel.Wheel attempts to read the list of extensions from the undocumented JSON EXTENSIONS metadata file. This command will add the special file JSON EXTENSIONS metadata file to the *.dist-info directory in the wheel if the MINIMEXT_EMBED_EXTENSIONS_METADATA environment variable is set to 1. """ def run(self): egg_info.egg_info.run(self) if EMBED_EXTENSIONS_METADATA: build_ext = self.get_finalized_command('build_ext') extensions_dict = { ext_module.name: build_ext.get_ext_filename(ext_module.name) for ext_module in self.distribution.ext_modules } with open(os.path.join(self.egg_info, 'EXTENSIONS'), 'wb') as ( extensions_file): json.dump(extensions_dict, codecs.getwriter('utf-8')(extensions_file), indent=2) setup( name='minimext' + ('_metadata' if EMBED_EXTENSIONS_METADATA else ''), version='0.1', description='Calculates Fibonacci numbers.', long_description=( 'Distribution that provides calculate.fib() and calculate_py.fib() ' 'which calculate Fibonacci numbers. minimext.calculate is implemented ' 'as a C extension to test distlib.wheel.Wheel.mount().'), packages=['minimext'], ext_modules=[ Extension(name='minimext.calculate', sources=['calculate.c'], py_limited_api=True, define_macros=[ ('Py_LIMITED_API', str(sys.version_info.major)), ]), ], # The extension uses the limited API so tag the wheel as compatible with # Python 3.2 and later. # # Unfortunately the py_limited_api argument to Extension does not mark the # wheel as supporting the limited API, so set the see compatibility # manually. options={'bdist_wheel': {'py_limited_api': 'cp32'}}, cmdclass={'egg_info': EggInfo}, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/minimext-0.1-cp27-none-linux_x86_64.whl0000644000175000017500000001746713453041443022153 0ustar00vinayvinayPKeB&{C minimext.so|Ϙ_1`;.5U5~Y8}9souWyҕ4Jd!AhzHM5!<s8N$寗%$?UG>󴝚ԫy4$$jKWTVȿ%N$޶BmJAm/D>L+ijq !W',jrsw>F/FAR_G+*(ղ⥿{oGWm𾳑I-o^ky5k?KEE;cח_R~9rz_-DdK2QN$ (I<)O>1l>gXvh1ҹMvh`(n{x뾺E<ΛXz<㗯\d=V3G@6pB~YMχʎq/gL6c҉|.LcR6aF0 o{{2Ўǰ6^-2Qa{/X[wD"Nh)&&ֵD_ӖDwEb*6EE-.Q-$F5;gggggקzUVwPAV7?c{F21yRg Te˶^#aꬵEjRg zaL;Gd>n$}UHxWǥ[ 0N:O7 "UyչZo]/8ְ>Q٥i+Vn8cE%uIA̦ =hRmB2Q<XMl(bx6 T| ^ MEE0!1(m1D5p vL`ABBH pkWQŌР=m@0J(hax 8( `͇`K`=GHY)Q0a D(;;YzT`W)8z-p]t #5a'eC3:_Oj=>Q(m.}{(;՜~?ebLVHeW>BNG)ۋJ:Q֎VsQo''(EăUWcHB!iQق~ Ǔ40?8Gau"rЫó8xƓ 6'b**NU@ūHf o] QX+V=4"qҥT2NJbH5e2ڣr9c#`ѹ# #"G"ë?ĵ ,o'{4/ s5I^;4GKwHcCP &>~Dc%Fsdo7>ٛh=1}x< 0ڻWsBc{ОJ7K }M='3{g!]%RLn'ZpcwuoK"'!{D ZNMOq_;T=T{(pYN6'R;uh0ԇg 8Ea%a580=}5ʰJ`Mȩnv{$G9O'ΥHzOR< J8#X`gb'Z0nlL+UR c$c.uuÅ=Unv.N{CW d|n R; -VVQqk3fW?"zۘL &?}_1@SA3/y@@M0t(uWn#aRV #cOc;+ÕjN~^\罄Pv &OOph8"UywцUH5JQrvSI r%IΣ?=G>}KĮ.𔀘j=-~ ePL5" Hj.$&255x_ZHi۠FаEipV@:.*7a).}ZΧBM,#V.$=.tmAwx q]L}OG.!Z厧ߏ i/G^~w Ca #55w\ߙa WH"0w$ ~k0wQ]°% ݟQ ~Tch`6-WTwPnJf`$r K,SxGү2+߷;xmQ9@)ߔ(]Y{8<E!r,5׭ ȐqKѶZU?FΙ:xMn[Y򤢓Sh|Unu3S;ytRd 4S;"C`']_#B ta"A*]  +̃WB::UHXM!42 # ʯlA%?ZGϭ>ACkFK`7RaʏqѐtQu)T1 rX1A_'Zǹs*d3 Fx"4̀ ч}J6+ Bum\{Zj&mkc촒UI'u$Ӻ5ΞIJ]S;ri{i\2SJViix vg>7BܘD-;2;R`Z pt:](I` }p~)7&eZɢ9jRDXcKM]v7T; 5mR.I #n$0ҹB&4 Mk |(Eb$;)3npna @ niKI;_Dq|h>`M|f/1ۑ H#Ϛĺʀ$ N泅x6HYl1+] *>f(tGLf˅vPֽQ׀B!XŧB<|l*Ns>[؅m'R̓:dpbRN2@?^,[\O!aZҙGa.#+|CH<ꤦ\n HfV ߞ9$q+T]ewa(_P'KEL)sM ZaI-m)|&e(L`(B9Ϥ 0PNyLs`S)R(R L/Z2J[6"YЖ|N TOx?ߗIxJ0DŽBxjMQ&dϴ@3.ʼn%T:KtHR\\R2A0> +tts😟L>y%zUÔadLfr>@1]Ti%7<}IExxCAIE`\%R[r^kڸxVJdLW0A.4V6q$̎dcNΝ iźׂyDB#XT VeHk]PRM0rGH" ',XH6x(rbl Xڗ0ᐥ\)l7WfPpl+K䲳a[imXh@:٩: NVPߋ+}bMSak]jsNU%K"l:CL%٢#&(i#\22!\Ss)7g lA%9/kysB^ز{ K>Är;SKJ0&~)Q.nB ֗\;STV 6ET)&_ejMZ|ྒྷEU4ۓ"R4T?ԇ~u^TN>R2S^u';Jldϻ,UXd]gA9:]Q4ZY(zEv^.opҘʧSC|7kLuSPx,x18_ǑP 4hq{kO7FNbDoBlMx6yBN2(oY38(FuyPCDܦ ;3U'dQx%/>sozJg¤Oz>KOWM5Iي_=>}WWv~bWo6F짤)o oOT_5{/|jo_z6}^WoW6U?S§=e_ſ{]71o{ޟ?J~}y /?} S?'EÏ;1} xk]OlN؟>W{Hy_=n|RW}H}x?Ňo_Wi.S:0bh_ i_:IOC0u1~k(7ROxSYf}vwVOSe -}`g}ߋps/>^|z/[ |yo/{?gx5ξGv_i/~ȀwPzuN9uЋN J+2eG)'Y[A{a4?/S8RͧwZN[%~=.)ħig!/2'/W6I .q~M?IUѿHD9#!ǟ ?GӁo=|:䗖227~S{I~gtN I׫$Y-ވߘ-^2c劵 tct H华L>܈~{1m38wyt ӉOng%\ֲ˚W-u T}Z[뷐~ŧϥ%W7ZWii^iYluƺ斕KމWo=o{_?[^w2pw=75_ww PKeB)G!minimext-0.1.dist-info/EXTENSIONSRPPM(QBjPKeB`'4minimext-0.1.dist-info/METADATAm0}P"p0GA&ŶU!fKv&G-8g4xY#.Iħcl4H fxA&X1Ի]iq,iԦg-Qp9wfJnvs _h$ѣ4-~PV$dݼo->i9F=/PKeB2dcminimext-0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,.LR033KI-3 /, (-JJY)%r$[)$dVWXśPKeB-7gminimext-0.1.dist-info/RECORDuKr0нg .@#MxnʒwЖ6sYXlG\P ϋ 5z}&TuƼyuȚ6"H,fm RYpTi9E<'}_GaUS7s)PA؀68D3evUM:;$V.[Eڋi9;|0#7H3;LŊ9N yWn=7y``_+dz`PKeB&{C minimext.soPKeB)G!minimext-0.1.dist-info/EXTENSIONSPKeB`'4minimext-0.1.dist-info/METADATAPKeB2dcminimext-0.1.dist-info/WHEELPKeB-7gminimext-0.1.dist-info/RECORDPKj././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/minimext-0.1-cp32-cp32mu-linux_x86_64.whl0000644000175000017500000001760713453041443022315 0ustar00vinayvinayPKbB_h0Dminimext.cpython-32mu.so /.ErK@h<6uiWQW4s۴=f`]ݼr7x꣗r v+E?TD4=00Bǰk/E#?N?_D~ 9qIt]JeҺiEr=NXDbF ѯ6eV&#iH%}-.#3-lޚO$DyցL="SFL<49#bKH2Zf&n#V 0P8Y۾u$$|$#c(CI8`.ۅ _'=.\Yr…t*/%"wk_R <-@|I!y0? =ҡ2. EԳ%+ y?ߪPCYΜk34>sO!]=3'Fmd۝g$WsP][,.Olٜ]Īhۿ<M-^n{mYv ;1@}ӡex³@:О;}GoU3~Xd+`MLXRh>~Qܲ/[wn[ #޲z}Uȍ-7׶vQہ-s+ =p3|܊ü-|]lq@Exm=M`w|{!&:z ۄvIjB{tjZ.߁@*mCVL|ҹ<]:|k[{<wyB{KԮ [j>K7#bU t ";x?>>>>W1X]IY{oTLo?dT{crFrQ&ZYѐG=vYG}&QLn:rN6V9 ,,aRQ:x,e,վ%wdOv}wxRj$u]Ph>m) - 7_ȥ "Y" B2>N u" 35 0Xomo1 ,"A5tTFJKn+eutCt^;v'o a""aPVX&#Yt6 n.e|;;$e)k탔-@8Fç(kEOu҇=Cu]1wӒ*ߡ1Oх{X-s*yXڻ~_n@%K]=a^/G=Щ\e݌)q'Sa{?'qz礇^Kƒ0K}𐇕 K< h/B过 2f $ޭEP 3l}د"s6C8q\9*Ÿ#ɎdLz˜MGQ~$GR3s6ehNp '@W#=RVp"ս=j球J <^96cX_O@QwbL&?UF?% >#kHjQ{ s Rv Gj~j(zjm_mN MC? Y=ʔaExVtҋ; &([-2X 4SːjVjp6UĦR9 ?hp¼gБ/ iH@Am:@]?PH{ 4X*4R1ß IĞL@Z B?x~!:аM{m/mJ@ڽSjePr=yYlQyh=HZDX 4NP!tΐ* 3O_Gx+{\FTzBM!E1dx9]΁"S&Ep.ǡAqI+eiR" Z1kEYtleL|>)>mvґyW%Bݨe>5(vtZye_\Yjn, H[oӶڴ|Yu_o3,A9>0̺!EDgrk+rc > 쵷PCг2 a(_w]E*'zg"Cx&Fʪ 6ė%&+Xkhxz?? Pw\ՔOW~U*щנK3-/u8D&s'2AQU |!J> ֖KiIH{&m5,)7GTB)rY"ڶiWFb~ x 9/]Y>N,[ͩ yaI,q)tLM4%PFW=sPI: @=qiR~ G̏Co)C03L- 'dFW#䨧]7{nMsܨ=M+A}ayt^?,}qrϣd\0e^ة8{c4v̈́8z5 )UlA ּqt< b8o2rm]5\iWB;*޳WAF WSo@ӀGdBo]u2%&ԐPU!a_9n|8OV&mSWT6"u`D`)FX@.Z;^^n8&V2| 7#CGd\W$ qm)ip/=vOĹH|t *ٝ62"; ӂٕ;*.pL T$,ٝ.GHFf`PL:3yaz*v堇|aј'Q5A)~Iy |$匬2M9mD \kX KN;hF܈! p\e"ɤ& ғ0eԂh&DIxiqD\"dF/4cͺJ 20%LqBX)-od' -9D.8?D ;HԄygh-4QxJإo|vGr]"i^vrovL!"v Jk;"he.D`Ner3oqR"KA&ǹc R7saD&[.i),"d rSrYSp$1,H&.F# JMĺcT6ƈ0Cj&Zó5@fمbn NS*HD$gA8q@7tgF.Z5Z6TDqy 00&POMR3QGGf9j+%: i*g}t-u9a-Tp.7 g:O1*)W*xh| 2ԹPr*;PFrZh1nL5R-X0pda"p .MIOnN.w1U/UbW Fp!U/SyhW؁:LVUonpS+ $zRU XPJd+Ef_ VNݪXFnOQYW/=W1Y~EH~#w /7.:_7s7In[DN !9~}*㗹9!tƐ4?&eGE._BwΉOi.Qc7Ϳj(ϻݴ'J;*7SSGoK.d?q~+dBZzQx\m21΂F2:*^@_@}b /X|nL^{=w}:ėU'?L/7'^-'~w_i3N|^?*{]pvw܉oi'zs^2ΫND;_:5vt'w ?~J"iEӋg‰y6z|qk=. 9~ׇe{ХzN?O5nx&*\zCb!WXEL=t?uqğkz}-?xğxVy/rwǭ7"(O=6w'ZߟwGuwE"_/ӱ{?Nt >,ri;;pmMW6o/Uh ѻh$[s=%1iXF<ܸ& z" P""TOYq mըU>yZw%ѪBEq3wGAUmlٰn%׶ojmi7Yy}KKkj^~bzs׋/O47~oQ?PKbBP%,!minimext-0.1.dist-info/EXTENSIONSRPPM(QB *K2trKjPKbB`'4minimext-0.1.dist-info/METADATAm0}P"p0GA&ŶU!fKv&G-8g4xY#.Iħcl4H fxA&X1Ի]iq,iԦg-Qp9wfJnvs _h$ѣ4-~PV$dݼo->i9F=/PKbBXKceminimext-0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,.LR033KI-3 /, (-JJY)%r$[)$邈Rݜ̼Ҋ x3PKbByTuminimext-0.1.dist-info/RECORDu1v0ݳ$:($X eC BDH/ 6(rUܠ1M6@t?˥O..we]g- N29#5\*oEA W]<2Byl?L(Q`zj"]Dk(EPlϧK׼vl%rQho$0t] پb~y2Z:0tN6Iay>'y 0PKbB_h0Dminimext.cpython-32mu.soPKbBP%,!minimext-0.1.dist-info/EXTENSIONSPKbB`'46minimext-0.1.dist-info/METADATAPKbBXKce%minimext-0.1.dist-info/WHEELPKbByTuminimext-0.1.dist-info/RECORDPKw././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/minimext-0.1-cp33-cp33m-linux_x86_64.whl0000644000175000017500000001752213453041443022126 0ustar00vinayvinayPKTeB]D minimext.soTQZV2Ļ|.,%*ɮI;Y^Ȳ%3.)Y^F+% ːWjg#dMkΟ?P՞n~/솩_{Xp:<qj|Ox۞{O2^mϫ?&eBhzn(x~EGȟ3F;ppGo,c^{ p~X&tJ9 _鉦$)0YSӈOMuCM0VÉnhkviaD0t #s%ۣuޕ$t@hsDls8CdjIMצcf/$x$c PQ5M01 a"R1}y00D;H~LseHm 7_!MG3>/!~%rJKM:]/ 3xoHsY_k |PTT E3j^ }~5C ^l:{fi~@]qEta#l>RצK/tmE-1+,jC_D)FAqpv o-t}wmҶOi+sh~|ho ΀*g_Uם讚v$s K׵\f) :irKW6-0૮!I|^ӭMk m. Uз/ ?؍?5'3b ɥ&| } 4`/{trX695Wrb_x\\/qbe L,D4\Sh4L=kB:}Eדc~D% q7 (#b‹@hN2\q!?_ehO} -ڛܫUrϔ͈X~Z =x?>>>?^*Ke" P7. <'^.4UҧR흆>Ke7 };.R9YX n9 \mOj^%Ͳ宋Ī}yK‰`3ڥj߮MK>^ٳyUZ`ݦH.e;+.5̿<Ǒ DD NE$nEO; RĝgquMPu3XPcvy{#KqO H0.@'<$Xnzj%d=%XjP$ 6Qd 6 3Eͬf w;Y '(-W(y6a6wY{ewQD$hzpmCBOKWß lxՙi((;'9!.GKM.#0ؾFP%eGQP:n{17k=OK|8O /p'| g Dwa`XT``*x2Rj?b]VҳG'l FGQ?A]lҔ#`HZ|݁Dh"(5`R3zRİ4{KE>C.vR!)u,]" Cp=Z!r'w3C@:Rn.x*E@>^G"XW/ 5~*z4\)"OHKe6hc*e^)#qtvۓ4"@8]#LF+]Ct;Pq'gF4/o?M2?Mʖn_ Q֍H9aMT!6eG|t.vڳ>h~迋]z3迋q17ʿ?bFtMJ_G]l7=.IoϡW '"}z!b^(#n}TLvRl'o :1w_-Cg}GdנEl;*Y_ߕ8O2HEqP}O@;WzJj1Hp10ϲ=L""Pf+ӏs'w=i#&.4_rX"= >r)*+&9\բR9n|58E=*,51s (C޿ w3z]@AIR_c$yT8\%&O'o By O1J&tz'aRKgyFM2.I'ѕ^Se)r[u*D6i|V0NuFj`ggk JH"X^cXOۀel[HU$4./bZhغ[⭸×gx*k2A^AU,q;W JK έDJ" bήDecf2 .c`ʼnB?)T}ҖyWE.@ԲzW Ja5HJ-}K0Ʈ*5&WWY|I+eJy{%,A9><:Lě˹Cr[_ ƱyU"+6G,XM1.^h+P/2|v&) Zg oP:*yrFq(YA 0ޅ=`ykA_y+.䎦|| |U>ìe|Xd"w"gHgE <#~w-AΗ@h|ޅBS﫨i*=b-%im[|W&{6/chwtex':Sy"N_|Pǥ=@09I<dS@us^A="@>5Hk%2.MQ? 1a-eHr L- N)9ꉇmC 앳6tfe[aP_r/޸R:{?>NOtyO#s>>L[=r`"dν؛q9z5I7V!rDW JlCc q?^ ?|WA;*ޯWAF VS&U r0D p}H諴T!aՈNګ?*Q4/cxzi` 奎CllVScN k3%C0 g*R걨\t 汼Th?}6c*]->\g(nhؒ3'@$.b믄_~ tRO3v=j+O#=h}Sָx~"Ix*tA+iHt42fo:((I׼(i@F7x.JU dYY$I 11݈fm$I!ڥkW mß~3 ]fz~tmq +RԳݹThZ[3I*SD8kF.bfQh5<ݢe,Ѣx$ÆwuzF=l.j(;NgX|Wkf{Z_лH -N-q&:s[rXd⩞| Z>(4 4 >F>p P9~ PV%s 3I @:~x@6z9?ȍݸ0z@WYi BbWMz6HǤiptG.#&4È!Ph.EӉ\8!@ϣrCt")ub۲gw1mI)p/=rOLɂܙplt *6GX01| ar DC+HxHģ:Q2*ʬAn:>ASѰ U5D3rWї'ED[RqSd= i~Ш4%*>(ı|Q212,[~H;70c!N,Ubj8b>@ѡ#\!gV*OusnyUW8P_asaWܐ t:ݐq(gb~5DP, weU{J%O%="DSβxjg.G+NH "P0L? B4,FMRzJ&V)x9{b& \/P]a(@8D`6YICߙS8vȓ)"?lQW|$D,eX"2NifGZ 8Vc<8#(ŤG8`<,Ȅm _}Pj<M'30FjTçd[)[W fRY^X84 o S'Vʷqb^Si,bx>]dYV"j(byW'00PK 1QkFF9jq+%:]i2zt"uJ 9aMTp.7 1O*s)W,yUr\(N@9 (ZyEQU-6V7`r}*L8A /\@V` e%=^e}LK󸀪Wq~iTt^_ O򸢇 gJ6 '@ ,UXd}4oA1,.Um/EB@fVJznLB<>?#U(6NQB-2 QP2 xPL\B|(Cz/laQzcLG}"vOG^{ѿg'0u͚֮]Ei-bi'9¹~B4l~g\֪ -kWZQ >?G-x7PQHkT,mŵZֶmu-U6ҺZwԬ/;>ʗ2G|oh2O47~oSPKTeB)G!minimext-0.1.dist-info/EXTENSIONSRPPM(QBjPKTeB`'4minimext-0.1.dist-info/METADATAm0}P"p0GA&ŶU!fKv&G-8g4xY#.Iħcl4H fxA&X1Ի]iq,iԦg-Qp9wfJnvs _h$ѣ4-~PV$dݼo->i9F=/PKTeBײlabdminimext-0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,.LR033KI-3 /, (-JJY)%r$[)$낈\ݜ̼Ҋ x3PKTeBh>\hminimext-0.1.dist-info/RECORDuIv0нg 4HAXt&[5ȴ!DHwS^(,šC,CoH4_)m{>R%TMG8;i.k PČ@yѼٺmɄC=vVNDmYޛ;=iYzg 3fwĜ¾U4.Yd*wad>e70g3Ehs-=:_"l@@yXPKTeB]D minimext.soPKTeB)G!minimext-0.1.dist-info/EXTENSIONSPKTeB`'4minimext-0.1.dist-info/METADATAPKTeBײlabdminimext-0.1.dist-info/WHEELPKTeBh>\hminimext-0.1.dist-info/RECORDPKj././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/pydist.json0000644000175000017500000000354213453041443015624 0ustar00vinayvinay{ "description": "Longer description", "summary": "Short description", "build_requires": [], "metadata_version": "2.0", "version": "0.1", "provides": [ "foobar (0.1)" ], "extensions": { "python.details": { "license": "PSF", "keywords": [ "foo", "bar" ], "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Python Software Foundation License", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration", "Topic :: Utilities" ] } }, "meta_requires": [ { "requires": ["bar (1.0)"] } ], "extras": ["ssl", "certs"], "run_requires": [ { "requires": ["foo"] }, { "requires": ["certifi (0.0.8)"], "extra": "certs" }, { "requires": ["wincertstore (0.1)"], "extra": "ssl", "environment": "sys_platform=='win32'" }, { "requires": ["ctypes (1.0.2)"], "extra": "ssl", "environment": "sys_platform=='win32' and python_version=='2.4'" }, { "requires": ["ssl (1.16)"], "extra": "ssl", "environment": "python_version in '2.4, 2.5'" } ], "test_requires": [ { "requires": ["nose"] }, { "requires": ["nose-udp"], "extra": "udp" } ], "name": "foobar" } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1781251341.5195367 distlib-0.4.3/tests/scripts/0000775000175000017500000000000015212736416015110 5ustar00vinayvinay././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/foo.py0000644000175000017500000000013613453041443016235 0ustar00vinayvinaydef main(): print('Hello from foo') def other_main(): print('Hello again from foo') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script1.py0000644000175000017500000000004013453041443017031 0ustar00vinayvinay#! /usr/bin/env python2.3 pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script2.py0000644000175000017500000000003013453041443017031 0ustar00vinayvinay#!/usr/bin/python pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script3.py0000644000175000017500000000003613453041443017040 0ustar00vinayvinay#!/usr/local/bin/python pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script4.py0000644000175000017500000000002013453041443017032 0ustar00vinayvinay#!pythonw pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script5.py0000644000175000017500000000003713453041443017043 0ustar00vinayvinay#!python -mzippy.activate pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script6.py0000644000175000017500000000025413453041443017045 0ustar00vinayvinay#!python import sys import os input = repr(sys.stdin.read()) print(os.path.basename(sys.argv[0])) print(sys.argv[1:]) print(input) if __debug__: print('non-optimized') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script7.pyw0000644000175000017500000000014113453041443017230 0ustar00vinayvinay#!pythonw import sys with open(sys.argv[1], 'wb') as f: f.write(sys.argv[2].encode('ascii')) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/script8.py0000644000175000017500000000005213453041443017043 0ustar00vinayvinay#!python import sys print(sys.executable) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/shell.sh0000644000175000017500000000002213453041443016535 0ustar00vinayvinay#!/bin/sh exit 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1554793251.0 distlib-0.4.3/tests/scripts/uwsgi_part0000644000175000017500000000175013453041443017212 0ustar00vinayvinayELF>pA@8.@8 @$!@@@@@88@8@@@   n nHЂ   n nTT@T@DDPtd LLd4d4QtdRtd  n n``/lib64/ld-linux-x86-64.so.2GNUGNU3sjhӂ̏s(  ¼2 URYHJ{C  9V#@A B aJtLP#`8 D4J(DG l9 I@YJ$#h@I(!& P@$DB @ G@d+ ,H 3@<4JHTq +> ~ PM |'8Dh 1`F6$ZPRPTHvp%k1  Bjdh"8 / dU p././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710844614.0 distlib-0.4.3/tests/support.py0000644000175000017500000002272214576265306015521 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2013 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # import codecs import os import logging import logging.handlers import shutil import socket try: import ssl except ImportError: ssl = None import sys import tempfile try: import threading except ImportError: import dummy_threading as threading import weakref from compat import (unittest, HTTPServer as BaseHTTPServer, SimpleHTTPRequestHandler, urlparse) from distlib import logger HERE = os.path.dirname(__file__) class _TestHandler(logging.handlers.BufferingHandler, object): # stolen and adapted from test.support def __init__(self): super(_TestHandler, self).__init__(0) self.setLevel(logging.DEBUG) def shouldFlush(self): return False def emit(self, record): self.buffer.append(record) class LoggingCatcher(object): """TestCase-compatible mixin to receive logging calls. Upon setUp, instances of this classes get a BufferingHandler that's configured to record all messages logged to the 'distutils2' logger. Use get_logs to retrieve messages and self.loghandler.flush to discard them. get_logs automatically flushes the logs, unless you pass *flush=False*, for example to make multiple calls to the method with different level arguments. If your test calls some code that generates logging message and then you don't call get_logs, you will need to flush manually before testing other code in the same test_* method, otherwise get_logs in the next lines will see messages from the previous lines. See example in test_command_check. """ def setUp(self): super(LoggingCatcher, self).setUp() self.loghandler = handler = _TestHandler() self._old_level = logger.level logger.addHandler(handler) logger.setLevel(logging.DEBUG) # we want all messages def tearDown(self): handler = self.loghandler # All this is necessary to properly shut down the logging system and # avoid a regrtest complaint. Thanks to Vinay Sajip for the help. handler.close() logger.removeHandler(handler) for ref in weakref.getweakrefs(handler): logging._removeHandlerRef(ref) del self.loghandler logger.setLevel(self._old_level) super(LoggingCatcher, self).tearDown() def get_logs(self, level=logging.WARNING, flush=True): """Return all log messages with given level. *level* defaults to logging.WARNING. For log calls with arguments (i.e. logger.info('bla bla %r', arg)), the messages will be formatted before being returned (e.g. "bla bla 'thing'"). Returns a list. Automatically flushes the loghandler after being called, unless *flush* is False (this is useful to get e.g. all warnings then all info messages). """ messages = [log.getMessage() for log in self.loghandler.buffer if log.levelno == level] if flush: self.loghandler.flush() return messages class TempdirManager(object): """TestCase-compatible mixin to create temporary directories and files. Directories and files created in a test_* method will be removed after it has run. """ def setUp(self): super(TempdirManager, self).setUp() self._olddir = os.getcwd() self._basetempdir = tempfile.mkdtemp() self._files = [] def tearDown(self): for handle, name in self._files: if handle is not None: handle.close() os.remove(name) os.chdir(self._olddir) shutil.rmtree(self._basetempdir) super(TempdirManager, self).tearDown() def temp_filename(self): """Create a read-write temporary file name and return it.""" fd, fn = tempfile.mkstemp(dir=self._basetempdir) os.close(fd) self._files.append((None, fn)) return fn def mktempfile(self): """Create a read-write temporary file and return it.""" fd, fn = tempfile.mkstemp(dir=self._basetempdir) os.close(fd) fp = open(fn, 'w+') self._files.append((fp, fn)) return fp def mkdtemp(self): """Create a temporary directory and return its path.""" d = tempfile.mkdtemp(dir=self._basetempdir) return d def write_file(self, path, content='xxx', encoding=None): """Write a file at the given path. path can be a string, a tuple or a list; if it's a tuple or list, os.path.join will be used to produce a path. """ if isinstance(path, (list, tuple)): path = os.path.join(*path) f = codecs.open(path, 'w', encoding=encoding) try: f.write(content) finally: f.close() def assertIsFile(self, *args): path = os.path.join(*args) dirname = os.path.dirname(path) file = os.path.basename(path) if os.path.isdir(dirname): files = os.listdir(dirname) msg = "%s not found in %s: %s" % (file, dirname, files) assert os.path.isfile(path), msg else: raise AssertionError('%s not found. %s does not exist' % (file, dirname)) def assertIsNotFile(self, *args): path = os.path.join(*args) self.assertFalse(os.path.isfile(path), "%r exists" % path) class EnvironRestorer(object): """TestCase-compatible mixin to restore or delete environment variables. The variables to restore (or delete if they were not originally present) must be explicitly listed in self.restore_environ. It's better to be aware of what we're modifying instead of saving and restoring the whole environment. """ def setUp(self): super(EnvironRestorer, self).setUp() self._saved = [] self._added = [] for key in self.restore_environ: if key in os.environ: self._saved.append((key, os.environ[key])) else: self._added.append(key) def tearDown(self): for key, value in self._saved: os.environ[key] = value for key in self._added: os.environ.pop(key, None) super(EnvironRestorer, self).tearDown() class HTTPRequestHandler(SimpleHTTPRequestHandler): server_version = "TestHTTPS/1.0" # Avoid hanging when a request gets interrupted by the client timeout = 5 def translate_path(self, path): return os.path.join(HERE, 'testsrc', 'README.txt') def log_message(self, format, *args): pass class HTTPSServer(BaseHTTPServer): # Adapted from the one in Python's test suite. def __init__(self, server_address, handler_class, certfile): BaseHTTPServer.__init__(self, server_address, handler_class) self.certfile = certfile def get_request(self): try: sock, addr = self.socket.accept() if hasattr(ssl, 'SSLContext'): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain(self.certfile) sock = context.wrap_socket(sock, server_side=True) else: sock = ssl.wrap_socket(sock, server_side=True, certfile=self.certfile, keyfile=self.certfile, ssl_version=ssl.PROTOCOL_SSLv23) except socket.error as e: # socket errors are silenced by the caller, print them here sys.stderr.write("Got an error:\n%s\n" % e) raise return sock, addr class HTTPSServerThread(threading.Thread): def __init__(self, certfile): self.flag = None self.server = HTTPSServer(('localhost', 0), HTTPRequestHandler, certfile) self.port = self.server.server_port threading.Thread.__init__(self) self.daemon = True def start(self, flag=None): self.flag = flag threading.Thread.start(self) def run(self): if self.flag: self.flag.set() try: self.server.serve_forever(0.05) finally: self.server.server_close() def stop(self): self.server.shutdown() try: import zlib except ImportError: zlib = None requires_zlib = unittest.skipUnless(zlib, 'requires zlib') _can_symlink = None def can_symlink(): global _can_symlink if _can_symlink is not None: return _can_symlink fd, TESTFN = tempfile.mkstemp() os.close(fd) os.remove(TESTFN) symlink_path = TESTFN + "can_symlink" try: os.symlink(TESTFN, symlink_path) can = True except (OSError, NotImplementedError, AttributeError): can = False else: os.remove(symlink_path) _can_symlink = can return can def skip_unless_symlink(test): """Skip decorator for tests that require functional symlink""" ok = can_symlink() msg = "Requires functional symlink implementation" return test if ok else unittest.skip(msg)(test) def fake_dec(*args, **kw): """Fake decorator""" def _wrap(func): def __wrap(*args, **kw): return func(*args, **kw) return __wrap return _wrap def in_github_workflow(): return 'GITHUB_WORKFLOW' in os.environ SEP = '-' * 80 class DistlibTestCase(unittest.TestCase): def setUp(self): logger.debug(SEP) logger.debug(self.id().rsplit('.', 1)[-1]) logger.debug(SEP) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710845083.0 distlib-0.4.3/tests/test_all.py0000644000175000017500000000240514576266233015610 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2013 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # import logging import os import sys from compat import unittest # Always find our sources first sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) import distlib_tests sys.path.pop(0) def main(): verbosity = 1 if '-v' in sys.argv: verbosity = 2 loader = unittest.TestLoader() failfast = 'FAILFAST' in os.environ runner = unittest.TextTestRunner(verbosity=verbosity, failfast=failfast) results = runner.run(loader.loadTestsFromModule(distlib_tests)) return not results.wasSuccessful() if __name__ == '__main__': here = os.path.dirname(os.path.abspath(__file__)) rundir = os.path.join(here, 'run') if not os.path.exists(rundir): os.mkdir(rundir) elif not os.path.isdir(rundir): raise ValueError('Not a directory: %r' % rundir) fn = os.path.join(rundir, 'test_all_%d.%d.log' % sys.version_info[:2]) logging.basicConfig(level=logging.DEBUG, filename=fn, filemode='w', format='%(levelname)-8s %(name)-20s %(message)s') sys.exit(main()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1710846412.0 distlib-0.4.3/tests/test_database.py0000644000175000017500000012076214576270714016612 0ustar00vinayvinay# -*- coding: utf-8 -*- # # Copyright (C) 2012-2013 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # from __future__ import unicode_literals import base64 import io import os import hashlib import logging import random import re import shutil import sys import tempfile from compat import unittest from distlib import DistlibException from distlib.compat import text_type, StringIO from distlib.metadata import Metadata, METADATA_FILENAME, LEGACY_METADATA_FILENAME from distlib.database import (InstalledDistribution, EggInfoDistribution, BaseInstalledDistribution, EXPORTS_FILENAME, DistributionPath, make_graph, get_required_dists, get_dependent_dists) from distlib.util import get_resources_dests, CSVReader, read_exports from test_util import GlobTestCaseBase from support import LoggingCatcher, requires_zlib, DistlibTestCase logger = logging.getLogger(__name__) # TODO Add a test for getting a distribution provided by another distribution # TODO Add a test for absolute path RECORD items (e.g. /etc/myapp/config.ini) # TODO Add tests from the former pep376 project (zipped site-packages, etc.) class FakeDistsMixin(object): def setUp(self): super(FakeDistsMixin, self).setUp() # make a copy that we can write into for our fake installed # distributions tmpdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmpdir) self.fake_dists_path = os.path.realpath(os.path.join(tmpdir, 'fake_dists')) fake_dists_src = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fake_dists')) shutil.copytree(fake_dists_src, self.fake_dists_path) # XXX ugly workaround: revert copystat calls done by shutil behind our # back (to avoid getting a read-only copy of a read-only file). we # could pass a custom copy_function to change the mode of files, but # shutil gives no control over the mode of directories :( # see http://bugs.python.org/issue1666318 for root, dirs, files in os.walk(self.fake_dists_path): os.chmod(root, 0o755) for f in files: os.chmod(os.path.join(root, f), 0o644) for d in dirs: os.chmod(os.path.join(root, d), 0o755) class CommonDistributionTests(FakeDistsMixin): """Mixin used to test the interface common to InstalledDistribution and EggInfoDistribution classes. Derived classes define cls, sample_dist, dirs and records. These attributes are used in test methods. See source code for details. """ def _get_dist_path(self, distdir): here = os.path.abspath(os.path.dirname(__file__)) return os.path.join(here, 'fake_dists', distdir) def test_instantiation(self): # check that useful attributes are here name, version, distdir = self.sample_dist dist_path = self._get_dist_path(distdir) dist = self.dist = self.cls(dist_path) self.assertEqual(dist.path, dist_path) self.assertEqual(dist.name, name) self.assertEqual(dist.metadata.name, name) self.assertIsInstance(dist.metadata, Metadata) self.assertEqual(dist.version, version) self.assertEqual(dist.metadata.version, version) @requires_zlib def test_repr(self): dist = self.cls(self.dirs[0]) # just check that the class name is in the repr self.assertIn(self.cls.__name__, repr(dist)) @requires_zlib def test_str(self): name, version, distdir = self.sample_dist dist = self.cls(self._get_dist_path(distdir)) self.assertEqual(name, dist.name) # Sanity test: dist.name is unicode, # but str output contains no u prefix. self.assertIsInstance(dist.name, text_type) self.assertEqual(version, dist.version) self.assertEqual(str(dist), self.expected_str_output) @requires_zlib def test_comparison(self): # tests for __eq__ and __hash__ dist = self.cls(self.dirs[0]) dist2 = self.cls(self.dirs[0]) dist3 = self.cls(self.dirs[1]) self.assertIn(dist, {dist: True}) self.assertEqual(dist, dist) self.assertIsNot(dist, dist2) self.assertEqual(dist, dist2) self.assertNotEqual(dist, dist3) self.assertNotEqual(dist, ()) def test_list_installed_files(self): for dir_ in self.dirs: dist = self.cls(dir_) for path, hash, size in dist.list_installed_files(): record_data = self.records[dist.path] self.assertIn(path, record_data) self.assertEqual(hash, record_data[path][0]) self.assertEqual(size, record_data[path][1]) def test_hash(self): datalen = random.randrange(0, 500) data = os.urandom(datalen) for dir_ in self.dirs: dist = self.cls(dir_) for hasher in ('sha1', 'sha224', 'sha384', 'sha256', 'sha512', None): dist.hasher = hasher actual = dist.get_hash(data) if hasher is None: digester = hashlib.md5(data) else: digester = getattr(hashlib, hasher)(data) digest = digester.digest() digest = base64.urlsafe_b64encode(digest).rstrip(b'=') digest = digest.decode('ascii') if hasher is None: expected = digest else: expected = '%s=%s' % (hasher, digest) self.assertEqual(actual, expected) class TestDistribution(CommonDistributionTests, DistlibTestCase): cls = InstalledDistribution sample_dist = 'choxie', '2.0.0.9', 'choxie-2.0.0.9.dist-info' expected_str_output = 'choxie 2.0.0.9' def setUp(self): def get_files(location): for path in ('REQUESTED', 'INSTALLER', LEGACY_METADATA_FILENAME, METADATA_FILENAME, EXPORTS_FILENAME): p = os.path.join(location + '.dist-info', path) if os.path.exists(p): yield p for path, dirs, files in os.walk(location): for f in files: yield os.path.join(path, f) super(TestDistribution, self).setUp() self.dirs = [ os.path.join(self.fake_dists_path, f) for f in os.listdir(self.fake_dists_path) if f.endswith('.dist-info') ] self.records = {} for distinfo_dir in self.dirs: dist_location = distinfo_dir.replace('.dist-info', '') record_file = os.path.join(distinfo_dir, 'RECORD') # Write the files using write_installed_files. # list_installed_files should read and match. dist = self.cls(distinfo_dir) prefix = os.path.dirname(dist_location) dist.write_installed_files(get_files(dist_location), prefix) with CSVReader(path=record_file) as record_reader: record_data = {} for row in record_reader: if row == []: continue path, hash, size = (row[:] + [None for i in range(len(row), 3)]) record_data[path] = hash, size self.records[distinfo_dir] = record_data def test_instantiation(self): super(TestDistribution, self).test_instantiation() self.assertIsInstance(self.dist.requested, bool) def test_get_distinfo_file(self): # Test the retrieval of dist-info file objects. distinfo_name = 'choxie-2.0.0.9' other_distinfo_name = 'grammar-1.0a4' distinfo_dir = os.path.join(self.fake_dists_path, distinfo_name + '.dist-info') dist = InstalledDistribution(distinfo_dir) # Test for known good file matches distinfo_files = [ # Relative paths 'INSTALLER', METADATA_FILENAME, # Absolute paths os.path.join(distinfo_dir, 'RECORD'), os.path.join(distinfo_dir, 'REQUESTED'), ] for distfile in distinfo_files: value = dist.get_distinfo_file(distfile) self.assertTrue(os.path.isfile(value)) self.assertEqual(value, os.path.join(distinfo_dir, distfile)) # Test an absolute path that is part of another distributions dist-info other_distinfo_file = os.path.join(self.fake_dists_path, other_distinfo_name + '.dist-info', 'REQUESTED') self.assertRaises(DistlibException, dist.get_distinfo_file, other_distinfo_file) # Test for a file that should not exist self.assertRaises(DistlibException, dist.get_distinfo_file, 'MAGICFILE') def test_list_distinfo_files(self): distinfo_name = 'towel_stuff-0.1' distinfo_dir = os.path.join(self.fake_dists_path, distinfo_name + '.dist-info') dist = InstalledDistribution(distinfo_dir) # Test for the iteration of the raw path distinfo_files = [os.path.join(distinfo_dir, filename) for filename in os.listdir(distinfo_dir)] found = list(dist.list_distinfo_files()) base = self.fake_dists_path for i, p in enumerate(found): if not os.path.isabs(p): found[i] = os.path.join(base, p) self.assertEqual(sorted(found), sorted(distinfo_files)) # Test for the iteration of local absolute paths distinfo_files = [os.path.join(sys.prefix, distinfo_dir, path) for path in distinfo_files] found = sorted(dist.list_distinfo_files()) if os.sep != '/': self.assertNotIn('/', found[0]) self.assertIn(os.sep, found[0]) self.assertEqual(found, sorted(distinfo_files)) def test_get_resources_path(self): distinfo_name = 'babar-0.1' distinfo_dir = os.path.join(self.fake_dists_path, distinfo_name + '.dist-info') dist = InstalledDistribution(distinfo_dir) resource_path = dist.get_resource_path('babar.png') self.assertEqual(resource_path, 'babar.png') self.assertRaises(KeyError, dist.get_resource_path, 'notexist') def test_check_installed_files(self): for dir_ in self.dirs: dist = self.cls(dir_) mismatches = dist.check_installed_files() self.assertEqual(mismatches, []) # pick a non-empty file at random and change its contents # but not its size. Check the failure returned, # then restore the file. files = [f for f in dist.list_installed_files() if f[-1] not in ('', '0')] bad_file = random.choice(files) bad_file_name = bad_file[0] if not os.path.isabs(bad_file_name): base = os.path.dirname(dir_) bad_file_name = os.path.join(base, bad_file_name) with open(bad_file_name, 'rb') as f: data = f.read() bad_data = bytes(bytearray(reversed(data))) bad_hash = dist.get_hash(bad_data) with open(bad_file_name, 'wb') as f: f.write(bad_data) mismatches = dist.check_installed_files() self.assertEqual(mismatches, [(bad_file_name, 'hash', bad_file[1], bad_hash)]) # now truncate the file by one byte and see what's returned with open(bad_file_name, 'wb') as f: f.write(bad_data[:-1]) bad_size = str(len(bad_data) - 1) mismatches = dist.check_installed_files() self.assertEqual(mismatches, [(bad_file_name, 'size', bad_file[2], bad_size)]) # now remove the file and see what's returned os.remove(bad_file_name) mismatches = dist.check_installed_files() self.assertEqual(mismatches, [(bad_file_name, 'exists', True, False)]) # restore the file with open(bad_file_name, 'wb') as f: f.write(data) class TestEggInfoDistribution(CommonDistributionTests, LoggingCatcher, DistlibTestCase): cls = EggInfoDistribution sample_dist = 'bacon', '0.1', 'bacon-0.1.egg-info' expected_str_output = 'bacon 0.1' def setUp(self): super(TestEggInfoDistribution, self).setUp() self.dirs = [ os.path.join(self.fake_dists_path, f) for f in os.listdir(self.fake_dists_path) if f.endswith('.egg') or f.endswith('.egg-info') ] self.records = {} for egginfo_dir in self.dirs: # dist_location = egginfo_dir.replace('.egg-info', '') record_file = os.path.join(egginfo_dir, 'installed-files.txt') # dist = self.cls(egginfo_dir) # prefix = os.path.dirname(dist_location) # dist.write_installed_files(get_files(dist_location), prefix) record_data = {} if os.path.exists(record_file): with open(record_file) as fp: for line in fp: line = line.strip() if line == './': break record_data[line] = None, None self.records[egginfo_dir] = record_data @unittest.skip('not implemented yet') def test_list_installed_files(self): # EggInfoDistribution defines list_installed_files but there is no # test for it yet; someone needs to add a file with the list of # installed files for one of the egg fake dists and write the support # code to populate self.records (and then delete this method) pass class TestDatabase(LoggingCatcher, FakeDistsMixin, DistlibTestCase): def setUp(self): super(TestDatabase, self).setUp() sys.path.insert(0, self.fake_dists_path) self.addCleanup(sys.path.remove, self.fake_dists_path) def test_caches(self): # sanity check for internal caches d = DistributionPath() for name in ('_cache', '_cache_egg'): self.assertEqual(getattr(d, name).name, {}) self.assertEqual(getattr(d, name).path, {}) def test_distinfo_dirname(self): # Given a name and a version, we expect the distinfo_dirname function # to return a standard distribution information directory name. items = [ # (name, version, standard_dirname) # Test for a very simple single word name and decimal version # number ('docutils', '0.5', 'docutils-0.5.dist-info'), # Test for another except this time with a '-' in the name, which # needs to be transformed during the name lookup ('python-ldap', '2.5', 'python_ldap-2.5.dist-info'), # Test for both '-' in the name and a funky version number ('python-ldap', '2.5 a---5', 'python_ldap-2.5 a---5.dist-info'), ] # Loop through the items to validate the results for name, version, standard_dirname in items: dirname = DistributionPath.distinfo_dirname(name, version) self.assertEqual(dirname, standard_dirname) @requires_zlib def test_get_distributions(self): # Lookup all distributions found in the ``sys.path``. # This test could potentially pick up other installed distributions non_egg_dists = [('grammar', '1.0a4'), ('choxie', '2.0.0.9'), ('towel-stuff', '0.1'), ('babar', '0.1')] egg_dists = [('bacon', '0.1'), ('cheese', '2.0.2'), ('coconuts-aster', '10.3'), ('banana', '0.4'), ('strawberry', '0.6'), ('truffles', '5.0'), ('nut', 'funkyversion')] all_dists = non_egg_dists + egg_dists d = DistributionPath() ed = DistributionPath(include_egg=True) cases = ((d, non_egg_dists, InstalledDistribution), (ed, all_dists, BaseInstalledDistribution)) fake_dists_path = self.fake_dists_path for enabled in (True, False): if not enabled: d.cache_enabled = False ed.cache_enabled = False d.clear_cache() ed.clear_cache() for distset, fake_dists, allowed_class in cases: found_dists = [] # Verify the fake dists have been found. dists = list(distset.get_distributions()) for dist in dists: self.assertIsInstance(dist, allowed_class) if (dist.name in dict(fake_dists) and dist.path.startswith(fake_dists_path)): found_dists.append((dist.name, dist.version)) else: # check that it doesn't find anything more than this self.assertFalse(dist.path.startswith(fake_dists_path)) # otherwise we don't care what other dists are found # Finally, test that we found all that we were looking for self.assertEqual(sorted(found_dists), sorted(fake_dists)) @requires_zlib def test_get_distribution(self): # Test for looking up a distribution by name. # Test the lookup of the towel-stuff distribution name = 'towel-stuff' # Note: This is different from the directory name d = DistributionPath() ed = DistributionPath(include_egg=True) # Lookup the distribution dist = d.get_distribution(name) self.assertIsInstance(dist, InstalledDistribution) self.assertEqual(dist.name, name) # Verify that an unknown distribution returns None self.assertIsNone(d.get_distribution('bogus')) # Verify partial name matching doesn't work self.assertIsNone(d.get_distribution('towel')) # Verify that it does not find egg-info distributions, when not # instructed to self.assertIsNone(d.get_distribution('bacon')) self.assertIsNone(d.get_distribution('cheese')) self.assertIsNone(d.get_distribution('strawberry')) self.assertIsNone(d.get_distribution('banana')) # Now check that it works well in both situations, when egg-info # is a file and directory respectively. for name in ('cheese', 'bacon', 'banana', 'strawberry'): dist = ed.get_distribution(name) self.assertIsInstance(dist, EggInfoDistribution) self.assertEqual(dist.name, name) @requires_zlib def test_provides(self): # Test for looking up distributions by what they provide checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) d = DistributionPath() ed = DistributionPath(include_egg=True) test_list = [dist.name for dist in d.provides_distribution('truffles')] checkLists(test_list, ['choxie', 'towel-stuff']) test_list = [dist.name for dist in d.provides_distribution('truffles', '1.0')] checkLists(test_list, ['choxie', 'towel-stuff']) test_list = [dist.name for dist in ed.provides_distribution('truffles', '1.0')] checkLists(test_list, ['choxie', 'cheese', 'towel-stuff']) test_list = [dist.name for dist in d.provides_distribution('truffles', '1.1.2')] checkLists(test_list, ['towel-stuff']) test_list = [dist.name for dist in d.provides_distribution('truffles', '1.1')] checkLists(test_list, ['towel-stuff']) test_list = [dist.name for dist in d.provides_distribution('truffles', '!=1.1,<=2.0')] checkLists(test_list, ['choxie', 'towel-stuff']) test_list = [dist.name for dist in ed.provides_distribution('truffles', '!=1.1,<=2.0')] checkLists(test_list, ['choxie', 'bacon', 'cheese', 'towel-stuff']) test_list = [dist.name for dist in d.provides_distribution('truffles', '>1.0')] checkLists(test_list, ['towel-stuff']) test_list = [dist.name for dist in d.provides_distribution('truffles', '>1.5')] checkLists(test_list, []) test_list = [dist.name for dist in ed.provides_distribution('truffles', '>1.5')] checkLists(test_list, ['bacon', 'truffles']) test_list = [dist.name for dist in d.provides_distribution('truffles', '>=1.0')] checkLists(test_list, ['choxie', 'towel-stuff']) test_list = [dist.name for dist in ed.provides_distribution('strawberry', '0.6')] checkLists(test_list, ['coconuts-aster', 'strawberry']) test_list = [dist.name for dist in ed.provides_distribution('strawberry', '>=0.5')] checkLists(test_list, ['coconuts-aster', 'strawberry']) test_list = [dist.name for dist in ed.provides_distribution('strawberry', '>0.6')] checkLists(test_list, []) test_list = [dist.name for dist in ed.provides_distribution('banana', '0.4')] checkLists(test_list, ['banana', 'coconuts-aster']) test_list = [dist.name for dist in ed.provides_distribution('banana', '>=0.3')] checkLists(test_list, ['banana', 'coconuts-aster']) test_list = [dist.name for dist in ed.provides_distribution('banana', '!=0.4')] checkLists(test_list, []) @requires_zlib def test_yield_distribution(self): # tests the internal function _yield_distributions checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) eggs = [('bacon', '0.1'), ('banana', '0.4'), ('strawberry', '0.6'), ('truffles', '5.0'), ('cheese', '2.0.2'), ('coconuts-aster', '10.3'), ('nut', 'funkyversion')] dists = [('choxie', '2.0.0.9'), ('grammar', '1.0a4'), ('towel-stuff', '0.1'), ('babar', '0.1')] d = DistributionPath(include_egg=False) d._include_dist = False checkLists([], d._yield_distributions()) d = DistributionPath(include_egg=True) d._include_dist = False found = [(dist.name, dist.version) for dist in d._yield_distributions() if dist.path.startswith(self.fake_dists_path)] checkLists(eggs, found) d = DistributionPath() found = [(dist.name, dist.version) for dist in d._yield_distributions() if dist.path.startswith(self.fake_dists_path)] checkLists(dists, found) d = DistributionPath(include_egg=True) found = [(dist.name, dist.version) for dist in d._yield_distributions() if dist.path.startswith(self.fake_dists_path)] checkLists(dists + eggs, found) def check_entry(self, entry, name, prefix, suffix, flags): self.assertEqual(entry.name, name) self.assertEqual(entry.prefix, prefix) self.assertEqual(entry.suffix, suffix) self.assertEqual(entry.flags, flags) def test_read_exports(self): d = DistributionPath().get_distribution('babar') r = d.exports self.assertIn('foo', r) d = r['foo'] self.assertIn('bar', d) self.check_entry(d['bar'], 'bar', 'baz', 'barbaz', ['a=10', 'b']) self.assertIn('bar.baz', r) d = r['bar.baz'] self.assertIn('foofoo', d) self.check_entry(d['foofoo'], 'foofoo', 'baz.foo', 'bazbar', []) self.assertIn('real', d) e = d['real'] self.check_entry(e, 'real', 'cgi', 'print_directory', []) if sys.version_info[:2] < (3, 12): import cgi self.assertIs(e.value, cgi.print_directory) # See issue #78. Test reading an entry_points.txt with leading spaces TEST_EXPORTS = b""" [paste.server_runner] main = waitress:serve_paste [console_scripts] waitress-serve = waitress.runner:run """ with io.BytesIO(TEST_EXPORTS) as f: exports = read_exports(f) self.assertEqual(set(exports.keys()), set(['paste.server_runner', 'console_scripts'])) def test_exports_iteration(self): d = DistributionPath() expected = set(( ('bar', 'baz', 'barbaz', ('a=10', 'b')), ('bar', 'crunchie', None, ()), ('bar', 'towel', 'towel', ()), ('baz', 'towel', 'beach_towel', ()), )) entries = list(d.get_exported_entries('foo')) for e in entries: t = e.name, e.prefix, e.suffix, tuple(e.flags) self.assertIn(t, expected) expected.remove(t) self.assertFalse(expected) # nothing left expected = set(( ('bar', 'baz', 'barbaz', ('a=10', 'b')), ('bar', 'crunchie', None, ()), ('bar', 'towel', 'towel', ()), )) entries = list(d.get_exported_entries('foo', 'bar')) for e in entries: t = e.name, e.prefix, e.suffix, tuple(e.flags) self.assertIn(t, expected) expected.remove(t) self.assertFalse(expected) # nothing left expected = set(( ('foofoo', 'baz.foo', 'bazbar', ()), ('real', 'cgi', 'print_directory', ()), ('foofoo', 'ferrero', 'rocher', ()), ('foobar', 'hoopy', 'frood', ('dent', )), )) entries = list(d.get_exported_entries('bar.baz')) for e in entries: t = e.name, e.prefix, e.suffix, tuple(e.flags) self.assertIn(t, expected) expected.remove(t) self.assertFalse(expected) # nothing left def test_modules(self): dp = DistributionPath(include_egg=True) dist = dp.get_distribution('banana') self.assertIsInstance(dist, EggInfoDistribution) self.assertEqual(dist.modules, ['banana', 'cavendish']) class DataFilesTestCase(GlobTestCaseBase): def assertRulesMatch(self, rules, spec): tempdir = self.build_files_tree(spec) expected = self.clean_tree(spec) result = get_resources_dests(tempdir, rules) self.assertEqual(expected, result) def clean_tree(self, spec): files = {} for path, value in spec.items(): if value is not None: files[path] = value return files def test_simple_glob(self): rules = [('', '*.tpl', '{data}')] spec = {'coucou.tpl': '{data}/coucou.tpl', 'Donotwant': None} self.assertRulesMatch(rules, spec) def test_multiple_match(self): rules = [('scripts', '*.bin', '{appdata}'), ('scripts', '*', '{appscript}')] spec = {'scripts/script.bin': '{appscript}/script.bin', 'Babarlikestrawberry': None} self.assertRulesMatch(rules, spec) def test_set_match(self): rules = [('scripts', '*.{bin,sh}', '{appscript}')] spec = { 'scripts/script.bin': '{appscript}/script.bin', 'scripts/babar.sh': '{appscript}/babar.sh', 'Babarlikestrawberry': None } self.assertRulesMatch(rules, spec) def test_set_match_multiple(self): rules = [('scripts', 'script{s,}.{bin,sh}', '{appscript}')] spec = { 'scripts/scripts.bin': '{appscript}/scripts.bin', 'scripts/script.sh': '{appscript}/script.sh', 'Babarlikestrawberry': None } self.assertRulesMatch(rules, spec) def test_set_match_exclude(self): rules = [('scripts', '*', '{appscript}'), ('', os.path.join('**', '*.sh'), None)] spec = { 'scripts/scripts.bin': '{appscript}/scripts.bin', 'scripts/script.sh': None, 'Babarlikestrawberry': None } self.assertRulesMatch(rules, spec) def test_glob_in_base(self): rules = [('scrip*', '*.bin', '{appscript}')] spec = { 'scripts/scripts.bin': '{appscript}/scripts.bin', 'scripouille/babar.bin': '{appscript}/babar.bin', 'scriptortu/lotus.bin': '{appscript}/lotus.bin', 'Babarlikestrawberry': None } self.assertRulesMatch(rules, spec) def test_recursive_glob(self): rules = [('', os.path.join('**', '*.bin'), '{binary}')] spec = { 'binary0.bin': '{binary}/binary0.bin', 'scripts/binary1.bin': '{binary}/scripts/binary1.bin', 'scripts/bin/binary2.bin': '{binary}/scripts/bin/binary2.bin', 'you/kill/pandabear.guy': None } self.assertRulesMatch(rules, spec) def test_final_example_glob(self): rules = [ ('mailman/database/schemas/', '*', '{appdata}/schemas'), ('', os.path.join('**', '*.tpl'), '{appdata}/templates'), ('', os.path.join('developer-docs', '**', '*.txt'), '{doc}'), ('', 'README', '{doc}'), ('mailman/etc/', '*', '{config}'), ('mailman/foo/', os.path.join('**', 'bar', '*.cfg'), '{config}/baz'), ('mailman/foo/', os.path.join('**', '*.cfg'), '{config}/hmm'), ('', 'some-new-semantic.sns', '{funky-crazy-category}'), ] spec = { 'README': '{doc}/README', 'some.tpl': '{appdata}/templates/some.tpl', 'some-new-semantic.sns': '{funky-crazy-category}/some-new-semantic.sns', 'mailman/database/mailman.db': None, 'mailman/database/schemas/blah.schema': '{appdata}/schemas/blah.schema', 'mailman/etc/my.cnf': '{config}/my.cnf', 'mailman/foo/some/path/bar/my.cfg': '{config}/hmm/some/path/bar/my.cfg', 'mailman/foo/some/path/other.cfg': '{config}/hmm/some/path/other.cfg', 'developer-docs/index.txt': '{doc}/developer-docs/index.txt', 'developer-docs/api/toc.txt': '{doc}/developer-docs/api/toc.txt', } self.maxDiff = None self.assertRulesMatch(rules, spec) def test_get_file(self): # Create a fake dist temp_site_packages = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, temp_site_packages) dist_name = 'test' dist_info = os.path.join(temp_site_packages, 'test-0.1.dist-info') os.mkdir(dist_info) metadata_path = os.path.join(dist_info, 'pydist.json') resources_path = os.path.join(dist_info, 'RESOURCES') md = Metadata() md.name = 'test' md.version = '0.1' md.summary = 'test' md.write(path=metadata_path) test_path = 'test.cfg' fd, test_resource_path = tempfile.mkstemp() os.close(fd) self.addCleanup(os.remove, test_resource_path) fp = open(test_resource_path, 'w') try: fp.write('Config') finally: fp.close() fp = open(resources_path, 'w') try: fp.write('%s,%s' % (test_path, test_resource_path)) finally: fp.close() # Add fake site-packages to sys.path to retrieve fake dist self.addCleanup(sys.path.remove, temp_site_packages) sys.path.insert(0, temp_site_packages) # Try to retrieve resources paths and files d = DistributionPath() self.assertEqual(d.get_file_path(dist_name, test_path), test_resource_path) self.assertRaises(KeyError, d.get_file_path, dist_name, 'i-dont-exist') class DepGraphTestCase(LoggingCatcher, DistlibTestCase): DISTROS_DIST = ('choxie', 'grammar', 'towel-stuff') DISTROS_EGG = ('bacon', 'banana', 'strawberry', 'cheese') BAD_EGGS = ('nut', ) EDGE = re.compile(r'"(?P.*)" -> "(?P.*)" \[label="(?P